diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f5849a2f1..8cce7032d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,10 +24,10 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-docs-${{ hashFiles('docs/requirements.txt') }} @@ -36,10 +36,11 @@ jobs: cd docs sudo apt install imagemagick zip pip install -r requirements.txt + python ./builds_overview.py make html cd .. zip -r -qq ESPEasy_docs.zip docs/build/* - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Documentation path: ESPEasy_docs.zip @@ -49,7 +50,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - id: set-matrix @@ -64,19 +65,19 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: - python-version: '3.11' - - uses: actions/cache@v3 + python-version: '3.x' + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-${{ hashFiles('requirements.txt') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 if: ${{ contains(matrix.env, 'esp32') }} with: path: ~/.platformio key: ${{ runner.os }}-esp32-${{ hashFiles('platformio*.ini') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 if: ${{ contains(matrix.env, 'esp8266') }} with: path: ~/.platformio @@ -89,6 +90,10 @@ jobs: pip install wheel pip install -r requirements.txt platformio update + - name: Get current date + id: date + run: | + echo "builddate=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - name: Build and archive id: build-and-archive env: @@ -96,23 +101,28 @@ jobs: ENV: ${{ matrix.env }} run: | python tools/ci/build-and-archive.py - - uses: actions/upload-artifact@v3 + - id: string + uses: Entepotenz/change-string-case-action-min-dependencies@v1 with: - name: Binaries + string: ${{ matrix.chip }} + - uses: actions/upload-artifact@v4 + 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 path: | bin if-no-files-found: ignore - # Repackage separately for ESP82xx and ESP32 - repackage: + # Package all ESP82xx and ESP32 into a single ESPEasy-all-Binaries-_PR#_.zip + combine_package: needs: build runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-docs-${{ hashFiles('docs/requirements.txt') }} @@ -120,92 +130,20 @@ jobs: id: date run: | echo "builddate=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v3 + - name: Download all artifacts + uses: actions/download-artifact@v4 with: - path: artifacts/ - - name: Prepare artifacts + path: artifacts/Binaries/bin/ + pattern: Bin-* + merge-multiple: true + - name: List all files in the package for single-archive upload run: | - # ESP82xx - mkdir ESPEasy_dist_ESP82xx - cd dist - find . -exec cp -r --parents {} ../ESPEasy_dist_ESP82xx/ \; - rm ../ESPEasy_dist_ESP82xx/bin/blank_8MB.bin - cd ../artifacts/Binaries - find . -not -name '*ESP32*' -exec mv {} ../../ESPEasy_dist_ESP82xx/bin/ \; - cd ../.. - # ESP32 and derived cpus, ESP32 (classic) _MUST_ be last in this list! - for cpu in ESP32s2 ESP32c3 ESP32s3 ESP32c2 ESP32c6 ESP32h2 ESP32solo1 ESP32 - do - mkdir ESPEasy_dist_${cpu} - spec="*${cpu}*" - _files=$(find ./artifacts/Binaries -name "$spec"|wc -l) - if [[ $_files > 0 ]]; then - cd dist - find . -exec cp -r --parents {} ../ESPEasy_dist_${cpu}/ \; - cd ../ESPEasy_dist_${cpu} - rm bin/blank_1MB.bin bin/blank_2MB.bin bin/ESPEasy_2step_UploaderMega_1024.bin - cd ../artifacts/Binaries - find . -name "$spec" -exec mv {} ../../ESPEasy_dist_${cpu}/bin/ \; - cd ../.. - fi - done - # Each supported cpu has to be listed separately, but empty folders are ignored - - uses: actions/upload-artifact@v3 + cd artifacts/Binaries/ + ls -R + - uses: actions/upload-artifact@v4 with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_PR#${{ github.event.number }}_${{ github.run_id }} + # 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 }} path: | - ESPEasy_dist_ESP32/* + artifacts/Binaries/ if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32solo1_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32solo1/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s2_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32s2/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c3_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32c3/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s3_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32s3/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c2_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32c2/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32c6_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32c6/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32h2_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP32h2/* - if-no-files-found: ignore - - uses: actions/upload-artifact@v3 - with: - name: ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_PR#${{ github.event.number }}_${{ github.run_id }} - path: | - ESPEasy_dist_ESP82xx/* - if-no-files-found: ignore - # When successfully re-packaged, the original Binaries can be removed - # comment below 3 lines to not remove the Binaries artifact after repackaging - - uses: geekyeggo/delete-artifact@v2 - with: - name: Binaries \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 347b17134..f4c15f37e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,7 +22,7 @@ jobs: matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - id: set-matrix @@ -37,19 +37,19 @@ jobs: matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-${{ hashFiles('requirements.txt') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 if: ${{ contains(matrix.env, 'esp32') }} with: path: ~/.platformio key: ${{ runner.os }}-esp32-${{ hashFiles('platformio*.ini') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 if: ${{ contains(matrix.env, 'esp8266') }} with: path: ~/.platformio @@ -66,9 +66,13 @@ jobs: ENV: ${{ matrix.env }} run: | python tools/ci/build-and-archive.py - - uses: actions/upload-artifact@v3 + - id: string + uses: Entepotenz/change-string-case-action-min-dependencies@v1 with: - name: Binaries + string: ${{ matrix.chip }} + - uses: actions/upload-artifact@v4 + with: + name: Bin-${{ steps.string.outputs.uppercase }}-${{ matrix.env }} path: | bin if-no-files-found: ignore @@ -78,10 +82,10 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-docs-${{ hashFiles('docs/requirements.txt') }} @@ -98,7 +102,7 @@ jobs: cd dist zip -r -qq ../ESPEasy_dist.zip * cd .. - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: Distribution path: | @@ -112,7 +116,7 @@ jobs: notes: ${{ steps.release-notes.outputs.result }} steps: - id: release-notes - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: result-encoding: string script: | @@ -134,48 +138,61 @@ jobs: needs: [build, prepare-dist, prepare-notes] runs-on: ubuntu-22.04 steps: - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: '3.x' - name: Get current date id: date run: | echo "builddate=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT - - uses: actions/download-artifact@v3 + - name: Download all successfully compiled artifacts + uses: actions/download-artifact@v4 with: - path: artifacts/ + path: artifacts/bin/ + pattern: Bin-* + merge-multiple: true + - name: Download dist and docs zip files + uses: actions/download-artifact@v4 + with: + path: distribution/ + name: Distribution - name: Repackage for release upload run: | ls -R sudo apt install zipmerge zip - cd artifacts/Binaries - mkdir bin - mv *.* bin - find . -not -name '*ESP32*' -print | zip -@ ../../ESPEasy_ESP82xx.zip + cd artifacts + # ESP8266 and ESP8285 + find . -not -name '*ESP32*' -print | zip -@ ../ESPEasy_ESP82xx.zip # ESP32 and derived chips - # TODO if/when available: ESP32c2 ESP32c6 ESP32h2 - find . -name '*ESP32s2*' -print | zip -@ ../../ESPEasy_ESP32s2.zip - find . -name '*ESP32c3*' -print | zip -@ ../../ESPEasy_ESP32c3.zip - find . -name '*ESP32s3*' -print | zip -@ ../../ESPEasy_ESP32s3.zip - find . -name '*ESP32solo1*' -print | zip -@ ../../ESPEasy_ESP32solo1.zip - find . -name '*ESP32_*' -print | zip -@ ../../ESPEasy_ESP32.zip - cd ../.. - mv artifacts/Distribution/ESPEasy_dist* . - cp ESPEasy_dist.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip + # TODO if/when available: ESP32h2 + find . -name '*ESP32s2*' -print | zip -@ ../ESPEasy_ESP32s2.zip + 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 '*ESP32c6*' -print | zip -@ ../ESPEasy_ESP32c6.zip + find . -name '*ESP32solo1*' -print | zip -@ ../ESPEasy_ESP32solo1.zip + find . -name '*ESP32_*' -print | zip -@ ../ESPEasy_ESP32.zip + cd .. + # Add dist tools to each package, after removing some unneeded files + # Copy dist zip for ESP82xx use + cp distribution/ESPEasy_dist.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip zip -d ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip "bin/blank_8MB.bin" - mv ESPEasy_dist.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip + # Move dist zip for ESP32 use + mv distribution/ESPEasy_dist.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip zip -d ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip "bin/blank_1MB.bin" "bin/blank_2MB.bin" "bin/ESPEasy_2step_UploaderMega_1024.bin" zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP82xx_binaries.zip ESPEasy_ESP82xx.zip - # TODO if/when available: ESP32c2 ESP32c6 ESP32h2 + # TODO if/when available: ESP32h2 zipmerge ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32s2_binaries.zip ESPEasy_mega_${{ steps.date.outputs.builddate }}_ESP32_binaries.zip ESPEasy_ESP32s2.zip 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 }}_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 - uses: ncipollo/release-action@v1 with: - # Include all separately supported CPU models - # TODO if/when available: ESP32c2 ESP32c6 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,artifacts/Distribution/*.zip" + # 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" body: ${{ needs.prepare-notes.outputs.notes }} token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index 018de7db0..0be60e9b1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Introduction https://espeasy.readthedocs.io/en/latest/ (and, mostly outdated, wi This is the development branch of ESPEasy. All new features go into this branch, and it has become the current stable branch. If you want to do a bugfix, do it on this branch. -Check here to learn how to use this branch and help us improving ESPEasy: http://www.letscontrolit.com/wiki/index.php/ESPEasy#Source_code_development +Check here to learn how to use this branch and help us improving ESPEasy: [Starter guide for (local) development on ESPEasy](https://espeasy.readthedocs.io/en/latest/Participate/PlatformIO.html#starter-guide-for-local-development-on-espeasy) ## Web based flasher (experimental) @@ -58,6 +58,7 @@ hard | hardware specific builds | Minimal minimal | minimal plugins for specific use-cases | Switch and Controller | spec_* | specialized technical builds | Not intended for regular use | IRext | Infra-red hardware specific | Sending and receiving IR cmd | +safeboot | (Experimental) `safeboot` build to enable
most/all plugins on 4MB Flash boards | None | *[opt-arduino-library]* (optional) can be any of: @@ -81,6 +82,8 @@ ESP32solo1 | Espressif ESP32-Solo1 generic boards | ESP32s2 | Espressif ESP32-S2 generic boards | ESP32c3 | Espressif ESP32-C3 generic boards | ESP32s3 | Espressif ESP32-S3 generic boards | +ESP32c2 | Espressif ESP32-C2 generic boards | +ESP32c6 | Espressif ESP32-C6 generic boards | ESP32-wrover-kit | Espressif ESP32 wrover-kit boards | SONOFF | Sonoff hardware specific | other_POW | Switch with power measurement | @@ -98,6 +101,7 @@ Flash size | Description | 1M | 1 MB with 128 kB filesystem | 2M | 2 MB with 128 kB filesystem | 2M256 | 2 MB with 256 kB filesystem | +2M320k | 2 MB with 320 kB filesystem | 4M1M | 4 MB with 1 MB filesystem | 4M2M | 4 MB with 2 MB filesystem | 16M | 16 MB with 14 MB filesystem | @@ -106,34 +110,37 @@ Flash size | Description | 16M1M | 16 MB with 1 MB filesystem | 16M8M | 16 MB with 8 MB filesystem | +N.B. Starting with release 2023/12/25, All ESP32 LittleFS builds use IDF 5.1, 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 available in IDF 5 and later. A migration plan will be made available in 2024. *[opt-build-features]* can be any of: -Build features | Description | -----------------|----------------------------------------------------------------------------| -LittleFS | Use LittleFS instead of SPIFFS filesystem (SPIFFS is unstable \> 2 MB) | -VCC | Analog input configured to measure VCC voltage (ESP8266 only) | -OTA | Arduino OTA (Over The Air) update feature enabled | -Domoticz | Only Domoticz controllers (HTTP+MQTT) and plugins included | -FHEM_HA | Only FHEM/OpenHAB/Home Assistant (MQTT) controllers and plugins included | -ETH | Ethernet interface enabled (ESP32 only) | -OPI_PSRAM | Specific configuration to enable PSRAM detection, ESP32-S3 only | -CDC | Support USBCDC/HWCDC-serial console on ESP32-C3, ESP32-S2 and ESP32-S3 | +Build features | Description | +----------------|-----------------------------------------------------------------------------------------------------------| +LittleFS | Use LittleFS instead of SPIFFS filesystem (SPIFFS is unstable \> 2 MB, and no longer supported in IDF \> 5) | +VCC | Analog input configured to measure VCC voltage (ESP8266 only) | +OTA | Arduino OTA (Over The Air) update feature enabled | +Domoticz | Only Domoticz controllers (HTTP) and plugins included | +Domoticz_MQTT | Only Domoticz controllers (MQTT) and plugins included | +FHEM_HA | Only FHEM/OpenHAB/Home Assistant (MQTT) controllers and plugins included | +ETH | Ethernet interface enabled (ESP32-classic and IDF 5.x based builds) | +OPI_PSRAM | Specific configuration to enable PSRAM detection, ESP32-S3 only | +CDC | Support USBCDC/HWCDC-serial console on ESP32-C3, ESP32-S2, ESP32-S3 and ESP32-C6 | +noOTA/NO_OTA | Does not support OTA (Over The Air-updating of the firmware) Use [the flash page](https://td-er.nl/ESPEasy/) or ESPTool via USB Serial | 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.bin | ESP32 with 4MB flash | Stable | -ESPEasy_mega-20230822_collection_A_ESP32_4M316k.bin | ESP32 with 4MB flash | Stable + Collection base + set A | -ESPEasy_mega-20230822_collection_B_ESP32_4M316k.bin | ESP32 with 4MB flash | Stable + Collection base + set B | -ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_CDC.bin | ESP32-S3 with 8MB flash, CDC-serial | All available plugins | -ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC.bin | ESP32-S3 8MB flash, PSRAM, CDC-serial | All available plugins | -ESPEasy_mega-20230822_max_ESP32_16M1M.bin | ESP32 with 16MB flash | All available plugins | -ESPEasy_mega-20230822_max_ESP32_16M8M_LittleFS.bin | ESP32 with 16MB flash | 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.bin | ESP32 with 4MB flash | Stable | +ESPEasy_mega-20230822_collection_A_ESP32_4M316k.bin | ESP32 with 4MB flash | Stable + Collection base + set A | +ESPEasy_mega-20230822_collection_B_ESP32_4M316k.bin | ESP32 with 4MB flash | Stable + Collection base + set B | +ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_CDC_ETH.bin | ESP32-S3 with 8MB flash, CDC-serial, Ethernet | All available plugins | +ESPEasy_mega-20230822_max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH.bin | ESP32-S3 8MB flash, PSRAM, CDC-serial, Ethernet | All available plugins | +ESPEasy_mega-20230822_max_ESP32_16M1M.bin | ESP32 with 16MB flash | All available plugins | +ESPEasy_mega-20230822_max_ESP32_16M8M_LittleFS_ETH.bin | ESP32 with 16MB flash, Ethernet | All available plugins | -NB: Since 2023-05-10 the binary files for the different ESP32 variants (S2, C3, S3, 'Classic') are available in separate archives. +The binary files for the different ESP32 variants (S2, C3, S3, C2, C6, 'Classic') are available in separate archives. To see what plugins are included in which collection set, you can find that on the [ESPEasy Plugin overview page](https://espeasy.readthedocs.io/en/latest/Plugin/_Plugin.html) diff --git a/boards/esp32-cam.json b/boards/esp32-cam.json index 6fd7bdb53..fdaff2c31 100644 --- a/boards/esp32-cam.json +++ b/boards/esp32-cam.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "dio", diff --git a/boards/esp32-m5core2.json b/boards/esp32-m5core2.json index 0defb1d60..ac8f88085 100644 --- a/boards/esp32-m5core2.json +++ b/boards/esp32-m5core2.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_M5STACK_Core2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_M5STACK_Core2 -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "dio", diff --git a/boards/esp32-odroid.json b/boards/esp32-odroid.json index a63cbfbc5..37f33f934 100644 --- a/boards/esp32-odroid.json +++ b/boards/esp32-odroid.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ODROID_ESP32 -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ODROID_ESP32 -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "dio", diff --git a/boards/esp32_16M1M.json b/boards/esp32_16M1M.json index 58feece14..f636c5588 100644 --- a/boards/esp32_16M1M.json +++ b/boards/esp32_16M1M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_16M8M.json b/boards/esp32_16M8M.json index 453be7e5a..ac7aca353 100644 --- a/boards/esp32_16M8M.json +++ b/boards/esp32_16M8M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_16M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_4M.json b/boards/esp32_4M.json index c2704e19c..a48b32c22 100644 --- a/boards/esp32_4M.json +++ b/boards/esp32_4M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_4M2M.json b/boards/esp32_4M2M.json index aac31ebdc..96109a9ee 100644 --- a/boards/esp32_4M2M.json +++ b/boards/esp32_4M2M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_4M_fix.json b/boards/esp32_4M_fix.json index 5a2327b19..445eb5055 100644 --- a/boards/esp32_4M_fix.json +++ b/boards/esp32_4M_fix.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DHAS_PSRAM_FIX -mfix-esp32-psram-cache-issue -mfix-esp32-psram-cache-strategy=memw -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_8M.json b/boards/esp32_8M.json index 02d5fb362..ff72fa307 100644 --- a/boards/esp32_8M.json +++ b/boards/esp32_8M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_8M -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_8M -DESP32_CLASSIC", "f_cpu": "240000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32_solo1_4M.json b/boards/esp32_solo1_4M.json index edee14a57..dfc2b039e 100644 --- a/boards/esp32_solo1_4M.json +++ b/boards/esp32_solo1_4M.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-DARDUINO_ESP32_DEV -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DCORE32SOLO1 -DESP32_CLASSIC", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_ESP32_DEV -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DCORE32SOLO1 -DESP32_CLASSIC", "f_cpu": "160000000L", "f_flash": "40000000L", "flash_mode": "dio", diff --git a/boards/esp32c2.json b/boards/esp32c2.json index f8b826fa2..2304758ec 100644 --- a/boards/esp32c2.json +++ b/boards/esp32c2.json @@ -4,7 +4,7 @@ "ldscript": "esp32c2_out.ld" }, "core": "esp32", - "extra_flags": "-DESP32_4M -DESP32C2", + "extra_flags": "-DARDUINO_TASMOTA -DESP32_4M -DESP32C2", "f_cpu": "120000000L", "f_flash": "60000000L", "flash_mode": "qio", diff --git a/boards/esp32c2_2M.json b/boards/esp32c2_2M.json index 741790d71..0d0cd9a37 100644 --- a/boards/esp32c2_2M.json +++ b/boards/esp32c2_2M.json @@ -4,7 +4,7 @@ "ldscript": "esp32c2_out.ld" }, "core": "esp32", - "extra_flags": "-DESP32_2M -DESP32C2", + "extra_flags": "-DARDUINO_TASMOTA -DESP32_2M -DESP32C2", "f_cpu": "120000000L", "f_flash": "60000000L", "flash_mode": "qio", diff --git a/boards/esp32c2_safeboot.json b/boards/esp32c2_safeboot.json index 0154e9a24..a0ef79f7c 100644 --- a/boards/esp32c2_safeboot.json +++ b/boards/esp32c2_safeboot.json @@ -4,7 +4,7 @@ "ldscript": "esp32c2_out.ld" }, "core": "esp32", - "extra_flags": "-DESP32_4M -DESP32C2", + "extra_flags": "-DARDUINO_TASMOTA -DESP32_4M -DESP32C2", "f_cpu": "120000000L", "f_flash": "60000000L", "flash_mode": "qio", diff --git a/boards/esp32c3cdc.json b/boards/esp32c3cdc.json index 54b81e9d0..206367fb9 100644 --- a/boards/esp32c3cdc.json +++ b/boards/esp32c3cdc.json @@ -4,7 +4,7 @@ "ldscript": "esp32c3_out.ld" }, "core": "esp32", - "extra_flags": "-DARDUINO_USB_MODE=1 -DESP32_4M -DESP32C3 -DUSE_USB_CDC_CONSOLE -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_USB_MODE=1 -DESP32_4M -DESP32C3 -DUSE_USB_CDC_CONSOLE -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "160000000L", "f_flash": "80000000L", "flash_mode": "dio", diff --git a/boards/esp32c6cdc-16M.json b/boards/esp32c6cdc-16M.json new file mode 100644 index 000000000..efa16120c --- /dev/null +++ b/boards/esp32c6cdc-16M.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32c6_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_16M -DESP32C6 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c6", + "variant": "esp32c6", + "partitions": "boards/partitions/esp32_partition_app4096k_spiffs8124k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32c6.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif Generic ESP32-C6 16M Flash, ESPEasy 4096k Code/OTA 8M FS", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "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/esp32c6cdc-8M.json b/boards/esp32c6cdc-8M.json new file mode 100644 index 000000000..9bfc4e34c --- /dev/null +++ b/boards/esp32c6cdc-8M.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino":{ + "ldscript": "esp32c6_out.ld" + }, + "core": "esp32", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32C6 -DARDUINO_USB_CDC_ON_BOOT=1", + "f_cpu": "160000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32c6", + "variant": "esp32c6", + "partitions": "boards/partitions/esp32_partition_app3520k_spiffs1088k.csv" + }, + "connectivity": [ + "wifi", + "bluetooth" + ], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": [ + "esp-builtin" + ], + "openocd_target": "esp32c6.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Espressif Generic ESP32-C6 >= 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/esp32c6cdc.json b/boards/esp32c6cdc.json index 339126b2b..b6615cba4 100644 --- a/boards/esp32c6cdc.json +++ b/boards/esp32c6cdc.json @@ -4,7 +4,7 @@ "ldscript": "esp32c6_out.ld" }, "core": "esp32", - "extra_flags": "-DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32C6 -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32C6 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "160000000L", "f_flash": "80000000L", "flash_mode": "qio", diff --git a/boards/esp32s2cdc.json b/boards/esp32s2cdc.json index f358f6a3c..1f71388af 100644 --- a/boards/esp32s2cdc.json +++ b/boards/esp32s2cdc.json @@ -5,7 +5,7 @@ "memory_type": "dio_qspi" }, "core": "esp32", - "extra_flags": "-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", "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 ff62fb130..05b680768 100644 --- a/boards/esp32s3cdc-qio_opi-16M.json +++ b/boards/esp32s3cdc-qio_opi-16M.json @@ -5,7 +5,7 @@ "memory_type": "qio_opi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_16M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "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", "flash_mode": "qio", diff --git a/boards/esp32s3cdc-qio_opi-8M.json b/boards/esp32s3cdc-qio_opi-8M.json index 49511be40..064cb4019 100644 --- a/boards/esp32s3cdc-qio_opi-8M.json +++ b/boards/esp32s3cdc-qio_opi-8M.json @@ -5,7 +5,7 @@ "memory_type": "qio_opi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "qio", diff --git a/boards/esp32s3cdc-qio_opi.json b/boards/esp32s3cdc-qio_opi.json index 63f69a7d7..03943d071 100644 --- a/boards/esp32s3cdc-qio_opi.json +++ b/boards/esp32s3cdc-qio_opi.json @@ -5,7 +5,7 @@ "memory_type": "qio_opi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "qio", diff --git a/boards/esp32s3cdc-qio_qspi-16M.json b/boards/esp32s3cdc-qio_qspi-16M.json index b93dc474d..e924d3dbd 100644 --- a/boards/esp32s3cdc-qio_qspi-16M.json +++ b/boards/esp32s3cdc-qio_qspi-16M.json @@ -5,7 +5,7 @@ "memory_type": "qio_qspi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_16M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "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", "flash_mode": "qio", diff --git a/boards/esp32s3cdc-qio_qspi-8M.json b/boards/esp32s3cdc-qio_qspi-8M.json index 705df27c4..669a50bb5 100644 --- a/boards/esp32s3cdc-qio_qspi-8M.json +++ b/boards/esp32s3cdc-qio_qspi-8M.json @@ -5,7 +5,7 @@ "memory_type": "qio_qspi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_8M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "qio", diff --git a/boards/esp32s3cdc-qio_qspi.json b/boards/esp32s3cdc-qio_qspi.json index e040168ac..e1ecb7910 100644 --- a/boards/esp32s3cdc-qio_qspi.json +++ b/boards/esp32s3cdc-qio_qspi.json @@ -5,7 +5,7 @@ "memory_type": "qio_qspi" }, "core": "esp32", - "extra_flags": "-DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", + "extra_flags": "-DARDUINO_TASMOTA -DBOARD_HAS_PSRAM -DARDUINO_USB_MODE=1 -DUSE_USB_CDC_CONSOLE -DESP32_4M -DESP32S3 -DARDUINO_USB_CDC_ON_BOOT=1", "f_cpu": "240000000L", "f_flash": "80000000L", "flash_mode": "qio", diff --git a/boards/esp8266_16M14M_board.json b/boards/esp8266_16M14M_board.json index 49de40110..b9632c297 100644 --- a/boards/esp8266_16M14M_board.json +++ b/boards/esp8266_16M14M_board.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.16m14m.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_16M14M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_16M14M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_1M128k.json b/boards/esp8266_1M128k.json index edeb0be95..4dd7f0305 100644 --- a/boards/esp8266_1M128k.json +++ b/boards/esp8266_1M128k.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.1m128.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_1M128k_OTA.json b/boards/esp8266_1M128k_OTA.json index 0f21682ee..d6de103f6 100644 --- a/boards/esp8266_1M128k_OTA.json +++ b/boards/esp8266_1M128k_OTA.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.1m128.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_2M1M.json b/boards/esp8266_2M1M.json index e1bf85b78..ea484598f 100644 --- a/boards/esp8266_2M1M.json +++ b/boards/esp8266_2M1M.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.2m1m.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_2M256.json b/boards/esp8266_2M256.json index d7bfd158a..91fa85039 100644 --- a/boards/esp8266_2M256.json +++ b/boards/esp8266_2M256.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.2m256.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M256", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M256", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_4M1M_board.json b/boards/esp8266_4M1M_board.json index 45112964e..9b98f1b16 100644 --- a/boards/esp8266_4M1M_board.json +++ b/boards/esp8266_4M1M_board.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.4m1m.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_4M2M_board.json b/boards/esp8266_4M2M_board.json index c04af6056..e5aa17f16 100644 --- a/boards/esp8266_4M2M_board.json +++ b/boards/esp8266_4M2M_board.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.4m2m.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M2M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M2M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_4M3M.json b/boards/esp8266_4M3M.json index 7cf3a4a3f..90b9450e9 100644 --- a/boards/esp8266_4M3M.json +++ b/boards/esp8266_4M3M.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.4m3m.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M3M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_4M -DESP8266_4M3M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8266_zbbridge.json b/boards/esp8266_zbbridge.json index 4589dfc7d..f32388560 100644 --- a/boards/esp8266_zbbridge.json +++ b/boards/esp8266_zbbridge.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.2m256.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M256", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_2M -DESP8266_2M256", "f_cpu": "160000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8285_1M128k.json b/boards/esp8285_1M128k.json index a7df5b088..955e750c3 100644 --- a/boards/esp8285_1M128k.json +++ b/boards/esp8285_1M128k.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.1m128.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DESP8285 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DESP8285 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/boards/esp8285_1M128k_OTA.json b/boards/esp8285_1M128k_OTA.json index e6934c535..be1576c29 100644 --- a/boards/esp8285_1M128k_OTA.json +++ b/boards/esp8285_1M128k_OTA.json @@ -4,7 +4,7 @@ "ldscript": "eagle.flash.1m128.ld" }, "core": "esp8266", - "extra_flags": "-DESP8266 -DESP8285 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", + "extra_flags": "-DARDUINO_TASMOTA -DESP8266 -DESP8285 -DARDUINO_ARCH_ESP8266 -DARDUINO_ESP8266_ESP01 -DESP8266_1M", "f_cpu": "80000000L", "f_flash": "40000000L", "flash_mode": "dout", diff --git a/dist/Release_notes.txt b/dist/Release_notes.txt index 034b9e136..94f29ea10 100644 --- a/dist/Release_notes.txt +++ b/dist/Release_notes.txt @@ -1,3 +1,384 @@ +------------------------------------------------- +Changes in release mega-20240414 (since mega-20240401) +------------------------------------------------- + +Release date: Sun Apr 14 03:08:46 PM CEST 2024 + +TD-er (16): + [HWCDC] Revert to older ESP-IDF/Arduino build + [Time] Fix updating %sunrise% and %sunset% when no NTP available + [HWCDC] Fix bootloop on ESP32-C3/C6 + [HWCDC] Cleanup unused test code + [ESP-IDF5.1] Move to newly network refactored code + [ESP-IDF5.x] Fix build issue + [ESP-IDF5.x] Fix building ESP32-solo1 builds + [Ethernet[ Fix crashes when using Ethernet + IPv6 on LittleFS builds + [Ethernet] Fix getting DNS from DHCP switch from WiFi to Ethernet + [ESP-IDF5.x] Fix LittleFS builds without Ethernet + [IMPROV] Fix provisioning WiFi via web flasher + [ESP-IDF5.1] Update to latest 2024.04.11 platform build + [SPI ETH] Fix SPI selection to actual SPI bus for Ethernet + [SPI Eth] Fix build on ESP32-C3 + [HWCDC] Tweak HWCDC to be more stable + [Build] Revert unintended change of default PIO env. + +Ton Huisman (2): + [Dist] Update Espressif Flash Download tool 3.9.6 + [JSON] Add extra data in `/json` output + + +------------------------------------------------- +Changes in release mega-20240401 (since mega-20240331) +------------------------------------------------- + +Release date: Mon Apr 1 12:13:54 AM CEST 2024 + +Ton Huisman (1): + [Bugfix] Release script fixes and simplifications + + +------------------------------------------------- +Changes in release mega-20240331 (since mega-20240229) +------------------------------------------------- + +Release date: Sun Mar 31 03:40:08 PM CEST 2024 + +Ernest (ErNis) (1): + INA219 26V 8A range added + +Jason2866 (1): + small refactor + +TD-er (13): + [JL1101] Fix JL1101 Ethernet + update to latest ESP-IDF5.1 code + [PlatformIO] Fix installing pygit2 + [ESP8266 WiFi] Initialize flags for AP capabilities + [ESP8266] Get rid of several union structs which may cause weird issues + [ESP8266] Remove use of union due to issues on ESP8266 + [ESP-IDF5.1] Revert to older SDK code due to issues with HWCDC + [Build] Reduce build size regarding WiFi AP Candidate duplicate code + [HWCDC] Test for ESP32-C3/C6/S3 HWCDC issues + [CUL Reader] Cherry pick code from ESPEasy_NOW pull request + [CUL Reader] Fix not being able to set flags + [Save Settings] Fix issue where data may get corrupted saving task + [Display] Reduce ESP8266 'display' build size + [MQTT] Fix crash and disconnect sending to MQTT using formula + +Ton Huisman (22): + [P087] Add serialproxy_test command and Get the parsed data + [P087] Fix typo + [P087] Documentation clarification + [P087] Apply log-string and code optimizations + [P087] Always process Global Match so values can be retrieved + [Bugfix] Release.yml shouldn't try to move a non-existing file + [Build] Add ESP32-C6 MAX builds (preliminary) + [Docs] Update ESP chip info + [Docs] Update ESP chip info + [P116] Add alternative model selections for ST7789 + [Bugfix] Remove duplicate define in Custom-sample.h + [Build] Custom IR ESP32 configurations not using the pre_custom_esp32_IR.py Python script + [P116] Add alternative model selection for ST7735 + [P029][C002] Add option Invert On/Off value + [Docs] Update P029 documentation with new option + [C002] Reduce .bin size (slightly) + [P116] Fine-tuning the rotation column-offset for ST7735 135x240 display + [P116] Documentation improvement. + [P095] Documentation improvement. + [P116] Update documentation for supported displays + [Build] Disable Notifiers in full binaries + [Build] Fix typo + + +------------------------------------------------- +Changes in release mega-20240229 (since mega-20231225) +------------------------------------------------- + +Release date: Thu Feb 29 03:10:58 PM CET 2024 + +Fabio Ancona (1): + Added missing P166 + +TD-er (104): + [IPv6] Fix node with IPv6 not seen by other p2p nodes with only IPv4 + [PVS Studio] Fix "dangerous expression" macros. + [PVS Studio] Fix missing 'break;' in switch statement + [PVS Studio] Fix Not all members of a class are initialized inside the constructor + [PluginStats] Add PluginStats chart data to /json + [PluginStats] Fix pluginstats via JSON & improve speed + [ESPEasy p2p] Fix Ethernet connected nodes not listed on other nodes + [Build] Make ESP8266 Energy build fit again + [Build] Fix duplicate extra_scripts declaration in some ESP8266 builds + [HW CDC] Fix delay when no client connected to read serial logs + [UDP] Only process UDP packets while connected + [Factory Reset] Fix applying NVS stored settings only when these exist + [ESP-IDF5.1] Add custom_ESP32s2_4M316k_LittleFS_CDC + [Factory Reset] Add default flags for factory reset + [ESP-IDF5.1] Fix starting DHCP on ESP AP during setup + [Cleanup] Reduce bin size (NVS storage & P128 NeoPixelBus) + [Cleanup] Reduce build size (P128 NeoPixelBus) + [Cleanup] Reduce build size (P104/P128) + [Cleanup] Reduce build size (P104 load/save functions) + [Cleanup] Reduce build size + [Cleanup] Reduce build size (AdaFruitGFX_Helper fonts) + [ESP32] Disable PSRAM for ESP32 4M builds (WT32-ETH01 issues) + [Docs] Add credits to origin of ESP32-S3 flash/PSRAM options + [ESP-IDF5.1] Improve IPv6 for ESP32-xx + [ESP-IDF5.1] Fix IPv6 for ESP32-S3 + [Build] Fix build due to typo in source code + Fix "Enable SDK WiFi Auto Reconnect" state on sysinfo page (#4935) + [build] Fix another build issue due to includes + Revert "[Cleanup] Reduce build size (AdaFruitGFX_Helper fonts)" + [Factory reset] Fix using factory reset defaults on new devices + [ESP-IDF5.1] Update to latest ESP-IDF & Arduino fixes + Fix crash in command arg parsing with nullptr string + [IPv6] Allow `ip6` command from all command sources + [P077] Add `cseclearpulses` command to reset CF pulses counter + [Cleanup] Reduce build size by simplifying logs + [Provisioning] Show error and event on failed firmware update (#4941) + [Build] Fix build error on ESP8266 + [P077_CSE] Fix cseclearpulses command + [P077 CSE7766] Call PLUGIN_READ right after `cseclearpulses` + [P016 IR] Reduce stack usage executing commands + [P016 IR] Fix some build mistakes reducing stack usage + [P016 IR] Reduce stack usage execute command + [P016 IR] Add debug code to reproduce crashes Uwe + [Cleanup] Reduce stack usage P036 OLED Framed + [P036 Framed OLED] Apply cleanup from Ton's TAR pull request + [P036 OLED Framed] Add bound checks when drawing fonts + [Commands] Allow commands from external source to be queued (stack) + [Cleanup] Reduce stack usage P036 OLED Framed + [P036 Framed OLED] Apply cleanup from Ton's TAR pull request + [P036 OLED Framed] Add bound checks when drawing fonts + [ESP32 IPv6] Fix getting IPv6 address on ESP32 classic + [Build] Add ESP8266 custom IR 1M build + IPv6 to custom ESP32 IR build + [IPv6] Move log to more logical place + [SPI Ethernet] Add support for SPI Ethernet modules + [SPI Ethernet] Fix build for pre ESP-IDF5.1 + [SPI Ethernet] Fix build + [SPI Ethernet] Use user configurable SPI bus for SPI Ethernet + [SPI Ethernet] Fix build pre-ESP-IDF5.1 + [SPI Ethernet] Fix detect Link Up/Down + cleanup code + [SPI Ethernet] Fix build on ESP-IDF < 5.x + [Build] Fix building on ESP8266 + [SPI Ethernet] Document SPI Ethernet + Eth config for available boards + [SPI Ethernet] Fix typo in docs + [Cleanup] Fix crashes on ESP32 sending log from ISR callback functions + [SPI Ethernet] Add "- None -" Ethernet type option on Hardware Page + [ESP-IDF5.1] Update to latest SDK/Arduino commits + [IPv6] Fix IPv6 on ESP32-S3 + [Build] Fix missing #endif + [SPI Ethernet] Allow selecting RMII pins for SPI Ethernet + [SPI Ethernet] Fix concurrency issue with shared SPI bus for ETH/display + [Build] Fix build on ESP32-C3 + [PWM] Fix ESP32 LittleFS build PWM GPIO : port#2 is out of range (#4962) + [SPI Ethernet] Make default Ethernet parameters more neutral + [PWM] Fix deadlock on ESP32-classic with PWM fade + [ESP32 PWM] Fix calling ledcAttach for Servo and PWM on ESP32 IDF5.1 + [ESP-IDF5.1] Update to latest Arduino and ESP-IDF commits + [Email] Fix sending multiple lines in body (#4967) + [ESP-IDF5.1] Fix build ESP32-C2 + [Build] Test ESP32-solo1 build on CI + [ESP-IDF5.1] Update to latest platform build + [SPI Eth] Add ESP32-S3 ETH builds + [ESP32 WiFi] Fix slow connect after WiFi scan + [SPI Eth] Allow SPI Ethernet W5500 to have not connected INT pin + [Build] Fix missing include + [ESP-IDF5.1] Update to latest IDF and Arduino commits + [ESP-IDF5.1] Update platform package for ESP32-solo1 + [ESP-IDF5.1] Fix renamed platform zip file + [ESP32 WiFi] Fix crashing on wifi disconnect + [WiFi] Fix crash when calling disconnect or wifimode,off from remote src + [WiFi] Fix crash when WiFi was turned off while data is sent + [Docs] Add warning changing ECO mode when using SPI Ethernet + [GPIO] Add form note setting pull-up on Hardware tab (#4800) + [Rules] Remove warning for max size on Rules edit page + [Eth] Show Ethernet adapter used in JSON/Sysinfo + [ESP-IDF5.x] Add fix to ESP-IDF/Arduino for W5500 without RST (AtomPoE) + [HW info] Add checks for properly identify embedded flash/PSRAM + [ESP8266] Reduce memory usage by actively flushing webpage buffers + [IPv6] Fix slowdown restarting services on receiving IPv6 address + [ESP32-C3/C6] Fix web slowdown by reverting Arduino/IDF code + [Build] Fix build issue on ESP-IDF4.4 + [Build] Fix build on non-ESP32-S3 + [Build] Fix typo in ESP32-S2 code + [ESP32-C3/C6] Fix stalling on concurrent connections + [Build] Add flag to help not compile/download unused libs + +Ton Huisman (116): + [P044] Uncrustify format source + [P044] Make Led pin configurable + [P044] Correct auto-complete typo + [Plugins] Call PLUGIN_WEBFORM_PRE_SERIAL_PARAMS before serial settings are displayed to allow settings conversion + [Plugins] Call PLUGIN_WEBFORM_PRE_SERIAL_PARAMS before serial settings are displayed to allow settings conversion + [P020] Merge [P044] code into [P020], so P020 van emulate P044 + [P020/P044] Migrate settings also on plugin start without prior save from UI + [P020] Code improvement + [P020/P044] Add option for including P1 data in #data event + [UI] Add Separator character input selector + [P020/P044] Add feature to replace spaces and newlines in received data + [P020] Add plugin documentation + [P037] Implement replacement character input selector, update documentation + [UI] Fix unsupported feature compilation error, fix signed/unsigned warning + [P044] Only show usable settings + [P044] Update plugin documentation (based on P020) + [Build] Adjust Custom ESP8266 as already planned to fix build failure + [P020/P044] Adjustments to complete the merge + [P020] Add extra check before sending P1 data + [P020] Revert extra check before sending P1 data, minor improvements + [StringConverter] Add parseHexTextString() + [P020] Add command `serialsendmix` + [StringConverter] Improvements to parseHexTextString() + [P020] Add command documentation + [P020] Correct some typos in documentation + [StringConverter] Add parseHexTextData() to handle 0x00 + [P020] Use parseHextTextData() to handle 0x00 in data + [P020] Update documentation for 0x00..0xFF hex data support in serialsendmix + [P020] Update changelog + [P020] Unification of Ser2Net log messages + [Build] Manually apply WiFi build fixes (missing includes) + [ESPEasySerial] Add shortName option to be used as eventname + [Helpers] Implement ESPEasySerial shortName option to be used as eventname + [P020] Increase max. buffer size [P020] Add options for using Serial Port name as event and/or append the task number [P020/P044] Fix plugin initialization when USB (HW)CDC or I2C Serial is selected + [P020] Update documentation for new options + [P020] Fix receiving P1 data + [P020] Fix merge conflict + [P020] Allow some extra time-out while receiving P1 data, add missing CR/LF + [P044/P020] Improved defaults for P1 WiFigateway + [P020][P044] Minor improvements, update with latest mega changes + [Docs] Hardware page: Add missing documentation for new I2C Slow default + [P043] Add support for %sunrise% and %sunset% based time-values + [Docs] Add missing %syssec_d% variable + [P151] Add missing PLUGIN_I2C_GET_ADDRESS function + [Docs] Update in Plugin-template embedded documentation + [Docs] Add Rules example for registering daily working time + [TimeCalc] Fix parse errors + [Text input] Add `datalist` support, manually cherry picked from P123 PR + [Text input] Implement `datalist` support + [P043] Add selection list (datalist) support for input time string + [P043] Rework Day,Time inputs, add configurable Day,Time count + [P043] Change Value input layout to be on same line as the Day,Time input, code optimization + [Docs] Update Commands and Events references + [P043] Add option for On/Off value input + [P043] Add 'config' command support and get config value support + [P043] Add documentation + [Build] Disable some failing build envs + [Docs] Add P043 to commands and events reference pages + [P043] Fix simple Yes/No behavior like GPIO mode + [P043] Update documentation + [Build] Fix buildscripts for v4 Actions `upload-artifact`, `download-artifact` and `delete-artifact` + [Build] GH Actions v4 use unique upload-id + [Build] GH Actions use setup-python v5, improved glob for delete-artifact + [Build] GH Actions v4 try fix artifact download permissions issue + [Docs] Update `README.md` with new builds/ESPs/options + [Build] GH Actions v4 Sort builds by ESP, remove repackage step + [Build] GH Actions v4 Fix build names vs dependencies, add all-binaries artifact + [Build] GH Actions v4 Rename `repackage` job to `combine_package` + [Build] GH Actions v4 Apply also to `release.yml`, add ESP32-C2 and ESP32-C6 artifact uploads + [Lib] NeoPixelBus_Wrapper: Allow fall-back to Adafruit_NeoPixel lib for ESP8266 + [Lib] NeoPixelBus_Wrapper: Fix compilation for IR builds + [Lib] NeoPixelBus_Wrapper: Correct includes attribute + [Docs] Add Rules example for measuring daily used power/energy + [Adafruit_NeoPixel] Fix compiler warning + [NeoPixelBus_wrapper] Update README.md + [NeoPixelBus_wrapper] Correct class inheritance error + [Adafruit_NeoPixel] Exclude from ESP32 builds (unused) + [Adafruit_NeoPixel] Correct function signature issues causing compiler warnings + [P131] Fix compiler warning -Wreorder + [SuperTinyCron] Remove unused commandline tool + [P020] Replace WiFiServer->available() by accept() (for IDF >= 5) + [Build] Combine_package use separate downloads to reduce chance of failure + [Build] Combine_package use wretry.action to try and succeed the downloads + [Build] Remove RTTTL from `normal_IRext_no_rx` build for size, add some libs to lib_ignore for `custom_IR` builds + [P043] Exclude some code that won't work for PLUGIN_BUILD_MINIMAL_OTA builds + [Release] Add ESP32-C2 and ESP32-C6 downloads + [Build/Release] Fix some issues, apply workarounds where needed + [Docs] Update copyright year to 2024 + [Docs] Update copyright year to 2024 + [Docs] Update copyright year to 2024 + [Docs] Update Sphinx tools and configuration + [IR] Enable all protocols for ESP32 IR builds + [Bugfix] Update bootstrap version for Sphinx to fix documentation menus + [P166] Add plugin GP8403 DAC Dual channel 0-10V + [P166] Add isI2CEnabled check on PLUGIN_INIT + [Build/Release] Update actions/cache to v4 + [P166] Send out datat for events/controllers when changing output values + [Build/Release] Remove Wdalen/retry.action plugin for Node 20 compatibility + [P166] Send out data by scheduling a timer + [P166] Update changelog + [P166] Set default I2C address to 0x5F, as that's how is delivered + [UI] Add alternative 'default' selection to `addFormSelectorI2C()` + [P166] Restore I2C address selector order, fix compiler warning 're-order' + [UI] Add alternative 'default' selection to `addFormSelectorI2C()`, select by address + [P166] Adjust I2C address selector default + [UI] Update plugin I2C address selectors to normal order, with matching default + [P166] Optionally restore last output value on warm boot (setting, default enabled) + [P166] Fix bug that Initial values where not applied after settings save + [P166] Add documentation + [P166] Documentation improvements + [Build] Allow FEATURE_SERVO for Custom builds with LIMIT_BUILD_SIZE set + [Bugfix] `%vcc%` variable isn't recognized + [MQTT] Add PublishR command: publish with will-retain + [Docs] Documentation and EasyColorCode updates and fixes, Feb 2024 + [MQTT] Fix debug strformat message + [EasyColorCode] Correction and var rename commonEvents + +chromoxdor (30): + Update Networking.cpp + Update Networking.cpp + Update Networking.cpp + make thinkspeak event optional + compare also the last part of the uri to generate an event + add check if we got an answer otherwise we get -1 as a value + Update Networking.cpp + Update Networking.cpp + Update define_plugin_sets.h + Update define_plugin_sets.h + Update Networking.cpp + Add ThingspeakReply to wordlist + Update Networking.cpp + Update Networking.cpp + added documentation + Update Rules.rst + single + multifield reply combined + Update Rules.rst + Update Rules.rst + a little formatting + Update espeasy.min.js + Update Rules.rst + add %sysec_d% and fix typo + remove change since ton already did it :) + Update StringGenerator_System.cpp + initial changes + Update _P102_PZEM004Tv3.ino + Update _P102_PZEM004Tv3.ino + Update Rules.rst + Update codemirror to 5.65.16 + +flashmark (9): + Initial plugin structure + Rough, working version + Added recovery from communication failures + Rework after review, added documentation + Fixed compiler issue after rebase + Minor cleanup + Some fixes after review + Added to CLimate and Collection G plugin sets. Removed some leftovers. + Merge + +uwekaditz (11): + [P016] Add protocol RAW to UI if 'Accept DecodeType UNKNOWN' is set + uint64ToString() changed to ll2String() + Check the vector size when checking for commands, and check that the command is really not empty + Reduce code for debug message + Revert "Merge branch 'P016]-PullRequest' of https://github.com/uwekaditz/ESPEasy into P016]-PullRequest" + Revert "Reduce code for debug message" + Reduce code for debug message + Use the new property addToQueue in ExecuteCommand_all() + Uncrustify + Uncrustify #ifdef's + [P016] BUG: Decode type UNKNOWN was not added to the web settings + + ------------------------------------------------- Changes in release mega-20231225 (since mega-20231130) ------------------------------------------------- @@ -4526,7 +4907,7 @@ TD-er (106): [WiFi] Process Station event Auth Changed [Controller] Debug log on remove duplicate queued message [GPIO] Fix warning of unused variable - [Build] Remove HWL8012 from "energy" build + [Build] Remove HLW8012 from "energy" build [Energy build] Add P093 Mitsubishi Heat Pump [Cleanup] Remove ICACHE_RAM_ATTR on wifi event function [Build] LittleFS can only be built on 'beta' builds @@ -4534,7 +4915,7 @@ TD-er (106): [Cleanup] Reduce IRAM usage in Dallas bit read/write code [Build] Fix normal_beta_ESP8266_16M_LittleFS build [Cleanup] Remove unneeded ICACHE_RAM_ATTR on DHCP timeout callback - [Build] Remove P076 HWL8012 from "testing" builds due to build size + [Build] Remove P076 HLW8012 from "testing" builds due to build size [Build] Disable test_ESP8266_4M1M_VCC_MDNS_SD as it does not fit in IRAM [Build] Set normal_IRext_no_rx_ESP8266_4M2M to LIMIT_BUILD_SIZE [GPS] Fix running out of memory when no GPS data is received. diff --git a/dist/flash_download_tool_3.9.5/doc/Flash_Download_Tool__cn.pdf b/dist/flash_download_tool_3.9.5/doc/Flash_Download_Tool__cn.pdf deleted file mode 100644 index a8a19d798..000000000 Binary files a/dist/flash_download_tool_3.9.5/doc/Flash_Download_Tool__cn.pdf and /dev/null differ diff --git a/dist/flash_download_tool_3.9.6/configure/esp32/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32/security.conf new file mode 100644 index 000000000..95bb20a11 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32/security.conf @@ -0,0 +1,25 @@ +[SECURE BOOT] +secure_boot_en = False +secure_boot_version = 1 +public_key_digest_path = .\secure\public_key_digest.bin + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32 EFUSE BIT CONFIG] +jtag_disable = False +dl_encrypt_disable = False +dl_decrypt_disable = False +dl_cache_disable = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c2/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c2/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c2/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c2/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32c2/security.conf new file mode 100644 index 000000000..fd93e1068 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c2/security.conf @@ -0,0 +1,25 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 0 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32C* EFUSE BIT CONFIG] +dis_pad_jtag = False +dis_direct_boot = False +dis_download_icache = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c2/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c2/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c2/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c2/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32c2/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c2/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c3/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c3/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c3/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c3/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32c3/security.conf new file mode 100644 index 000000000..b2a1f86c6 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c3/security.conf @@ -0,0 +1,27 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 1 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32C* EFUSE BIT CONFIG] +dis_usb_jtag = False +dis_pad_jtag = False +soft_dis_jtag = 7 +dis_direct_boot = False +dis_download_icache = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c3/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c3/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c3/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c3/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32c3/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c3/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c6/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c6/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c6/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c6/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32c6/security.conf new file mode 100644 index 000000000..b2a1f86c6 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c6/security.conf @@ -0,0 +1,27 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 1 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32C* EFUSE BIT CONFIG] +dis_usb_jtag = False +dis_pad_jtag = False +soft_dis_jtag = 7 +dis_direct_boot = False +dis_download_icache = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c6/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32c6/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c6/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32c6/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32c6/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32c6/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32h2/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32h2/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32h2/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32h2/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32h2/security.conf new file mode 100644 index 000000000..d1070b7d8 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32h2/security.conf @@ -0,0 +1,26 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 1 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32H2 EFUSE BIT CONFIG] +dis_direct_boot = False +soft_dis_jtag = False +dis_pad_jtag = False +dis_usb_jtag = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32h2/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32h2/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32h2/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32h2/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32h2/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32h2/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s2/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32s2/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s2/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s2/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32s2/security.conf new file mode 100644 index 000000000..1b934d45f --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s2/security.conf @@ -0,0 +1,28 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 1 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32S2 EFUSE BIT CONFIG] +hard_dis_jtag = False +soft_dis_jtag = False +dis_legacy_spi_boot = False +dis_boot_remap = False +dis_download_icache = False +dis_download_dcache = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s2/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32s2/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s2/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s2/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32s2/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s2/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s3/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32s3/multi_download.conf new file mode 100644 index 000000000..264959675 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s3/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s3/security.conf b/dist/flash_download_tool_3.9.6/configure/esp32s3/security.conf new file mode 100644 index 000000000..acc04f562 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s3/security.conf @@ -0,0 +1,29 @@ +[SECURE BOOT] +secure_boot_en = False +public_key_digest_path = .\secure\public_key_digest.bin +public_key_digest_block_index = 0 + +[FLASH ENCRYPTION] +flash_encryption_en = False +reserved_burn_times = 0 +flash_encrypt_key_block_index = 1 + +[SECURE OTHER CONFIG] +flash_encryption_use_customer_key_enable = False +flash_encryption_use_customer_key_path = .\secure\flash_encrypt_key.bin +flash_force_write_enable = False + +[FLASH ENCRYPTION KEYS LOCAL SAVE] +keys_save_enable = False +encrypt_keys_enable = False +encrypt_keys_aeskey_path = + +[ESP32S3 EFUSE BIT CONFIG] +dis_usb_jtag = False +hard_dis_jtag = False +soft_dis_jtag = 7 +dis_usb_otg_download_mode = False +dis_direct_boot = False +dis_download_icache = False +dis_download_dcache = False + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s3/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp32s3/spi_download.conf new file mode 100644 index 000000000..ebc596813 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s3/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 2 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp32s3/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp32s3/utility.conf new file mode 100644 index 000000000..8f8e1facf --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp32s3/utility.conf @@ -0,0 +1,16 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + +[ESP32_EFUSE_CONFIG] +config_voltage = OFF + diff --git a/dist/flash_download_tool_3.9.6/configure/esp8266/hspi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp8266/hspi_download.conf new file mode 100644 index 000000000..c65baad16 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp8266/hspi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 0 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp8266/multi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp8266/multi_download.conf new file mode 100644 index 000000000..689dcd206 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp8266/multi_download.conf @@ -0,0 +1,161 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = ./bin/ + +[LOCK] +lock_setting_password = + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 0 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 +autostart2 = 0 +com_port2 = +baudrate2 = 0 +checkmac2 = 1 +autostart3 = 0 +com_port3 = +baudrate3 = 0 +checkmac3 = 1 +autostart4 = 0 +com_port4 = +baudrate4 = 0 +checkmac4 = 1 +autostart5 = 0 +com_port5 = +baudrate5 = 0 +checkmac5 = 1 +autostart6 = 0 +com_port6 = +baudrate6 = 0 +checkmac6 = 1 +autostart7 = 0 +com_port7 = +baudrate7 = 0 +checkmac7 = 1 +autostart8 = 0 +com_port8 = +baudrate8 = 0 +checkmac8 = 1 +autostart9 = 0 +com_port9 = +baudrate9 = 0 +checkmac9 = 1 +autostart10 = 0 +com_port10 = +baudrate10 = 0 +checkmac10 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + +[STATISTICS] +pass1 = 0 +fail1 = 0 +pass2 = 0 +fail2 = 0 +pass3 = 0 +fail3 = 0 +pass4 = 0 +fail4 = 0 +pass5 = 0 +fail5 = 0 +pass6 = 0 +fail6 = 0 +pass7 = 0 +fail7 = 0 +pass8 = 0 +fail8 = 0 +pass9 = 0 +fail9 = 0 +pass10 = 0 +fail10 = 0 + diff --git a/dist/flash_download_tool_3.9.6/configure/esp8266/spi_download.conf b/dist/flash_download_tool_3.9.6/configure/esp8266/spi_download.conf new file mode 100644 index 000000000..c65baad16 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp8266/spi_download.conf @@ -0,0 +1,100 @@ +[EFUSE CHECK] +efuse_mode = 1 +efuse_err_halt = 1 + +[MULTI_UI_CONFIG] +multi_col = 2 + +[DOWNLOAD PATH] +file_sel0 = 0 +file_path0 = +file_flag0 = False +file_offset0 = +file_sel1 = 0 +file_path1 = +file_flag1 = False +file_offset1 = +file_sel2 = 0 +file_path2 = +file_flag2 = False +file_offset2 = +file_sel3 = 0 +file_path3 = +file_flag3 = False +file_offset3 = +file_sel4 = 0 +file_path4 = +file_flag4 = False +file_offset4 = +file_sel5 = 0 +file_path5 = +file_flag5 = False +file_offset5 = +file_sel6 = 0 +file_path6 = +file_flag6 = False +file_offset6 = +file_sel7 = 0 +file_path7 = +file_flag7 = False +file_offset7 = +file_sel8 = 0 +file_path8 = +file_flag8 = False +file_offset8 = +file_sel9 = 0 +file_path9 = +file_flag9 = False +file_offset9 = +file_sel10 = 0 +file_path10 = +file_flag10 = False +file_offset10 = +file_sel11 = 0 +file_path11 = +file_flag11 = False +file_offset11 = +file_sel12 = 0 +file_path12 = +file_flag12 = False +file_offset12 = +file_sel13 = 0 +file_path13 = +file_flag13 = False +file_offset13 = +default_path = D:\DOWNLOAD_TOOL\ÏÂÔØ¹¤¾ß\release\3.9.6 + +[FLASH_CRYSTAL] +spicfgdis = 1 +spispeed = 0 +spimode = 0 + +[DOWNLOAD] +erase_button_en = True +autostart1 = 0 +com_port1 = +baudrate1 = 0 +checkmac1 = 1 + +[LOG_CHECK] +log_check_enable = False +log_check_baud = 115200 +log_check_str = 1.0.0 +log_check_delaytime = 3 +log_check_timeout = 3 +log_check_cmd_str = AT+GMR +log_check_enable_cmd = False + +[MAC_SAVE] +mac_save_enable = False + +[ESPTOOL_PARAM] +after = no_reset +before = default_reset +compress = True +flash_size = keep +no_stub = False +verify = True +flash_freq = keep +flash_mode = keep + diff --git a/dist/flash_download_tool_3.9.6/configure/esp8266/utility.conf b/dist/flash_download_tool_3.9.6/configure/esp8266/utility.conf new file mode 100644 index 000000000..fb5988fb9 --- /dev/null +++ b/dist/flash_download_tool_3.9.6/configure/esp8266/utility.conf @@ -0,0 +1,14 @@ +[LOG_LEVEL] +utility_log_level = ERROR +spi_log_level = ERROR +hspi_log_level = ERROR +multi_log_level = ERROR + +[MAC_SAVE] +mac_save_enable = False + +[SECTOR_PROTECT] +sector_protect_enable = False +sector_protect_start = 0xe000 +sector_protect_end = 0x3000 + diff --git a/dist/flash_download_tool_3.9.6/doc/Flash_Download_Tool__cn.pdf b/dist/flash_download_tool_3.9.6/doc/Flash_Download_Tool__cn.pdf new file mode 100644 index 000000000..b25d319ff Binary files /dev/null and b/dist/flash_download_tool_3.9.6/doc/Flash_Download_Tool__cn.pdf differ diff --git a/dist/flash_download_tool_3.9.5/doc/Flash_Download_Tool__en.pdf b/dist/flash_download_tool_3.9.6/doc/Flash_Download_Tool__en.pdf similarity index 100% rename from dist/flash_download_tool_3.9.5/doc/Flash_Download_Tool__en.pdf rename to dist/flash_download_tool_3.9.6/doc/Flash_Download_Tool__en.pdf diff --git a/dist/flash_download_tool_3.9.5/doc/release_note.txt b/dist/flash_download_tool_3.9.6/doc/release_note.txt similarity index 61% rename from dist/flash_download_tool_3.9.5/doc/release_note.txt rename to dist/flash_download_tool_3.9.6/doc/release_note.txt index 5f96d588d..e296315dd 100644 --- a/dist/flash_download_tool_3.9.5/doc/release_note.txt +++ b/dist/flash_download_tool_3.9.6/doc/release_note.txt @@ -1,3 +1,8 @@ +3.9.6: +* support ESP32/ESP32H2/ESP32C6/ESP32C2/ESP32S2 secure boot version2 and flash encryption +* config DUT number in multiconfig file ,up to 20pcs +* update secure config file, see docs in detail + 3.9.5: * support esp32-h2 * support erase button disable diff --git a/dist/flash_download_tool_3.9.5/flash_download_tool_3.9.5.exe b/dist/flash_download_tool_3.9.6/flash_download_tool_3.9.6.exe similarity index 70% rename from dist/flash_download_tool_3.9.5/flash_download_tool_3.9.5.exe rename to dist/flash_download_tool_3.9.6/flash_download_tool_3.9.6.exe index 2ea17a6e1..3895e175e 100644 Binary files a/dist/flash_download_tool_3.9.5/flash_download_tool_3.9.5.exe and b/dist/flash_download_tool_3.9.6/flash_download_tool_3.9.6.exe differ diff --git a/docs/builds_overview.py b/docs/builds_overview.py new file mode 100644 index 000000000..204cb4a03 --- /dev/null +++ b/docs/builds_overview.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python + +# builds_overview.py +# +############################################################################################################# +# 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 +# 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: +# 2024-05-04 tonhuisman: Working and documented +# 2024-04-28 tonhuisman: Initial script + +import os +import re +import json + +# Must be a subfolder of source, as the included filenames have a ../ prefix! +basePath = "source/_templates/" + +# Gather data +allBuilds = {} + +# Not mentioned as a build in documentation, implicit +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'} + } + +# Ignore these, not real build sets +excludeBuilds = {'DEVELOPMENT', 'RETIRED'} + +# Plugins not included +excludePlugins = { + 'CLIMATE': {'P007', 'P008', 'P009', 'P017', 'P022', 'P027', 'P030', 'P035', 'P040', 'P041', 'P042', 'P045'}, + 'DISPLAY': {'P070'}, + 'MAX': {'P089'}, + # 'NEOPIXEL': {''}, + 'NORMAL': {'P016', 'P035'}, +} + +# This list determines the order and color of the build sets to include in the generated output +buildColors = { + 'NORMAL': 'green', + 'COLLECTION A': 'yellow', + 'COLLECTION B': 'yellow', + 'COLLECTION C': 'yellow', + 'COLLECTION D': 'yellow', + 'COLLECTION E': 'yellow', + 'COLLECTION F': 'yellow', + 'COLLECTION G': 'yellow', + 'CLIMATE': 'yellow', + 'DISPLAY': 'yellow', + 'ENERGY': 'yellow', + 'IR': 'yellow', + 'IRext': 'yellow', + 'NEOPIXEL': 'yellow', + 'MAX': 'yellow', +} + +# Add/update a single plugin in the list +def addOnePlugin(build, plugin, pluginName): + if not build in allBuilds: + allBuilds[build] = {} + allBuilds[build].update({plugin: pluginName}) + +# Add a plugin to all builds it should go in +def addToAllBuilds(plugin, pluginName, builds:dict): + for b in appendBuilds: + if not b in builds: + builds += {b} + for b in builds: + if b: + includeIt = True + # builds to ignore + if b in excludeBuilds: + includeIt = False + # plugins per build to ignore + if includeIt and b in excludePlugins: + if plugin in excludePlugins[b]: + includeIt = False + if includeIt: + addOnePlugin(b, plugin, pluginName) + # Add in other builds too? + if b in appendAlso: + for n in appendAlso[b]: + if includeIt and n in excludePlugins: + if plugin in excludePlugins[n]: + includeIt = False + # Except when not to be included + if includeIt: + addOnePlugin(n, plugin, pluginName) + +# Parse a single substitution file +def parseSingleSubstitutionFile(fileName): + filepath = os.path.relpath(os.path.join(basePath, fileName), '.') + # print(filepath) # For debugging + pfile = open(filepath, "r") + # Start empty + plugin = "" + pluginName = "" + builds = [] + while True: + line = pfile.readline() + if not line: + 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) + 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 + addToAllBuilds(plugin, pluginName, builds) + plugin = m.group(1) + pluginName = m.group(4) + + if m.group(3) == "_status": + builds = [m.group(4), m.group(5), m.group(6), m.group(7)] + pfile.close() + if plugin != "": # Store last one too + addToAllBuilds(plugin, pluginName, builds) + +# Parse all .. include :: files +def parseSubstitutionFiles(rootFile): + rfile = open(basePath + rootFile, "r") + while True: + line = rfile.readline() + if not line: + break + m = re.search(r"[^:]+::(.*)", line) + fn = m.group(1).strip() + if fn and fn != "": + parseSingleSubstitutionFile(fn) + rfile.close() + +# Sort Plugins on top, anything else below that +def sortPluginsBeforeControllers(pluginid): + if pluginid[0] != 'P': + return pluginid.lower()[0] # Lowercase sorts after uppercase, so P goes first + return pluginid[0] + +# Generate the output +def generateBuildOverview(fileName): + filepath = os.path.relpath(os.path.join(basePath, fileName), '.') + + print('Writing build sets overview to:', filepath) + + output = open(filepath, "w") + output.write('Plugins per build set\n') + output.write('=====================\n') + output.write('\n') + for b in buildColors: + if b in allBuilds: + output.write('Build set: :' + buildColors[b] + ':`' + b + '`\n') + output.write('---------------------------------------------\n') + output.write('\n') + output.write('.. collapse:: Details...\n') + output.write('\n') + output.write(' .. csv-table::\n') + output.write(' :header: "Plugin name", "Plugin number"\n') + output.write(' :widths: 10, 5\n') + output.write('\n') + for p in sorted(allBuilds[b], key=sortPluginsBeforeControllers): + output.write(' ":ref:`' + p + '_page`","' + p + '"\n') + output.write('\n') + output.close() + +# Main entrypoint +print('Parsing substitutions for build sets...') +# Parse all Plugin substitutions +parseSubstitutionFiles('../Plugin/_plugin_substitutions.repl') +# Parse all Controller substitutions +parseSingleSubstitutionFile('../Controller/_controller_substitutions.repl') + +# Generate output +generateBuildOverview('../Plugin/_plugin_sets_overview.repl') + +# print(json.dumps(allBuilds,indent=2,sort_keys=True)) # For debugging diff --git a/docs/make.bat b/docs/make.bat index 4d9eb83d9..0077a9fde 100644 --- a/docs/make.bat +++ b/docs/make.bat @@ -25,6 +25,8 @@ if errorlevel 9009 ( exit /b 1 ) +python builds_overview.py + %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% goto end diff --git a/docs/requirements.txt b/docs/requirements.txt index 90474f4ec..daa701fdc 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,4 +1,7 @@ -Sphinx==4.5.0 +Sphinx==7.* sphinx-autobuild==2021.3.14 sphinx-bootstrap-theme==0.8.1 -recommonmark==0.7.1 \ No newline at end of file +recommonmark==0.7.1 +sphinxcontrib-htmlhelp>=2.0.5 +sphinxcontrib-applehelp>=1.0.8 +sphinx-toolbox>=3.5.0 \ No newline at end of file diff --git a/docs/source/Controller/_controller_substitutions.repl b/docs/source/Controller/_controller_substitutions.repl index 5ee958e39..0683e988a 100644 --- a/docs/source/Controller/_controller_substitutions.repl +++ b/docs/source/Controller/_controller_substitutions.repl @@ -114,7 +114,7 @@ .. |C011_name| replace:: :cyan:`Generic HTTP Advanced` .. |C011_type| replace:: :cyan:`Controller` .. |C011_typename| replace:: :cyan:`Controller - Generic HTTP Advanced` -.. |C011_status| replace:: :yellow:`COLLECTION` +.. |C011_status| replace:: :yellow:`COLLECTION` :yellow:`CLIMATE` .. |C011_github| replace:: C011.cpp .. _C011_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_C011.cpp .. |C011_usedby| replace:: `.` diff --git a/docs/source/ESPEasy/ESPchips.rst b/docs/source/ESPEasy/ESPchips.rst index 76d9dbe00..f821ea9b2 100644 --- a/docs/source/ESPEasy/ESPchips.rst +++ b/docs/source/ESPEasy/ESPchips.rst @@ -10,8 +10,8 @@ ESPEasy does support a number of variants of the processors manufactured by Espr * **ESP32-S2** Has more GPIO pins than the ESP32, but only 1 CPU core. Initial support in ESPEasy added since 2021-09-19. * **ESP32-S3** Support added: 2023-05-03 * **ESP32-C3 / ESP8685** Support added: 2023-05-03 -* **ESP32-C2 / ESP8684** Not yet supported -* **ESP32-C6** Not yet supported +* **ESP32-C2 / ESP8684** Support added: 2023-11-10 +* **ESP32-C6** Support added: 2023-11-10 * **ESP32-H2** Not yet supported @@ -83,7 +83,7 @@ ESPEasy does support a number of variants of the processors manufactured by Espr - 2022 - 2021 - 2021 - * - Status (2023/05) + * - Status (2024/03) - NRND - Mass Production (solo1: NRND) - NRND @@ -91,7 +91,7 @@ ESPEasy does support a number of variants of the processors manufactured by Espr - Mass Production - Mass Production - Mass Production - - Sample + - Mass Production * - Wi-Fi - IEEE 802.11 b/g/n; 2.4 GHz; HT20; up to 72 Mbps - IEEE 802.11 b/g/n; 2.4 GHz; HT20/40; up to 150 Mbps @@ -328,12 +328,12 @@ ESPEasy does support a number of variants of the processors manufactured by Espr - 0 * - Ethernet - 0 - - 1 (RMII) - - 0 - - 0 - - 0 - - 0 - - 0 + - 1 (RMII and SPI) + - 1 (SPI) + - 1 (SPI) + - 1 (SPI) + - 1 (SPI) + - 1 (SPI) - 0 * - TWAI (CAN) - 0 @@ -651,6 +651,8 @@ To support all modes, we simply need to make several versions - 2 MB (Quad SPI) - ``qio_qspi`` +`Table Source `_ + Build versions: @@ -681,7 +683,7 @@ ESP32-C2/ESP8684 Added: 2023/11/10 -The ESP32-C2 is only available with embedded flash and can only be found labelled as "ESP8684". +The ESP32-C2 is only available with embedded flash and can also be found labeled as "ESP8684". It looks like it is aimed to be used in single purpose devices, due to its low GPIO count and only requiring a bare minimum of external parts. @@ -698,9 +700,9 @@ Added: 2023/11/10 The ESP32-C6 seems to be aimed at being used as a gateway for the new Thread protocol and Wi-Fi. -It is the more powerful version of the ESP32-H2 and also includes not only the traditional 2.4 GHz Wi-Fi, but also the new Wi-Fi6 standard on 2.4 GHz. +It is the more powerful version of the ESP32-H2 and also includes not only the traditional 2.4 GHz Wi-Fi, but also the new Wi-Fi6 standard on 2.4 GHz and IEEE 802.15.4 (Zigbee/Thread). Zigbee/Thread not yet supported by ESPEasy (March 2024). -.. note:: Labelled as "unstable" by the Arduino team (as of Nov 2023), preliminary support in ESPEasy +.. note:: Labeled as "unstable" by the Arduino team (as of Nov 2023), preliminary support in ESPEasy ESP32-H2 ======== diff --git a/docs/source/Hardware/Hardware.rst b/docs/source/Hardware/Hardware.rst index 371be7221..7fc5fbec2 100644 --- a/docs/source/Hardware/Hardware.rst +++ b/docs/source/Hardware/Hardware.rst @@ -3,8 +3,6 @@ Hardware page ************* -Overview -======== ESPEasy has some centralized hardware configuration settings, shown in this page, and divided in sections. @@ -55,6 +53,9 @@ ESPEasy has a separate setting for Slow I2C devices, and per I2C device this slo .. image:: Device_I2COptionsShort.png +Added: 2023-11-23 + +A device flag has been added for specific devices to have **Force Slow I2C speed** set by default. After adding the device this option will be checked, but can still be unchecked to use (try) Fast I2C speed (400 kHz). --------------- I2C Multiplexer @@ -200,13 +201,34 @@ To activate a new configuration, a reboot is needed. Ethernet PHY type ^^^^^^^^^^^^^^^^^ -Select the used PHY controller type: +ESP boards can be equiped with Ethernet. +This is more stable and reliable compared to WiFi and allows for better responsiveness. -* LAN8710 (LAN8720 is also supported, but none of the newer features are supported) -* TLK110 -* RTL8201 (since ESP32 IDF 4.4) -* DP83848 (since ESP32 IDF 4.4) -* DM9051 (since ESP32 IDF 4.4) +Actual transfer speed (at least when using RMII) is also higher than can be achieved via WiFi, but this is less of a concern for typical use cases where ESPEasy is used. + +Ethernet chips/boards for ESP32-variant boards exist with 2 types of interfaces to the ESP. + +* RMII interface - Faster actual transfer speeds possible, uses more GPIO pins, only supported on ESP32-classic (and upcoming ESP32-P4). +* SPI interface - (Added: 2024/02) Supported on all ESP32-variants (not all tested) on builds based on ESP-IDF 5.1 + SPI Ethernet adapters do obviously require the SPI interface to be configured. + +N.B. Only ESP32-variant builds with LittleFS support SPI Ethernet. (starting February 2024) + +Supported Ethernet chips: + +* RMII Interface: + * LAN8710 (LAN8720 is also supported, but none of the newer features are supported) + * TLK110 + * RTL8201 (since ESP32 IDF 4.4) + * JL1101 (since ESP32 IDF 4.4) + * DP83848 (since ESP32 IDF 4.4) + * KSZ8041 (since ESP32 IDF 4.4) + * KSZ8081 (since ESP32 IDF 4.4) + +* SPI Interface: (since ESP32 IDF 5.1) + * DM9051 + * W5500 + * KSZ8851 .. note:: The LAN8710 and LAN8720 are also available with an "A" suffix. These are the same chips, only produced after the brand SMSC was taken over by Microchip Technology. @@ -216,19 +238,22 @@ Ethernet PHY Address The PHY address depends on the hardware and the PHY configuration. On some chips, like the LAN8720, the board designer may set this address by pulling some pins either high or low at power on. -In theory, one could use multiple PHY adapters on the same RMII bus, but this is not supported by ESPEasy. +In theory, one could use multiple PHY adapters on the same RMII/SPI bus, but this is (currently) not supported by ESPEasy. * Espressif's Ethernet board with TLK110 PHY use PHY address 31. * Common Waveshare LAN8720 PHY breakout board (and clones) use PHY address 1. * Olimex ESP32 EVB REV B IoT LAN8710 PHY Board with CAN use PHY address 0. * Other LAN8720 breakouts often use PHY address 0. +* ETH01-EVO (ESP32-C3 based board) uses PHY address 1. If the PHY address is incorrect then the EMAC will initialise but all attempts to read/write configuration registers on the PHY will fail. N.B. There is support for an auto detect of this PHY address, by setting it to -1, but at least on the LAN8720 this does not seem to work. -GPIO pins -^^^^^^^^^ +RMII Ethernet +^^^^^^^^^^^^^ + +As mentioned above, the RMII interface is only present on ESP32-classic (and is mentioned on the announced ESP32-P4). RMII PHY SMI Wiring """"""""""""""""""" @@ -278,7 +303,43 @@ Apart from these GPIO pins, there is a number of other pins reserved on the ESP3 Since these GPIO pin assignments cannot be changed, it is also not needed to configure them. However, they also cannot be used when *RMII PHY* is used. -.. include:: ../Reference/Ethernet_PHY_ESP32.rst +.. include:: ../Reference/RMII_Ethernet_PHY_ESP32.rst + +RMII Ethernet ESP32 Boards +"""""""""""""""""""""""""" + +.. include:: ../Reference/RMII_Ethernet_ESP32_boards.rst + + +SPI Ethernet +^^^^^^^^^^^^ + +(Added: 2024/02) + +As mentioned above, these SPI based Ethernet interfaces require the SPI interface to be configured. + +The SPI bus can be shared, but the SPI Ethernet chips are a bit specific about the used frequency. +Currently the default SPI frequency of 20 MHz is used, so not all other SPI devices may work together with SPI Ethernet. + +Some boards like the ETH01-EVO (ESP32-C3 based) do not even have the SPI bus pins made accesible. + +.. note:: Switching to ECO mode can sometimes result in an unreachable node when using SPI Ethernet. After a reboot the node works just fine again (in ECO mode). This is currently being investigated. + + +GPIO Configuration +"""""""""""""""""" + +* CS pin: Just as any SPI device, it needs a CS pin to tell the device it is being addressed. +* IRQ/INT pin: Allows the Ethernet chip to signal the ESP about new data. (Optional for W5500) +* RST pin: ESP will try to reset the Ethernet adapter during boot. Not all boards may have this wired. + + +SPI Ethernet ESP32 Boards +""""""""""""""""""""""""" + +.. include:: ../Reference/SPI_Ethernet_ESP32_boards.rst + + Ethernet with PoE ^^^^^^^^^^^^^^^^^ @@ -288,13 +349,33 @@ Some ethernet boards support Power over Ethernet (PoE), so only a single (ethern For Olimex boards in the ESP32-POE range, the supplier has documented this warning: .. warning:: - **Important notice**: Olimex ESP32-PoE has **no galvano isolation** from Ethernet's power supply, when you program the board via the micro USB connector the Ethernet cable should be disconnected (if you have power over the Ethernet cable)! + **Important notice**: Olimex ESP32-PoE has **no galvanic isolation** from Ethernet's power supply, when you program the board via the micro USB connector the Ethernet cable should be disconnected (if you have power over the Ethernet cable)! Consider using Olimex USB-ISO to protect your computer and board from accidental short circuit. Also consider instead using Olimex ESP32-PoE-ISO board, which *is* insulated. Most likely, this warning is applicable to other brands as well. +Ethernet Isolation +^^^^^^^^^^^^^^^^^^ + +Most Ethernet RJ45 phy (the connector on the PCB) have isolation transformers in them. +This does isolate the TX/RX pins to make sure there is no direct connection between the long cables and the Ethernet controller chip. +The isolation does protect the Ethernet chip from picked up high voltage spikes and ESD surges when inserting the Ethernet cable. +However not all ESP boards with Ethernet have these installed. + +Apart from the isolation of the TX/RX pins the metallic enclosure of the phy should also be isolated from the rest of the circuit of the ESP board. +Typically this is done by connecting the metal enclosure of the phy via a capacitor to GND of the rest of the circuit. +A lot of boards with Ethernet have these directly connected to GND, which may impose a problem when connecting the ESP board to your PC. + +.. warning:: + **Important notice**: For ESP boards with Ethernet which need debugging, never use Ethernet cables with metal shielding on the Ethernet connector. + +Using shielded Ethernet cable will connect the metal shield of the RJ45 phy to the ground of the switch and this may be connected to other appliances which may be badly grounded. +This will add a significant voltage offset between the ESP board and your PC while debugging. +Such a high voltage is very likely to destroy electronics. + + ------------------- GPIO boot states ------------------- diff --git a/docs/source/Participate/ProjectStructure.rst b/docs/source/Participate/ProjectStructure.rst index b25676a9c..8e7b61199 100644 --- a/docs/source/Participate/ProjectStructure.rst +++ b/docs/source/Participate/ProjectStructure.rst @@ -29,7 +29,7 @@ Below a list of the most important directories and files used in this project. * ``platformio.ini`` Configuration file for PlatformIO to define various build setups. * ``uncrustify.cfg`` Configuration file for Uncrustify, to format source code using some uniform formatting rules. * ``requirements.txt`` List of used Python libraries and their version (result of ``pip freeze`` with Virtual env active) -* ``esp32_partition_app1810k_spiffs316k.csv`` Used partition layout in ESP32 builds. +* ``boards/partitions/esp32_partition_app1810k_spiffs316k.csv`` Used partition layout in ESP32 4M builds. ESPEasy src dir @@ -90,11 +90,11 @@ The filename is quite descriptive: Build Type ---------- -Build type can be: (differ in included plugins) +Build type can be: (differences in included plugins) -* normal => Only Stable plugins and controllers -* test => Stable + Testing (split into multiple sets, A/B/C/D) -* max => All available plugins +* normal => Only Stable plugins and controllers +* collection => Stable + Collection (split into multiple sets, A/B/C/D/E/F/G) +* max => All available plugins and features There is also a number of special builds: @@ -111,8 +111,10 @@ ESP Chip Type * ``ESP8285`` Supported in ``ESP8266`` builds. Used in some Sonoff modules. This chip has embedded flash, so no extra flash chip. * ``ESP32`` Allows for more memory and more GPIO pins. * ``ESP32-S2`` Newer version of ESP32. Has even more GPIO pins, but some specific features of ESP32 were removed. -* ``ESP32-S3`` Not yet available. -* ``ESP32-C3`` Support will be added soon. +* ``ESP32-S3`` Newer version of ESP32 and ESP32-S2. Has even more GPIO pins, some specific features of ESP32 were removed, and some design choices of ESP32-S2 are reverted and implemented differently. +* ``ESP32-C2`` Preliminary supported. Cheaper variant of ESP32-C3, and also an ESP8266 replacement. Available as pin-compatible module for ESP8266. Single core, and max 120 MHz clock speed. +* ``ESP32-C3`` Intended as a replacement for ESP8266, using ESP32 technology, though single-core and with limited clock speed (160 MHz, some models 120 MHz). +* ``ESP32-C6`` Preliminary supported. Will allow connectivity with IEEE 802.15.4 (Thread/Zigbee) wireless protocol. Memory Size and Partitioning ---------------------------- @@ -124,23 +126,27 @@ Memory Size and Partitioning * ``4M1M`` 4 MB flash modules with 1 MB filesystem (usually SPIFFS) * ``4M2M`` 4 MB flash modules with 2 MB filesystem (usually SPIFFS) * ``4M316k`` 4 MB flash modules using 1.8 MB sketch size, with 316 kB filesystem (usually SPIFFS) (for ESP32) +* ``8M1M`` 8 MB flash modules using 3.5MB sketch size, with 1 MB filesystem (LittleFS) (ESP32 only a.t.m.) * ``16M1M`` 16 MB flash modules using 4MB sketch size, with 1 MB filesystem (usually SPIFFS) (ESP32 only a.t.m.) -* ``16M2M`` 16 MB flash modules using 4MB sketch size, with 2 MB filesystem (LittleFS) (ESP32 only a.t.m.) * ``16M8M`` 16 MB flash modules using 4MB sketch size, with 8 MB filesystem (LittleFS) (ESP32 only a.t.m.) Optional build options ---------------------- -* ``LittleFS`` Use LittleFS instead of SPIFFS filesystem (SPIFFS is unstable > 2 MB) +* ``LittleFS`` Use LittleFS instead of SPIFFS filesystem (SPIFFS is unstable \> 2 MB and no longer available from IDF 5.x) * ``VCC`` Analog input configured to measure VCC voltage * ``OTA`` Arduino OTA (Over The Air) update feature enabled * ``Domoticz`` Only Domoticz controllers (HTTP+MQTT) and plugins included * ``FHEM_HA`` Only FHEM/OpenHAB/Home Assistant (MQTT) controllers and plugins included * ``lolin_d32_pro`` Specific Lolin hardware options enabled +* ``PSRAM`` Additional PSRAM support (ESP32 only) +* ``OPI`` Flash via OPI protocol support (ESP32 only) +* ``QIO`` Flash via QIO protocol support (ESP32 only) +* ``CDC`` CDC Serial (built-in USB) support (ESP32 only) * ``ETH`` Ethernet interface enabled (ESP32 only) -Please note that the performance of 14MB SPIFFS (16M flash modules) is really slow. +Please note that the performance of 14MB SPIFFS (16M flash ESP8266 modules) is really slow. All file access takes a lot longer and since the settings are also read from flash, the entire node will perform slower. See `Arduino issue - SPIFFS file access slow on 16/14M flash config `_ @@ -150,7 +156,7 @@ Special memory partitioning: * ``2M256`` 2 MB flash modules (e.g. Shelly1/WROOM02) with 256k SPIFFS (only core 2.5.0 or newer) * ``4M316k`` For ESP32 with 4MB flash, sketch size is set to 1.8 MByte (default: 1.4 MByte) -* ``4M1M`` 4MB flash, 1 MB SPIFFS. Default layout for 4MB flash. +* ``4M1M`` 4MB flash, 1 MB SPIFFS. Default layout for ESP8266 4MB flash. * ``4M2M`` 4MB flash, 2 MB SPIFFS. Introduced in October 2019. Only possible with core 2.5.2 or newer. .. warning:: @@ -165,7 +171,7 @@ Difference between .bin and .bin.gz Starting on esp8266/Arduino core 2.7.0, it is possible to flash images that have been compressed using GZip. -Please note that this only can be used on installs already running a very recent build. +Please note that this only can be used on installs already running a recent build. This also means we still need to update the 2-step updater to support .bin.gz files. @@ -182,17 +188,17 @@ There are several builds for ESP32: * ``normal_ESP32_4M316k`` Build using the "stable" set of plugins for ESP32 * ``normal_ESP32_4M316k_ETH`` Build using the "stable" set of plugins for ESP32, with support for an on-board Ethernet controller * ``custom_ESP32_4M316k`` Build template using either the plugin set defined in ``Custom.h`` or ``tools/pio/pre_custom_esp32.py`` -* ``test_A_ESP32_4M316k`` Build using the "testing" set "A" of plugins for ESP32 -* ``test_B_ESP32_4M316k`` Build using the "testing" set "B" of plugins for ESP32 -* ``test_C_ESP32_4M316k`` Build using the "testing" set "C" of plugins for ESP32 -* ``test_D_ESP32_4M316k`` Build using the "testing" set "D" of plugins for ESP32 -* ``test_A_ESP32-wrover-kit_4M316k`` A build for ESP32 including build flags for the official WRover test kit. +* ``collection_A_ESP32_4M316k`` Build using the "Collection" set "A" of plugins for ESP32 +* ``collection_B_ESP32_4M316k`` Build using the "Collection" set "B" of plugins for ESP32 +* ``collection_C_ESP32_4M316k`` Build using the "Collection" set "C" of plugins for ESP32 +* ``collection_D_ESP32_4M316k`` Build using the "Collection" set "D" of plugins for ESP32 +* ``collection_A_ESP32-wrover-kit_4M316k`` A build for ESP32 including build flags for the official WRover test kit. * ``max_ESP32_16M8M_LittleFS`` Build using all available plugins and controllers for ESP32 with 16 MB flash (some lolin_d32_pro boards) Since ESP32 does have its flash partitioned in several blocks, we have 2 bin files of each ESP32 build, f.e.: -* ``test_D_ESP32_4M316k.bin`` Use for OTA upgrades. -* ``test_D_ESP32_4M316k.factory.bin`` Use on clean nodes as initial inistall. +* ``collection_D_ESP32_4M316k.bin`` Use for OTA upgrades. +* ``collection_D_ESP32_4M316k.factory.bin`` Use on clean nodes as initial inistall. The binary with ``.factory`` in the name must be flashed on a new node, via the serial interface of the board. This flash must be started at address 0. @@ -208,7 +214,8 @@ To help recover from a bad flash, there are also blank images included. * ``blank_1MB.bin`` * ``blank_2MB.bin`` * ``blank_4MB.bin`` +* ``blank_8MB.bin`` * ``blank_16MB.bin`` When the wrong image is flashed, or the module behaves unstable, or is in a reboot loop, -flash these images first and then the right image for the module. +flash these images first to clear out any remaining or hidden settings (Arduino framework...) and then the right image for the module. diff --git a/docs/source/Plugin/AdaGFX_commands.repl b/docs/source/Plugin/AdaGFX_commands.repl index 6ad286eb0..cf7b1ee1f 100644 --- a/docs/source/Plugin/AdaGFX_commands.repl +++ b/docs/source/Plugin/AdaGFX_commands.repl @@ -81,7 +81,7 @@ Some display types may limit or extend the maximum accepted size. " " - ``,txtfull,,,,,, ,,`` + ``,txtfull,,,,,,`` ``,,`` "," Write text at position X/Y with all options. Depending on the setting **Text Coordinates in col/row**, these coordinates are pixels (default) or column/rows. @@ -116,6 +116,7 @@ * *sevenseg24* A rather large 7-segment 21 * 48 font * *sevenseg18* A somewhat less large 7-segment 16 * 34 font * *freesans* A sans-serif 10 * 21 font + * *tomthumb* A small 3 * 5 font, for use on a 5x29 NeoPixel display. Not available in limited builds. Usually disabled fonts: (can be enabled in a Custom build, default enabled in the MAX builds) @@ -136,7 +137,11 @@ * *whiterabbit16pt* A modern 16 * 20 font * *robotomono16pt* A modern 16 * 20 font * *whiterabbit18pt* A modern 18 * 22 font + * *sevenseg18b* A better 18 * 22 font, where the 1 isn't proportionally spaced, but doesn't have much non-alphanumeric characters + * *lcd14cond18pt* A 14 segment, 18pt, LCD-like font * *whiterabbit20pt* A modern 20 * 24 font + * *sevenseg24b* A better 24 * 34 font, where the 1 isn't proportionally spaced, but doesn't have much non-alphanumeric characters + * *lcd14cond24pt* A 14 segment, 24pt, LCD-like font Standard disabled fonts (even on MAX builds), that can be enabled in a custom build: @@ -148,6 +153,8 @@ * *robotocond16pt* A modern 16 * 20 font (Roboto Condensed, proportionally spaced) NB: Roboto is used as the default Android font since Android 4.1, and very readable, even when using small fonts on a small display. + + NB2: The 18pt fonts are included by default in the ESP32 builds, using this helper. " " ``,l,,,,,`` @@ -252,7 +259,7 @@ The file will be read from SD-card, when available, and the bmp file is not found on the internal file storage. " " - ``,btn,,,,,,,,,,, ,,,,, ,,,, ,`` + ``,btn,,,,,,,,,,,`` ``,,,,,`` ``,,,,`` ``,`` "," As a companion to the ESPEasy_TouchHelper, the AdafruitGFX_helper takes care of drawing button objects via this subcommand. diff --git a/docs/source/Plugin/AdaGFX_values.repl b/docs/source/Plugin/AdaGFX_values.repl index d27bb00d3..e7e674151 100644 --- a/docs/source/Plugin/AdaGFX_values.repl +++ b/docs/source/Plugin/AdaGFX_values.repl @@ -1,94 +1,94 @@ -.. csv-table:: - :escape: ^ - :widths: 20, 30 - - " - Generic variables, available for all ``AdafruitGFX_Helper`` enabled plugins, currently: :ref:`P095_page`, :ref:`P096_page`, :ref:`P116_page`, :ref:`P131_page` and :ref:`P141_page`. - "," - Generic notes: - - * If an argument has comma's or spaces, then that part should be 'wrapped' in either double quotes ``^"``, single quotes ``^'`` or back-ticks ``^```. - * The ```` part is the name of the Device task. - * True(1)/False(0) values can optionally return -1 to indicate an invalid request, like a missing Window Id. - * All sizes, lengths etc. are returned in pixels. - " - " - ``[#win]`` - "," - Get the currently active Window Id, expected range: 0..255. - " - " - ``[#iswin,]`` - "," - Is the request Window Id valid, expected result: 1 = true, 0 = false. - " - " - ``[#width]`` - "," - Get the width of the currently active Window and rotation, expected range 0... - " - " - ``[#height]`` - "," - Get the height of the currently active Window and rotation, expected range 0... - " - " - ``[#length,^"^"]`` - "," - Get the length of the text in pixels for the current font and text scaling. Needs quotes if text contains space(s), comma(s) or quote(s). - " - " - ``[#textheight,^"^"]`` - "," - Get the height of the text in pixels for the current font and text scaling. Needs quotes if the text contains space(s), comma(s) or quote(s). - " - " - ``[#rot]`` - "," - Get the currently active rotation, expected range 0..3 (0 = 0 degrees, 1 = +90 degrees, 2 = +180 degrees, 3 = +270 degrees). - " - " - ``[#txs]`` - "," - Get the currently active text scaling, expected range 1..10, limited to the max. font scaling allowed for the display. - " - " - ``[#tpm]`` - "," - Get the currently active text print mode, expected range 0..3, see the ``tpm`` subcommand for details. - " - -.. csv-table:: - :escape: ^ - :widths: 20, 10 - - " - Example rules for centering a value (time) in a window: - - .. code:: none - - on centertime do // NB: Comments & extra spaces should be removed to reduce rules size! - if [st7796#iswin,%eventvalue1|2%]=1 // default window: 2 - let,120,[st7796#win] // store current window - st77xx,win,%eventvalue1|2% // switch to window - let,121,[st7796#txs] // store textscaling - st77xx,txs,3 // set text scaling - let,122,[st7796#rot] // store rotation - st77xx,rot,%eventvalue2|0% // set rotation, default: 0 - let,123,([st7796#width]-[st7796#length,%systm_hm%])/2 // (width - textlength)/2 - let,124,([st7796#height]-[st7796#textheight,%systm_hm%])/2 // (height - textheight)/2 - st77xx,txtfull,[int#123],[int#124],3,red,black,%systm_hm% // Display time red on black - st77xx,rot,%v122% // restore rotation - st77xx,txs,%v121% // restore text scaling - st77xx,win,%v120% // restore window - endif - endon - on Clock#Time=All,**:** do - asyncevent,centertime=2 // Update the display every minute - endon - - "," - Display Task is named ``st7796``, using trigger ``st77xx`` - - Usage: ``asyncevent,centertime[=[,]]`` - " +.. csv-table:: + :escape: ^ + :widths: 20, 30 + + " + Generic variables, available for all ``AdafruitGFX_Helper`` enabled plugins, currently: :ref:`P095_page`, :ref:`P096_page`, :ref:`P116_page`, :ref:`P131_page` and :ref:`P141_page`. + "," + Generic notes: + + * If an argument has comma's or spaces, then that part should be 'wrapped' in either double quotes ``^"``, single quotes ``^'`` or back-ticks ``^```. + * The ```` part is the name of the Device task. + * True(1)/False(0) values can optionally return -1 to indicate an invalid request, like a missing Window Id. + * All sizes, lengths etc. are returned in pixels. + " + " + ``[#win]`` + "," + Get the currently active Window Id, expected range: 0..255. + " + " + ``[#iswin.]`` + "," + Is the request Window Id valid, expected result: 1 = true, 0 = false. + " + " + ``[#width]`` + "," + Get the width of the currently active Window and rotation, expected range 0... + " + " + ``[#height]`` + "," + Get the height of the currently active Window and rotation, expected range 0... + " + " + ``[#length.^"^"]`` + "," + Get the length of the text in pixels for the current font and text scaling. Needs quotes if text contains space(s), comma(s) or quote(s). + " + " + ``[#textheight.^"^"]`` + "," + Get the height of the text in pixels for the current font and text scaling. Needs quotes if the text contains space(s), comma(s) or quote(s). + " + " + ``[#rot]`` + "," + Get the currently active rotation, expected range 0..3 (0 = 0 degrees, 1 = +90 degrees, 2 = +180 degrees, 3 = +270 degrees). + " + " + ``[#txs]`` + "," + Get the currently active text scaling, expected range 1..10, limited to the max. font scaling allowed for the display. + " + " + ``[#tpm]`` + "," + Get the currently active text print mode, expected range 0..3, see the ``tpm`` subcommand for details. + " + +.. csv-table:: + :escape: ^ + :widths: 20, 10 + + " + Example rules for centering a value (time) in a window: + + .. code:: none + + on centertime do // NB: Comments & extra spaces should be removed to reduce rules size! + if [st7796#iswin.%eventvalue1|2%]=1 // default window: 2 + let,120,[st7796#win] // store current window + st77xx,win,%eventvalue1|2% // switch to window + let,121,[st7796#txs] // store textscaling + st77xx,txs,3 // set text scaling + let,122,[st7796#rot] // store rotation + st77xx,rot,%eventvalue2|0% // set rotation, default: 0 + let,123,([st7796#width]-[st7796#length.%systm_hm%])/2 // (width - textlength)/2 + let,124,([st7796#height]-[st7796#textheight.%systm_hm%])/2 // (height - textheight)/2 + st77xx,txtfull,[int#123],[int#124],3,red,black,%systm_hm% // Display time red on black + st77xx,rot,%v122% // restore rotation + st77xx,txs,%v121% // restore text scaling + st77xx,win,%v120% // restore window + endif + endon + on Clock#Time=All,**:** do + asyncevent,centertime=2 // Update the display every minute + endon + + "," + Display Task is named ``st7796``, using trigger ``st77xx`` + + Usage: ``asyncevent,centertime[=[,]]`` + " diff --git a/docs/source/Plugin/P000_commands.repl b/docs/source/Plugin/P000_commands.repl index 1a9361d02..d26d7c3d3 100644 --- a/docs/source/Plugin/P000_commands.repl +++ b/docs/source/Plugin/P000_commands.repl @@ -232,17 +232,37 @@ :red:`Internal`"," Run I2C scanner to find connected I2C chips. Output will be sent to the serial port. - ``I2Cscanner`` + ``I2Cscanner[,1]`` + Added: 2024-06-14: + With the optional debug argument ``1`` provided, the returned I2C status (error) code will be listed for *all* tested I2C addresses. + + Added: 2024-06-14: + Scan is performed at the Low I2C speed configured (default 100 kHz). When having an I2C multiplexer configured, all channels of the multiplexer will also be scanned. + Example output: .. code-block:: none - 4500043 : Info : Command: i2cscanner + >i2cscanner + Standard I2C bus I2C : Found 0x3c I2C : Found 0x40 I2C : Found 0x5a + >i2cscanner,1 + Standard I2C bus + I2C : Error 2 at 0x01 + I2C : Error 2 at 0x02 + I2C : Error 2 at 0x03 + ... + I2C : Error 2 at 0x3f + I2C : Found 0x40 + I2C : Error 2 at 0x41 + ... + I2C : Error 2 at 0x7e + I2C : Error 2 at 0x7f + " " Inc"," @@ -363,6 +383,18 @@ ``Name,``" " + Notify"," + :green:`Rules`"," + Trigger a notification. Requires the notifier at the numbered index (1..3) to be enabled. + + Optionally include the message body (quoted) and subject (quoted) to be passed. NB: The buzzer doesn't support a message body or subject. + + Syntax: ``Notify,[,[,]]`` + + Example: + + ``Notify,1,'The temperature is currently [ds#temp] degrees!','Temperature warning'``" + " NTPHost[,]"," :red:`Internal`"," Set the name of the NTP-host @@ -371,6 +403,23 @@ ``NTPHost,""`` or ``NTPHost,`` -> Clear the NTP host so a default host from pool.ntp.org will be used. (As second argument *is* provided but empty.)" " + OWScan"," + :red:`Internal`"," + Scan for & list 1-wire (One Wire = OW) devices on a GPIO pin. + + Syntax: ``owscan,[,]`` + + If no separate TX pin is provided, the RX pin will be used as the TX pin too, as is the normal behavior for 1-wire communication. The separate TX pin is used on a Shelly device when adding the Shelly Temperature add-on (RX = GPIO-0 and TX = GPIO-3). + + Example: + + .. code-block:: none + + >owscan,5 + 01-6e-56-8c-01-00-00-84 [DS1990A] + + " + " Password"," :red:`Internal`"," Set the password of the unit. @@ -469,10 +518,16 @@ " Publish"," :green:`Rules`"," - Send command using MQTT broker service. Uses the first enabled MQTT Controller. + Send command using MQTT broker service. The 'Will Retain' option as configured in the MQTT Controller settings is used. Uses the first enabled MQTT Controller. ``Publish,[,]``" " + PublishR"," + :green:`Rules`"," + Send command using MQTT broker service, with the MQTT 'Will Retain' flag enabled. Uses the first enabled MQTT Controller. + + ``PublishR,[,]``" + " PutToHTTP"," :green:`Rules`"," *Syntax format 1:* diff --git a/docs/source/Plugin/P000_events.repl b/docs/source/Plugin/P000_events.repl index 19f009365..b23bac702 100644 --- a/docs/source/Plugin/P000_events.repl +++ b/docs/source/Plugin/P000_events.repl @@ -196,6 +196,50 @@ Reboot endon + " + " + ``p2pNode#Connected`` + + Added: 2024-05-01 + + Triggered when a new ESPEasy p2p node has been seen. + N.B. Only for nodes with a valid unit ID (not 0) + + Eventvalues: + + - Unit ID + + - Node name + + - Build number/date + "," + + .. code-block:: none + + on p2pNode#Connected do + LogEntry,'ESPEasy p2p node %eventvalue1% added: %eventvalue2% with build %eventvalue3%' + endon + + " + " + ``p2pNode#Disconnected`` + + Added: 2024-05-01 + + Triggered when a ESPEasy p2p node has been removed from the nodes list. + N.B. Only for nodes with a valid unit ID (not 0) + + Eventvalues: + + - Unit ID + "," + + .. code-block:: none + + on p2pNode#Disconnected do + LogEntry,`ESPEasy p2p node %eventvalue1% not seen for a while` + endon + " " ``WiFi#Connected`` @@ -208,6 +252,25 @@ SendToHTTP,url.com,80,/report.php?hash=123abc456&t=[temp2#out] endon + " + " + ``WiFi#Disconnected`` + Triggered when the ESP has disconnected from Wi-Fi. + "," + + .. code-block:: none + + On WiFi#Disconnected Do + LongPulse,2,1,1,1,-1 // 0.5 Hz flashing of WiFi led + Endon + + .. code-block:: none + + On WiFi#Connected Do + GPIO,2,1 // Turn off WiFi led + SendToHTTP,url.com,80,/report.php?hash=123abc456&t=[temp2#out] + Endon + " " ``WiFi#ChangedAccesspoint`` diff --git a/docs/source/Plugin/P001_commands_GPIO.repl b/docs/source/Plugin/P001_commands_GPIO.repl index 20a0cc061..5f576c15d 100644 --- a/docs/source/Plugin/P001_commands_GPIO.repl +++ b/docs/source/Plugin/P001_commands_GPIO.repl @@ -11,7 +11,7 @@ Supported hardware: |P000_usedby_GPIO| " ``GPIO,,`` - GPIO: 0 ... 16 + GPIO: 0 ... State: @@ -28,12 +28,12 @@ Supported hardware: |P000_usedby_GPIO| " ``GPIOtoggle,`` - GPIO: 0 ... 16 + GPIO: 0 ... "," **Toggle on/off.**. Toggle the current (output) state of the given GPIO pin. - When executed, it changes the pin mode to output. + When executed, it changes the pin mode to output, for output-capable GPIO pins. " " ``LongPulse,,,`` @@ -130,7 +130,9 @@ Supported hardware: |P000_usedby_GPIO| " ``Servo,,,`` - GPIO: 0 ... **15** + ESP8266 GPIO: 0 ... **15** + + ESP32 GPIO: All GPIO pins with output capabilities Servo: 1/2 @@ -149,7 +151,7 @@ Supported hardware: |P000_usedby_GPIO| " ``Monitor,G,`` - GPIO: 0 ... 16 + GPIO: 0 ... "," **To monitor a GPIO state.** By the use of the command you will receive events when the GPIO state of that pin is changed from 1 to 0 and from 0 to 1. @@ -157,7 +159,7 @@ Supported hardware: |P000_usedby_GPIO| " ``UnMonitor,G,`` - GPIO: 0 ... 16 + GPIO: 0 ... "," **To cancel the monitor of a GPIO state.** By the use of the command you will stop receiving events when the GPIO state of that pin is changed from 1 to 0 and from 0 to 1. @@ -165,7 +167,7 @@ Supported hardware: |P000_usedby_GPIO| " ``Status,G,`` - GPIO: 0 ... 16 + GPIO: 0 ... "," **Returns the status of a pin.** By the use of the command you will receive the status of the relevant pin. diff --git a/docs/source/Plugin/P001_commands_RTTTL.repl b/docs/source/Plugin/P001_commands_RTTTL.repl index 7b2e0ef9d..a42d3b0d5 100644 --- a/docs/source/Plugin/P001_commands_RTTTL.repl +++ b/docs/source/Plugin/P001_commands_RTTTL.repl @@ -11,13 +11,16 @@ Supported hardware: |P000_usedby_RTTTL| (Ringtones etc.) " ``tone,,,`` - GPIO: 12 ... 16 + ESP8266 GPIO: 0 ... 16 + + ESP32 GPIO: All GPIO pins with output capabilities Tone: 20 ... 13000 Hz Duration: 100 ... 15000 msec "," - You should try to use GPIO 12...16 since these generally aren't used. + ESP8266: You should try to use GPIO 12...16 since these generally aren't used. + The recommended tone range is 20 Hz ... 13 kHz. Up-to 40 kHz should be possible to generate, but will be inaudible for humans. Frequencies above 30 kHz are not stable and will likely crash the ESP. @@ -30,13 +33,14 @@ Supported hardware: |P000_usedby_RTTTL| (Ringtones etc.) " ``rtttl,,`` - GPIO: 12 ... 16 + ESP8266 GPIO: 0 ... 16 + + ESP32 GPIO: All GPIO pins with output capabilities Value: d=,o=,b=, "," - You should try to use GPIO 12...16 since these generally aren't used by ESP internal functions. - N.B. Playing a tune is blocking for as long as the tune is playing. - + ESP8266: You should try to use GPIO 12...16 since these generally aren't used by ESP internal functions. + Value can be defined like For example: diff --git a/docs/source/Plugin/P004.rst b/docs/source/Plugin/P004.rst index ba8ee3f3a..732e2dfc8 100644 --- a/docs/source/Plugin/P004.rst +++ b/docs/source/Plugin/P004.rst @@ -39,6 +39,13 @@ Supported hardware .. .. include:: P004_events.repl +Get Config Values +----------------- + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P004_config_values.repl + Change log ---------- diff --git a/docs/source/Plugin/P004_DS18b20.rst b/docs/source/Plugin/P004_DS18b20.rst index 9a10a7a7a..9253470fe 100644 --- a/docs/source/Plugin/P004_DS18b20.rst +++ b/docs/source/Plugin/P004_DS18b20.rst @@ -227,7 +227,8 @@ Data Acquisition * **Interval**: How often should the task publish its value (5..15 seconds is normal). Values -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +^^^^^^ + * **Name**: Value Name of temperature indicator. * **Formula**: Optional math conversion. The measured temperature defaults to Celsius. It can be converted to Fahrenheit by entering this formula: @@ -251,7 +252,7 @@ Rules examples .. code-block:: none On Temperature1#Celsius Do - If [Temperature1#Celsius]>37 + If %eventvalue1%>37 NeoPixelAll,255,0,0 //Your body temperature is too high! Else NeoPixelAll,0,255,0 //Body temperature is OK. diff --git a/docs/source/Plugin/P004_config_values.repl b/docs/source/Plugin/P004_config_values.repl new file mode 100644 index 000000000..3c7c4f807 --- /dev/null +++ b/docs/source/Plugin/P004_config_values.repl @@ -0,0 +1,46 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + | ``[#SensorStats..Success]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the number of successful reads for this sensor as shown in the settings page. + " + " + | ``[#SensorStats..Retry]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the number of retries for this sensor as shown in the settings page. + " + " + | ``[#SensorStats..Failed]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the number of failed reads for this sensor as shown in the settings page. + " + " + | ``[#SensorStats..InitFailed]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the number of failed initialization-reads (second retry failed too) for this sensor as shown in the settings page. + " + " + | ``[#SensorStats..Resolution]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the resolution in bits for this sensor as shown in the settings page. + " + " + | ``[#SensorStats..Address]`` + + | ````: The number of the device address within the configuration, range 1..4. + "," + | Returns the formatted Device Address, including the device type, for this sensor as shown in the settings page. + " diff --git a/docs/source/Plugin/P011.rst b/docs/source/Plugin/P011.rst index 024c3dffc..61f7dc14f 100644 --- a/docs/source/Plugin/P011.rst +++ b/docs/source/Plugin/P011.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p01x.repl +.. include:: ../Plugin/_plugin_substitutions_p01x.repl .. _P011_page: |P011_typename| @@ -26,22 +26,83 @@ Supported hardware |P011_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +This plugin supports the Pro Mini Extender, that's a software solution installed on an Arduino Nano, providing 5 available analog input/output pins and 14 digital input/output pins. -.. .. include:: P011_commands.repl +The Pro Mini Extender software has to be manually installed on the Arduino Nano (or a Chinese clone), using the ``MiniProExtender`` software project from this `ESPEasySlaves repository `_ via the Arduino IDE. + +After installing the software on the Arduino Nano, the simplest solution is to power the PME with 3.3V, even if it's specified for 5V, so no level converter is needed for the I2C connection with the ESP, that only allows for 3.3V signal levels. The 3.3V power has to be connected to the 5V pin on the Arduino Nano, **not** on the 3.3V pin, as that's a low-power output-only pin! + +NB: This software can also be installed on other Arduino models, that support I2C, Analog and Digital pins, but this hasn't been actively tested. Depending on the available IO pins, some of the features may not match with the Arduino Nano. + +Device Configuration +-------------------- + +.. image:: P011_DeviceConfiguration.png + +* **Name** A unique name should be entered here. + +* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. + +Sensor +^^^^^^ + +* **Port** Select the port (pin) of the Pro Mini Extender that is addressed by this task. For a *Digital* or *Input (Switch)* the range is 0..13, for an *Analog* input, the available pins are 0..3 and 6..7, as A4 and A5 are the (fixed) I2C pins of the Arduino Nano. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +* **Port Type**: + +.. image:: P011_PortTypeOptions.png + +* *Digital*: An On/Off Input/Output type of port, every **Interval** the pin is read and the state is made available in the **Value** field. + +* *Analog*: Read the current analog value of the port. + +* *Input (switch)*: Act like an input switch, the pin is read every 20 msec, and if the state changes, the new state is reported as an event, with the new value. The **Interval** is ignored when this Port Type is selected. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +**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. + +Values +^^^^^^ + +The single **Value** available holds the last digital, analog or input (switch) state value. + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P011_commands.repl .. Events .. ~~~~~~ .. .. include:: P011_events.repl +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P011_config_values.repl + Change log ---------- .. versionchanged:: 2.0 ... + |added| 2024-03: Add Input (switch) option. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P011_DeviceConfiguration.png b/docs/source/Plugin/P011_DeviceConfiguration.png new file mode 100644 index 000000000..543306198 Binary files /dev/null and b/docs/source/Plugin/P011_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P011_PortTypeOptions.png b/docs/source/Plugin/P011_PortTypeOptions.png new file mode 100644 index 000000000..7ab01b0d1 Binary files /dev/null and b/docs/source/Plugin/P011_PortTypeOptions.png differ diff --git a/docs/source/Plugin/P011_commands.repl b/docs/source/Plugin/P011_commands.repl new file mode 100644 index 000000000..b84f8e7b1 --- /dev/null +++ b/docs/source/Plugin/P011_commands.repl @@ -0,0 +1,53 @@ +.. csv-table:: + :header: "Command", "Extra information" + :widths: 20, 30 + + " + ``extgpio,,<0|1>`` + + ```` : The IO pin on the PME board, range 0..13 for Digital output. + + ``<0|1>`` : Select 0 for Off (low), and 1 for On (high) level output + + "," + Switch the pin to either low or high level output. + " + " + ``extpwm,,`` + + ```` : The IO pin on the PME board, range 0..7 for Analog output. + + ```` : The PWM level, range 0..255 + + "," + Set the Analog pin (0..7) to the PWM level, where 0 = 0% and 255 = 100% of the VCC voltage the PME is running at. + " + " + ``extpulse,,<0|1>,`` + + ```` : The IO pin on the PME board, range 0..13 for Digital output. + + ``<0|1>`` : Select 0 for Off (low), and 1 for On (high) level output + + ```` : The time in milliseconds the IO pin state should be set, after which it is restored in the previous state. + "," + Switch the pin to either low or high level output, and after the duration has passed, return to the previous state. + " + " + ``extlongpulse,,<0|1>,`` + + ```` : The IO pin on the PME board, range 0..13 for Digital output. + + ``<0|1>`` : Select 0 for Off (low), and 1 for On (high) level output + + ```` : The time in **seconds** the IO pin state should be set, after which it is restored in the previous state. + "," + Switch the pin to either low or high level output, and after the duration (seconds!) has passed, return to the previous state. + " + " + ``status,ext,`` + + ```` : The IO pin on the PME board, range 0..13 for Digital output, range 20..27 for the Analog pins A0..A7. + "," + Report the current state/value for the pin selected. + " diff --git a/docs/source/Plugin/P011_config_values.repl b/docs/source/Plugin/P011_config_values.repl new file mode 100644 index 000000000..0d422f460 --- /dev/null +++ b/docs/source/Plugin/P011_config_values.repl @@ -0,0 +1,20 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#D.]`` + + ```` : Range 0..13, corresponding with D0..D13 on the PME. + "," + Returns the current status (0/1) from the requested Digital port (pin) of the Pro Mini Extender. + " + " + ``[#A.]`` + + ```` : Range 0..3 and 6..7, corresponding with A0..A7 on the PME. + "," + Returns the current value (0..1023) at the requested Analog port (pin) of the Pro Mini Extender. + + Port A4 and A5 should be avoided, as that's occupied by the I2C connection at the PME. There is no check in the code to block those pins! + " diff --git a/docs/source/Plugin/P020.rst b/docs/source/Plugin/P020.rst index 1ec3a7609..0373e404d 100644 --- a/docs/source/Plugin/P020.rst +++ b/docs/source/Plugin/P020.rst @@ -26,23 +26,111 @@ Supported hardware |P020_usedby| +Configuration +------------- + +.. image:: P020_DeviceConfiguration.png + +* **Name** In the Name field a unique name should be entered. + +* **Enabled** When unchecked the plugin is not enabled. + Sensor ^^^^^^ See: :ref:`SerialHelper_page` +Device Settings +^^^^^^^^^^^^^^^ -**TODO**: Complete this documentation... +* **TCP Port**: The port for an external network client to read the data from, range 1..65535. The used port number must be unique within the device. -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +* **Baud Rate / Serial config**: See *Serial helper configuration*, above. -.. .. include:: P020_commands.repl +* **Event Processing**: Select the type of data that is expected, to enable correct preprocessing. Available options: -.. Events -.. ~~~~~~ +.. image:: P020_EventProcessingOptions.png -.. .. include:: P020_events.repl +* *None*: No special processing, what is received is sent out to the network client, not generating an event. + +* *Generic*: No special processing, received data is sent to the network client, and an event ``!Serial#``, containing the message as is, is generated. Spaces and newlines are processed as configured below. + +* *RFLink*: Specifically designed for receiving serial data from RFLink devices, it follows this process: + + * Remove the regular RFLink ``20;xx;`` prefix + * Check for prefix ``ESPEASY;``, if found, remove the prefix and generate event ``RFLink#``. The ```` will contain commands to be handled by ESPEasy. + * If previous prefix is not found, generate event ``!RFLink#``, containing the entire received data. Spaces and newlines are processed as configured below. + + *Also see the* **Multiple lines processing** *option, below.* + +* *P1 WiFi Gateway*: Process the data, received from a P1 Energy meter, that does a checksum validation, as included in the message. No separate data values are available in ESPEasy, these are usually handled by Home automation systems that support the P1 protocol via TCP network communication. An event ``#Data`` is generated when a valid P1 packet is received. + + Replacing spaces or newlines should be **disabled** for the P1 protocol data to be handled properly as these replacements will disturb the checksum calculation, and also, the **Multiple lines processing** should be disabled if the data is to be handled as P1 protocol data, as that does contain newlines. + +.. spacer + +* **P1 #data event with message**: When enabled, the *P1 WiFi Gateway* Event Processing option will include the received message. **WARNING** This may easily cause memory overflow exceptions, especially when running on ESP8266 or other low-memory situations! + +When selecting the **Event processing** options *Generic* or *RFLink*, after submitting the page will show extra options for the events generated: + +.. image:: P020_EventOptions.png + +* **Use Serial Port as eventname**: Instead of the default ``!Serial#`` event, the name of the configured serial port will be used: (**Only** available for *Generic* Event processing) + +.. spacer + +* *(Unchecked)* -> ``Serial`` +* *HW Serial0* -> ``serial0`` +* *HW Serial0 swap* -> ``serial0`` +* *HW Serial1* -> ``serial1`` +* *HW Serial2* -> ``serial2`` +* *SW Serial* -> ``serialsw`` +* *I2C Serial* -> ``seriali2c`` +* *USB HWCDC* -> ``serialhwcdc`` +* *USB CDC* -> ``serialcdc`` + +.. spacer + +* **Append Task Number to eventname**: Will append the task number to the event name, f.e. ``Serial8`` or ``RFLink12`` when task 8 or 12 is in use for this plugin. Can be combined with **Use Serial Port as eventname** if that is option is shown, resulting f.e. in ``serial0swap6`` etc. (Only available for *Generic* and *RFLink* Event processing) + +.. spacer + +* **Replace spaces in event by**: Here a single character can be selected to replace all spaces during receiving the data. +* **Replace newlines in event by**: Here a single character can be selected to replace all newlines during receiving the data. When enabled, all linefeeds are replaced, and all carriage returns (if any) are discarded. + +.. image:: P020_ReplaceCharInEventOptions.png + +The available set of replacement characters is ``, ; : . ! ^ | / \`` (comma, semicolon, colon, period, exclamation, caret, pipe, slash and backslash). When set to None, no replacement will be done. + +* **Process events without client**: By default, if no network client is connected, no serial data will be received and processed either. Enabling this option enables receiving data and generating events without a TCP client connected. + +* **Multiple lines processing**: When enabled, all received data will be split at a linefeed and sent out/event generated as separate messages. + +* **RX Receive timeout (mSec)**: If parts of serial data packets are somewhat delayed, but should still be handled as a single message, then the delay to wait for the next part can be configured here. 0 disables the delay. + +* **Reset target after init**: Select a GPIO pin that should be pulled low once during initialization of the plugin, used to synchronize the external serial data source with the plugin. + +* **RX Buffer size (bytes)**: To not overburden the memory use of the plugin, the buffer size is set rather low. Some serial devices, like energy meters may require a larger buffer if the message exceeds this size. Range: 256..1024. + +Led +^^^ + +* **Led enabled**: To enable a *data is being processed* activity led. + +* **Led pin**: The GPIO pin the Led is connected to. + +* **Led inverted**: Iverts the on/off state for the Led. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +The Data Acquisition and Send to Controller settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. *Single event with all values* option is not applicable for this plugin. + + +Commands +~~~~~~~~ + +.. include:: P020_commands.repl Change log ---------- @@ -50,6 +138,8 @@ Change log .. versionchanged:: 2.0 ... + |changed| 2022-12-13: Merge of P020 and P044 to reduce code size and combine features, as P044 was initially started as a spin-off from P020, but not evolved with the P020 features. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P020_DeviceConfiguration.png b/docs/source/Plugin/P020_DeviceConfiguration.png new file mode 100644 index 000000000..086d5bc32 Binary files /dev/null and b/docs/source/Plugin/P020_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P020_EventOptions.png b/docs/source/Plugin/P020_EventOptions.png new file mode 100644 index 000000000..5c5bb82a1 Binary files /dev/null and b/docs/source/Plugin/P020_EventOptions.png differ diff --git a/docs/source/Plugin/P020_EventProcessingOptions.png b/docs/source/Plugin/P020_EventProcessingOptions.png new file mode 100644 index 000000000..a65fcfbbd Binary files /dev/null and b/docs/source/Plugin/P020_EventProcessingOptions.png differ diff --git a/docs/source/Plugin/P020_ReplaceCharInEventOptions.png b/docs/source/Plugin/P020_ReplaceCharInEventOptions.png new file mode 100644 index 000000000..8786ca888 Binary files /dev/null and b/docs/source/Plugin/P020_ReplaceCharInEventOptions.png differ diff --git a/docs/source/Plugin/P020_commands.repl b/docs/source/Plugin/P020_commands.repl new file mode 100644 index 000000000..9827b2a65 --- /dev/null +++ b/docs/source/Plugin/P020_commands.repl @@ -0,0 +1,37 @@ +.. csv-table:: + :header: "Command", "Extra information" + :widths: 20, 30 + + " + ``serialsend,`` + + ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + "," + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + " + " + ``ser2netclientsend,`` + + ````: Text that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the network client. + "," + This command will only send data to the network client, when there is an active connection. + + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the network client. No extra data is added, other than any (system) variables that are included, being replaced. + " + " + ``serialsendmix,''[,...]`` + + ````: Text and/or hex byte(s) (having 0x prefix) that will be sent (nearly) unprocessed. Only the regular variable replacements will be applied before sending the content to the serial port. + "," + + This command requires quotes to be used if spaces or commas are part of the content. + + Any data can be sent, even if it can not be typed in a text content, by specifying that as a separate argument: ``serialsendmix,'text, optionally including spaces or commas',0xXX,'0xXXxx XX,xx-XX:xx'`` + + ``'text, optionally including spaces or commas'``: Any text content to be sent to the serial port. Can contain variables. Quotes are only required if spaces or commas (separators) are used. + + ``0xXX``: A single character in hexadecimal notation (range: 0x00..0xFF), that is appended to the data to send. + + ``'0xXXxx XX,xx-XX:xx'``: A sequence of hexadecimal values (range: 0x00..0xFF), that *can* be separated by a space, comma, dash, colon, semicolon or period, or are just entered adjecent. Only the first 2 characters should be ``0x`` or ``0X``, the rest is interpreted as hex bytes, and appended to the string to send. Quotes are only required if space or comma separators are used. + Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. + " diff --git a/docs/source/Plugin/P029.rst b/docs/source/Plugin/P029.rst index ee0b397f9..cd57c4c6a 100644 --- a/docs/source/Plugin/P029.rst +++ b/docs/source/Plugin/P029.rst @@ -113,6 +113,7 @@ Relevant Settings: * Plugin: Output - Domoticz MQTT Helper * 1st GPIO: 12 (the relay) * IDX: 475 +* Invert On/Off value: To ease the use of this plugin, without the need for rules, an Invert option has been added. This will cause the GPIO pin (and ``Output`` value) to be set to 1 when the Domoticz device is in Off state, and 0 for the On state. Rules @@ -184,6 +185,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2024-03: Add Invert On/Off value option. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P029_Domoticz_Helper.png b/docs/source/Plugin/P029_Domoticz_Helper.png index bdc10673b..afc6c614e 100644 Binary files a/docs/source/Plugin/P029_Domoticz_Helper.png and b/docs/source/Plugin/P029_Domoticz_Helper.png differ diff --git a/docs/source/Plugin/P034.rst b/docs/source/Plugin/P034.rst index 8a889b849..fec0aaee7 100644 --- a/docs/source/Plugin/P034.rst +++ b/docs/source/Plugin/P034.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl +.. include:: ../Plugin/_plugin_substitutions_p03x.repl .. _P034_page: |P034_typename| @@ -21,10 +21,64 @@ Maintainer: |P034_maintainer| Used libraries: |P034_usedlibraries| -Supported hardware ------------------- +.. Supported hardware +.. ------------------ + +.. .. |P034_usedby| + +Introduction +------------ + + +Specifications: + * Temperature (-20 to +60C) + * Humidity (20-95 % rel. humidity) + +Settings +-------- + +.. image:: P034_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. + +I2C options +^^^^^^^^^^^ + +The available settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +This device has no further configuration settings. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **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. + +Values +^^^^^^ + +The names for the values are initially set to a default name, but can be changed if desired. Also, a formula can be entered to re-calculate the value before display/sending to a controller, and the number of decimals can be changed, for Temperature, usually 1 decimal is enough to be displayed (value will be rounded). + + + + +Where to buy +------------ + +.. csv-table:: + :header: "Store", "Link" + :widths: 5, 40 + + "AliExpress","`Link 1 ($) `_" + +|affiliate| -|P034_usedby| .. Commands available .. ^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/Plugin/P034_DHT12.rst b/docs/source/Plugin/P034_DHT12.rst deleted file mode 100644 index 1cf2e3044..000000000 --- a/docs/source/Plugin/P034_DHT12.rst +++ /dev/null @@ -1,71 +0,0 @@ -.. include:: ../Plugin/_plugin_substitutions_p03x.repl -.. _P034_DHT12_page: - -DHT12 -===== - -|P034_typename| -|P034_status| - - -Introduction ------------- - - -Specifications: - * Temperature (-20 to +60C) - * Humidity (20-95 % rel. humidity) - - -Wiring ------- - - -.. code-block:: none - - ESP S8 - GPIO (X) <--> TX - GPIO (X) <--> RX - - - Power - 5.0V <--> VCC - GND <--> GND - - -Setup ------ - - - -Rules examples --------------- - -.. code-block:: none - - //Code below... - - -Indicators (recommended settings) ---------------------------------- - -.. csv-table:: - :header: "Indicator", "Value Name", "Interval", "Decimals", "Extra information" - :widths: 8, 5, 5, 5, 40 - - "XXXXXX", "N/A", "", "", "" - -Where to buy ------------- - -.. csv-table:: - :header: "Store", "Link" - :widths: 5, 40 - - "AliExpress","`Link 1 ($) `_" - -|affiliate| - - -More pictures -------------- diff --git a/docs/source/Plugin/P034_DeviceConfiguration.png b/docs/source/Plugin/P034_DeviceConfiguration.png new file mode 100644 index 000000000..b13623340 Binary files /dev/null and b/docs/source/Plugin/P034_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P037.rst b/docs/source/Plugin/P037.rst index 184813e95..5e3fa5a3c 100644 --- a/docs/source/Plugin/P037.rst +++ b/docs/source/Plugin/P037.rst @@ -190,11 +190,17 @@ Option: Limit events being generated * **Max. # events in event queue**: As a protection against event-overflow this configures a check for the queue-length, so if more than the selected number of events is still in the queue, new events will be discarded until some events are processed, and the remaining is less than this count. When set to 0 this check is disabled. -Option: Modify separater character in events +Option: Modify separator character in events ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * **To replace by comma in event**: Select a character that is to be replaced by a comma, before it is put into the event-queue. There is a limited set of characters that can be replaced, to avoid ending up with malformed events. +Available options: + +.. image:: P037_ReplaceByCommaOptions.png + +The available set of replacement characters is ``! @ $ % ^ & * ; : . | / \`` (exclamation, at, dollar, percent, caret, ampersand, semicolon, colon, period, pipe, slash and backslash). When set to None, no replacement will be done. + This can be used to 'transform' the content of a JSON message so the used separator is a comma, for easier use in rules. Topic Subscriptions diff --git a/docs/source/Plugin/P037_ReplaceByCommaOptions.png b/docs/source/Plugin/P037_ReplaceByCommaOptions.png new file mode 100644 index 000000000..bba17486e Binary files /dev/null and b/docs/source/Plugin/P037_ReplaceByCommaOptions.png differ diff --git a/docs/source/Plugin/P043.rst b/docs/source/Plugin/P043.rst index 0bba448ba..ee619c923 100644 --- a/docs/source/Plugin/P043.rst +++ b/docs/source/Plugin/P043.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p04x.repl +.. include:: ../Plugin/_plugin_substitutions_p04x.repl .. _P043_page: |P043_typename| @@ -21,20 +21,77 @@ Maintainer: |P043_maintainer| Used libraries: |P043_usedlibraries| -Supported hardware ------------------- - |P043_usedby| -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +Description +----------- -.. .. include:: P043_commands.repl +Every minute the set schedule will be checked for a match, and if a value for a scheduled time is set, the GPIO will be set to that state (On/Off) when configured, or when no GPIO is configured and the value is not 0 or empty, an event is generated with the field number that caused the trigger, and another event with the set value if the Number Output Values is set to Dual or higher. -.. Events -.. ~~~~~~ +Configuration +------------- -.. .. include:: P043_events.repl +.. image:: P043_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. + +Sensor +^^^^^^ + +* **GPIO -> Clock event** Select a GPIO that will get the stated as configured for the Day,Time. When a GPIO is configured, the allowed valies for a scheduled time will be empty, Off and On only. + +Device Settings +^^^^^^^^^^^^^^^ + +* **Nr. of Day,Time fields**: Select a number between 1 and 16 for the number of Day,Time fields to be available. Will be applied after the page is submitted. (Default: 8) + +* **Value input On/Off only**: When checked, the selection for Value will be for On/Off (or empty) only, to avoid misconfiguration for less experienced users, and the Output variable response the same as if a GPIO was selected. The default is unchecked. + +.. image:: P043_GPIO_Enabled.png + +* **Day,Time X**: Select the Day and Time that should be checked. For Limited builds the day and time have to be entered manually, for other builds, the Day can be selected from a combobox, and the time can be selected from a predefined list and set to a desired time. + +For Day, the available options are All, Sun, Mon, Tue, Wed, Thu, Fri, Sat, Wrk (workday), Wkd (weekend). + +.. image:: P043_DayOptions.png + +For time, besides the 24h HH:MM notation, there can also be chosen to use ``%sunrise%`` or ``%sunset%``, optionally with an offset in hours, minutes or seconds like ``%sunrise-1h%`` or ``%sunset+30m%``. + +.. image:: P043_TimeOptions.png + +Output Configuration +^^^^^^^^^^^^^^^^^^^^ + +* **Number Output Values**: Select Single (default), Dual, Triple or Quad. The Triple and Quad options aren't actually used in this plugin. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +Values +^^^^^^ + +The default names for the values are named ``Output``, and get a numeric suffix for the Dual, Triple and Quad configuration. + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P043_commands.repl + +Events +~~~~~~ + +.. include:: P043_events.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P043_config_values.repl Change log ---------- @@ -42,6 +99,9 @@ Change log .. versionchanged:: 2.0 ... + |added| 2023-12-16: + Selectors for Day and Time, ``%sunrise%`` and ``%sunset%`` support, ``config`` command and get config values. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P043_DayOptions.png b/docs/source/Plugin/P043_DayOptions.png new file mode 100644 index 000000000..8c23007a3 Binary files /dev/null and b/docs/source/Plugin/P043_DayOptions.png differ diff --git a/docs/source/Plugin/P043_DeviceConfiguration.png b/docs/source/Plugin/P043_DeviceConfiguration.png new file mode 100644 index 000000000..6bdbc0b87 Binary files /dev/null and b/docs/source/Plugin/P043_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P043_GPIO_Enabled.png b/docs/source/Plugin/P043_GPIO_Enabled.png new file mode 100644 index 000000000..23961b6c3 Binary files /dev/null and b/docs/source/Plugin/P043_GPIO_Enabled.png differ diff --git a/docs/source/Plugin/P043_TimeOptions.png b/docs/source/Plugin/P043_TimeOptions.png new file mode 100644 index 000000000..3df8c03f0 Binary files /dev/null and b/docs/source/Plugin/P043_TimeOptions.png differ diff --git a/docs/source/Plugin/P043_commands.repl b/docs/source/Plugin/P043_commands.repl new file mode 100644 index 000000000..17c932855 --- /dev/null +++ b/docs/source/Plugin/P043_commands.repl @@ -0,0 +1,33 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 30, 20 + + " + | ``config,task,,SetTime,,[,]`` + + Change the configuration of the plugin for the timeIndex provided: + + ``config,task``: The Config command to change a setting for a task. Not case-sensitive. + + ````: The name of the task to be changed. + + ``SetTime``: Literal command-text recognized by the plugin to set the time and optional value. Not case-sensitive. + + ````: The Day,Time fields *number* as shown in the UI. Has to be within the allowed range 1..Nr. of Day,Time field setting. + + ````: A time string as can be entered in the UI, a day (All,Sun, Mon,Tue,Wed,Thu,Fri,Sat,Wrk (workday),Wkd (weekend)) and a time (HH:MM or %Sunrise%/%Sunset%). When configuring for sunrise of sunset, an offset in hours, minutes or seconds can be provided by using ``%sunrise-1h%`` or ``%sunset+30m%`` etc. To avoid the sunrise or sunset time of today to be inserted, instead of ``%`` a ``$`` must be used! If an invalid ```` is provided, ``All,00:00`` will be stored! + + ````: The value to be set. If a GPIO is configured or the setting 'Value input On/Off only' is enabled, a 0 will configure Off and 1 will configure On. When not using a GPIO or the 'Value input On/Off only' setting is unchecked, the value will be stored as provided. When having value 0 set, **no event will be generated** when this time is triggered! + "," + This (generic) command allows to update the configuration. + + .. warning:: Every time this command is used, the configuration is saved to flash storage. When changing this often, the flash memory may wear out quickly! + + + + Example command to use, when using ``%Sunrise%``/``%Sunset%``: + + - ``config,task,Clock,settime,1,Wrk,$sunrise-1h$,1`` + + Will be stored like ``Wrk,%sunrise-1h%`` in Day,Time field 1, and Value set to 1, or ``On`` when having a GPIO configured or **Value input On/Off only** enabled. + " diff --git a/docs/source/Plugin/P043_config_values.repl b/docs/source/Plugin/P043_config_values.repl new file mode 100644 index 000000000..fb032bce3 --- /dev/null +++ b/docs/source/Plugin/P043_config_values.repl @@ -0,0 +1,23 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#GetTimeX]`` + "," + Returns the configured Day,Time string for line X (Range: 1..Nr. of Day,Time fields), as shown in the UI. + + Examples: + + - ``All,06:00`` + - ``Wrk,%sunset-1h%`` + " + " + ``[#GetValueX]`` + "," + Returns the configured value for line X (Range: 1..Nr. of Day,Time fields), with a twist: + + If a GPIO is configured, or the setting 'Value input On/Off only' is checked, a 0 is returned for Off and 1 for On (the stored value is actualy 1 higher) + + When no GPIO is configured and the setting 'Value input On/Off only' is unchecked, the shown value is returned. + " diff --git a/docs/source/Plugin/P043_events.repl b/docs/source/Plugin/P043_events.repl new file mode 100644 index 000000000..22e9ac30c --- /dev/null +++ b/docs/source/Plugin/P043_events.repl @@ -0,0 +1,14 @@ +.. csv-table:: + :header: "Event", "Example" + :widths: 30, 20 + + " + ``clock#Output`` The value provided with this event is the field (1..Nr. of Day,Time fields) that was triggered. + + ``clock#Output2`` When the 'Number Output Values' is set to Dual, Triple or Quad, and a GPIO is configured or 'Value input On/Off only' is checked the On (1) or Off (0) value is included as an event argument (``%eventvalue1%``). + + If no GPIO is configured and 'Value input On/Off only' is unchecked, the configured value is provided as ``%eventvalue1%``. + + Events are only generated if the value is not set to 0 or empty. + "," + " diff --git a/docs/source/Plugin/P044.rst b/docs/source/Plugin/P044.rst index f50fd2bd7..3065fe445 100644 --- a/docs/source/Plugin/P044.rst +++ b/docs/source/Plugin/P044.rst @@ -26,23 +26,46 @@ Supported hardware |P044_usedby| +.. note:: This plugin is now merged (back) into :ref:`P020_page` where it initially was forked off from, with using some predefined settings. + +Configuration +------------- + +.. image:: P044_DeviceConfiguration.png + +* **Name** In the Name field a unique name should be entered. + +* **Enabled** When unchecked the plugin is not enabled. + Sensor ^^^^^^ See: :ref:`SerialHelper_page` +Device Settings +^^^^^^^^^^^^^^^ -**TODO**: Complete this documentation... +* **TCP Port**: The port for an external network client to read the data from, range 1..65535. The used port number must be unique within the device. -.. Commands available -.. ^^^^^^^^^^^^^^^^^^ +* **Baud Rate / Serial config**: See *Serial helper configuration*, above. -.. .. include:: P044_commands.repl +* **P1 #data event with message**: When enabled, the *P1 WiFi Gateway* Event Processing option will include the received message. **WARNING** This may easily cause memory overflow exceptions, especially when running on ESP8266 or other low-memory situations! -.. Events -.. ~~~~~~ +* **Process events without client**: By default, if no network client is connected, no serial data will be received and processed either. Enabling this option enables receiving data and generating events without a TCP client connected. + +* **RX Receive timeout (mSec)**: If parts of serial data packets are somewhat delayed, but should still be handled as a single message, then the delay to wait for the next part can be configured here. 0 disables the delay. + +* **Reset target after init**: Select a GPIO pin that should be pulled low once during initialization of the plugin, used to synchronize the external serial data source with the plugin. + +Led +^^^ + +* **Led enabled**: To enable a *data is being processed* activity led. + +* **Led pin**: The GPIO pin the Led is connected to. + +* **Led inverted**: Iverts the on/off state for the Led. -.. .. include:: P044_events.repl Change log ---------- @@ -50,6 +73,8 @@ Change log .. versionchanged:: 2.0 ... + |changed| 2022-10-08: Merge of P020 and P044 to reduce code size and combine features, as P044 was initially started as a spin-off from P020, but not evolved with the P020 features. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P044_DeviceConfiguration.png b/docs/source/Plugin/P044_DeviceConfiguration.png new file mode 100644 index 000000000..a8d83191c Binary files /dev/null and b/docs/source/Plugin/P044_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P044_ReplaceCharInEventOptions.png b/docs/source/Plugin/P044_ReplaceCharInEventOptions.png new file mode 100644 index 000000000..df68e230c Binary files /dev/null and b/docs/source/Plugin/P044_ReplaceCharInEventOptions.png differ diff --git a/docs/source/Plugin/P047.rst b/docs/source/Plugin/P047.rst index 938809dee..1bf6b3386 100644 --- a/docs/source/Plugin/P047.rst +++ b/docs/source/Plugin/P047.rst @@ -40,6 +40,15 @@ Supported hardware .. image:: P047_BeFlESensor.png +* Adafruit: + + Where to buy: `Adafruit webshop or local distributor `_ + + Arduino library: `Adafruit SeeSaw library on GitHub.com `_ (only uswed for inspiration) + + .. image:: P047_Adafruit4026.png + :height: 115px + Though not directly applicable, as it is about selecting an Analog Soil-moisture sensor, `this Youtube video `_ has usable tips on finding a reliable sensor. Configuration @@ -58,30 +67,42 @@ The available settings here depend on the build used. At least the **Force Slow * **I2C Address (Hex)**: The address the device is using. The default I2C address is prefilled, and can be configured to use a different address, see below. As this can be any valid I2C address in range: 0x01..0x7F, it has to be typed here, using a hexadecimal value. +For the Adafruit I2C Capacitive Moisture sensor, only addresses 0x36..0x39 can be configured, by connecting the A0/A1 address soldering pads. For this sensor only these addresses can be selected. + .. note:: If the device doesn't stay enabled after setting the Enabled checkbox, then possibly the address of the sensor is changed from the default. The I2C Scan on the Tools page can be used to detect what I2C device addresses are in use (disconnect other I2C devices to avoid confusion). Possibly, another device-model is listed for this sensor, but that is caused by the feature that it can have any address set that's in the I2C allowed range of 0x00 to 0x7F. Device Settings ^^^^^^^^^^^^^^^^ -* **Sensor model**: Select the model of the used sensor. When changing the selection the page will be reloaded to adjust the content of the screen. +* **Sensor model**: Select the model of the used sensor. When changing the selection the page will be reloaded to adjust the available configuration options, tailored to the selected sensor. .. image:: P047_SensorModelOptions.png * *Catnip electronics/miceuz (default)*: The originally supported soil moisture sensor, that supports temperature, moisture and light measurement, has a sleep-mode, and can report a version number, to optionally validate if the sensor is supported. -* *BeFlE*: An I2C soil moisture sensor that supports temperature and moisture measurement. There is no sleep mode or version check available. +* *BeFlE v2.2*: An I2C soil moisture sensor that supports temperature and moisture measurement. There is no sleep mode or version check available. -When selecting the BeFlE sensor, only these **Device Settings** will be available: +* *BeFlE v3.x*: An I2C soil moisture sensor that supports temperature and moisture measurement, has a low-power Sleep mode and can report it's version, but this is not explicitly checked. + +* *Adafruit (4026)*: An I2C soil moisture sensor that supports temperature and moisture measurement. There is no sleep mode available, and the version is only logged at startup. + +When selecting the BeFlE v2.2 sensor, only these **Device Settings** will be available: .. image:: P047_SensorModelBeFlE.png -* **Send sensor to sleep**: When not actively reading the sensor, the device can be set to sleep-mode, to conserve some power. (Catnip sensor only). +When selecting the BeFlE v3.x sensor, only these **Device Settings** will be available: + +.. image:: P047_SensorModelBeFlEv3.png + +* **Send sensor to sleep**: When not actively reading the sensor, the device can be set to sleep-mode, to conserve some power. (Catnip and BeFlE v3 sensors only). * **Check sensor version**: To validate if the sensor is of the correct mode, the version can be validated. (Catnip sensor only). -* **Change sensor address**: To use multiple sensors connected to a single ESP unit, a sensor can be configured to use a different I2C address. To change that address, this checkbox has to be enabled, and a different I2C address should be entered in the **Change I2C Address to (Hex)** field in hexadecimal format, range: 0x01..0x7F. +* **Change sensor address**: To use multiple sensors connected to a single ESP unit, a sensor can be configured to use a different I2C address. To change that address, this checkbox has to be enabled, and a different I2C address should be entered in the **Change I2C Address to (Hex)** field in hexadecimal format, range: 0x01..0x7F. This option isn't available for the Adafruit sensor. -* **Change I2C Addr. to (Hex)**: The new address to be used by the sensor. The change will be applied the next time the task is enabled. The **I2C Address (Hex)** field, above, will be updated with the new value. After the change is applied, **the tasks Settings have to be saved once more to save the new address** as the address to use. +* **Change I2C Addr. to (Hex)**: The new address to be used by the sensor. The change will be applied the next time the task is enabled. The **I2C Address (Hex)** field, above, will be updated with the new value. After the change is applied, **the tasks Settings have to be saved once more to save the new address** as the address to use. This option isn't available for the Adafruit sensor. + +When using the I2C scanner, the changed address will show up, but it probably will not show the sensor name, as only the original I2C address is added to the list. NB: Not all builds show known names for sensors to reduce the build binary size. .. note:: The newly set address is permanently stored in the sensor, and can be changed again at a later time if desired. @@ -97,7 +118,9 @@ Values The measured values are available in ``Temperature``, ``Moisture`` and ``Light``. A formula can be set to recalculate. The number of decimals is by default set to 2, and can be set to 0 for ``Moisture``, as no decimals are provided from the measurement. -NB: The BeFlE sensor doesn't support a Light value, so that won't be available if that **Sensor model** is selected. +NB: The BeFlE and Adafruit sensors don't support a Light value, so that won't be available if that **Sensor model** is selected. + +NB2: The Adafruit sensor has a different range for the moisture values: 200..2000 compared to the Catnip and BeflE sensors: 1..800, so any moisture calibration has to be based on actual observation, and not on absolute numbers. .. Commands available @@ -116,6 +139,12 @@ Change log .. versionchanged:: 2.0 ... + |added| 2024-05: Add support for BeFlE v3.x I2C moisture sensor. + + |added| 2024-04: Add support for Adafruit I2C moisture sensor. + + |added| 2023-03: Add support for BeFlE v2.2 I2C moisture sensor. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P047_Adafruit4026.png b/docs/source/Plugin/P047_Adafruit4026.png new file mode 100644 index 000000000..67caa6009 Binary files /dev/null and b/docs/source/Plugin/P047_Adafruit4026.png differ diff --git a/docs/source/Plugin/P047_SensorModelBeFlE.png b/docs/source/Plugin/P047_SensorModelBeFlE.png index 81191f037..501803f52 100644 Binary files a/docs/source/Plugin/P047_SensorModelBeFlE.png and b/docs/source/Plugin/P047_SensorModelBeFlE.png differ diff --git a/docs/source/Plugin/P047_SensorModelBeFlEv3.png b/docs/source/Plugin/P047_SensorModelBeFlEv3.png new file mode 100644 index 000000000..59300e6a2 Binary files /dev/null and b/docs/source/Plugin/P047_SensorModelBeFlEv3.png differ diff --git a/docs/source/Plugin/P047_SensorModelOptions.png b/docs/source/Plugin/P047_SensorModelOptions.png index 0dee8720e..b8d1a8667 100644 Binary files a/docs/source/Plugin/P047_SensorModelOptions.png and b/docs/source/Plugin/P047_SensorModelOptions.png differ diff --git a/docs/source/Plugin/P064.rst b/docs/source/Plugin/P064.rst index cad607d35..e20e31d38 100644 --- a/docs/source/Plugin/P064.rst +++ b/docs/source/Plugin/P064.rst @@ -26,43 +26,75 @@ Description The APDS9960 sensor provides Gesture, Proximity and Ambient Light data or R/G/B color values from the light sensor, depending on the Plugin Mode. After changing the Plugin Mode, the Values arguments may need to be adjusted according to their function (Gesture, Proximity, Light or R, G, B). If the initial Values names are used, they will be replaced when switching the Plugin Mode, and the settings are actually saved. +Device Configuration +-------------------- + +.. image:: P064_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. + +I2C options +^^^^^^^^^^^ + The available settings here depend on the build 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` There is only 1 I2C address available for this sensor, ``0x39`` (not shown). The Gain, LED Drive and LED Boost parameters may need adjustment from the defaults as some of the low-cost clone sensors aren't as carefully calibrated as the original SparkFun or AdaFruit sensors. The suggested defaults are taken from the original SparkFun driver software settings. When first configuring this plugin it is advised to start with these default settings, and adjust when the sensor isn't responding as required. +Device Settings +^^^^^^^^^^^^^^^ + +* **Plugin Mode**: Select the desired operation mode. + +.. image:: P064_PluginModeOptions.png + +* *Gesture/Proximity/Ambient Light Sensor*: Will measure the Gesture, Proximity and Ambient light values. If **Separate Gesture events** is disabled, then events will be generated when a Gesture is detected. Also, when **Interval** is set, events are generated. +* *R/G/B Colors*: Will measure the R, G and B components of the current Ambient light condition. Will generate events if **Interval** is set. + For the R/G/B Colors mode, only Light Sensor Gain and Light Sensor LED Drive parameters are available. They correspond with the Ambient Light Sensor Gain and Proximity/ALS LED Drive parameters (and use the same settings storage), but with different labels. NB: Defaults are *not* automatically set after adding the plugin! Gesture parameters ------------------- +^^^^^^^^^^^^^^^^^^ -Gesture Gain: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. +* **Gesture Gain**: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. -Gesture LED Drive: Selection of the current to drive the Gesture IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. +* **Gesture LED Drive**: Selection of the current to drive the Gesture IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. -Gesture LED Boost: Selection of the LED Boost factor, select from 100%, 150%, 200% or 300% (default). +* **Gesture LED Boost**: Selection of the LED Boost factor, select from 100%, 150%, 200% or 300% (default). Proximity & Ambient Light Sensor parameters -------------------------------------------- +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Proximity Gain: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. +* **Proximity Gain**: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. -Ambient Light Sensor Gain: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. +* **Ambient Light Sensor Gain**: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. -Proximity/ALS LED Drive: Selection of the current to drive the Proximity/Ambient Light Sensor IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. +* **Proximity/ALS LED Drive**: Selection of the current to drive the Proximity/Ambient Light Sensor IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. R/G/B Colors parameters ------------------------ +^^^^^^^^^^^^^^^^^^^^^^^ -Light Sensor Gain: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. +* **Light Sensor Gain**: Selection of the gain factor, select from 1x, 2x, 4x (default) or 8x. -Light Sensor LED Drive: Selection of the current to drive the Light Sensor IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. +* **Light Sensor LED Drive**: Selection of the current to drive the Light Sensor IR LED, select from 100 mA (default), 50 mA, 25 mA or 12.5 mA. -Supported hardware ------------------- +Event generation +^^^^^^^^^^^^^^^^ + +* **Separate Gesture events**: When enabled will generate a ``#Swipe=`` event (see below), independent from **Interval**. This allows gestures to be handled independently from measuring the proximity and ambient light, or R/G/B colors. The ``Gesture`` value will be updated for every gesture detected, but the Values events will only trigger on Interval, if this checkbox is enabled. + +Values +^^^^^^ + +Depending on the **Plugin Mode** setting either the Values are ``Gesture``, ``Proximity`` and ``Light`` or ``R``, ``G`` and ``B``. When not manually changed, they will be switched by the plugin when selecting the other Plugin Mode setting. + +.. Supported hardware +.. ^^^^^^^^^^^^^^^^^^ |P064_usedby| @@ -71,10 +103,10 @@ Supported hardware .. .. include:: P064_commands.repl -.. Events -.. ~~~~~~ +Events +~~~~~~ -.. .. include:: P064_events.repl +.. include:: P064_events.repl Change log ---------- @@ -82,6 +114,9 @@ Change log .. versionchanged:: 2.0 ... + |added| 2024-03-30: Separate Gesture events option. + + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P064_DeviceConfiguration.png b/docs/source/Plugin/P064_DeviceConfiguration.png new file mode 100644 index 000000000..0d7cc7478 Binary files /dev/null and b/docs/source/Plugin/P064_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P064_PluginModeOptions.png b/docs/source/Plugin/P064_PluginModeOptions.png new file mode 100644 index 000000000..8b0811408 Binary files /dev/null and b/docs/source/Plugin/P064_PluginModeOptions.png differ diff --git a/docs/source/Plugin/P064_events.repl b/docs/source/Plugin/P064_events.repl new file mode 100644 index 000000000..fa7592c51 --- /dev/null +++ b/docs/source/Plugin/P064_events.repl @@ -0,0 +1,21 @@ +.. csv-table:: + :header: "Event", "Example" + :widths: 30, 20 + + " + ``#Swipe,`` + + ```` values: + + * ``0``: None + * ``1``: Left + * ``2``: Right + * ``3``: Up + * ``4``: Down + * ``5``: Near (towards the sensor) + * ``6``: Far (away from the sensor) + "," + Will trigger when the configuration option **Separate Gesture events** is enabled. + + These values are the same for the ``Gesture`` value when that is used. + " diff --git a/docs/source/Plugin/P077_commands.repl b/docs/source/Plugin/P077_commands.repl index a38b65a56..8ab81efb1 100644 --- a/docs/source/Plugin/P077_commands.repl +++ b/docs/source/Plugin/P077_commands.repl @@ -24,3 +24,11 @@ "," Will reset the calibration values to the default values, causing auto-calibration. " + " + ``cseclearpulses`` + + "," + (Added: 2024-01-16) + Will reset the CF-pulse counter. + N.B. this also clears the kWh counter as it is derived from the CF-pulse counter. + " diff --git a/docs/source/Plugin/P080.rst b/docs/source/Plugin/P080.rst index 6ba8f2d21..279b294a5 100644 --- a/docs/source/Plugin/P080.rst +++ b/docs/source/Plugin/P080.rst @@ -1,4 +1,4 @@ -.. include:: ../Plugin/_plugin_substitutions_p08x.repl +.. include:: ../Plugin/_plugin_substitutions_p08x.repl .. _P080_page: |P080_typename| @@ -24,17 +24,86 @@ Used libraries: |P080_usedlibraries| Supported hardware ------------------ -|P080_usedby| +.. .. |P080_usedby| + +.. image:: P080_iButtonReader1.png + :width: 200px + +.. image:: P080_iButtonReader2.png + :width: 200px + +This type of reader is available at sites like Aliexpress and eBay. The wiring for these units can be somewhat confusing: + +.. code-block:: none + + ESP iButton (4 wires) + GPIO <--> 1-wire/D (green) with 1k..10k Pull-up to VCC + GND <--> GND (red) + + ESP Resistor LED + VCC <--> Anode (black) + GPIO <--> 470 ohm <--> Cathode (white) + +The value for the pull-up resistor on the 1-wire GPIO pin depends somewhat, sometimes, 10k doesn't work reliable, especially when using longer wires (> 2m), then lowering to 4k7 or 2k2 usually fixes that. For really problematic installations with high (electrical) noise levels, a 1k resistor may be needed to make it work reliable. + +Description +----------- + +The iButton, developed by Dallas, now Maxim, is a coded key or button, that can be used for access control or similar identity checks. They use RFID technology to transfer their ID to the receiver, once in close proximity of the receiver. + +The iButtons often come in the shape shown in the image below, and can be attached to a keyring for easy access and use. + +These buttons are available as read-only iButtons, and as rewritable iButtons, where the user can change the ID of the button. Both types can be read by ESPEasy, but ESPEasy does not provide tools or features to write an ID to the rewritable buttons, separate tools for that can be obtained elsewhere. + +.. image:: P080_iButtonExamples.png + :width: 200px + +Configuration +------------- + +.. image:: P080_DeviceConfiguration.png + :alt: Device configuration + + +* **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 +^^^^^^ + +* **GPIO 1-Wire**: The reader only needs a single GPIO pin (and GND) to be connected. The 1-Wire hardware configured in ESPEasy requires a ca. 4k7 ohm (range 1k..10k depending on wire-length) pull-up resistor between VCC (3.3V) and the GPIO pin. Internal pull-up of the ESP is not sufficient! + +Any additional wires on the receiver unit mostly are used for indicator leds, that can optionally be controlled using standard GPIO commands from rules. (Don't forget a resistor to limit the current...) + +Device Settings +^^^^^^^^^^^^^^^ + +* **Device Address**: Select the desired device ID that the plugin should respond to. This requires the device to be enabled, and an iButton in contact with the receiver when opening the device settings page! When set, the task will only respond to this iButton ID, when read. + +* **Event with iButton address**: With this option enabled, and the **Device Address** selection left to ``- None -``, instead of responding to a single iButton, an event is generated for any iButton that is recognized by the receiver. And once removed, the same event is generated without the iButton address. This can be processed in rules. See below in the **Events** chapter for a more detailed description. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 0 sec. as it is optional. It is the frequency used to send the value to any Controllers configured for this device. + +Values +^^^^^^ + +The plugin provides the ``iButton`` value, that shows either 0 (no iButton on the receiver) or 1 (iButton recognized). .. Commands available .. ^^^^^^^^^^^^^^^^^^ .. .. include:: P080_commands.repl -.. Events -.. ~~~~~~ +Events +~~~~~~ -.. .. include:: P080_events.repl +.. include:: P080_events.repl Change log ---------- @@ -42,6 +111,8 @@ Change log .. versionchanged:: 2.0 ... + |added| 2024-05-10 Support for Event with iButton address. + |added| Major overhaul for 2.0 release. diff --git a/docs/source/Plugin/P080_DeviceConfiguration.png b/docs/source/Plugin/P080_DeviceConfiguration.png new file mode 100644 index 000000000..5125c5a81 Binary files /dev/null and b/docs/source/Plugin/P080_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P080_events.repl b/docs/source/Plugin/P080_events.repl new file mode 100644 index 000000000..046078fef --- /dev/null +++ b/docs/source/Plugin/P080_events.repl @@ -0,0 +1,32 @@ +.. csv-table:: + :header: "Event", "Example" + :widths: 30, 30 + + " + ``#Address=[,,]`` + + ````: 0 or 1, depending on wether the iButton is on the receiver (1) or not (0). + + ````: The high 4 bytes of the (64 bit) iButton address in hexadecimal presentation, with a ``0x`` prefix. The address parts are only included if the iButton is detected by the receiver. + + ````: The low 4 bytes of the iButton address in hexadecimal presentation. + + "," + + This event is generated when the **Event with iButton address** setting is *enabled*, and no **Device Address** is selected (*None*). + + As the iButton address is in fact a 64 bit value (though only 57 bits are used), this value is too large to be stored in a regular variable in the rules, so they are split into 2 parts, as often sets of iButtons are produced in the same batch, where the higher bits are the same, and only the lower bits vary. + + These numbers can be processed, f.e. in a lookup table, similar to the example `Validate a RFID tag against a sorted list <../Rules/Rules.html#validate-a-rfid-tag-against-a-sorted-list>`_ (though somewhat different...) + + Simply logging the data could look like this: + + .. code-block:: none + + On iButton#Address Do + Let,1,%eventvalue2% + Let,2,%eventvalue3% + LogEntry,'iButton %eventvalue2%_%eventvalue3% = %eventvalue1% (%v1% / %v2%)' + Endon + + " diff --git a/docs/source/Plugin/P080_iButtonExamples.png b/docs/source/Plugin/P080_iButtonExamples.png new file mode 100644 index 000000000..85d983e32 Binary files /dev/null and b/docs/source/Plugin/P080_iButtonExamples.png differ diff --git a/docs/source/Plugin/P080_iButtonReader1.png b/docs/source/Plugin/P080_iButtonReader1.png new file mode 100644 index 000000000..bcbf245f0 Binary files /dev/null and b/docs/source/Plugin/P080_iButtonReader1.png differ diff --git a/docs/source/Plugin/P080_iButtonReader2.png b/docs/source/Plugin/P080_iButtonReader2.png new file mode 100644 index 000000000..767d409e2 Binary files /dev/null and b/docs/source/Plugin/P080_iButtonReader2.png differ diff --git a/docs/source/Plugin/P087.rst b/docs/source/Plugin/P087.rst index 721a19752..4582df69d 100644 --- a/docs/source/Plugin/P087.rst +++ b/docs/source/Plugin/P087.rst @@ -48,7 +48,7 @@ This section only shows settings when the plugin is configured and enabled. .. image:: P087_FilteringConfiguration.png -* **RegEx**: Specify a regular expression to apply to the received data before it is accepted as acceptable data. +* **RegEx**: Specify a regular expression to apply to the received data before it is accepted as acceptable data. This regular expression is based on the Lua script language, and is documented here: http://www.lua.org/manual/5.2/manual.html#6.4.1 * **Nr Chars use in RegEx**: Limit the length of the data to be checked using the **RegEx** regular expression filter. When set to 0 all data is checked. @@ -109,6 +109,13 @@ Commands available .. include:: P087_commands.repl +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P087_config_values.repl + Change log ---------- @@ -116,6 +123,8 @@ Change log .. versionchanged:: 2.0 ... + |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. |added| 2020-02-22 diff --git a/docs/source/Plugin/P087_commands.repl b/docs/source/Plugin/P087_commands.repl index 60effc0a9..ca2b9f1c8 100644 --- a/docs/source/Plugin/P087_commands.repl +++ b/docs/source/Plugin/P087_commands.repl @@ -26,3 +26,12 @@ ``'0xXXxx XX,xx-XX:xx'``: A sequence of hexadecimal values (range: 0x00..0xFF), that *can* be separated by a space, comma, dash, colon, semicolon or period, or are just entered adjecent. Only the first 2 characters should be ``0x`` or ``0X``, the rest is interpreted as hex bytes, and appended to the data to send. Quotes are only required if space or comma separators are used. Using this command, either from rules, via http or mqtt, the text that is provided as content is completely sent to the serial port. No extra data is added, other than any (system) variables that are included, being replaced. " + " + ``serialproxy_test,'[,...]'`` + + ````: Some text to be processed as if it was received via the serial port. + "," + This command is intended for testing the regular expression and filtering. + + The sentence provided with this command is handed over for processing as if it was received via the serial port, so it will be processed and filtered, and an event will be generated if it's matched. + " diff --git a/docs/source/Plugin/P087_config_values.repl b/docs/source/Plugin/P087_config_values.repl new file mode 100644 index 000000000..1fb8539cf --- /dev/null +++ b/docs/source/Plugin/P087_config_values.repl @@ -0,0 +1,70 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#group.]`` + "," + Get the contents from groupnr after processing received data. + + NB: The regular expression parsen uses 0-based group numbers (where most other regex parsers use 1-based group numbers!) + " + " + ``[#next.]`` + "," + Get the value of the next group that holds ````. + + Example: + + Regular expression: ``((node)=(%d+);?)((weight)=(%d+);?)((temp%d?)=(%-?%d+);?)((rssi)=(%-?%d+);?)`` + + Received data: ``node=1;weight=40;temp1=20;rssi=-30`` + + This will result in these groups: + + .. list-table:: + :widths: 20, 50, 200 + :header-rows: 1 + + * - Group + - Data + - + * - 0 + - node=1 + - + * - 1 + - node + - + * - 2 + - 1 + - + * - 3 + - weight=40 + - + * - 4 + - weight + - + * - 5 + - 40 + - + * - 6 + - temp1=20 + - + * - 7 + - temp1 + - + * - 8 + - 20 + - + * - 9 + - rssi=-30 + - + * - 10 + - rssi + - + * - 11 + - -30 + - + + Requesting ``[SerialProxy#next.weight]`` will return the value ``40``. + " diff --git a/docs/source/Plugin/P095.rst b/docs/source/Plugin/P095.rst index a1aacc067..3d1f22e78 100644 --- a/docs/source/Plugin/P095.rst +++ b/docs/source/Plugin/P095.rst @@ -53,6 +53,7 @@ The text on most displays is somewhat confusing, as not the usual SPI names are 3V3 --- VCC (most displays only support 3.3V) GND --- GND MOSI --> SDA/SDI + MISO --> SDO/DO (optional, not used) CLK --> SCL/SCK (gpio) --> DC (gpio) --> CS @@ -68,23 +69,35 @@ Device configuration .. image:: P095_DeviceConfiguration.png :alt: Device configuration -* **Name** A unique name should be entered here. +* **Name**: A unique name should be entered here. -* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. +* **Enabled**: The device can be disabled or enabled. When not enabled the device should not use any resources. Actuator ^^^^^^^^ -* **GPIO -> TFT CS** Select the GPIO pin to use for the ``CS`` connection. If the display doesn't have a ``CS`` connection it can be set to *None*. -* **GPIO -> TFT DC** The GPIO pin to use for the ``DC`` connection (Data/Command). -* **GPIO -> TFT RST** Select the GPIO pin to use for the ``RES`` (reset) connection. If the display doesn't have a ``RES`` (or RST) connection, or no free pin is available, it can be set to *None*. If it is set to None, for proper operation it may need too be wired to the Reset connection on the ESP, so the device is initialized correctly. -* **GPIO -> Backlight (optional)** Select the GPIO pin to use for controlling the backlight. To save power, the backlight can be dimmed, or turned off if the display is turned off. If set to *None*, usually the max. brightness is used for the backlight. -* **Backlight percentage** The backlight can be controlled via PWM modulation on the Backlight (BLK) pin of the display. This is set as a percentage between 1 and 100. -* **Display button** A GPIO pin can be configured to wake the display on demand. This, combined with the **Display Timeout** setting, can preserve the lifetime of the display, and save some power. -* **Inversed Logic** When checked, reverses the pin-state action of the **Display button** gpio. This allows an external circuit, f.e. an IR sensor, that may provide a *high* signal when activated, to wake the display. -* **Display Timeout** Select the timeout in seconds to turn off the display after the last update or wake-up. Only used if the **Display button** is *also* configured. -* **TFT Display model** Select the hardware model that is connected. Currently there are only preset resolutions available. -* **Invert display** Default value for Invert display, some displays have foreground and background colors swapped (f.e. M5Stack Core2 using ILI9342C), this option is applied at plugin initialization. +* **GPIO -> TFT CS**: Select the GPIO pin to use for the ``CS`` connection. If the display doesn't have a ``CS`` connection it can be set to *None*. + +* **GPIO -> TFT DC**: The GPIO pin to use for the ``DC`` connection (Data/Command). + +* **GPIO -> TFT RST**: Select the GPIO pin to use for the ``RES`` (reset) connection. If the display doesn't have a ``RES`` (or RST) connection, or no free pin is available, it can be set to *None*. If it is set to None, for proper operation it may need too be wired to the Reset connection on the ESP, so the device is initialized correctly. + +Device Settings +^^^^^^^^^^^^^^^ + +* **GPIO -> Backlight (optional)**: Select the GPIO pin to use for controlling the backlight. To save power, the backlight can be dimmed, or turned off if the display is turned off. If set to *None*, usually the max. brightness is used for the backlight, but sometimes the BL/LED pin of the display has to be connected to ``VCC`` to get the content visible. + +* **Backlight percentage**: The backlight can be controlled via PWM modulation on the Backlight (BLK) pin of the display. This is set as a percentage between 1 and 100. + +* **Display button**: A GPIO pin can be configured to wake the display on demand. This, combined with the **Display Timeout** setting, can preserve the lifetime of the display, and save some power. + +* **Inversed Logic**: When checked, reverses the pin-state action of the **Display button** gpio. This allows an external circuit, f.e. an IR sensor, that may provide a *high* signal when activated, to wake the display. + +* **Display Timeout**: Select the timeout in seconds to turn off the display after the last update or wake-up. Only used if the **Display button** is *also* configured. + +* **TFT Display model**: Select the hardware model that is connected. Currently there are only preset resolutions available. + +* **Invert display**: Default value for Invert display, some displays have foreground and background colors swapped (f.e. M5Stack Core2 using ILI9342C and some ILI9486/ILI9488 models), this option is applied at plugin initialization. Available options: @@ -94,10 +107,12 @@ Available options: .. warning:: The **ILI9481** display controller does have issues when rotating, by using the ``,rot,`` command, to change the display orientation after some content is already displayed on the screen (content may move, rotate and/or mirror unexpectedly). It is advised to clear the screen after changing the rotation setting. +.. note:: ILI9486 displays tested so far (sourced from Aliexpress) either are *compatible* with ILI9488 or **are** ILI9488 displays, that's why they share the same entry in the list. If you find an ILI9486 display that doesn't work with this setting, please raise an issue in ESPEasy Github Issues list, so this can be adjusted. + Layout ^^^^^^^^ -* **Rotation** Depending on how the display is to be mounted/installed, it may be needed to rotate the content, or with a non-square resolution, to use the display in *Landscape* layout instead of the default *Portrait*. +* **Rotation**: Depending on how the display is to be mounted/installed, it may be needed to rotate the content, or with a non-square resolution, to use the display in *Landscape* layout instead of the default *Portrait*. Available options: @@ -120,13 +135,18 @@ Available options: Default setting is *Continue to next line*. -* **Font scaling** The scaling factor for the currently active font. Select a factor between 1 and 10. +* **Default font**: If fonts are available in the build (excluded from some builds because of .bin size issues), the default font at initialization can be selected. The names/numbers that can be used in the ``,font,`` are shown in the combobox, and documented below in the **AdafruitGFX_Helper** documentation. The font numbers aren't consecutive if all fonts aren't included, to keep them consistent across builds. -* **Show splash on start** When available, and enabled, will show an ESPEasy & plugin name text-splash during start of the plugin. If also some content is configured, this will be written over the splash, as that is not cleared after being displayed. +.. image:: P095_DefaultFontOptions.png + :alt: Default font -* **Clear display on exit** When checked, will clear the display when the task is disabled, either from settings or via the ``TaskDisable`` command. The screen will be turned off, and when a backlight pin is configured, also the backlight is turned off. +* **Font scaling**: The scaling factor for the currently active font. Select a factor between 1 and 10. -* **Write Command trigger** The command to handle any commands for this device can be selected here. This can make the commands compatible with other (tft) displays, using the same command structure via the ESPEasy Adafruit Graphics helper class. +* **Show splash on start**: When available, and enabled, will show an ESPEasy & plugin name text-splash during start of the plugin. If also some content is configured, this will be written over the splash, as that is not cleared after being displayed. + +* **Clear display on exit**: When checked, will clear the display when the task is disabled, either from settings or via the ``TaskDisable`` command. The screen will be turned off, and when a backlight pin is configured, also the backlight is turned off. + +* **Write Command trigger**: The command to handle any commands for this device can be selected here. This can make the commands compatible with other (tft) displays, using the same command structure via the ESPEasy Adafruit Graphics helper class. Available options: @@ -142,19 +162,20 @@ Available options: The command is handled non-case sensitive. See below for available commands and subcommands. -* **Wake display on receiving text** When checked, the display will be enabled once any content is written to the screen, either triggered by the Interval, or from a command. Default checked. +* **Wake display on receiving text**: When checked, the display will be enabled once any content is written to the screen, either triggered by the Interval, or from a command. Default checked. -* **Text Coordinates in col/row** When checked, the coordinates for the ``txp``, ``txz`` and ``txtfull`` subcommands will be handled in cursor columns & rows, instead of pixels. Column and row are calculated from the current font size and font scaling settings. +* **Text Coordinates in col/row**: When checked, the coordinates for the ``txp``, ``txz`` and ``txtfull`` subcommands will be handled in cursor columns & rows, instead of pixels. Column and row are calculated from the current font size and font scaling settings. -* **Use -1px offset for txp & txtfull** For backward compatibility with the previous, non-AdafruitGFX_helper based, plugin implementation, that used 0-based coordinates, an offset of -1 pixel can be applied to the ``txp``, ``txz`` and ``txtfull`` subcommands. This option is enabled by default. +* **Use -1px offset for txp & txtfull**: For backward compatibility with the previous, non-AdafruitGFX_helper based, plugin implementation, that used 0-based coordinates, an offset of -1 pixel can be applied to the ``txp``, ``txz`` and ``txtfull`` subcommands. This option is enabled by default. -* **Background-fill for text** When checked, for any text-line sent to the screen, the entire background (including top and bottom lines) will have the provided background color, *unless* transparent is used (Background color == Foreground color). Default checked. +* **Background-fill for text**: When checked, for any text-line sent to the screen, the entire background (including top and bottom lines) will have the provided background color, *unless* transparent is used (Background color == Foreground color). Default checked. Content ^^^^^^^^ -* **Foreground color** -* **Background color** +* **Foreground color**: + +* **Background color**: These are the default colors, used to display the content as configured below (if any). The background color is also used as the Clear screen color. @@ -166,7 +187,7 @@ Colors can be specified in 3 ways: If the Foreground and Background colors are the same, the background color will become ``transparent``. If the Forground color is empty, as a default ``white`` will be set. -* **Line 1..24** Predefined content can be specified. The number of lines available depends on the size of the display, the font used, the font scaling that is set and the selected rotation. +* **Line 1..24**: Predefined content can be specified. The number of lines available depends on the size of the display, the font used, the font scaling that is set and the selected rotation. The usual variables, like ``[Taskname#Valuename]``, or ``%v1%``, system variables, formulas and functions can be used. @@ -174,14 +195,14 @@ Input length is limited to 60 characters per line. If a longer calculated text i Next to Line 24, the remaining capacity in characters is displayed. -The total combination of lines * input length can not exceed 1000 characters (sized dynamically), as there is limited storage per task available for these settings. An error message will be shown after (trying to) save the settings, **any excess content will be discarded!** +The total combination of lines * input length can not exceed 5192 characters (1000 on some builds) (sized dynamically), as there is limited storage per task available for these settings. An error message will be shown after (trying to) save the settings, **any excess content will be discarded!** Example: .. image:: P095_SaveError.png :alt: Save error -* **Interval** By default, Interval will be set to 0. If set to a non-zero value, the pre-configured content will be updated automatically using that interval (seconds). Depending on the **Text print Mode** setting, content that may have been draw from rules or external commands, may be erased. +* **Interval**: By default, Interval will be set to 0. If set to a non-zero value, the pre-configured content will be updated automatically using that interval (seconds). Depending on the **Text print Mode** setting, content that may have been draw from rules or external commands, may be erased. Values ^^^^^^ @@ -209,6 +230,8 @@ Change log .. versionchanged:: 2.0 ... - |added| 2020-04-20 Initially added + |added| 2024-07-07 Add support for ILI9486/ILI9488 displays. |added| 2022-04-23 Rewrite of the plugin based on AdafruitGFX_helper, udated documentation based on shared settings with :ref:`P116_page` and AdafruitGFX_helper + + |added| 2020-04-20 Initially added diff --git a/docs/source/Plugin/P095_DefaultFontOptions.png b/docs/source/Plugin/P095_DefaultFontOptions.png new file mode 100644 index 000000000..615ef6484 Binary files /dev/null and b/docs/source/Plugin/P095_DefaultFontOptions.png differ diff --git a/docs/source/Plugin/P095_DeviceConfiguration.png b/docs/source/Plugin/P095_DeviceConfiguration.png index 9866daef5..18b1a3429 100644 Binary files a/docs/source/Plugin/P095_DeviceConfiguration.png and b/docs/source/Plugin/P095_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P095_TFTDisplayModelOptions.png b/docs/source/Plugin/P095_TFTDisplayModelOptions.png index e574d54ea..9eb88b673 100644 Binary files a/docs/source/Plugin/P095_TFTDisplayModelOptions.png and b/docs/source/Plugin/P095_TFTDisplayModelOptions.png differ diff --git a/docs/source/Plugin/P095_commands.repl b/docs/source/Plugin/P095_commands.repl index e88f9821e..98b3cf2dd 100644 --- a/docs/source/Plugin/P095_commands.repl +++ b/docs/source/Plugin/P095_commands.repl @@ -31,7 +31,7 @@ " | ``tftcmd,clear`` "," - | Clear the display, using the default background color. + | Clear the display, using the **default** background color. For clearing with a custom background color see the ``,clear[,]`` command. " " | ``tftcmd,backlight,`` diff --git a/docs/source/Plugin/P105.rst b/docs/source/Plugin/P105.rst index 72f5add00..49c21d21a 100644 --- a/docs/source/Plugin/P105.rst +++ b/docs/source/Plugin/P105.rst @@ -1,93 +1,95 @@ -.. include:: ../Plugin/_plugin_substitutions_p10x.repl -.. _P105_page: - -|P105_typename| -================================================== - -|P105_shortinfo| - -Plugin details --------------- - -Type: |P105_type| - -Name: |P105_name| - -Status: |P105_status| - -GitHub: |P105_github|_ - -Maintainer: |P105_maintainer| - -Used libraries: |P105_usedlibraries| - -Datasheet: |P105_datasheet| |P105_datasheet2| |P105_datasheet3| - -Description ------------ - -The AHT10/AHT20/AHT21 sensors provide Temperature and Humidity measurements (factory calibrated), via an I2C bus connection. - -.. warning:: - - * The **AHT10** device does sometimes not 'play nice' when there are also other I2C devices on the same bus (many complaints can be found on the internet). NB: This may be hardware-, vendor-, or chip-revision-specific. - -Because of this peculiarity, other sensors may be more appropriate, like the AHT20/AHT21 also supported by this plugin, or :ref:`P028_page`, :ref:`P106_page`, :ref:`P014_page`, :ref:`P034_page`, :ref:`P051_page`, :ref:`P068_page` or :ref:`P072_page`. - -This plugin tries to avoid such situations (I2C bus lock) by (soft) resetting the sensor if it doesn't respond for some time. - -Settings --------- - -.. image:: P105_DeviceConfiguration.png - -**Name**: The name for this task, should be unique. - -**Enabled**: Allows to enable/disable the device. - -I2C Options -^^^^^^^^^^^^ - -The available settings here depend on the build 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` - -**I2C Address**: The address the device is using. The AHT10 sensor allows to select a secondary address by pulling the AO (sometimes marked as A0) pin to high (3.3V) to select the secondary address. That address should then be selected here too. The AHT20/AHT21 sensors only support a single I2C address, so it will be forced to the default address. - -Device Settings -^^^^^^^^^^^^^^^^ - -If the plugin is configured for **Sensor model** AHT10 and other devices configured in a task use the I2C bus, a warning is displayed (see example below) that combining them with this device may cause issues on the I2C bus, resulting in all I2C devices no longer working as intended. This may occur immediately, or only after some time, like 10 minutes or an hour. - -.. image:: P105_DeviceWarning.png - -**Sensor model** Selection of the connected type of hardware. (AHT2x sensors have a slightly different intialization.) - -Available options: - -.. image:: P105_SensorModelOptions.png - -**AHT10** This sensor model should better be avoided, as it doesn't always work with other devices on the same I2C bus. Also, the AHT15 can be used, but that has similar issues. - -**AHT20** An more modern version of the sensor. - -**AHT20** An more modern version of the sensor, very similar to the AHT20, in a more compact chip package. - -Data Acquisition -^^^^^^^^^^^^^^^^ - -This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. - -**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. - -Values -^^^^^^ - -The names for the values are initially set to a default name, but can be changed if desired. Also, a formula can be entered to re-calculate the value before display/sending to a controller, and the number of decimals can be changed, for Temperature, usually 1 decimal is enough to be displayed (value will be rounded). - - -Change log ----------- - -.. versionchanged:: 2.0 - ... - - |added| 2021-08-01 Moved from ESPEasy PluginPlayground to the main repository. +.. include:: ../Plugin/_plugin_substitutions_p10x.repl +.. _P105_page: + +|P105_typename| +================================================== + +|P105_shortinfo| + +Plugin details +-------------- + +Type: |P105_type| + +Name: |P105_name| + +Status: |P105_status| + +GitHub: |P105_github|_ + +Maintainer: |P105_maintainer| + +Used libraries: |P105_usedlibraries| + +Datasheet: |P105_datasheet| |P105_datasheet2| |P105_datasheet3| + +Description +----------- + +The AHT10/AHT15/AHT20/AHT21/DHT20/AM2301B sensors provide Temperature and Humidity measurements (factory calibrated), via an I2C bus connection. + +.. warning:: + + * The **AHT10** and **AHT15** devices do sometimes not 'play nice' when there are also other I2C devices on the same bus (many complaints can be found on the internet). NB: This may be hardware-, vendor-, or chip-revision-specific. + +Because of this peculiarity, other sensors may be more appropriate, like the AHT20/AHT21 also supported by this plugin, or :ref:`P028_page`, :ref:`P106_page`, :ref:`P014_page`, :ref:`P034_page`, :ref:`P051_page`, :ref:`P068_page` or :ref:`P072_page`. + +This plugin tries to avoid such situations (I2C bus lock) by (soft) resetting the sensor if it doesn't respond for some time. + +This plugin also supports the **DHT20** and **AM2301B** sensors, as these are just **AHT20** sensors with a specific housing. + +Settings +-------- + +.. image:: P105_DeviceConfiguration.png + +* **Name**: The name for this task, should be unique. + +* **Enabled**: Allows to enable/disable the device. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build 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` + +* **I2C Address**: The address the device is using. The AHT10 sensor allows to select a secondary address by pulling the AO (sometimes marked as A0) pin to high (3.3V) to select the secondary address. That address should then be selected here too. The AHT20/AHT21 based sensors only support a single I2C address, so it will be forced to the default address. + +Device Settings +^^^^^^^^^^^^^^^^ + +If the plugin is configured for **Sensor model** AHT1x and other devices configured in a task use the I2C bus, a warning is displayed (see example below) that combining them with this device may cause issues on the I2C bus, resulting in all I2C devices no longer working as intended. This may occur immediately, or only after some time, like 10 minutes or an hour. + +.. image:: P105_DeviceWarning.png + +* **Sensor model** Selection of the connected type of hardware. (AHT2x based sensors have a slightly different intialization.) + +Available options: + +.. image:: P105_SensorModelOptions.png + +**AHT1x** AHT10/AHT15. These sensor models should better be avoided, as it doesn't always work with other devices on the same I2C bus. + +**AHT20** An more modern version of the sensor. Use this option also when connecting a DHT20 or AM2301B sensor. + +**AHT21** An more modern version of the sensor, very similar to the AHT20, in a more compact chip package. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **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. + +Values +^^^^^^ + +The names for the values are initially set to a default name, but can be changed if desired. Also, a formula can be entered to re-calculate the value before display/sending to a controller, and the number of decimals can be changed, for Temperature, usually 1 decimal is enough to be displayed (value will be rounded). + + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2021-08-01 Moved from ESPEasy PluginPlayground to the main repository. diff --git a/docs/source/Plugin/P105_DeviceConfiguration.png b/docs/source/Plugin/P105_DeviceConfiguration.png index 736f5a8ec..0064657c4 100644 Binary files a/docs/source/Plugin/P105_DeviceConfiguration.png and b/docs/source/Plugin/P105_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P105_DeviceWarning.png b/docs/source/Plugin/P105_DeviceWarning.png index 1552690e5..f8a569322 100644 Binary files a/docs/source/Plugin/P105_DeviceWarning.png and b/docs/source/Plugin/P105_DeviceWarning.png differ diff --git a/docs/source/Plugin/P105_SensorModelOptions.png b/docs/source/Plugin/P105_SensorModelOptions.png index 7505fb028..07831b213 100644 Binary files a/docs/source/Plugin/P105_SensorModelOptions.png and b/docs/source/Plugin/P105_SensorModelOptions.png differ diff --git a/docs/source/Plugin/P110.rst b/docs/source/Plugin/P110.rst index c6b81d636..48ec9d372 100644 --- a/docs/source/Plugin/P110.rst +++ b/docs/source/Plugin/P110.rst @@ -1,88 +1,100 @@ -.. include:: ../Plugin/_plugin_substitutions_p11x.repl -.. _P110_page: - -|P110_typename| -================================================== - -|P110_shortinfo| - -Plugin details --------------- - -Type: |P110_type| - -Name: |P110_name| - -Status: |P110_status| - -GitHub: |P110_github|_ - -Maintainer: |P110_maintainer| - -Used libraries: |P110_usedlibraries| - -Description ------------ - -This I2C sensor with 2 ranges offers distance measurement based on a Laser Time-of-Flight sensor. Output result is in millimeters. - -Configuration --------------- - -.. image:: P110_DeviceConfiguration.png - -**Name** A unique name should be entered here. - -**Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. - -I2C Options -^^^^^^^^^^^^ - -The available settings here depend on the build 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` - -**I2C Address**: The address the device is using. Some boards holding this chip offer an extra connection SDO that can be used to select the address, other boards just allow to select the secondary address and keep that active until the next power-cycle of the sensor. - -Device Settings -^^^^^^^^^^^^^^^^ - -**Timing**: The timing setting of the sensor determines the accuracy of the measurement. There are 3 options available: - -.. image:: P110_TimingOptions.png - -*Normal* The default value (80 msec.) - -*Fast* A faster but less accurate measurement (20 msec.) Can be used with a high speed read interval (1-5 sec). - -*Accurate* A slower but far more accurate measurement (320 msec.) For use with a longer read interval setting (30-60 sec). - -**Range**: the measuring ranges: - -.. image:: P110_RangeOptions.png - -*Normal* For measurements in the 0 to 800 millimeter range. - -*Long* For measurements in the 0 to 2000 millimeter range (but somewhat less accurate). - -The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. - -Values -^^^^^^ - -There is only 1 value available for this sensor, with the default name ``Distance``. A formula can be set to recalculate, f.e. to centimeters using ``%value%/10``. The number of decimals is by default set to 2, but for use with millimeters distance it can be set to 0, as no decimals are provided from the measurement. - -.. Events -.. ~~~~~~ - -.. .. include:: P110_events.repl - - - -Change log ----------- - -.. versionchanged:: 2.0 - ... - - |added| 2021-02-06 Moved to main repository Plugin 110 from PluginPlayground Plugin 133 - - |added| 2021-02-06 Refactoring to allow multiple instances of the plugin (when using an I2C multiplexer) +.. include:: ../Plugin/_plugin_substitutions_p11x.repl +.. _P110_page: + +|P110_typename| +================================================== + +|P110_shortinfo| + +Plugin details +-------------- + +Type: |P110_type| + +Name: |P110_name| + +Status: |P110_status| + +GitHub: |P110_github|_ + +Maintainer: |P110_maintainer| + +Used libraries: |P110_usedlibraries| + +Description +----------- + +This I2C sensor with 2 ranges offers distance measurement based on a Laser Time-of-Flight sensor. Output result is in millimeters. + +Configuration +-------------- + +.. image:: P110_DeviceConfiguration.png + +* **Name** A unique name should be entered here. + +* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build 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` + +* **I2C Address**: The address the device is using. Some boards holding this chip offer an extra connection SDO that can be used to select the address, other boards just allow to select the secondary address and keep that active until the next power-cycle of the sensor. + +Device Settings +^^^^^^^^^^^^^^^^ + +* **Timing**: The timing setting of the sensor determines the accuracy of the measurement. There are 3 options available: + +.. image:: P110_TimingOptions.png + +*Normal* The default value (80 msec.) + +*Fast* A faster but less accurate measurement (20 msec.) Can be used with a high speed read interval (1-5 sec). + +*Accurate* A slower but far more accurate measurement (320 msec.) For use with a longer read interval setting (30-60 sec). + +* **Range**: the measuring ranges: + +.. image:: P110_RangeOptions.png + +*Normal* For measurements in the 0 to 800 millimeter range. + +*Long* For measurements in the 0 to 2000 millimeter range (but somewhat less accurate). + +* **Send event when value unchanged**: When checked, will generate events for every **Interval**, when unchecked *and* **Interval** is set to 0, a change in **Distance** will immediately trigger events. (NB: Behavior changed since 2024/04/27, when this option was added) + +* **Trigger delta** To avoid triggering many events with only a small difference in distance the 'Trigger delta' option is available. This can be set to only trigger an event when the new distance is at least the delta less or more than the previous measurement. + +NB: This setting is ignored if 'Send event when value unchanged' is checked! +n +The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. + +``Interval`` can now be set to 0, causing events to only be generated when changed, with ``Trigger delta`` calculated in. + +Values +^^^^^^ + +There are 2 values available for this sensor, with the default name ``Distance`` and ``Direction``. A formula can be set to recalculate the distance, f.e. to centimeters using ``%value%/10``. The number of decimals can be set to 0, when using millimeters, as no decimals are provided from the measurement. + +Value ``Direction`` holds the direction relative to the *previous* distance value: ``-1`` = smaller distance, ``0`` = unchanged distance, ``1`` = greater distance. + +.. Events +.. ~~~~~~ + +.. .. include:: P110_events.repl + + + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-04-27 Add options for ``Send event when value unchanged`` and ``Trigger delta``, causing somewhat changed behavior. + + |added| 2021-02-06 Moved to main repository Plugin 110 from PluginPlayground Plugin 133 + + |added| 2021-02-06 Refactoring to allow multiple instances of the plugin (when using an I2C multiplexer) diff --git a/docs/source/Plugin/P110_DeviceConfiguration.png b/docs/source/Plugin/P110_DeviceConfiguration.png index 9dd788d9e..1451dd1c1 100644 Binary files a/docs/source/Plugin/P110_DeviceConfiguration.png and b/docs/source/Plugin/P110_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P110_RangeOptions.png b/docs/source/Plugin/P110_RangeOptions.png index cc0c42266..548c0b776 100644 Binary files a/docs/source/Plugin/P110_RangeOptions.png and b/docs/source/Plugin/P110_RangeOptions.png differ diff --git a/docs/source/Plugin/P110_TimingOptions.png b/docs/source/Plugin/P110_TimingOptions.png index 69b4034f8..89d0c45d3 100644 Binary files a/docs/source/Plugin/P110_TimingOptions.png and b/docs/source/Plugin/P110_TimingOptions.png differ diff --git a/docs/source/Plugin/P113.rst b/docs/source/Plugin/P113.rst index 4dc57aade..608032882 100644 --- a/docs/source/Plugin/P113.rst +++ b/docs/source/Plugin/P113.rst @@ -1,104 +1,109 @@ -.. include:: ../Plugin/_plugin_substitutions_p11x.repl -.. _P113_page: - -|P113_typename| -================================================== - -|P113_shortinfo| - -Plugin details --------------- - -Type: |P113_type| - -Name: |P113_name| - -Status: |P113_status| - -GitHub: |P113_github|_ - -Maintainer: |P113_maintainer| - -Used libraries: |P113_usedlibraries| - -Description ------------ - -This I2C sensor with 2 ranges offers distance measurement based on a Laser Time-of-Flight sensor. Output result for Distance is in millimeters. - -The VL53L1X sensor can measure either 0 - 130cm (Normal range) or 0 - 400cm (Long range), where the closely related VL53L0X can measure up to 200cm. The API's for these sensors are not compatible, so different libraries are used. - -Configuration --------------- - -.. image:: P113_DeviceConfiguration.png - -**Name** A unique name should be entered here. - -**Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. - -I2C Options -^^^^^^^^^^^^ - -The available settings here depend on the build 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` - -**I2C Address**: The address the device is using. Because of an issue with changing the I2C address of the sensor, this list is limited to 1 item, and the address isn't changed/set. - -Device Settings -^^^^^^^^^^^^^^^^ - -**Timing**: The timing setting of the sensor determines the accuracy of the measurement. There are 6 options available: - -.. image:: P113_TimingOptions.png - -*100 ms (Normal)* The default value, using an integration time of 100 msec. - -*20ms (Fastest)* The fastest but least accurate measurement. Should only be used for Normal range (see below). - -*33ms (Fast)* A fast but not very accurate measurement. - -*50ms* A somewhat more accurate measurement. - -*200ms Accurate* A slower but far more accurate measurement. For use with a longer read interval setting (30-60 sec). - -*500ms* The longest integration time available. For use with long read interval settings. - -**Range**: the measuring ranges: - -.. image:: P113_RangeOptions.png - -*Normal (~130cm)* For measurements in the 0 to 1300 millimeter (130cm) range. - -*Long (~400cm)* For measurements in the 0 to 4000 millimeter (400cm) range (but somewhat less accurate). - -**Send event when value unchanged** To avoid many of the same events when the measurement is stable, this option is off by default. When enabled, every measurement will cause an event, and send the data to any enabled Controller. - -**Trigger delta** To avoid triggering many events with only a small difference in distance the 'Trigger delta' option is available. This can be set to only trigger an event when the new distance is at least the delta less or more than the previous measurement. - -NB: This setting is ignored if 'Send event when value unchanged' is checked! - - -The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. - -**Interval** By default, Interval will be set to 60 sec. Setting this to 1 or 2 seconds, usually offers a reasonable response time. - -Values -^^^^^^ - -The measured distance is available in ``Distance``. A formula can be set to recalculate, f.e. to centimeters using ``%value%/10``. The number of decimals is by default set to 2, but for use with millimeters distance it can be set to 0, as no decimals are provided from the measurement. - -The Ambient lighting condition during measurement is available in ``Ambient``. The unit is kcps (Photons per second, recalculated to kilo count per second) - -.. Events -.. ~~~~~~ - -.. .. include:: P113_events.repl - - - -Change log ----------- - -.. versionchanged:: 2.0 - - |added| 2021-04-05 Added to main repository as Plugin 113 Distance - VL53L1X (400cm), based on a copy of Plugin 110 Distance - VL53L0X (200cm) +.. include:: ../Plugin/_plugin_substitutions_p11x.repl +.. _P113_page: + +|P113_typename| +================================================== + +|P113_shortinfo| + +Plugin details +-------------- + +Type: |P113_type| + +Name: |P113_name| + +Status: |P113_status| + +GitHub: |P113_github|_ + +Maintainer: |P113_maintainer| + +Used libraries: |P113_usedlibraries| + +Description +----------- + +This I2C sensor with 2 ranges offers distance measurement based on a Laser Time-of-Flight sensor. Output result for Distance is in millimeters. + +The VL53L1X sensor can measure either 0 - 130cm (Normal range) or 0 - 400cm (Long range), where the closely related VL53L0X can measure up to 200cm. The API's for these sensors are not compatible, so different libraries are used. + +Configuration +-------------- + +.. image:: P113_DeviceConfiguration.png + +* **Name** A unique name should be entered here. + +* **Enabled** The device can be disabled or enabled. When not enabled the device should not use any resources. + +I2C Options +^^^^^^^^^^^^ + +The available settings here depend on the build 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` + +* **I2C Address**: The address the device is using. Because of an issue with changing the I2C address of the sensor, this list is limited to 1 item, and the address isn't changed/set. + +Device Settings +^^^^^^^^^^^^^^^^ + +* **Timing**: The timing setting of the sensor determines the accuracy of the measurement. There are 6 options available: + +.. image:: P113_TimingOptions.png + +*100 ms (Normal)* The default value, using an integration time of 100 msec. + +*20ms (Fastest)* The fastest but least accurate measurement. Should only be used for Normal range (see below). + +*33ms (Fast)* A fast but not very accurate measurement. + +*50ms* A somewhat more accurate measurement. + +*200ms Accurate* A slower but far more accurate measurement. For use with a longer read interval setting (30-60 sec). + +*500ms* The longest integration time available. For use with long read interval settings. + +* **Range**: the measuring ranges: + +.. image:: P113_RangeOptions.png + +*Normal (~130cm)* For measurements in the 0 to 1300 millimeter (130cm) range. + +*Long (~400cm)* For measurements in the 0 to 4000 millimeter (400cm) range (but somewhat less accurate). + +* **Send event when value unchanged** To avoid many of the same events when the measurement is stable, this option is off by default. When enabled, every measurement will cause an event, and send the data to any enabled Controller. + +* **Trigger delta** To avoid triggering many events with only a small difference in distance the 'Trigger delta' option is available. This can be set to only trigger an event when the new distance is at least the delta less or more than the previous measurement. + +NB: This setting is ignored if 'Send event when value unchanged' is checked! + + +The Data Acquisition, Send to Controller and Interval settings are standard available configuration items. Send to Controller only when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. Setting this to 1 or 2 seconds, usually offers a reasonable response time. + +Values +^^^^^^ + +The measured distance is available in ``Distance``. A formula can be set to recalculate, f.e. to centimeters using ``%value%/10``. The number of decimals can be set to 0, when using millimeters, as no decimals are provided from the measurement. + +The Ambient lighting condition during measurement is available in ``Ambient``. The unit is kcps (Photons per second, recalculated to kilo count per second) + +Value ``Direction`` holds the direction relative to the *previous* distance value: ``-1`` = smaller distance, ``0`` = unchanged distance, ``1`` = greater distance. + +.. Events +.. ~~~~~~ + +.. .. include:: P113_events.repl + + + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-04-27 Add value ``Direction``. + + |added| 2021-04-05 Added to main repository as Plugin 113 Distance - VL53L1X (400cm), based on a copy of Plugin 110 Distance - VL53L0X (200cm) diff --git a/docs/source/Plugin/P113_DeviceConfiguration.png b/docs/source/Plugin/P113_DeviceConfiguration.png index 147c4dead..0c73a12b3 100644 Binary files a/docs/source/Plugin/P113_DeviceConfiguration.png and b/docs/source/Plugin/P113_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P113_RangeOptions.png b/docs/source/Plugin/P113_RangeOptions.png index 647190567..25bf3fef1 100644 Binary files a/docs/source/Plugin/P113_RangeOptions.png and b/docs/source/Plugin/P113_RangeOptions.png differ diff --git a/docs/source/Plugin/P113_TimingOptions.png b/docs/source/Plugin/P113_TimingOptions.png index ee318e7d6..14cb802c1 100644 Binary files a/docs/source/Plugin/P113_TimingOptions.png and b/docs/source/Plugin/P113_TimingOptions.png differ diff --git a/docs/source/Plugin/P116.rst b/docs/source/Plugin/P116.rst index 6e2a42d13..afddf033a 100644 --- a/docs/source/Plugin/P116.rst +++ b/docs/source/Plugin/P116.rst @@ -28,7 +28,7 @@ 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 and 80 x 160 pixels +* **ST7735** with resolutions 128 x 128, 128 x 160, 80 x 160 and 135 x 240 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. @@ -140,6 +140,7 @@ Available options: * *ST7735 128 x 160px* Allows 16 lines of text in the smallest font scaling setting. * *ST7735 80 x 160px* Allows 16 lines of text in the smallest font scaling setting. * *ST7735 80 x 160px (Color inverted)* Special color inverted configuration as used in f.e. M5Stack StickC. +* *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. * *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. @@ -260,6 +261,10 @@ Change log .. versionadded:: 2.0 ... + |added| 2024-03-26 Add support for ST7735, 135x240 resolution + + |added| 2022-07-06 Add support for ST7735 (Color inverted), for M5StickC support + |added| 2021-11-06 Add support for ST7796 displays |added| 2021-08 Moved from an external forum to ESPEasy. diff --git a/docs/source/Plugin/P116_TFTDisplayModelOptions.png b/docs/source/Plugin/P116_TFTDisplayModelOptions.png index a17199a9c..6719b106c 100644 Binary files a/docs/source/Plugin/P116_TFTDisplayModelOptions.png and b/docs/source/Plugin/P116_TFTDisplayModelOptions.png differ diff --git a/docs/source/Plugin/P116_commands.repl b/docs/source/Plugin/P116_commands.repl index 50d2b9062..6a6702d43 100644 --- a/docs/source/Plugin/P116_commands.repl +++ b/docs/source/Plugin/P116_commands.repl @@ -28,7 +28,7 @@ " | ``st77xxcmd,clear`` "," - | Clear the display, using the default background color. + | Clear the display, using the **default** background color. For clearing with a custom background color see the ``,clear[,]`` command. " " | ``st77xxcmd,backlight,`` diff --git a/docs/source/Plugin/P123.rst b/docs/source/Plugin/P123.rst new file mode 100644 index 000000000..e2e27af94 --- /dev/null +++ b/docs/source/Plugin/P123.rst @@ -0,0 +1,110 @@ +.. include:: ../Plugin/_plugin_substitutions_p12x.repl +.. _P123_page: + +|P123_typename| +================================================== + +|P123_shortinfo| + +Plugin details +-------------- + +Type: |P123_type| + +Name: |P123_name| + +Status: |P123_status| + +GitHub: |P123_github|_ + +Maintainer: |P123_maintainer| + +Used libraries: |P123_usedlibraries| + +Supported hardware +------------------ + +Some displays are available with a touch overlay mounted on top of the visible side of the display. There's a choice of resistive and capacitive touch overlays. The I2C touch overlays supported in this plugin are of the Capacitive kind, being very easy to interact with, comparable to modern smartphones, where you can use a finger for the interaction. (Resistive touch overlays usually require a special pen to be used, or pressed with a finger-nail, to get a response.) + +The supported touch overlays (or touch screens), can be found on several displays, f.e. some M5Stack devices, the WT32-SC01 display unit and the LilyGO LILY Pi ESP32 unit. + +Device configuration +-------------------- + +.. image:: P123_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. + +I2C options +^^^^^^^^^^^ + +The available settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +* **Display task**: Select the display task the touch screen is mounted on. By default, the current task is selected (and ignored), as any other guess would be wrong, and there is no 'None' selection available. + +When choosing the correct task, the current display resolution, color depth and rotation settings are tried to be fetched from that task, and copied here in the matching settings. If no settings can be obtained, defaults will be applied. + +The configured display will be used to draw the objects, if any, that can be configured below, on. + +* **Screen Width (px) (x)**: Width of the display, the (horizontal) ``x`` coordinate, in pixels. + +* **Screen Height (px) (y)**: Height of the display, the (vertical) ``y`` coordinate, in pixels. Top/Left coordinate is 0,0. + +* **Rotation**: The rotation setting should match the rotation setting of the display, and can be selected as Normal (0), +90, +180 or +270 degrees. + +* **Display Color-depth**: If the display settings have been retrieved successfully, this setting can not be changed, but will be used from the display. This value is used to select the correct color mappings for displaying the Objects. + +* **Touch minimum pressure**: This setting determines the relative pressure or sensitivity of the FT62x6 touch displays. Lower values make it more sensitive. The range is 0 to 255. Only applicable for the FT62x6 controller. + +* **Touchscreen Type (Address)**: Select the type of touchscreen installed, available options: + +.. image:: P123_TouchscreenTypeOptions.png + +* *FT62x6 (0x38)*: Focaltech FT62x6/FT63x6/FT52x6 touchscreen controller family. Default selection. + +* *GT911 (0x5D)*: GT911 touchscreen controller, default address. + +* *GT911 (0x14)*: GT911 touchscreen controller, alternate address. + +* *CST820 (0x15)*: CST816/CST820 touchscreen controller family. + +* *CST226 (0x5A)*: CST226 touchscreen controller. (Not yet tested on real hardware) + +* *AXS15231 (0x3B)*: AXS15231 touchscreen controller. (Not yet tested on real hardware) + +* *CHSC5816 (0x2E)*: CHSC5816 touchscreen controller. (Not yet tested on real hardware) + +* *Auto-detect*: Select this option to have the plugin auto-detect the used touchscreen controller. This auto-detect is mostly based on an I2C device being available at the shown addresses, so might get confused if a non-touchscreen I2C device at one of these addresses is installed. + +| + +* **Interrupt pin**: Select the GPIO that is connected to the IRQ connection of a GT911 touchscreen controller. The IRQ pin is only used for detection & address configuration, combined with the **Reset pin**, if available and configured. Only used for GT911 controller. + +* **Reset pin**: Select the GPIO that is connected to the Reset connection of the touchscreen controller. When set, this will be used to reset the controller during initialization. (See the Warning below). Do not set this GPIO setting if the RST pin is shared with another device, like on a WT32 SC01 Plus, where the display and touch controllers use the same GPIO pin. **Only** set the RST pin on the display configuration, and give that Display task a **lower task number** than the touch screen, as the touchscreen task will try to display defined objects on the connected Display device! + +.. note:: The **Interrupt** and **Reset** GPIO pins are optional. Interrupt is only used by the GT911 controller during initialization. + +.. warning:: Some devices that have the reset pin connected, initially set that GPIO to low/0, causing the touch controller not to start, and it won't even show up on the I2C Scan. This can be solved by setting the involved GPIO pin to **Output High** on the :ref:`Hardware_page`. + +.. warning:: During testing we found that specifically the CST816 touchscreen controller, whiel working as expected, **doesn't** show up in an I2C Scan. This also implies they can't be auto-detected, and have to be set explicitly. + +.. include:: Touch_Configuration.repl + + +Change log +---------- + +.. versionadded:: 2.0 + ... + + |added| 2024-06-05 Initial release version. + + + + + diff --git a/docs/source/Plugin/P123_DeviceConfiguration.png b/docs/source/Plugin/P123_DeviceConfiguration.png new file mode 100644 index 000000000..01298c71f Binary files /dev/null and b/docs/source/Plugin/P123_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P123_TouchscreenTypeOptions.png b/docs/source/Plugin/P123_TouchscreenTypeOptions.png new file mode 100644 index 000000000..4d4b89798 Binary files /dev/null and b/docs/source/Plugin/P123_TouchscreenTypeOptions.png differ diff --git a/docs/source/Plugin/P131.rst b/docs/source/Plugin/P131.rst index 76a892a69..a8daa50bc 100644 --- a/docs/source/Plugin/P131.rst +++ b/docs/source/Plugin/P131.rst @@ -193,6 +193,14 @@ Default setting is *Continue to next line*. * **Maximum allowed brightness**: The brightness that is the maximum it can be set to, either from UI or via the ``brightness`` command, to optionally help protect both the eyes and the power-supply powering the display. Default is 255. Range: 1..255. +* **Default font**: Select from the currently available fonts, the font that will be active when the plugin is started. + +Available fonts are depending on the build used, some builds don't show this option, as then only the ``default`` font is included: + +.. image:: P131_DefaultFontOptions.png + +The complete list of possible fonts is available at the ``font`` subcommand, below. (This screenshot is taken from a MAX build) + * **Font scaling** The scaling factor for the currently active font. Select a factor between 1 and 4. * **Clear display on exit** When checked, will clear the display when the task is disabled, either from settings or via the ``TaskDisable`` command. This will fill the display with black pixels, turning all NeoPixels off. diff --git a/docs/source/Plugin/P131_DefaultFontOptions.png b/docs/source/Plugin/P131_DefaultFontOptions.png new file mode 100644 index 000000000..48dc4d35d Binary files /dev/null and b/docs/source/Plugin/P131_DefaultFontOptions.png differ diff --git a/docs/source/Plugin/P131_DeviceConfiguration.png b/docs/source/Plugin/P131_DeviceConfiguration.png index 49a528a6a..90159126d 100644 Binary files a/docs/source/Plugin/P131_DeviceConfiguration.png and b/docs/source/Plugin/P131_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P162.rst b/docs/source/Plugin/P162.rst new file mode 100644 index 000000000..bad15fc4f --- /dev/null +++ b/docs/source/Plugin/P162.rst @@ -0,0 +1,96 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P162_page: + +|P162_typename| +================================================== + +|P162_shortinfo| + +Plugin details +-------------- + +Type: |P162_type| + +Name: |P162_name| + +Status: |P162_status| + +GitHub: |P162_github|_ + +Maintainer: |P162_maintainer| + +Used libraries: |P162_usedlibraries| + +Description +----------- + +The MCP42xxx/MCP41xxx (the xxx specifies the max resistance value) are dual/single digital potentiometers (digipot), that can be controlled via SPI, and offer a 256 step resolution. + +NB: The daisy-chain feature, available in the MCP42xxx chips, is not implemented in this plugin. + +Hardware +-------- + +This plugin is built and tested using a DFRobot `DFR0520 Dual Digipot board `_ where also a suggestion for wiring up the board is shown. + +The board will tolerate 5V power and signals, but when powering the board at 5V and connecting to an ESP, the signal levels **must** be adjusted to 3.3V by using a level converter, to avoid damaging the ESP! + +Configuration +------------- + +.. image:: P162_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. + +Sensor +^^^^^^ + +* **GPIO -> CS PIN**: Configuring the ``CS`` (Chip select) pin is required to correctly address the board, and start the plugin. Also, the standard SPI interface pins ``MOSI`` and ``CLK`` have to be configured and connected. + +* **GPIO -> RST PIN (optional)**: Not all boards have the ``RST`` (Reset) pin of the chip available. When not available or not used, it should be set to *- None -*. This pin is not available on the MCP41xxx chips. + +* **GPIO -> SHDN PIN (optional)**: Not all boards have the ``SHDN`` (Shutdown) pin of the chip available. When not available or not used, it should be set to *- None -* (Shutdown is explained below). This pin is not available on the MCP41xxx chips. + +Device Settings +^^^^^^^^^^^^^^^ + +* **Initial value Wx**: Range: 0..255. Set the value of the wiper (W) after initialization. By default it is set at the center of the range, 128, corresponding to the value after power-on or reset. Lower values bring the wiper closer to PBx, and higher values closer to PAx level. + +* **Initial shutdown Wx**: The wiper can be initially set to Shutdown mode, where the PAx connection is disconnected, and Wx is connected to PBx. When both outputs are initially set to Shutdown, and the SHDN pin is configured, that pin will be used to shutdown the chip. Shutdown mode is undone by setting a value to one of the outputs, using the command described below. + +These settings are available for both W0 and W1. NB: The W1 settings can't be applied on the MCP41xxx chips, as that has only 1 digipot. + +* **Value at Shutdown**: Range -1..256. Value for the output variables when Shutdown mode for that output is set. + +* **Send values on change**: When checked, and Interval set to 0, events will only be generated, and sent to any configured Controllers, when a Value is changed. On startup, the initial values will be sent out, but that's the default behavior for ESPEasy. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will not be set. The values will be optionally sent to any configured controllers using the interval, when set. + +Values +^^^^^^ + +The plugin provides the ``W0`` and ``W1`` values. A formula can be set to recalculate. The number of decimals can be set as desired, and defaults to 0, as these can only be set to integer values, but a formula could result in decimal values. + +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: ` + + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P162_commands.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-04-15 Initial release version. + diff --git a/docs/source/Plugin/P162_DeviceConfiguration.png b/docs/source/Plugin/P162_DeviceConfiguration.png new file mode 100644 index 000000000..34e84d6e4 Binary files /dev/null and b/docs/source/Plugin/P162_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P162_commands.repl b/docs/source/Plugin/P162_commands.repl new file mode 100644 index 000000000..a2fa851f5 --- /dev/null +++ b/docs/source/Plugin/P162_commands.repl @@ -0,0 +1,43 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + ``digipot,reset`` + "," + Sending this command to the task the digipot will be reset to the default wiper settings (128). When a ``RST`` pin is configured, that will be shortly pulled to GND to reset the hardware, else the initial settings will be set to the chip. + + On reset, the Initial Shutdown settings will *not* be applied! + " + " + ``digipot,shutdown[,]`` + + ````: Optional wiper to shutdown. + + ``0`` = W0 + + ``1`` = W1 + + ``2`` = Both wipers (default when ommitted) + "," + This command will set the indicated wiper to Shutdown mode. When no wiper is given, then both wipers will be set to shutdown mode, optionally using the hardware ``SHDN`` pin if that's configured. + + Shutdown mode will be released when a value is set to a wiper. + " + " + ``digipot,,`` + + ````: Wiper to set the value for. + + ``0`` = W0 + + ``1`` = W1 + + ``2`` = Both wipers + + ````: Range 0..255 to set the wiper to. + "," + This command will set the provided value to the indicated wiper. When using wiper 2 (both), then a single command will change both values at the same moment. + + When setting a value to a wiper, it will also release the shutdown mode, when enabled. + " diff --git a/docs/source/Plugin/P164.rst b/docs/source/Plugin/P164.rst new file mode 100644 index 000000000..3662aae8d --- /dev/null +++ b/docs/source/Plugin/P164.rst @@ -0,0 +1,117 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P164_page: + +|P164_typename| +================================================== + +|P164_shortinfo| + +Plugin details +-------------- + +Type: |P164_type| + +Name: |P164_name| + +Status: |P164_status| + +GitHub: |P164_github|_ + +Maintainer: |P164_maintainer| + +Used libraries: |P164_usedlibraries| + +Description +----------- + +Sciosense ENS160 and its successor ENS161 are multi-gas sensors based on metal oxide (MOX) technology. They can detect multiple VOCs including ethanol, toluene, hydrogen and oxidizing gases. +The sensors are connected to the I2C bus and provide preprocessed data as TVOC and eCO2 values. See https://www.sciosense.com/ens16x-digital-metal-oxide-multi-gas-sensor-family/ for details about the device. + +Hardware +-------- + +Various boards with the ENS160 are available through electronics market places like Aliexpress. A popular board contains both ENS160 and AHT21 on a single PCB. The AHT21 can be used for temperature compensation as provided by this plugin. + +.. image:: P164_board.jpg + +Configuration +------------- + +.. image:: P164_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. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: The sensor supports two addresses. The actual address depends on the voltage on the ADDR pin of the device. + +.. csv-table:: + :header: "Address", "Remark" + :widths: 10, 50 + + "0x52", "Connect ADDR GND" + "0x53", "Connect ADDR to VDD (default on most boards)" + +The available I2C settings here depend on the build 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` + +The chip supports both SPI and I2C. The plugin only support I2C using the following pins: + +* 1 SDA: Data [MOSI/SDA] +* 2 SCL: Serial Clock [SCLK/SCL] +* 3 ADDR: Address [MISO/ADDR] +* 6 INTn: Interrupt [INTn] +* 7 CSn: SPI interface select, low-> SPI, high->I2C [CSn] + +For this plugin the CSn must be wired to VDD to enable the I2C protocol. + +On boards sold online, SDO and SDI are often already pulled-up, setting the default I2C address to 0x53. + + +Device Settings +^^^^^^^^^^^^^^^ + +* **Detected Sensor Type**: Shows either ``ENS160`` or ``ENS161`` or a number if no sensor or an unknown sensor ID is detected. It will also show the firmware version read from the device if the data is available. +* **Temperature Task**: Task (plugin) providing the temperature compensation +* **Temperature Value**: Value for the temperature compensation +* **Humidity Task**: Task (plugin) providing the humidity compensation +* **Humidity Value**: Value for the humidity compensation + +The ENS16x provides temperature and humidity compensation. The plugin must provide the actual temperature and humidity to the device to enable this compensation. For this the plugin needs to read these values from other tasks. + +Using the **Temperature Task** and **Temperature Value** the task and value for the Temperature compensation can be selected. Compensation can be switched off by selecting **Not Set**. + +Using the **Humidity Task** and **Humidity Value** the task and value for the Humidity compensation can be selected. Compensation can be switched off by selecting **Not Set**. + +Note that if either one of the tasks is set to **Not Set** compensation is switched off and a default value is used. + +The ENS160 is sold on popular websites on a board including the AHT21 temperature and humidity sensor. This sensor can be used for the compensation algorithm of the ENS160. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start! + +Values +^^^^^^ + +The plugin provides the ``TVOC`` and ``eCO2`` values. A formula can be set to recalculate. 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: ` + +Currently the extra features offered by the sensor are not configurable in this plugin. +These may be added later. + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2023-12-27 Initial release version. + diff --git a/docs/source/Plugin/P164_DeviceConfiguration.png b/docs/source/Plugin/P164_DeviceConfiguration.png new file mode 100644 index 000000000..86b7abb91 Binary files /dev/null and b/docs/source/Plugin/P164_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P164_board.jpg b/docs/source/Plugin/P164_board.jpg new file mode 100644 index 000000000..bc562c576 Binary files /dev/null and b/docs/source/Plugin/P164_board.jpg differ diff --git a/docs/source/Plugin/P166.rst b/docs/source/Plugin/P166.rst new file mode 100644 index 000000000..8d9941a5f --- /dev/null +++ b/docs/source/Plugin/P166.rst @@ -0,0 +1,117 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P166_page: + +|P166_typename| +================================================== + +|P166_shortinfo| + +Plugin details +-------------- + +Type: |P166_type| + +Name: |P166_name| + +Status: |P166_status| + +GitHub: |P166_github|_ + +Maintainer: |P166_maintainer| + +Used libraries: |P166_usedlibraries| + +Description +----------- + +The GP8403 DAC (Digital Analog Converter) has 2 output channels, can be configured for an output voltage range of 0-10V or 0-5V, has a 12 bit resolution (4096 steps) and works from a single 3.3V or 5V power source. When powering the module with 5V, a level converter should be used on the I2C lines, to protect the ESP and other I2C devices on the same bus. The outputs can supply up to 20 mA. + +.. image:: P166_DFRobot_GP8403_board.png + +(Image (c) DFRobot) + +Configuration +------------- + +.. image:: P166_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. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: The device supports 8 addresses, and by default comes configured for address ``0x5F``, as reflected in the available options: + +.. image:: P166_I2CAddressOptions.png + +Available addresses are in the range ``0x58`` to ``0x5F``. + +The available I2C settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +* **Output range**: Select the desired output voltage range: + +.. image:: P166_OutputRangeOptions.png + +Available options: + +* *0-5V*: The maximum available voltage is 5V. When not using a voltage > 5V, this would be the apropriate settings, both for accuracy, and to avoid the risk of applying up to 10V to an input that might not be able to handle that. + +* *0-10V*: (Preconfigured default) The maximum available voltage is 10V. + +The DAC does not support a different output range setting per output. + +The DAC can supply up to 20 mA per output, so the input or device that is connected to this output should be configured to not overload the output. + +* **Restore output on warm boot**: (Enabled by default) When the outputs are set to a specific value and the ESP unit is restarted, f.e. when updating the ESPEasy firmware, the output values will be restored. On cold boot, or after saving the settings, the configured Initial value per output will be set. + +* **Initial value output 0**: + +* **Initial value output 1**: The output value at startup, or after power-loss, can be configured here per output. The allowed range is 0-10V. Although the resolution is in milli Volt, the resolution of the DAC is limited to 12 bit (4096 steps). If a voltage is set here that exceeds the Output range (f.e. when set to 0-5V, and 7V is configured), the setting will **not** be applied! + +Preset values +^^^^^^^^^^^^^ + +* **Preset value 1..25**: This table allows to configure up to 25 named presets, where the name can be up to 16 characters long, and the voltage in range 0-10V. When all available inputs are filled, Submitting the page will add more inputs until the limit of 25 is reached. + +When leaving the *Name* field empty, that preset will not be saved, thus effectively deleted from the list. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced). + +Values +^^^^^^ + +The plugin provides the ``Output0`` and ``Output1`` values, analogue to the availabe output connections. 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: ` + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P166_commands.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P166_config_values.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2024-01-30 Initial release version. + diff --git a/docs/source/Plugin/P166_DFRobot_GP8403_board.png b/docs/source/Plugin/P166_DFRobot_GP8403_board.png new file mode 100644 index 000000000..ac6237ea8 Binary files /dev/null and b/docs/source/Plugin/P166_DFRobot_GP8403_board.png differ diff --git a/docs/source/Plugin/P166_DeviceConfiguration.png b/docs/source/Plugin/P166_DeviceConfiguration.png new file mode 100644 index 000000000..cec3d9fa2 Binary files /dev/null and b/docs/source/Plugin/P166_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P166_I2CAddressOptions.png b/docs/source/Plugin/P166_I2CAddressOptions.png new file mode 100644 index 000000000..7730d42f5 Binary files /dev/null and b/docs/source/Plugin/P166_I2CAddressOptions.png differ diff --git a/docs/source/Plugin/P166_OutputRangeOptions.png b/docs/source/Plugin/P166_OutputRangeOptions.png new file mode 100644 index 000000000..4ad4f3aca Binary files /dev/null and b/docs/source/Plugin/P166_OutputRangeOptions.png differ diff --git a/docs/source/Plugin/P166_commands.repl b/docs/source/Plugin/P166_commands.repl new file mode 100644 index 000000000..1fbeb23cb --- /dev/null +++ b/docs/source/Plugin/P166_commands.repl @@ -0,0 +1,40 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + ```` = Output channel, 0, 1 or 2 (both channels) + "," + When successfully changing an output value, the data will be sent to any configured Controller, and an event will also be generated when the Rules are enabled (Tools/Advanced). + " + " + ``gp8403,volt,,`` + "," + Set the output value for the channel(s) in volt. The range is determined by the configured Output range setting. + " + " + ``gp8403,mvolt,,`` + "," + Set the output value for the channel(s) in milli volt. The range is determined by the configured Output range setting. + " + " + ``gp8403,range,<5|10>`` + "," + Set the output range to either 5 (0-5V) or 10 (0-10V). Is also set to the Device configuration, but not saved. + + The range is applied to both channels, no separate configuration per channel supported by the chip. + " + " + ``gp8403,preset,,`` + "," + Set the output value for the channel(s) to the value configured for the preset stored with ````. + + The value is range-checked before it is applied, so when configured for Output range 0-5V, a preset of 7V will be ignored. + + When using duplicate names, the first matched preset will be used. + " + " + ``gp8403,init,`` + "," + Set the output value for the channel(s) to the configured initial value(s). When both channels are addressed (2), each will get its own configured value. + " diff --git a/docs/source/Plugin/P166_config_values.repl b/docs/source/Plugin/P166_config_values.repl new file mode 100644 index 000000000..fc20b7add --- /dev/null +++ b/docs/source/Plugin/P166_config_values.repl @@ -0,0 +1,24 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#preset]`` + "," + Returns the numbered preset value ````, range ``preset1``..``preset25``, as shown in the configuration. Only configured values, having a name, can be retrieved. + " + " + ``[#initial0]`` + "," + Returns the configured initial voltage for output 0. + " + " + ``[#initial1]`` + "," + Returns the configured initial voltage for output 1. + " + " + ``[#range]`` + "," + Returns the configured Output range value 5 (0-5V) or 10 (0-10V) + " diff --git a/docs/source/Plugin/P167.rst b/docs/source/Plugin/P167.rst new file mode 100644 index 000000000..e058ae85a --- /dev/null +++ b/docs/source/Plugin/P167.rst @@ -0,0 +1,144 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P167_page: + +|P167_typename| +================================================== + +|P167_shortinfo| + +Plugin details +-------------- + +Type: |P167_type| + +Name: |P167_name| + +Status: |P167_status| + +GitHub: |P167_github|_ + +Maintainers: |P167_maintainer| + +Used libraries: |P167_usedlibraries| + +Description +----------- + +The Sensirion SEN5x series of sensors measure Particle matter (all models), Temperature, Humidity and tVOC (SEN54, SEN55) and NOx (SEN55). + +This plugin can read all values from these sensors. + +In addition, the IKEA Vindstryka, that has a Sensirion SEN54 installed, can be 'piggy-backed' with this plugin, eavesdropping on the I2C communication to retrieve the values. + +Hardware setup +-------------- + +When connecting a stand-alone Sensirion SEN5x sensor, be sure to power the unit with 5V! The I2C SDA/SCL signals are 3.3V safe, so these can be directly connected to an ESP. Pull-up resistors to 3.3V might be needed, as they are not installed on the device! + +When installing an ESP inside an IKEA Vindstyrka, the procedure of wiring this is rather simple: Connect GND, SDA and SCL to the configured ESP pins (VCC for the ESP can also be reused from the power source for the Vindstryka), connect the **MonPin SCL** configured GPIO pin *also* to SCL, and the plugin will wait for the IKEA controller to finish communication and then fetch the data from the sensor. + +Configuration +------------- + +.. image:: P167_DeviceConfiguration.png + :alt: Device configuration + + +* **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. + +I2C options +^^^^^^^^^^^ + +The available settings here depend on the build 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` + +.. note:: According to the documentation, the SEN5x sensors support a max. I2C Clock Speed of 100 kHz, **Force Slow I2C speed** should be checked. (ESPEasy has a default setting of 100 kHz for I2C Slow device Clock Speed). + +Device Settings +^^^^^^^^^^^^^^^ + +* **Model Type**: Select the sensor model used, available options: + +.. image:: P167_ModelTypeOptions.png + :alt: Model Type options + +* *IKEA Vindstyrka* The default, with the ability to eavesdrop on the I2C communication of the Vindstyrka controller and the installed SEN54. + +* *SEN54* The stand-alone model, providing the same values as the Vindstyrka. This setting can also be used when installing a SEN50, but only the PM\* values can be used on that sensor. + +* *SEN55* The most advanced model, adding support for the NOx index. + +When this setting is changed, the page is saved and reloaded to show/hide the extra setting to configure **MonPin SCL**. + +* **MonPin SCL**: (Only available for IKEA Vindstryka) Select the GPIO pin that is connected to the I2C SCL pin. It is used to monitor the I2C communication by the IKEA controller, so we don't interfere with that. + +If the plugin is enabled, Device info for the sensor is shown in the Configuration page: + +.. image:: P167_DeviceInfo.png + +This includes any warnings received from the sensor, and counters for pass/fail/errCode. + +The fail value is usually 1 or 2, because of the startup time of the sensor, that often returns a failed reading. If this number increases, then a check for I2C pull-up resistors should be done, as that is a requirement for I2C devices, and also, short wires have to be used for stable communication. + +* **Technical logging**: When debugging the hardware setup, especially when installing an ESP inside an IKEA Vindstryka, it can be helpful to have some extra technical logging available (INFO level). + +Output Configuration +^^^^^^^^^^^^^^^^^^^^ + +* **Number Output Values**: Select the number of values that should be available, available options: + +.. image:: P167_NumberOutputValuesOptions.png + +.. .. + +* **Value 1..4**: For each of the output values configured, the value to show can be configured, some defaults have been preselected. Available options: + +.. image:: P167_ValueOptions.png + +A description per available value is documented below, in the **Get Config Values** section. + +N.B.: NOx is only available when a Sensirion SEN55 is installed, but is always shown as an option. + +N.B.2: The selected options determine the names of the output Values. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. + +Values +^^^^^^ + +The plugin provides the configured values, with their default names. + +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: ` + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P167_commands.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P167_config_values.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-05-05 Enable support for SEN54 and SEN55 stand-alone sensors, add Get Config Values and Commands. + + |added| 2024-04-15 Initial release version supporting IKEA Vindstyrka. + + + + + diff --git a/docs/source/Plugin/P167_DeviceConfiguration.png b/docs/source/Plugin/P167_DeviceConfiguration.png new file mode 100644 index 000000000..6db0ae2f4 Binary files /dev/null and b/docs/source/Plugin/P167_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P167_DeviceInfo.png b/docs/source/Plugin/P167_DeviceInfo.png new file mode 100644 index 000000000..4a89e8f60 Binary files /dev/null and b/docs/source/Plugin/P167_DeviceInfo.png differ diff --git a/docs/source/Plugin/P167_ModelTypeOptions.png b/docs/source/Plugin/P167_ModelTypeOptions.png new file mode 100644 index 000000000..6963121da Binary files /dev/null and b/docs/source/Plugin/P167_ModelTypeOptions.png differ diff --git a/docs/source/Plugin/P167_NumberOutputValuesOptions.png b/docs/source/Plugin/P167_NumberOutputValuesOptions.png new file mode 100644 index 000000000..d663799bb Binary files /dev/null and b/docs/source/Plugin/P167_NumberOutputValuesOptions.png differ diff --git a/docs/source/Plugin/P167_ValueOptions.png b/docs/source/Plugin/P167_ValueOptions.png new file mode 100644 index 000000000..5aefb9d60 Binary files /dev/null and b/docs/source/Plugin/P167_ValueOptions.png differ diff --git a/docs/source/Plugin/P167_commands.repl b/docs/source/Plugin/P167_commands.repl new file mode 100644 index 000000000..a1f5efc6d --- /dev/null +++ b/docs/source/Plugin/P167_commands.repl @@ -0,0 +1,18 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + | ``sen5x,Startclean`` + "," + | Starts the built-in cleaning cycle of the device. + + | N.B.: Normally this shouldn't be needed, as the device automatically starts a weekly cleaning cycle, but if the device is not switched on continuously, this timer is reset on power-on, but the device still needs a cleaning cycle once a week. + " + " + | ``sen5x,Techlog,<0|1>`` + "," + | Enable or disable the ``Technical logging`` option, available in the UI. This logging is mostly useful when debugging either the hardware or software setup. + + | The setting is *not* stored automatically. + " diff --git a/docs/source/Plugin/P167_config_values.repl b/docs/source/Plugin/P167_config_values.repl new file mode 100644 index 000000000..59ebe561d --- /dev/null +++ b/docs/source/Plugin/P167_config_values.repl @@ -0,0 +1,41 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + | ``[#Temperature]`` + "," + | Returns the last measured Temperature in degrees Celcius. + " + " + | ``[#Humidity]`` + "," + | Returns the last measured Humidity (%RH). + " + " + | ``[#tVOC]`` + "," + | Returns the last measured tVOC (total volatile organic compounds) value in range 0..500 index points. Measurement range 0..1000 ppm. + " + " + | ``[#NOx]`` + "," + | Returns the last measured NOx value (Nitrogen Oxides) value in range 0..500 index points. + " + " + | ``[#PM1p0]`` + + | ``[#PM2p5]`` + + | ``[#PM4p0]`` + + | ``[#PM10p0]`` + + "," + | Returns the last measured Particle concentration in μg/m\ :sup:`3` for respectively 1.0 μm, 2.5 μm, 4.0 μm and 10.0 μm particle size. + " + " + | ``[#Dewpoint]`` + "," + | Returns the calculated Dew point, from the Temperature and Humidity values. + " diff --git a/docs/source/Plugin/P168.rst b/docs/source/Plugin/P168.rst new file mode 100644 index 000000000..d2eaa10cc --- /dev/null +++ b/docs/source/Plugin/P168.rst @@ -0,0 +1,122 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P168_page: + +|P168_typename| +================================================== + +|P168_shortinfo| + +Plugin details +-------------- + +Type: |P168_type| + +Name: |P168_name| + +Status: |P168_status| + +GitHub: |P168_github|_ + +Maintainer: |P168_maintainer| + +Used libraries: |P168_usedlibraries| + +Description +----------- + +The VEML6030 and VEML7700 are Light/Lux sensors, that can measure also in the high regions of lux values, like direct sunlight and very bright outside light conditions. + + +Configuration +------------- + +.. image:: P168_DeviceConfiguration.png + :alt: Device configuration + + +* **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. + +I2C options +^^^^^^^^^^^ + +The available settings here depend on the build 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` + +The VEML6030 has the option of selecting an alternative I2C address of ``0x48`` by pulling up the ADDR pin to VCC, when that pin is available on the board used. The VEML6030 also supports interrupt-driven operation, but that's not implemented in this plugin. + +Device Settings +^^^^^^^^^^^^^^^ + +* **Lux Read-method**: Select the read method to be used, available options: + +.. image:: P168_LuxReadMethodOptions.png + :alt: Lux read-method options + +* *Normal*: Normal read mode, will report the uncorrected ``Lux`` value. + +* *Corrected*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity. + +* *Auto*: Show the corrected value, auto-scaled with Gain factor and Integration time to avoid measuring errors because of over-exposing the sensor. This is the default setting. + +* *Normal (no wait)*: Normal read mode, will report the uncorrected ``Lux`` value, doesn't wait for the sensor to complete a measurement. + +* *Corrected (no wait)*: Corrected read mode, Raw calculated to ``Lux`` and corrected for non-linearity, doesn't wait for the sensor to complete a measurement. + +| + +* **Gain factor**: Manually configure the Gain factor. Higher gain factors may distort/clip measurements, as the sensor might return max. values. + +.. image:: P168_GainFactorOptions.png + :alt: Gain factor options + + +* **Integration time**: Manually configure the Integration time. Longer integration times increase accuracy of the measurement. + +.. image:: P168_IntegrationTimeOptions.png + +.. warning:: When the **Lux Read-method** is set to *Auto* the Gain factor and Integration time settings are ignored. + +* **Power Save Mode**: The sensor can go into Power Save Mode on several levels, to conserve power when used in a battery operated device. When in power save mode, the integration time increases. + +.. image:: P168_PowerSaveModeOptions.png + :alt: Power Save Mode options + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values**, **Send to Controller** and **Interval** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. + +Values +^^^^^^ + +The plugin provides measurements ``Lux``, ``White``, and ``Raw``. + +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: ` + +.. Commands available +.. ^^^^^^^^^^^^^^^^^^ + +.. .. include:: P168_commands.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: P168_config_values.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-05-19 Initial release version. + + + + + diff --git a/docs/source/Plugin/P168_DeviceConfiguration.png b/docs/source/Plugin/P168_DeviceConfiguration.png new file mode 100644 index 000000000..802c0abe7 Binary files /dev/null and b/docs/source/Plugin/P168_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P168_GainFactorOptions.png b/docs/source/Plugin/P168_GainFactorOptions.png new file mode 100644 index 000000000..6572289c3 Binary files /dev/null and b/docs/source/Plugin/P168_GainFactorOptions.png differ diff --git a/docs/source/Plugin/P168_IntegrationTimeOptions.png b/docs/source/Plugin/P168_IntegrationTimeOptions.png new file mode 100644 index 000000000..b4ed216bf Binary files /dev/null and b/docs/source/Plugin/P168_IntegrationTimeOptions.png differ diff --git a/docs/source/Plugin/P168_LuxReadMethodOptions.png b/docs/source/Plugin/P168_LuxReadMethodOptions.png new file mode 100644 index 000000000..1d14ae69a Binary files /dev/null and b/docs/source/Plugin/P168_LuxReadMethodOptions.png differ diff --git a/docs/source/Plugin/P168_PowerSaveModeOptions.png b/docs/source/Plugin/P168_PowerSaveModeOptions.png new file mode 100644 index 000000000..1a58e0873 Binary files /dev/null and b/docs/source/Plugin/P168_PowerSaveModeOptions.png differ diff --git a/docs/source/Plugin/P168_config_values.repl b/docs/source/Plugin/P168_config_values.repl new file mode 100644 index 000000000..6dde9386f --- /dev/null +++ b/docs/source/Plugin/P168_config_values.repl @@ -0,0 +1,14 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + | ``[#Gain]`` + "," + | Returns the current **Gain factor** of the sensor, not reflected in the settings. Useful when the **Lux Read-method** is set to *Auto*. + " + " + | ``[#Integration]`` + "," + | Returns the current **Integration time** of the sensor, not reflected in the settings. Useful when the **Lux Read-method** is set to *Auto*. + " diff --git a/docs/source/Plugin/P169.rst b/docs/source/Plugin/P169.rst new file mode 100644 index 000000000..4d0b9bfee --- /dev/null +++ b/docs/source/Plugin/P169.rst @@ -0,0 +1,362 @@ +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. _P169_page: + +|P169_typename| +================================================== + +|P169_shortinfo| + +Plugin details +-------------- + +Type: |P169_type| + +Name: |P169_name| + +Status: |P169_status| + +GitHub: |P169_github|_ + +Maintainer: |P169_maintainer| + +Used libraries: |P169_usedlibraries| + +Description +----------- + +The AS3935 is a programmable fully integrated Lightning Sensor IC that detects the presence and approach of potentially +hazardous lightning activity in the vicinity and provides an estimation on the distance to the head of the storm. +The embedded lightning algorithm checks the incoming signal pattern to reject the potential man-made disturbers. + +Highlights: + +* Can detect lightning storm activity within a 40 km range. +* Provides distance estimation to the head of the storm. +* Detects both cloud-to-ground and intra-cloud (cloud-to-cloud) flashes. +* Internal algorithm to reject false disturbances. + + + +This chip can be found on a number of boards, like this one from DFRobot. + +.. image:: P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg + +(Image (c) DFRobot) + + +Calibration Procedure +--------------------- + +The AS3935 sensor is using 3 separate oscillators: + +* **LCO**: 500 kHz resonance frequency used to tune the antenna. +* **SRCO**: Typ. ~1.1 MHz signal used internally in the sensor. +* **TRCO**: Typ. 32768 Hz signal used internally in the sensor. + +The LCO oscillator needs to be calibrated within 3.5% of its intended 500 kHz. +For this the sensor can connect upto 15 tuning capacitors of 8 pF parallel to another capacitor to tune the resonance frequency of the antenna to 500 kHz. + +The frequency of all of these three oscillators depends on environmental factors like temperature, but also the presence of other materials close to the antenna. + +The LCO calibration is performed by feeding the LCO clock via some divisor to a GPIO pin on the ESP board. +This signal is then measured several times to find the best tuning capacitor. + +By default this LCO calibration does a quick test with antenna capacitor ``0`` and ``15`` and then computes the most likely capacitor. +This candidate and its neighbors are then measured for a longer period (about 30 msec) to reduce the error in measurement. + +When the checkbox for "Slow LCO Calibration" is checked, each antenna capacitor is tested for about 30 msec. +This may improve the accuracy and the success rate of the calibration. + +Below the calibration charts of a quick and slow LCO calibration of the same sensor: + +.. image:: P169_Quick_LCO_calibration_curve.png + :width: 500 + :alt: Quick LCO Calibration Curve + +.. image:: P169_Slow_LCO_calibration_curve.png + :width: 500 + :alt: Slow LCO Calibration Curve + +As can be seen, both were successful in calibrating the resonance frequency within 3.5% of 500 kHz. + +However, the best one on this specific board and setup is antenna capacitor 15, which is the last one. +So in this specific setup, it is very well possible the calibration may fail when some external factor changes. (e.g. temperature) + +For setups like these, where the best option is close to the edge of the adjustable range, it is best to check the checkbox "Tolerate out-of-range calibration". +This way the calibration will not be considered failed when the tolerance ends up slightly above 3.5%. + +On the other hand, if the best calibration is significantly further off from the optimal 500 kHz, there is something wrong with the setup. + +For example: + +* Metal parts mounted close to the antenna. +* Noisy environment. (See also the reported noise level, as this should be around 2 or 3) +* Unstable power supplied to the sensor. + +See the Wiring section below for more tips. + + +.. note:: The **SRCO** and **TRCO** frequencies are calibrated after the **LCO** frequency. When the **LCO** frequency is off by too much, the calibration of the other two may also fail. + + +Sensor Operating Modes +---------------------- + +The sensor can signal some event via the IRQ pin to ESPEasy. +This pin state remains high until the sensor state is read. + +Power-Down Mode +^^^^^^^^^^^^^^^ + +This mode is set when ESPEasy enters deep sleep to reduce current consumption +(typ 1μA). + +Listening Mode +^^^^^^^^^^^^^^ + +The sensor will be operating in this mode for most of the time. +Typical current consumption in this mode is about 60μA. (70μA when the internal voltage regulator is enabled) + +There will always be some noise picked up by the antenna. +When this noise exceeds the set noise floor, the sensor will pull the IRQ pin high and return to "Listening mode". + +ESPEasy will then try to increase the noise floor. + +After 15 seconds of not receiving any interrupt signal, ESPEasy will try to lower the noise floor. + +Every time the set watchdog threshold is passed, the sensor will enter "Signal Validation" mode. + + +Signal Validation Mode +^^^^^^^^^^^^^^^^^^^^^^ + +Typical current consumption in this mode is about 350μA. + +In case the incoming signal does not have the shape characteristic to lightning, the signal validation fails and the +event is classified as disturber. + +If the signal is classified as disturber the chip immediately aborts the signal processing and goes back into the "Listening Mode". +Otherwise, the energy calculation is performed and the distance estimate provided. + +.. note:: The calculated energy does not reflect any physical unit of measure. It is just a number. However it seems the sensor does use it internally to estimate the distance. + +The received signal of a typical lightning strike consists of 3 .. 4 pulses about 40 msec apart. + +According to the datasheet, the sensor needs roughly 1 second to classify an event as a lightning strike. +However tests have shown the sensor might be able to classify _some_ lightning strikes in as little as 250 msec. + +If the classification takes longer than 1.5 seconds, it will be classified as a disturber. + +This implies the practial shortest time span between two lightning strikes that the sensor can resolve is approximately one second. + +.. note:: Sometimes during intense thunderstorms there might be several lightning strikes in short succession. This sensor might classify those as disturbances as the total evaluation time might exceed 1.5 seconds. + +As soon as the event has been classified, the sensor will return to "Listening Mode". + + +Signal Validation Parameters +---------------------------- + +During the signal validation phase the shape of the incoming signal is analyzed. +The sensor can differentiate between signals that show the pattern characteristic of lightning strikes and man-made disturbers such as random impulses. + +Noise Floor +^^^^^^^^^^^ + +Range: ``0`` .. ``7`` (default: ``2``) + +The noise floor acts as a threshold to differentiate real signals from noise. + +When an interrupt signal for "noise floor threshold exceeded" is received, ESPEasy will increase the noise floor. + + +Watchdog +^^^^^^^^ + +Range: ``0`` .. ``15`` (default: ``2``) + +This sets the duration for a signal to last before entering "Signal Validation" mode. + +Spike Rejection +^^^^^^^^^^^^^^^ + +Range: ``0`` .. ``15`` (default: ``2``) + +Can be used to increase the robustness against false alarms from such disturbers. +Larger values correspond to more robust disturber rejection, yet with the +drawback of a decrease in detection efficiency. + +When an "disturber detected" event is triggered, ESPEasy will try to increase either the Spike Rejection setting or Watchdog. + + +AFE gain and Energy level +^^^^^^^^^^^^^^^^^^^^^^^^^ + +The AS3935 sensor does have an amplifier with an adjustable gain factor which amplifies the incoming signal. +This amplified signal is then used to estimate the "Energy" value. + +Tests have shown the AS3935 sensor does use a lookup table to estimate the distance based on this energy level. + + + +Dynamic Adjustment +^^^^^^^^^^^^^^^^^^ + +ESPEasy does dynamically change these three settings. +Upon interrupt signals for either noise floor exceeded or disturbance detected, these will be increased. + +After 15 seconds ESPEasy will try to lower these values again to get as close as possible to the optimal settings. + + +Lightning Threshold +^^^^^^^^^^^^^^^^^^^ + +Values: ``1``, ``5``, ``9``, ``16`` (default: ``1``) + +This set minimum number of lightning events counted within 15 minutes must occur before the first "Lightning detected" interrupt is sent. +Once this threshold is passed, the sensor will resume its normal interrupt handling with an interrupt per detected lightning. + +This internal count will also be cleared when the internal statistics are cleared or when the sensor is put to sleep. (when the ESPEasy task is ended, the sensor is put to sleep) + + +Wiring +------ + +This sensor can be used via SPI or I2C. + +For ESPEasy it needs to be wired for I2C: + +* ``SI`` pin ("Select Interface") must be pulled high. +* ``MOSI`` pin is I2C SDA. +* ``SCL`` pin is I2C SCL. +* ``A1`` and ``A0`` pulled high for the default address of ``0x03``. + +.. image:: P169_Guideline_I2C_PullUp_resistors.png + :width: 700 + :alt: Guideline for Pull Up Resistors on I2CL and I2CD + + +Apart from the I2C connection, there is an ``IRQ`` signal which must also be connected to the ESP board. +This IRQ signal is used to calibrate the sensor as well as to signal the ESP about some changed condition like a detected lightning strike or noise disturbances. + +Board Specific +^^^^^^^^^^^^^^ + +Some boards already have resistors present to pull the unused pins high or low where needed and are default set for I2C. +For example 8-pin brown or purple boards with ``GY-AS3935`` written on the back is default configured for I2C with address ``0x03``. + +Other boards like a slightly larger 11-pin purple board with ``WCMCU-3935`` written on the board, +might need to have the unused pins (``CS`` & ``MISO``) explicitly pulled to GND. +The ``EN-V`` pin should be pulled high. + + +Power Supply and Noise +^^^^^^^^^^^^^^^^^^^^^^ + +This sensor is quite sensitive to noise in its direct surroundings. +Especially signals around 500 kHz will cause this sensor to perform significantly worse. + +This sensor needs to have its resonance frequency calibrated within 3.5% of 500 kHz. + +Some tips: + +* Keep DC/DC converters and other power supplies away from this sensor. +* Keep smart phone and smart watch displays away from the sensor. +* Add some 100 uF ... 220 uF capacitor close to the power supply pins of the sensor. +* Do not use any metal close to the antenna of the sensor as this will change the antenna resonance frequency. +* Use short wires. +* Lower I2C clock frequency to 100 kHz for all I2C devices on this ESPEasy board. +* Lower I2C clock frequency on devices within a few meters from this sensor. (Explicitly stay away from 500 kHz) +* Do not leave unused pins 'floating'. Either pull them to 3V3 or GND. +* Orientation of the antenna is not really important to measure lightning strikes, since lightning is not discharging straight, but in a zigzag pattern and the distance is very far away. However the orientation of the antenna can have an effect on the noise picked up from nearby sources. + +The sensor chip does have an internal voltage regulator. + +On some boards, this regulator can be enabled by pulling the ``EN-V`` or ``EN_VREG`` pin high (if made availabe on the board). + +Onboard voltage regulator: + +* Enabled: ``EN_VREG`` pin high, ``VREG`` via 1uF to GND. Supply voltage range is 2.4V to 5.5V +* Disabled: ``EN_VREG`` pin low, ``VREG`` connected to VDD pin. Supply voltage range is 2.4V to 3.6V + +With the onboard voltage regulator enabled, the current consumption will be slight higher. +But the supplied voltage to the sensor will be more stable. + +See the `datasheet `_ pages 15 and 16 for more information. + +.. image:: P169_VoltageRegulator_OFF.png + :width: 500 + :alt: AS3935 Application Diagram (Voltage Regulator OFF, I²C Active) + +.. image:: P169_VoltageRegulator_ON.png + :width: 500 + :alt: AS3935 Application Diagram (Voltage Regulator ON, I²C Active) + + +Configuration +------------- + +.. image:: P169_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. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: The device supports 83 addresses, and by default comes configured for address ``0x03``. + +Available addresses are in the range ``0x01`` to ``0x03``. + +The available I2C settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +* **IRQ**: Configure the GPIO pin on the ESP board connected to the IRQ pin of the sensor. + This pin is used for both the antenna calibration as well as to notify the ESP board of any lightning strike or detected disturbance. +* **Lightning Threshold**: Minimum number of detected strikes in 15 minutes to let the sensor trigger the IRQ pin. +* **Mode**: Set the Analog Front-End (AFE) gain for typical indoor/outdoor use case. +* **Ignore Disturbance**: The sensor may trigger the IRQ pin to signal a lightning strike, high noise or detected disturbances. With "Ignore Disturbance" checked, this last one is ignored. + + + + +Current Sensor Data +^^^^^^^^^^^^^^^^^^^ + + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced). + +Values +^^^^^^ + + +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: ` + +Commands available +^^^^^^^^^^^^^^^^^^ + +.. include:: P169_commands.repl + +Get Config Values +^^^^^^^^^^^^^^^^^ + + +.. include:: P169_config_values.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2024-05-24 Initial release version. + diff --git a/docs/source/Plugin/P169_DeviceConfiguration.png b/docs/source/Plugin/P169_DeviceConfiguration.png new file mode 100644 index 000000000..3f8594c8f Binary files /dev/null and b/docs/source/Plugin/P169_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png b/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png new file mode 100644 index 000000000..5a86da14a Binary files /dev/null and b/docs/source/Plugin/P169_Guideline_I2C_PullUp_resistors.png differ diff --git a/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png b/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png new file mode 100644 index 000000000..ce7dd45ba Binary files /dev/null and b/docs/source/Plugin/P169_Quick_LCO_calibration_curve.png differ diff --git a/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png b/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png new file mode 100644 index 000000000..dbb200365 Binary files /dev/null and b/docs/source/Plugin/P169_Slow_LCO_calibration_curve.png differ diff --git a/docs/source/Plugin/P169_Stats_no_lightning_strikes.png b/docs/source/Plugin/P169_Stats_no_lightning_strikes.png new file mode 100644 index 000000000..14efb8875 Binary files /dev/null and b/docs/source/Plugin/P169_Stats_no_lightning_strikes.png differ diff --git a/docs/source/Plugin/P169_VoltageRegulator_OFF.png b/docs/source/Plugin/P169_VoltageRegulator_OFF.png new file mode 100644 index 000000000..528158381 Binary files /dev/null and b/docs/source/Plugin/P169_VoltageRegulator_OFF.png differ diff --git a/docs/source/Plugin/P169_VoltageRegulator_ON.png b/docs/source/Plugin/P169_VoltageRegulator_ON.png new file mode 100644 index 000000000..38c6c78f3 Binary files /dev/null and b/docs/source/Plugin/P169_VoltageRegulator_ON.png differ diff --git a/docs/source/Plugin/P169_commands.repl b/docs/source/Plugin/P169_commands.repl new file mode 100644 index 000000000..3c28b5e65 --- /dev/null +++ b/docs/source/Plugin/P169_commands.repl @@ -0,0 +1,37 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + ``as3935,clearstats`` + "," + Clear statistics in the sensor, like lightning strike counts and intermediate values used to estimate the distance of the storm front. + " + " + ``as3935,calibrate`` + "," + Perform calibration of all oscillators in the sensor. + " + " + ``as3935,setgain,`` + "," + Set the AFE gain to given value. + Input can either be the internal sensor register value of ``10`` ... ``18`` or a floating point value signifying the gain factor. + This latter one will then be matched to the closest matching internal register value. + The floating point factor values range from ``0.30x`` ... ``3.34x`` . + " + " + ``as3935,setnf,`` + "," + Set the Noise Floor threshold to given value. + " + " + ``as3935,setwd,`` + "," + Set the Watchdog threshold to given value. + " + " + ``as3935,setsrej,`` + "," + Set Spike Rejection threshold to given value. + " diff --git a/docs/source/Plugin/P169_config_values.repl b/docs/source/Plugin/P169_config_values.repl new file mode 100644 index 000000000..2f7f01749 --- /dev/null +++ b/docs/source/Plugin/P169_config_values.repl @@ -0,0 +1,25 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + ``[#noisefloor]`` + "," + Returns the current set Noise Floor threshold of the sensor. + " + " + ``[#watchdog]`` + "," + Returns the current set Watchdog threshold of the sensor. + " + " + ``[#srej]`` + "," + Returns the current set Spike Rejection threshold of the sensor. + " + " + ``[#gain]`` + "," + Returns the current active AFE gain factor of the sensor. (``0.30x`` ... ``3.34x``) + " + diff --git a/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg b/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg new file mode 100644 index 000000000..477f83cbe Binary files /dev/null and b/docs/source/Plugin/P169_dfrobot-gravity-lightning-distance-sensor-as3935-600x600.jpg differ diff --git a/docs/source/Plugin/P170.rst b/docs/source/Plugin/P170.rst new file mode 100644 index 000000000..0cc265861 --- /dev/null +++ b/docs/source/Plugin/P170.rst @@ -0,0 +1,94 @@ +.. include:: ../Plugin/_plugin_substitutions_p17x.repl +.. _P170_page: + +|P170_typename| +================================================== + +|P170_shortinfo| + +Plugin details +-------------- + +Type: |P170_type| + +Name: |P170_name| + +Status: |P170_status| + +GitHub: |P170_github|_ + +Maintainer: |P170_maintainer| + +Used libraries: |P170_usedlibraries| + +Description +----------- + +The Seeed Studio I2C Liquid level sensor can measure a liquid level in the range of 0..10cm, in a resolution of 5mm. It uses a capacitive sensor method. + +.. image:: P170_Seeed_Liquidlevel_board.png + :width: 300px + +(Image (c) Seeed Studio) + +Configuration +------------- + +.. image:: P170_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. + +I2C options +^^^^^^^^^^^ + +* **I2C Address**: Is not available in the configuration, but the device has a fixed address, or actually 2 fixed adjacent addresses, ``0x77`` and ``0x78``, as there are to separate but similar microcontrollers on the board, each delivering a part of the measurement range. + +The available I2C settings here depend on the build 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 +^^^^^^^^^^^^^^^ + +* **Sensitivity**: This determines the signal level measured for each sensor-pad to be submerged in the liquid. Depending on the type of liquid to be measured, this may need adjusting. + +Events +^^^^^^ + +* **Trigger on Low level**: When set above 0 (in steps of 5 mm, rounded down on save), an event will be generated when the liquid level goes *below* this level. The generated event is ``#LowLevel=``. + +* **Trigger on High level**: When set above 0 (in steps of 5 mm, rounded down on save), an event will be generated when the liquid level rises *above* this level. The generated event is ``#HighLevel=``. + +When a Trigger is set to 0 it will be disabled. Rules have to be enabled to be able to process the events. + +* **Trigger only once**: When enabled, only a single event will be generated for a High or Low level trigger, until that state is reset and again passes the trigger level. + +* **Log signal level**: When enabled will log at Info level the received data from the sensor after reading. Can be used to find a suitable **Sensitivity** setting when the default doesn't work as expected. Should best be disabled during normal operation. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 0 sec. The data will be collected and optionally sent to any configured controllers using this interval. When an output value is changed, the data will be sent to any configured controller, and an event will also be generated when the Rules are enabled (Tools/Advanced). + +Values +^^^^^^ + +The plugin provides the ``Level`` (mm) and ``Steps`` (range 0..20) values. A formula can be set to recalculate the displayed, and sent, value. + +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: ` + +Events +^^^^^^ + +.. include:: P170_events.repl + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| 2024-05-21 Initial release version. + diff --git a/docs/source/Plugin/P170_DeviceConfiguration.png b/docs/source/Plugin/P170_DeviceConfiguration.png new file mode 100644 index 000000000..33f2971f5 Binary files /dev/null and b/docs/source/Plugin/P170_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/P170_Seeed_Liquidlevel_board.png b/docs/source/Plugin/P170_Seeed_Liquidlevel_board.png new file mode 100644 index 000000000..a1d43016a Binary files /dev/null and b/docs/source/Plugin/P170_Seeed_Liquidlevel_board.png differ diff --git a/docs/source/Plugin/P170_events.repl b/docs/source/Plugin/P170_events.repl new file mode 100644 index 000000000..5d2eb24ea --- /dev/null +++ b/docs/source/Plugin/P170_events.repl @@ -0,0 +1,18 @@ +.. csv-table:: + :header: "Event", "Extra information" + :widths: 20, 30 + + " + | ``#LowLevel=`` + + | ````: Current level value. + "," + | This event is generated if the level goes below the set **Trigger on Low level** value (5..100 mm), and is repeated every **Interval** seconds (or 1 sec. if Interval is 0). When **Trigger only once** is set, only a single event will be generated, until the measured level rises above the configured level and goes below that again. + " + " + | ``#HighLevel=`` + + | ````: Current level value. + "," + | This event is generated if the level rises above the set **Trigger on High level** value (5..100 mm), and is repeated every **Interval** seconds (or 1 sec. if Interval is 0). When **Trigger only once** is set, only a single event will be generated, until the measured level goes below the configured level and rises above that again. + " diff --git a/docs/source/Plugin/P172.rst b/docs/source/Plugin/P172.rst new file mode 100644 index 000000000..75805fc9e --- /dev/null +++ b/docs/source/Plugin/P172.rst @@ -0,0 +1,109 @@ +.. include:: ../Plugin/_plugin_substitutions_p17x.repl +.. _P172_page: + +|P172_typename| +================================================== + +|P172_shortinfo| + +Plugin details +-------------- + +Type: |P172_type| + +Name: |P172_name| + +Status: |P172_status| + +GitHub: |P172_github|_ + +Maintainer: |P172_maintainer| + +Used libraries: |P172_usedlibraries| + +Description +----------- + +As a successor to their BMP280, Bosch made the BMP388 and the even higher resolution BMP390 temperature and air pressure sensors. + +The main improvements compared to the BMP280 are: + +* Lower power consumption +* Higher possible sample rate +* Option to store a number of samples in the 512 byte buffer to burst read +* Higher resolution +* Less noise + + +.. note:: This plugin uses the same code as plugin P154: :ref:`P154_page`, only connected via the SPI interface instead of I2C. + +Configuration +------------- + +.. image:: P172_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. + +Sensor +^^^^^^ + +* **GPIO -> CS**: Select the GPIO pin that is connected to the CS pin of the board. + +Since this chip supports both SPI and I2C, the pin naming may be slightly confusing: + +* SCL: SPI Clock (CLK) +* SDA: Data In (MOSI) +* SDO: Data Out (MISO) +* CS: Chip Select (CS). +* INT: Not used. + +Device Settings +^^^^^^^^^^^^^^^ + +* **Detected Sensor Type**: Shows either ``BMP38x`` or ``BMP390`` or a number if an unknown sensor ID is detected. (Only shown when the plugin is active.) +* **Altitude**: Optionally set the offset from sea level (in meters) of the sensor to convert pressure measurements to sea level pressure. + +Data Acquisition +^^^^^^^^^^^^^^^^ + +This group of settings, **Single event with all values** and **Send to Controller** settings are standard available configuration items. Send to Controller is only visible when one or more Controllers are configured. + +* **Interval** By default, Interval will be set to 60 sec. The data will be collected and optionally sent to any configured controllers using this interval. If the Interval is set lower or equal than the required 10 * Heater time, the plugin will not start! + +Values +^^^^^^ + +The plugin provides the ``Temperature`` and ``Pressure`` values. A formula can be set to recalculate. The number of decimals can be set as desired, and defaults to 2. + +In selected builds, per Value are a **Stats** and **Hide** checkbox available, and a coordinate axis combo, that when Stats is checked, gathers the data and presents recent data in a graph, as described here: :ref:`Task Value Statistics: ` + +Currently the extra features offered by the sensore are not configurable in this plugin. +These may be added later. + +The internal filtering in the sensor is fixed to these settings: + +* Temperature: 8x oversampling +* Pressure: 4x oversampling +* IIR Filter Coefficient: 3 +* Sample rate: 50 Hz + + +.. figure:: P154_HighResolutionTemperatureStatsExample.png + :alt: Example of the high temperature resolution of a BMP388 + :width: 50 % + :align: center + + Example of the high temperature resolution of a BMP388 + + +Change log +---------- + +.. versionchanged:: 2.0 + ... + + |added| + 2024-07-13 Initial release of SPI version. + diff --git a/docs/source/Plugin/P172_DeviceConfiguration.png b/docs/source/Plugin/P172_DeviceConfiguration.png new file mode 100644 index 000000000..7da287ed0 Binary files /dev/null and b/docs/source/Plugin/P172_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/Touch_CalibrationSettings.png b/docs/source/Plugin/Touch_CalibrationSettings.png new file mode 100644 index 000000000..9245158d2 Binary files /dev/null and b/docs/source/Plugin/Touch_CalibrationSettings.png differ diff --git a/docs/source/Plugin/Touch_ColorSelectionPart.png b/docs/source/Plugin/Touch_ColorSelectionPart.png new file mode 100644 index 000000000..528da090c Binary files /dev/null and b/docs/source/Plugin/Touch_ColorSelectionPart.png differ diff --git a/docs/source/Plugin/Touch_Configuration.repl b/docs/source/Plugin/Touch_Configuration.repl new file mode 100644 index 000000000..c912ab7b9 --- /dev/null +++ b/docs/source/Plugin/Touch_Configuration.repl @@ -0,0 +1,246 @@ + + +Touch configuration +^^^^^^^^^^^^^^^^^^^ + +.. note:: This part of the configuration describes the generic ESPEasy Touch Helper module, used for all touch-screen plugins. + +.. image:: Touch_DeviceConfiguration.png + + +* **Flip rotation 180°**: This checkbox allowes to compensate for when the touch overlay is mounted rotated on the display. + +* **Events**: Select the events that should be generated on touch actions. + +.. image:: Touch_EventsOptions.png + +* *None*: No events will be generated. + +* *X and Y*: Only an event with the X and Y positions on touch will be generated. + +* *X, Y and Z*: Similar to the X and Y event, but with the Z value, pressure strength, added. + +* *Objectnames and Button groups*: An event is generated only when a defined object, see below, is touched, or a button group is changed. + +* *Objectnames, Button groups, X and Y*: An event is generated when a defined object is touched, or a button group is changed. Also an event with the X and Y touch position is generated. + +* *Objectnames, Button groups, X, Y and Z*: An event is generated when a defined object is touched, or a button group is changed. Also an event with the X and Y touch position and Z pressure strength is generated. + +.. .. separator + +* **Draw buttons when started**: When enabled the objects (often touch-buttons) will be drawn when the plugin is started. This requires that the display is already initialized, so that should have a lower task number than this task. + +* **Prevent duplicate events**: Suppress duplicate events. + +* **Ignore touch-screen**: When enabled will not get any touch data from the touch-screen, so the plugin can be used to draw objects, and control them using other buttons, usually below or at a side of the display. For example when using a M5Stack Core, that doesn't have a touch-screen, but does have 3 buttons below the display. + +Calibration +^^^^^^^^^^^ + +To be able to adjust for an improper aligned touch overlay on a display, there is an option for calibration available. When changing the **Calibrate to screen resolution** setting from No to Yes, the page will be submitted, and the calibration input fields will be available. + +.. image:: Touch_CalibrationSettings.png + +* **Top-left**: Enter the adjusted X and Y position for the most top/left position that matches with the touch screen. + +* **Bottom-right**: Enter the adjusted X and Y position for the most bottom/right position that matches with the touch screen. + +To get accurate values, a touch-pen could be used to touch the screen, and enabling the **Enable logging for calibration** option will add Info logging with the exact coordinates that are touched. It might help to set the **Events** temporarily to *None*. + +NB: This calibration is usually only needed for Resistive touch panels, that can also use this generic TouchHelper module, so for a Capacitive touch screen it can be set to No. + +Object settings +^^^^^^^^^^^^^^^ + +This section has some generic and default settings for the Touch Objects defined below. + +.. note:: *Definition*: **An On/Off button is any defined object that's not a slider (Layout)** + +* **Default On/Off button colors**: The default state-colors to be used for On/Off buttons: On, Off, Border, Caption, Disabled, Disabled caption. + +The colors can be selected by name (limited set available), ``#RRGGBB`` (24 bit) value or ``#hhhh`` (RGB565) (16 bit) value (and that's also how they are stored). + +Color input fields have a list of predefined color names available, that can be filtered/selected by typing a part of the name, or using the dropdown arrow in the input field (Chromium-based browsers): + +.. image:: Touch_ColorSelectionPart.png + +To select another value from the list, the input has to be (partially) cleared before it will show other options. + +Customized colors can be selected from a color picker that supports RGB565 color selection. For example `here `_ + +* **Initial button group**: Select the button group that should be activated on plugin start (Button groups will be explained below). Should be left at 0 if no button groups are used. + +* **Draw buttons via Rules**: This setting should be left disabled, as it requires specific rules for drawing the buttons on the display. Documentation to be added. + +* **Enable/Disable page buttons**: Paging buttons (navigation, + or - 10 action), disabled by default, can be enabled here, when they need to be used. + +* **Navigation Left/Right/Up/Down menu reversed**: Navigation across button groups can be seen as 'Moving the view up, down, left or right' (normal) or 'Moving the buttons behind the viewport' (reversed). Enabling this setting will also revert the ``nextgrp``, ``prevgrp``, ``nextpage`` and ``prevpage`` subcommands, to move in the other direction. + +* **Swipe Left/Right/Up/Down menu reversed**: Similar to the navigation buttons, this setting is for reversing the swipe actions left/right and up/down. Swipe actions have to be processed in Rules, no default actions are currently available. + +* **Debounce delay for On/Off buttons**: The minimum time a button has to be touched before it's toggled. + +* **Minimal swipe movement**: When dragging this amount of pixels a swipe action is recognized. + +* **Maximum swipe margin**: The margin for a swipe to be completed. + +Touch objects +^^^^^^^^^^^^^ + +For each column, or related group of columns, a description is available: + +.. image:: Touch_Touch_On.png + +* **#**: The (internal) object number, can be used to address an object in the commands (see below). + +* **On**: Enable or Disable the button (shown disabled and not state changed when touched). A Slide control that is set to disabled, can not be changed by dragging the handle up/down or left/right (depending on orientation), but the value **can** be set using the ``touch,set,...`` command, see below. + +.. image:: Touch_Touch_Name.png + +* **Objectname**: The name is a required field, when this is left or made empty, **the object will be removed from the list!** when saved. When no Caption is set, this will be used as the caption, with any underscore characters ``_`` replaced by a space. + +* **Button-group**: For paging to work, buttons (objects) have to be placed in Button groups. Buttons in group 0 are *always* drawn, so buttons in group 0 are intended to be used as global navigation buttons, or non-menu buttons/objects. + +.. image:: Touch_Touch_Position.png + +* **Top-left x**: The X position for the top/left corner of the button. + +* **Width**: The width of the button. + +* **Top-left y**: The Y position for the top/left corner of the button. + +* **Height**: The height of the button. + +(All positions and sizes are in pixels) + +.. image:: Touch_Touch_Shape.png + +* **Button**: This selection defines the shape of the button on screen: + +* *None*: No button is drawn. + +* *Square*: A square button is drawn. + +* *Rounded*: A rounded button with a corner radius of 5% of the longest side is drawn. + +* *Circle*: A circle or ellipsis is drawn within the width/height defined. + +* *Arrow, left*, *Arrow, up*, *Arrow, right*, *Arrow, down*: A triangle button, pointing in the direction as named, is drawn. To be used as navigation or +/- button. + +.. .. separator + +* **Inverted**: Invert the On/Off values for the button, so the value can immediately be used to set f.e. a GPIO state. + +.. image:: Touch_Touch_Layout.png + +* **Layout**: Select the layout of the caption within the button, it determines a) the caption alignment of the button, or b) the button to be a bitmap-button or c) the button to behave like a slide control: + +* *Centered*, *Left-aligned*, *Top-aligned*, *Right-aligned*, *Bottom-aligned*, *Left-Top-aligned*, *Right-Top-aligned*, *Left-Bottom-aligned*, *Right-Bottom-aligned*: Align the caption as the layout implies. + +* *No caption*: Just draw a button without any caption. + +* *Bitmap image*: Draw a bitmap, starting at the left-top, instead of a caption. The color is drawn first, so when using a smaller bitmap, the surface color is still reflecting the On/Off state. The name of the bitmap is to be entered in the ON caption/OFF caption fields, and can be *prepended* with an x/y offset in pixels to 'move' the bitmap to a desired position on the button. Example: ``5,5,shape.bmp`` will draw shape.bmp starting at offset 5,5 from the left top of the button. + +* *Slide control*: Draw the configured button shape, and include a slide-bar that can be swiped up/down or left/right, determined by the width/height. In the center of the button, the current value is shown. By default the range is 0..100%, but the min/max values can be set by entering a ``,`` value-pair in the **OFF caption** field. The direction can be reverted by swapping the ```` and ```` values. An initial value can be set in the **ON caption** field. These values can use decimals for fine control, f.e. ``18.5,24.9``. + +.. .. separator + +* **Font scale**: Sets the font-size of the currently active font for drawing the captions on a button, or the value of the slide control. Range 0..10, 0 works as if 1 was set. + +.. image:: Touch_Touch_Color.png + +* **ON color**: A non-default ON color can be set here. + +* **OFF color**: A non-default OFF color can be set here. + +.. image:: Touch_Touch_Caption.png + +* **ON caption**: The caption to show if the button-state is ON. When empty, and the layout is not set to No caption, the Objectname will be used as ON caption. Any underscores ``_`` will be replaced by a space. + +* **OFF caption**: The caption to show if the button-state is OFF. When empty, the ON caption will also be used for the OFF state, though the color(s) will change to either the default or configured OFF color. + +Also, variables can be used for ON and OFF captions, that will be evaluated when the button is (re-)drawn. The caption content can be updated from rules, see the ``touch,updatebutton...`` command, below. + +For a Bitmap layout, the name of the bitmap file should be set in the ON caption field, and for the Off state, another bitmap (or the same if it is to be used as a single-action button) filename can be entered. To shift the bitmap across the button, an x and or y offset for the image can be prepended to the filename, like ``[,[,]]``. The x/y offset will be applied from the top/left position of the button. + +.. image:: Touch_Touch_Border.png + +* **Border color**: The color to use for drawing a 1 pixel border around the button. When not set the default border color is used, and when that's also not set, no border is drawn. + +* **Caption color**: A specific caption color, instead of the default Caption color, can be used. + +.. image:: Touch_Touch_Disabled.png + +* **Disab. cap clr**: Disabled-caption color, the caption color to use when the button is disabled. + +* **Disabled clr**: Disabled color, the button surface color when the button is disabled. When empty, the default disabled color is used. + +.. image:: Touch_Touch_Action.png + +* **Touch action**: What action to perform when the object is activated (touched). + +* *Default*: The regular On/Off action will be applied. + +* *Activate Group*: Activate the group configured in **Action group**. For the Home button this could activate group 1, the first set of buttons. + +* *Next Group*: Increment the currently active group by 1, if that group is defined. + +* *Previous Group*: Decrement the currently active group by 1, unless that would activate group 0, as group 0 is always active. + +* *Next Page (+10)*: Increment the current group by 10, to go to the next 'page', if that group is defined. + +* *Previous Page (-10)*: Decrement the current group by 10, to go to the previous 'page', if that group is defined, and \> 0. + +.. .. separator + +The Next/Previous Group and Page buttons will be automatically enabled and disabled, based on availability of the Button Group they are supposed to jump to. + +.. .. separator + +* **Action group**: The group to activate for the *Activate group* touch action. + +Button groups +~~~~~~~~~~~~~ + +To show a larger number of button-like touch objects in a small space, button groups have been designed. + +The basic idea is that all buttons in a group are displayed when the group is activated. To be able to navigate from group to group, some navigation controls are required, and these reside by default in the always visible group 0, though they can also be included in a group, but would immediately disappear when touched and a different group is activated. Groups are identified by positive numbers in range 0..255. + +To enable not only linear navigation across these groups, paging is also implemented, where switching to the next page implies adding or subtracting 10 to/from the current group number. + +The navigation buttons check themselves if it is possible to navigate in their configured direction, and depending on a group they can navigate to, the button is disabled or enabled accordingly. These navigation buttons will ignore switching to group 0, as that's the group these navigation buttons should be in. + +Example layout with Groups and Pages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Below image shows a possible menu system, to be displayed in a small area on the display. The numbers on the buttons are the Button group they belong to. There is only 1 group visible, and of course the navigation- or base-group 0. 2 button sizes are used, defining smaller buttons makes it quite hard to show a meaningful caption on each button. + +The display that is used for this setup has a resolution of 480x320 pixels, and is used in landscape mode. The menu is drawn at the bottom of the display, where the height of all buttons is 49 (pixels). The small buttons, like in group 1, have a width of 99, and not 100, to have a 1 pixel gap between the buttons that makes it look better then when they are flush next to each other. The wide buttons, like group 11, are 149 pixels wide. + +The navigation buttons are all 49x49 pixels, and except for the Home button, have a triangle shape matching their direction. The navigation, by default, is 'moving' the [Visible area] window across the menu sections, though enabling the **Navigation Left/Right/Up/Down menu reversed** option, above, will change that to virtually move the buttons behind the viewport. + +.. image:: Touch_GroupLayoutExample.png + +Swiping +~~~~~~~ + +To support slider controls and generic swipe actions, as available on smartphones, tablets and even some computer screens, swipe support has been included. + +Swiping is supported by generating events during the swipe action, that include the directionId (1..8), and x,y offset since the last swipe action. See Get Config Values for ``swipedir``, below. + +Commands available +------------------ + +.. include:: Touch_commands.repl + +Events +------ + +.. include:: Touch_events.repl + +Get Config Values +----------------- + +Get Config Values retrieves values or settings from the sensor or plugin, and can be used in Rules, Display plugins, Formula's etc. The square brackets **are** part of the variable. Replace ```` by the **Name** of the task. + +.. include:: Touch_config_values.repl diff --git a/docs/source/Plugin/Touch_DeviceConfiguration.png b/docs/source/Plugin/Touch_DeviceConfiguration.png new file mode 100644 index 000000000..6b40df656 Binary files /dev/null and b/docs/source/Plugin/Touch_DeviceConfiguration.png differ diff --git a/docs/source/Plugin/Touch_DisplayColordepthOptions.png b/docs/source/Plugin/Touch_DisplayColordepthOptions.png new file mode 100644 index 000000000..e6f98da9b Binary files /dev/null and b/docs/source/Plugin/Touch_DisplayColordepthOptions.png differ diff --git a/docs/source/Plugin/Touch_EventsOptions.png b/docs/source/Plugin/Touch_EventsOptions.png new file mode 100644 index 000000000..af7f9dda2 Binary files /dev/null and b/docs/source/Plugin/Touch_EventsOptions.png differ diff --git a/docs/source/Plugin/Touch_GroupLayoutExample.png b/docs/source/Plugin/Touch_GroupLayoutExample.png new file mode 100644 index 000000000..b3f182bde Binary files /dev/null and b/docs/source/Plugin/Touch_GroupLayoutExample.png differ diff --git a/docs/source/Plugin/Touch_Touch_Action.png b/docs/source/Plugin/Touch_Touch_Action.png new file mode 100644 index 000000000..5ffce5b74 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Action.png differ diff --git a/docs/source/Plugin/Touch_Touch_Border.png b/docs/source/Plugin/Touch_Touch_Border.png new file mode 100644 index 000000000..fef924130 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Border.png differ diff --git a/docs/source/Plugin/Touch_Touch_Caption.png b/docs/source/Plugin/Touch_Touch_Caption.png new file mode 100644 index 000000000..231c28690 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Caption.png differ diff --git a/docs/source/Plugin/Touch_Touch_Color.png b/docs/source/Plugin/Touch_Touch_Color.png new file mode 100644 index 000000000..b9f6b199e Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Color.png differ diff --git a/docs/source/Plugin/Touch_Touch_Disabled.png b/docs/source/Plugin/Touch_Touch_Disabled.png new file mode 100644 index 000000000..22f51568c Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Disabled.png differ diff --git a/docs/source/Plugin/Touch_Touch_Layout.png b/docs/source/Plugin/Touch_Touch_Layout.png new file mode 100644 index 000000000..8bece80e3 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Layout.png differ diff --git a/docs/source/Plugin/Touch_Touch_Name.png b/docs/source/Plugin/Touch_Touch_Name.png new file mode 100644 index 000000000..0ff61872e Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Name.png differ diff --git a/docs/source/Plugin/Touch_Touch_On.png b/docs/source/Plugin/Touch_Touch_On.png new file mode 100644 index 000000000..3f3058474 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_On.png differ diff --git a/docs/source/Plugin/Touch_Touch_Position.png b/docs/source/Plugin/Touch_Touch_Position.png new file mode 100644 index 000000000..9b0a428ed Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Position.png differ diff --git a/docs/source/Plugin/Touch_Touch_Shape.png b/docs/source/Plugin/Touch_Touch_Shape.png new file mode 100644 index 000000000..bb407d645 Binary files /dev/null and b/docs/source/Plugin/Touch_Touch_Shape.png differ diff --git a/docs/source/Plugin/Touch_commands.repl b/docs/source/Plugin/Touch_commands.repl new file mode 100644 index 000000000..06e612e22 --- /dev/null +++ b/docs/source/Plugin/Touch_commands.repl @@ -0,0 +1,109 @@ +.. csv-table:: + :header: "Command Syntax", "Extra information" + :widths: 20, 30 + + " + + ``touch,enable,[,...]`` + + ```` Select an object either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + "," + + Enable 1 or more objects. Select the objects by name or number. Using names makes it more easy to re-use a script. + + " + " + ``touch,disable,[,...]`` + + ```` Select an object either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + "," + Disable 1 or more objects. Select the objects by name or number. Using names makes it more easy to re-use a script. + " + " + ``touch,on,[,...]`` + + ```` Select a button either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + "," + Switch 1 or more button objects On. Select the buttons by name or number. Using names makes it more easy to re-use a script. + " + " + ``touch,off,[,...]`` + + ```` Select a button either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + "," + Switch 1 or more buttons Off. Select the buttons by name or number. Using names makes it more easy to re-use a script. + " + " + ``touch,toggle,[,...]`` + + ```` Select a button either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + "," + Toggle the state for 1 or more buttons. Select the buttons by name or number. Using names makes it more easy to re-use a script. + " + " + ``touch,set,,`` + + ```` Select an object either by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + ```` Set to a value, for object buttons, only 0 and <> 0 count. 0 will set the state to Off, and <> 0 will set the state to On. + + "," + + Set the state or value of an object. Select the object by name or number. + + When selecting a value of 0, the state will be changed to Off. When selecting a numeric value <> 0, the state will be set to On. + + For non-button objects, like a slide control, set the current value for the slider. Must be within the range of the slider. + " + " + ``touch,swipe,`` + + ```` The numeric direction of a swipe, 1 = ``up``, 3 = ``right``, 5 = ``down``, 7 = ``left``, to change the currently selected group. + + "," + Select another button-group as if it was selected by a swipe on the display. + " + " + ``touch,setgrp,`` + + ```` Group to select. Numeric, and has to be an exisiting group number. + + "," + Select a button-group by number. + " + " + ``touch,nextgrp`` + "," + Select the next button-group. + " + " + ``touch,prevgrp`` + "," + Select the previous button-group. + " + " + ``touch,nextpage`` + "," + Select the next button-page (group + 10). When **Navigation Left/Right/Up/Down menu reversed** is enabled, the group number wil be *decreased* by 10! + " + " + ``touch,prevpage`` + "," + Select the previous button-page (group - 10). When **Navigation Left/Right/Up/Down menu reversed** is enabled, the group number wil be *increased* by 10! + " + " + ``touch,updatebutton,[,[,]]`` + + ```` Select either a button by name or number. Numbers match with the numbers shown in the web UI or ESPEasy. + + ```` The group number for the button. + + ```` The mode of the button. Available options: 0 = normal, -1 = initial, -2 = clear button area. + "," + Update the button according to the current state, on the display. To update a dynamic caption or external state/value change. + " diff --git a/docs/source/Plugin/Touch_config_values.repl b/docs/source/Plugin/Touch_config_values.repl new file mode 100644 index 000000000..5d99e98e6 --- /dev/null +++ b/docs/source/Plugin/Touch_config_values.repl @@ -0,0 +1,44 @@ +.. csv-table:: + :header: "Config value", "Information" + :widths: 20, 30 + + " + | ``[#buttongroup]`` + "," + | Get the currently active buttongroup. + " + " + | ``[#hasgroup,]`` + "," + | Returns ``1`` if ```` exists, or ``0`` if it doesn't exist. + " + " + | ``[#enabled,]`` + "," + | Returns ``1`` if an object with that ``name`` or ``number`` is enabled, and ``0`` if not. + " + " + | ``[#state,]`` + "," + | Returns the state for an object, by ``name`` or ``number``, ``1`` if ``On`` and ``0`` if ``Off``, or the current value for a Slide control (0..100 or in the range set for the slide control). + " + " + | ``[#pagemode]`` + "," + | Returns the Left/Right/Up/Down menu mode, ``0`` = normal, ``1`` = reversed. + " + " + | ``[#swipedir,]`` + "," + | Returns the name for the provided directionId. + + | 0 = ``None`` + | 1 = ``Up`` + | 2 = ``Up-Right`` + | 3 = ``Right`` + | 4 = ``Right-Down`` + | 5 = ``Down`` + | 6 = ``Down-Left`` + | 7 = ``Left`` + | 8 = ``Left-Up`` + " diff --git a/docs/source/Plugin/Touch_events.repl b/docs/source/Plugin/Touch_events.repl new file mode 100644 index 000000000..1a2cda1e8 --- /dev/null +++ b/docs/source/Plugin/Touch_events.repl @@ -0,0 +1,44 @@ +.. csv-table:: + :header: "Event", "Extra information" + :widths: 20, 30 + + " + | ``#Swiped=,,`` + + | ``directionId``: The direction that was swiped. + + | ``deltaX``: Difference from previous X position. + + | ``deltaY``: Difference from previous Y position. + "," + | This event is generated when a swipe-touch is detected. For Slide controls, no Swipe events are generated, but an event with the new value of the control. + " + " + | ``#=,,`` + + | ``x``: The x coordinate for the touched position. + + | ``y``: The y coordinate for the touched position. + + | ``z``: The pressure applied for the touch (when supported by the touch panel). + "," + | This event is generated on touch actions that do not involve switching a button-state, like non-button controls. + " + " + | ``#=,`` + + | ``state``: The new state for the button, 0 or 1, or the new value for a Slide control. + + | ``mode``: The mode of the button. (TODO: mode values to be added) + "," + | This event is generated on state-changing actions for Button and Slide controls. + " + " + | ``#Group=,`` + + | ``groupNr``: The new group number that is activated. + + | ``mode``: The mode used for activating the group. (TODO: group-mode values to be added) + "," + | This event is generated when a group is activated. + " diff --git a/docs/source/Plugin/_Plugin.rst b/docs/source/Plugin/_Plugin.rst index ed87fae27..463672ca2 100644 --- a/docs/source/Plugin/_Plugin.rst +++ b/docs/source/Plugin/_Plugin.rst @@ -222,13 +222,13 @@ There are different released versions of ESP Easy: :green:`NORMAL` is the regular set of plugins, this is the base set of plugins, and with all secondary features enabled, like I2C multiplexer, RTTTL, DEBUG logging, etc. -:yellow:`COLLECTION` (split into sets A..x) with plugins that don't fit into the NORMAL builds. Because of space limitations, this collection is split into a number of sets. When only :yellow:`COLLECTION` is mentioned, the plugin is available in **all** :yellow:`COLLECTION` builds. Also, some features are disabled to save space in the .bin files, like RTTTL, tooltips, and DEBUG-level logging. +:yellow:`COLLECTION` (split into sets A..x) with plugins that don't fit into the NORMAL builds. Because of space limitations, this collection is split into a number of sets. When only :yellow:`COLLECTION` is mentioned, the plugin is available in **all** :yellow:`COLLECTION` builds, though some exceptions may be applied. Also, some features are disabled to save space in the .bin files, like RTTTL, Servo, tooltips, and DEBUG-level logging. :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:`MAX` is the build that has all plugins that are available in the ESPEasy repository. Available for ESP32 16MB and ESP32-s3 8MB Flash units. +: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). :gray:`RETIRED` plugin has been retired and removed from ESPEasy. @@ -333,7 +333,7 @@ There are different released versions of ESP Easy: ":ref:`P092_page`","|P092_status|","P092" ":ref:`P093_page`","|P093_status|","P093" ":ref:`P094_page`","|P094_status|","P094" - ":ref:`P095_page`","|P095_status|","P095" + ":ref:`P095_page`","|P095_status| (ESP32)","P095" ":ref:`P097_page`","|P097_status|","P097" ":ref:`P098_page`","|P098_status|","P098" ":ref:`P099_page`","|P099_status|","P099" @@ -360,6 +360,7 @@ There are different released versions of ESP Easy: ":ref:`P120_page`","|P120_status|","P120" ":ref:`P121_page`","|P121_status|","P121" ":ref:`P122_page`","|P122_status|","P122" + ":ref:`P123_page`","|P123_status|","P123" ":ref:`P124_page`","|P124_status|","P124" ":ref:`P125_page`","|P125_status|","P125" ":ref:`P126_page`","|P126_status|","P126" @@ -386,8 +387,21 @@ There are different released versions of ESP Easy: ":ref:`P153_page`","|P153_status|","P153" ":ref:`P154_page`","|P154_status|","P154" ":ref:`P159_page`","|P159_status|","P159" + ":ref:`P162_page`","|P162_status|","P162" + ":ref:`P164_page`","|P164_status|","P164" + ":ref:`P166_page`","|P166_status|","P166" + ":ref:`P167_page`","|P167_status|","P167" + ":ref:`P168_page`","|P168_status|","P168" + ":ref:`P169_page`","|P169_status|","P169" + ":ref:`P170_page`","|P170_status|","P170" + ":ref:`P172_page`","|P172_status|","P172" +.. include:: _plugin_sets_overview.repl + +Plugins per Category +==================== + Internal GPIO handling ---------------------- diff --git a/docs/source/Plugin/_plugin_categories.repl b/docs/source/Plugin/_plugin_categories.repl index e9b11a450..31862cab9 100644 --- a/docs/source/Plugin/_plugin_categories.repl +++ b/docs/source/Plugin/_plugin_categories.repl @@ -8,27 +8,27 @@ .. |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_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` +.. |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:`P167_page`, :ref:`P169_page`, :ref:`P172_page` .. |Plugin_Extra_IO| replace:: :ref:`P011_page`, :ref:`P022_page` -.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page` +.. |Plugin_Gases| replace:: :ref:`P049_page`, :ref:`P052_page`, :ref:`P083_page`, :ref:`P090_page`, :ref:`P117_page`, :ref:`P127_page`, :ref:`P135_page`, :ref:`P145_page`, :ref:`P147_page`, :ref:`P164_page` .. |Plugin_Generic| replace:: :ref:`P003_page`, :ref:`P026_page`, :ref:`P033_page`, :ref:`P037_page`, :ref:`P081_page`, :ref:`P100_page`, :ref:`P146_page` .. |Plugin_Gesture| replace:: :ref:`P064_page` .. |Plugin_Gyro| replace:: :ref:`P045_page`, :ref:`P119_page` .. |Plugin_Hardware| replace:: :ref:`P046_page` -.. |Plugin_Input| replace:: :ref:`P129_page` +.. |Plugin_Input| replace:: :ref:`P129_page` :ref:`P170_page` .. |Plugin_Keypad| replace:: :ref:`P058_page`, :ref:`P061_page`, :ref:`P062_page`, :ref:`P063_page` .. |Plugin_Light_Color| replace:: :ref:`P050_page`, :ref:`P066_page` .. |Plugin_Light_UV| replace:: :ref:`P084_page`, :ref:`P107_page`, :ref:`P114_page`, :ref:`P133_page` -.. |Plugin_Light_Lux| replace:: :ref:`P010_page`, :ref:`P015_page`, :ref:`P074_page` +.. |Plugin_Light_Lux| replace:: :ref:`P010_page`, :ref:`P015_page`, :ref:`P074_page`, :ref:`P168_page` .. |Plugin_Motor| replace:: :ref:`P048_page`, :ref:`P079_page`, :ref:`P098_page` .. |Plugin_Notify| replace:: :ref:`P055_page`, :ref:`P065_page` -.. |Plugin_Output| replace:: :ref:`P029_page`, :ref:`P038_page`, :ref:`P041_page`, :ref:`P042_page`, :ref:`P043_page`, :ref:`P070_page`, :ref:`P124_page`, :ref:`P126_page`, :ref:`P128_page`, :ref:`P152_page` +.. |Plugin_Output| replace:: :ref:`P029_page`, :ref:`P038_page`, :ref:`P041_page`, :ref:`P042_page`, :ref:`P043_page`, :ref:`P070_page`, :ref:`P124_page`, :ref:`P126_page`, :ref:`P128_page`, :ref:`P152_page`, :ref:`P162_page`, :ref:`P166_page` .. |Plugin_Position| replace:: :ref:`P082_page`, :ref:`P121_page` .. |Plugin_PowerMgt| replace:: :ref:`P137_page`, :ref:`P138_page` .. |Plugin_Presence| replace:: :ref:`P159_page` .. |Plugin_Regulator| replace:: :ref:`P021_page` .. |Plugin_RFID| replace:: :ref:`P008_page`, :ref:`P017_page`, :ref:`P040_page`, :ref:`P111_page` .. |Plugin_Switch_input| replace:: :ref:`P001_page`, :ref:`P009_page`, :ref:`P019_page`, :ref:`P059_page`, :ref:`P080_page`, :ref:`P091_page`, :ref:`P097_page`, :ref:`P143_page` -.. |Plugin_Touch| replace:: :ref:`P097_page`, :ref:`P099_page` +.. |Plugin_Touch| replace:: :ref:`P097_page`, :ref:`P099_page`, :ref:`P123_page` .. |Plugin_Weight| replace:: :ref:`P067_page` diff --git a/docs/source/Plugin/_plugin_sets_overview.repl b/docs/source/Plugin/_plugin_sets_overview.repl new file mode 100644 index 000000000..71215421c --- /dev/null +++ b/docs/source/Plugin/_plugin_sets_overview.repl @@ -0,0 +1,1508 @@ +Plugins per build set +===================== + +Build set: :green:`NORMAL` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`COLLECTION 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P067_page`","P067" + ":ref:`P068_page`","P068" + ":ref:`P070_page`","P070" + ":ref:`P071_page`","P071" + ":ref:`P072_page`","P072" + ":ref:`P073_page`","P073" + ":ref:`P074_page`","P074" + ":ref:`P075_page`","P075" + ":ref:`P079_page`","P079" + ":ref:`P080_page`","P080" + ":ref:`P081_page`","P081" + ":ref:`P082_page`","P082" + ":ref:`P083_page`","P083" + ":ref:`P084_page`","P084" + ":ref:`P086_page`","P086" + ":ref:`P089_page`","P089" + ":ref:`P090_page`","P090" + ":ref:`P095_page`","P095" + ":ref:`P097_page`","P097" + ":ref:`P098_page`","P098" + ":ref:`P105_page`","P105" + ":ref:`P134_page`","P134" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":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:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + +Build set: :yellow:`COLLECTION 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P069_page`","P069" + ":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:`P100_page`","P100" + ":ref:`P101_page`","P101" + ":ref:`P106_page`","P106" + ":ref:`P107_page`","P107" + ":ref:`P108_page`","P108" + ":ref:`P110_page`","P110" + ":ref:`P113_page`","P113" + ":ref:`P115_page`","P115" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":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:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + +Build set: :yellow:`COLLECTION C` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P085_page`","P085" + ":ref:`P087_page`","P087" + ":ref:`P089_page`","P089" + ":ref:`P091_page`","P091" + ":ref:`P092_page`","P092" + ":ref:`P095_page`","P095" + ":ref:`P111_page`","P111" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P143_page`","P143" + ":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:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + +Build set: :yellow:`COLLECTION D` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P093_page`","P093" + ":ref:`P094_page`","P094" + ":ref:`P095_page`","P095" + ":ref:`P098_page`","P098" + ":ref:`P114_page`","P114" + ":ref:`P117_page`","P117" + ":ref:`P124_page`","P124" + ":ref:`P127_page`","P127" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":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:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + +Build set: :yellow:`COLLECTION E` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P119_page`","P119" + ":ref:`P120_page`","P120" + ":ref:`P121_page`","P121" + ":ref:`P125_page`","P125" + ":ref:`P126_page`","P126" + ":ref:`P129_page`","P129" + ":ref:`P133_page`","P133" + ":ref:`P135_page`","P135" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P144_page`","P144" + ":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:`C011_page`","C011" + ":ref:`C012_page`","C012" + ":ref:`C013_page`","C013" + ":ref:`C014_page`","C014" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + +Build set: :yellow:`COLLECTION F` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P112_page`","P112" + ":ref:`P118_page`","P118" + ":ref:`P122_page`","P122" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P145_page`","P145" + ":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:`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" + +Build set: :yellow:`COLLECTION G` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P146_page`","P146" + ":ref:`P152_page`","P152" + ":ref:`P154_page`","P154" + ":ref:`P159_page`","P159" + ":ref:`P162_page`","P162" + ":ref:`P164_page`","P164" + ":ref:`P166_page`","P166" + ":ref:`P168_page`","P168" + ":ref:`P170_page`","P170" + ":ref:`P172_page`","P172" + ":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" + +Build set: :yellow:`CLIMATE` +--------------------------------------------- + +.. 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:`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:`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:`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:`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" + ":ref:`P167_page`","P167" + ":ref:`P168_page`","P168" + ":ref:`P169_page`","P169" + ":ref:`P172_page`","P172" + ":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:`C013_page`","C013" + +Build set: :yellow:`DISPLAY` +--------------------------------------------- + +.. 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:`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:`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:`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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`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:`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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P088_page`","P088" + ":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:`NEOPIXEL` +--------------------------------------------- + +.. 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:`P038_page`","P038" + ":ref:`P039_page`","P039" + ":ref:`P040_page`","P040" + ":ref:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P070_page`","P070" + ":ref:`P073_page`","P073" + ":ref:`P079_page`","P079" + ":ref:`P128_page`","P128" + ":ref:`P131_page`","P131" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":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:`MAX` +--------------------------------------------- + +.. 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:`P030_page`","P030" + ":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:`P041_page`","P041" + ":ref:`P042_page`","P042" + ":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:`P067_page`","P067" + ":ref:`P068_page`","P068" + ":ref:`P069_page`","P069" + ":ref:`P070_page`","P070" + ":ref:`P071_page`","P071" + ":ref:`P072_page`","P072" + ":ref:`P073_page`","P073" + ":ref:`P074_page`","P074" + ":ref:`P075_page`","P075" + ":ref:`P076_page`","P076" + ":ref:`P077_page`","P077" + ":ref:`P078_page`","P078" + ":ref:`P079_page`","P079" + ":ref:`P080_page`","P080" + ":ref:`P081_page`","P081" + ":ref:`P082_page`","P082" + ":ref:`P083_page`","P083" + ":ref:`P084_page`","P084" + ":ref:`P085_page`","P085" + ":ref:`P086_page`","P086" + ":ref:`P087_page`","P087" + ":ref:`P088_page`","P088" + ":ref:`P090_page`","P090" + ":ref:`P091_page`","P091" + ":ref:`P092_page`","P092" + ":ref:`P093_page`","P093" + ":ref:`P094_page`","P094" + ":ref:`P095_page`","P095" + ":ref:`P097_page`","P097" + ":ref:`P098_page`","P098" + ":ref:`P099_page`","P099" + ":ref:`P100_page`","P100" + ":ref:`P101_page`","P101" + ":ref:`P102_page`","P102" + ":ref:`P103_page`","P103" + ":ref:`P104_page`","P104" + ":ref:`P105_page`","P105" + ":ref:`P106_page`","P106" + ":ref:`P107_page`","P107" + ":ref:`P108_page`","P108" + ":ref:`P109_page`","P109" + ":ref:`P110_page`","P110" + ":ref:`P111_page`","P111" + ":ref:`P112_page`","P112" + ":ref:`P113_page`","P113" + ":ref:`P114_page`","P114" + ":ref:`P115_page`","P115" + ":ref:`P116_page`","P116" + ":ref:`P117_page`","P117" + ":ref:`P118_page`","P118" + ":ref:`P119_page`","P119" + ":ref:`P120_page`","P120" + ":ref:`P121_page`","P121" + ":ref:`P122_page`","P122" + ":ref:`P123_page`","P123" + ":ref:`P124_page`","P124" + ":ref:`P125_page`","P125" + ":ref:`P126_page`","P126" + ":ref:`P127_page`","P127" + ":ref:`P128_page`","P128" + ":ref:`P129_page`","P129" + ":ref:`P131_page`","P131" + ":ref:`P132_page`","P132" + ":ref:`P133_page`","P133" + ":ref:`P134_page`","P134" + ":ref:`P135_page`","P135" + ":ref:`P137_page`","P137" + ":ref:`P138_page`","P138" + ":ref:`P141_page`","P141" + ":ref:`P143_page`","P143" + ":ref:`P144_page`","P144" + ":ref:`P145_page`","P145" + ":ref:`P146_page`","P146" + ":ref:`P147_page`","P147" + ":ref:`P148_page`","P148" + ":ref:`P150_page`","P150" + ":ref:`P151_page`","P151" + ":ref:`P152_page`","P152" + ":ref:`P153_page`","P153" + ":ref:`P154_page`","P154" + ":ref:`P159_page`","P159" + ":ref:`P162_page`","P162" + ":ref:`P164_page`","P164" + ":ref:`P166_page`","P166" + ":ref:`P167_page`","P167" + ":ref:`P168_page`","P168" + ":ref:`P169_page`","P169" + ":ref:`P170_page`","P170" + ":ref:`P172_page`","P172" + ":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:`C016_page`","C016" + ":ref:`C017_page`","C017" + ":ref:`C018_page`","C018" + diff --git a/docs/source/Plugin/_plugin_substitutions.repl b/docs/source/Plugin/_plugin_substitutions.repl index f6b9bf1f3..329d3f914 100644 --- a/docs/source/Plugin/_plugin_substitutions.repl +++ b/docs/source/Plugin/_plugin_substitutions.repl @@ -14,3 +14,5 @@ .. include:: ../Plugin/_plugin_substitutions_p13x.repl .. include:: ../Plugin/_plugin_substitutions_p14x.repl .. include:: ../Plugin/_plugin_substitutions_p15x.repl +.. include:: ../Plugin/_plugin_substitutions_p16x.repl +.. include:: ../Plugin/_plugin_substitutions_p17x.repl diff --git a/docs/source/Plugin/_plugin_substitutions_p09x.repl b/docs/source/Plugin/_plugin_substitutions_p09x.repl index 05bfe0d38..9c9dfc742 100644 --- a/docs/source/Plugin/_plugin_substitutions_p09x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p09x.repl @@ -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` +.. |P095_status| replace:: :yellow:`DISPLAY` :yellow:`COLLECTION` :yellow:`(ESP32)` .. |P095_github| replace:: _P095_ILI9341.ino .. _P095_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P095_ILI9341.ino .. |P095_usedby| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p10x.repl b/docs/source/Plugin/_plugin_substitutions_p10x.repl index a000b1b69..87af9e566 100644 --- a/docs/source/Plugin/_plugin_substitutions_p10x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p10x.repl @@ -1,145 +1,145 @@ -.. |P100_name| replace:: :cyan:`DS2423` -.. |P100_type| replace:: :cyan:`Generic` -.. |P100_typename| replace:: :cyan:`Pulse Counter - DS2423` -.. |P100_porttype| replace:: `.` -.. |P100_status| replace:: :yellow:`COLLECTION B` -.. |P100_github| replace:: P100_CCS811.ino -.. _P100_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P100_DS2423_counter.ino -.. |P100_usedby| replace:: `.` -.. |P100_shortinfo| replace:: `1-Wire 4kbit RAM with counter (only counter is currently used)` -.. |P100_maintainer| replace:: `TD-er` -.. |P100_compileinfo| replace:: `.` -.. |P100_usedlibraries| replace:: `.` - -.. |P101_name| replace:: :cyan:`Wake On LAN` -.. |P101_type| replace:: :cyan:`Communication` -.. |P101_typename| replace:: :cyan:`Communication - Wake On LAN` -.. |P101_porttype| replace:: `.` -.. |P101_status| replace:: :yellow:`COLLECTION B` -.. |P101_github| replace:: P101_WakeOnLan.ino -.. _P101_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P101_WakeOnLan.ino -.. |P101_usedby| replace:: `.` -.. |P101_shortinfo| replace:: Wake On LAN (WOL). Plugin can send a Magic Packet to wake-up device on local network. -.. |P101_maintainer| replace:: thomastech -.. |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_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_compileinfo| replace:: `.` -.. |P102_usedlibraries| replace:: https://github.com/olehs/PZEM004T - -.. |P103_name| replace:: :cyan:`Atlas Scientific EZO pH` -.. |P103_type| replace:: :cyan:`Environment` -.. |P103_typename| replace:: :cyan:`Environment - Atlas Scientific EZO pH` -.. |P103_porttype| replace:: `.` -.. |P103_status| replace:: :yellow:`CLIMATE` -.. |P103_github| replace:: P103_Atlas_EZO_pH.ino -.. _P103_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P103_Atlas_EZO_pH.ino -.. |P103_usedby| replace:: `.` -.. |P103_shortinfo| replace:: `.` -.. |P103_maintainer| replace:: TD-er -.. |P103_compileinfo| replace:: `.` -.. |P103_usedlibraries| replace:: `.` -.. |P103_datasheet| replace:: https://atlas-scientific.com/files/pH_EZO_Datasheet.pdf - -.. |P104_name| replace:: :cyan:`MAX7219 Dot matrix display` -.. |P104_type| replace:: :cyan:`Display` -.. |P104_typename| replace:: :cyan:`Display - MAX7219 dot matrix` -.. |P104_porttype| replace:: `SPI` -.. |P104_status| replace:: :yellow:`DISPLAY` -.. |P104_github| replace:: P104_max7219_Dotmatrix.ino -.. _P104_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P104_max7210_Dotmatrix.ino -.. |P104_usedby| replace:: `.` -.. |P104_shortinfo| replace:: `.` -.. |P104_maintainer| replace:: tonhuisman -.. |P104_compileinfo| replace:: `.` -.. |P104_usedlibraries| replace:: `MD_Parola, MD_MAX72XX (modified to work with ESPEasy)` -.. |P104_datasheet| replace:: `.` - -.. |P104-Font-Default_typename| replace:: `Default font characters` -.. |P104-Font-Numeric7Segment_typename| replace:: `Num, double height characters` -.. |P104-Font-DoubleHeight_typename| replace:: `Full, double height characters` -.. |P104-Font-Vertical_typename| replace:: `Vertical font characters` -.. |P104-Font-ExtASCII_typename| replace:: `Extended ASCII characters` -.. |P104-Font-Arabic_typename| replace:: `Arabic font characters` -.. |P104-Font-Greek_typename| replace:: `Greek font characters` -.. |P104-Font-Katakana_typename| replace:: `Katakana font characters` - - -.. |P105_name| replace:: :cyan:`AHT10/AHT2x` -.. |P105_type| replace:: :cyan:`Environment` -.. |P105_typename| replace:: :cyan:`Environment - AHT10/AHT2x` -.. |P105_porttype| replace:: `.` -.. |P105_status| replace:: :yellow:`COLLECTION A` :yellow:`CLIMATE` -.. |P105_github| replace:: P105_AHT.ino -.. _P105_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P105_AHT.ino -.. |P105_usedby| replace:: `.` -.. |P105_shortinfo| replace:: `.` -.. |P105_maintainer| replace:: sakinit tonhuisman -.. |P105_compileinfo| replace:: `.` -.. |P105_usedlibraries| replace:: `.` -.. |P105_datasheet| replace:: http://www.aosong.com/en/products-40.html -.. |P105_datasheet2| replace:: http://www.aosong.com/en/products-32.html -.. |P105_datasheet3| replace:: http://www.aosong.com/en/products-60.html - - -.. |P106_name| replace:: :cyan:`BME68x` -.. |P106_type| replace:: :cyan:`Environment` -.. |P106_typename| replace:: :cyan:`Environment - BME68x` -.. |P106_porttype| replace:: `.` -.. |P106_status| replace:: :yellow:`COLLECTION B` :yellow:`CLIMATE` -.. |P106_github| replace:: P106_BME680.ino -.. _P106_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P106_BME680.ino -.. |P106_usedby| replace:: `.` -.. |P106_shortinfo| replace:: `.` -.. |P106_maintainer| replace:: `TD-er, tonhuisman` -.. |P106_compileinfo| replace:: `.` -.. |P106_usedlibraries| replace:: https://github.com/adafruit/Adafruit_BME680 - -.. |P107_name| replace:: :cyan:`SI1145` -.. |P107_type| replace:: :cyan:`UV` -.. |P107_typename| replace:: :cyan:`UV - SI1145` -.. |P107_porttype| replace:: `.` -.. |P107_status| replace:: :yellow:`COLLECTION B` -.. |P107_github| replace:: P107_Si1145.ino -.. _P107_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P107_Si1145.ino -.. |P107_usedby| replace:: `.` -.. |P107_shortinfo| replace:: `.` -.. |P107_maintainer| replace:: TD-er -.. |P107_compileinfo| replace:: `.` -.. |P107_usedlibraries| replace:: https://github.com/adafruit/Adafruit_SI1145_Library - -.. |P108_name| replace:: :cyan:`DDS238-x` -.. |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_github| replace:: P108_DDS238.ino -.. _P108_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P108_DDS238.ino -.. |P108_usedby| replace:: `.` -.. |P108_shortinfo| replace:: `.` -.. |P108_maintainer| replace:: TD-er -.. |P108_compileinfo| replace:: `.` -.. |P108_usedlibraries| replace:: `.` - -.. |P109_name| replace:: :cyan:`ThermoOLED` -.. |P109_type| replace:: :cyan:`UV` -.. |P109_typename| replace:: :cyan:`Display - ThermoOLED` -.. |P109_porttype| replace:: `.` -.. |P109_status| replace:: :yellow:`DISPLAY` -.. |P109_github| replace:: P109_ThermoOLED.ino -.. _P109_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P109_ThermoOLED.ino -.. |P109_usedby| replace:: `.` -.. |P109_shortinfo| replace:: `.` -.. |P109_maintainer| replace:: TD-er -.. |P109_compileinfo| replace:: `.` -.. |P109_usedlibraries| replace:: `.` +.. |P100_name| replace:: :cyan:`DS2423` +.. |P100_type| replace:: :cyan:`Generic` +.. |P100_typename| replace:: :cyan:`Pulse Counter - DS2423` +.. |P100_porttype| replace:: `.` +.. |P100_status| replace:: :yellow:`COLLECTION B` +.. |P100_github| replace:: P100_CCS811.ino +.. _P100_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P100_DS2423_counter.ino +.. |P100_usedby| replace:: `.` +.. |P100_shortinfo| replace:: `1-Wire 4kbit RAM with counter (only counter is currently used)` +.. |P100_maintainer| replace:: `TD-er` +.. |P100_compileinfo| replace:: `.` +.. |P100_usedlibraries| replace:: `.` + +.. |P101_name| replace:: :cyan:`Wake On LAN` +.. |P101_type| replace:: :cyan:`Communication` +.. |P101_typename| replace:: :cyan:`Communication - Wake On LAN` +.. |P101_porttype| replace:: `.` +.. |P101_status| replace:: :yellow:`COLLECTION B` +.. |P101_github| replace:: P101_WakeOnLan.ino +.. _P101_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P101_WakeOnLan.ino +.. |P101_usedby| replace:: `.` +.. |P101_shortinfo| replace:: Wake On LAN (WOL). Plugin can send a Magic Packet to wake-up device on local network. +.. |P101_maintainer| replace:: thomastech +.. |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_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_compileinfo| replace:: `.` +.. |P102_usedlibraries| replace:: https://github.com/olehs/PZEM004T + +.. |P103_name| replace:: :cyan:`Atlas Scientific EZO pH` +.. |P103_type| replace:: :cyan:`Environment` +.. |P103_typename| replace:: :cyan:`Environment - Atlas Scientific EZO pH` +.. |P103_porttype| replace:: `.` +.. |P103_status| replace:: :yellow:`CLIMATE` +.. |P103_github| replace:: P103_Atlas_EZO_pH.ino +.. _P103_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P103_Atlas_EZO_pH.ino +.. |P103_usedby| replace:: `.` +.. |P103_shortinfo| replace:: `.` +.. |P103_maintainer| replace:: TD-er +.. |P103_compileinfo| replace:: `.` +.. |P103_usedlibraries| replace:: `.` +.. |P103_datasheet| replace:: https://atlas-scientific.com/files/pH_EZO_Datasheet.pdf + +.. |P104_name| replace:: :cyan:`MAX7219 Dot matrix display` +.. |P104_type| replace:: :cyan:`Display` +.. |P104_typename| replace:: :cyan:`Display - MAX7219 dot matrix` +.. |P104_porttype| replace:: `SPI` +.. |P104_status| replace:: :yellow:`DISPLAY` +.. |P104_github| replace:: P104_max7219_Dotmatrix.ino +.. _P104_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P104_max7210_Dotmatrix.ino +.. |P104_usedby| replace:: `.` +.. |P104_shortinfo| replace:: `.` +.. |P104_maintainer| replace:: tonhuisman +.. |P104_compileinfo| replace:: `.` +.. |P104_usedlibraries| replace:: `MD_Parola, MD_MAX72XX (modified to work with ESPEasy)` +.. |P104_datasheet| replace:: `.` + +.. |P104-Font-Default_typename| replace:: `Default font characters` +.. |P104-Font-Numeric7Segment_typename| replace:: `Num, double height characters` +.. |P104-Font-DoubleHeight_typename| replace:: `Full, double height characters` +.. |P104-Font-Vertical_typename| replace:: `Vertical font characters` +.. |P104-Font-ExtASCII_typename| replace:: `Extended ASCII characters` +.. |P104-Font-Arabic_typename| replace:: `Arabic font characters` +.. |P104-Font-Greek_typename| replace:: `Greek font characters` +.. |P104-Font-Katakana_typename| replace:: `Katakana font characters` + + +.. |P105_name| replace:: :cyan:`AHT1x/AHT2x/DHT20/AM2301B` +.. |P105_type| replace:: :cyan:`Environment` +.. |P105_typename| replace:: :cyan:`Environment - AHT1x/AHT2x/DHT20/AM2301B` +.. |P105_porttype| replace:: `.` +.. |P105_status| replace:: :yellow:`COLLECTION A` :yellow:`CLIMATE` +.. |P105_github| replace:: P105_AHT.ino +.. _P105_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P105_AHT.ino +.. |P105_usedby| replace:: `.` +.. |P105_shortinfo| replace:: `.` +.. |P105_maintainer| replace:: sakinit tonhuisman +.. |P105_compileinfo| replace:: `.` +.. |P105_usedlibraries| replace:: `.` +.. |P105_datasheet| replace:: http://www.aosong.com/en/products-40.html +.. |P105_datasheet2| replace:: http://www.aosong.com/en/products-32.html +.. |P105_datasheet3| replace:: http://www.aosong.com/en/products-60.html + + +.. |P106_name| replace:: :cyan:`BME68x` +.. |P106_type| replace:: :cyan:`Environment` +.. |P106_typename| replace:: :cyan:`Environment - BME68x` +.. |P106_porttype| replace:: `.` +.. |P106_status| replace:: :yellow:`COLLECTION B` :yellow:`CLIMATE` +.. |P106_github| replace:: P106_BME680.ino +.. _P106_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P106_BME680.ino +.. |P106_usedby| replace:: `.` +.. |P106_shortinfo| replace:: `.` +.. |P106_maintainer| replace:: `TD-er, tonhuisman` +.. |P106_compileinfo| replace:: `.` +.. |P106_usedlibraries| replace:: https://github.com/adafruit/Adafruit_BME680 + +.. |P107_name| replace:: :cyan:`SI1145` +.. |P107_type| replace:: :cyan:`UV` +.. |P107_typename| replace:: :cyan:`UV - SI1145` +.. |P107_porttype| replace:: `.` +.. |P107_status| replace:: :yellow:`COLLECTION B` +.. |P107_github| replace:: P107_Si1145.ino +.. _P107_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P107_Si1145.ino +.. |P107_usedby| replace:: `.` +.. |P107_shortinfo| replace:: `.` +.. |P107_maintainer| replace:: TD-er +.. |P107_compileinfo| replace:: `.` +.. |P107_usedlibraries| replace:: https://github.com/adafruit/Adafruit_SI1145_Library + +.. |P108_name| replace:: :cyan:`DDS238-x` +.. |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_github| replace:: P108_DDS238.ino +.. _P108_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P108_DDS238.ino +.. |P108_usedby| replace:: `.` +.. |P108_shortinfo| replace:: `.` +.. |P108_maintainer| replace:: TD-er +.. |P108_compileinfo| replace:: `.` +.. |P108_usedlibraries| replace:: `.` + +.. |P109_name| replace:: :cyan:`ThermoOLED` +.. |P109_type| replace:: :cyan:`UV` +.. |P109_typename| replace:: :cyan:`Display - ThermoOLED` +.. |P109_porttype| replace:: `.` +.. |P109_status| replace:: :yellow:`DISPLAY` +.. |P109_github| replace:: P109_ThermoOLED.ino +.. _P109_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P109_ThermoOLED.ino +.. |P109_usedby| replace:: `.` +.. |P109_shortinfo| replace:: `.` +.. |P109_maintainer| replace:: TD-er +.. |P109_compileinfo| replace:: `.` +.. |P109_usedlibraries| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p12x.repl b/docs/source/Plugin/_plugin_substitutions_p12x.repl index 4ae505c75..7d03c8ef9 100644 --- a/docs/source/Plugin/_plugin_substitutions_p12x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p12x.repl @@ -1,116 +1,129 @@ -.. |P120_name| replace:: :cyan:`ADXL345 I2C` -.. |P120_type| replace:: :cyan:`Acceleration` -.. |P120_typename| replace:: :cyan:`Acceleration - ADXL345 (I2C)` -.. |P120_porttype| replace:: `.` -.. |P120_status| replace:: :yellow:`COLLECTION E` -.. |P120_github| replace:: P120_ADXL345_Accelerometer.ino -.. _P120_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P120_ADXL345_Accelerometer.ino -.. |P120_usedby| replace:: `.` -.. |P120_shortinfo| replace:: `Acceleration X/Y/X sensor` -.. |P120_maintainer| replace:: `tonhuisman` -.. |P120_compileinfo| replace:: `.` -.. |P120_usedlibraries| replace:: `https://github.com/sparkfun/SparkFun_ADXL345_Arduino_Library (local copy)` - -.. |P121_name| replace:: :cyan:`HCM5883L` -.. |P121_type| replace:: :cyan:`Position` -.. |P121_typename| replace:: :cyan:`Position - HMC5883L` -.. |P121_porttype| replace:: `.` -.. |P121_status| replace:: :yellow:`COLLECTION E` -.. |P121_github| replace:: P121_HMC5883L.ino -.. _P121_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P121_HMC5883L.ino -.. |P121_usedby| replace:: `.` -.. |P121_shortinfo| replace:: `Adafruit HMC5883L Breakout - Triple-Axis Magnetometer Compass Sensor` -.. |P121_maintainer| replace:: `svn2208` -.. |P121_compileinfo| replace:: `.` -.. |P121_usedlibraries| replace:: `https://github.com/adafruit/Adafruit_HMC5883_Unified (modified local copy)` - -.. |P122_name| replace:: :cyan:`SHT2x` -.. |P122_type| replace:: :cyan:`Environment` -.. |P122_typename| replace:: :cyan:`Environment - SHT2x` -.. |P122_porttype| replace:: `.` -.. |P122_status| replace:: :yellow:`COLLECTION F` -.. |P122_github| replace:: P122_SHT2x.ino -.. _P122_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P122_SHT2x.ino -.. |P122_usedby| replace:: `.` -.. |P122_shortinfo| replace:: `SHT2x series Temperature/Humidity` -.. |P122_maintainer| replace:: `flashmark` -.. |P122_compileinfo| replace:: `.` -.. |P122_usedlibraries| replace:: `I2C` - -.. |P124_name| replace:: :cyan:`MultiRelay` -.. |P124_type| replace:: :cyan:`Output` -.. |P124_typename| replace:: :cyan:`Output - I2C Multi Relay` -.. |P124_porttype| replace:: `.` -.. |P124_status| replace:: :yellow:`COLLECTION D` -.. |P124_github| replace:: P124_MultiTelay.ino -.. _P124_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P124_MyltiRelay.ino -.. |P124_usedby| replace:: `.` -.. |P124_shortinfo| replace:: `Seeed Studio I2C Multi Relay boards` -.. |P124_maintainer| replace:: `tonhuisman` -.. |P124_compileinfo| replace:: `.` -.. |P124_usedlibraries| replace:: `https://github.com/Seeed-Studio/Multi_Channel_Relay_Arduino_Library (modified local copy)` - -.. |P125_name| replace:: :cyan:`ADXL345 SPI` -.. |P125_type| replace:: :cyan:`Acceleration` -.. |P125_typename| replace:: :cyan:`Acceleration - ADXL345 (SPI)` -.. |P125_porttype| replace:: `.` -.. |P125_status| replace:: :yellow:`COLLECTION E` -.. |P125_github| replace:: P125_ADXL345_SPI.ino -.. _P125_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P125_ADXL345_SPI.ino -.. |P125_usedby| replace:: `.` -.. |P125_shortinfo| replace:: `Acceleration X/Y/X sensor` -.. |P125_maintainer| replace:: `tonhuisman` -.. |P125_compileinfo| replace:: `.` -.. |P125_usedlibraries| replace:: `https://github.com/sparkfun/SparkFun_ADXL345_Arduino_Library (local copy)` - -.. |P126_name| replace:: :cyan:`Shift registers (74HC595)` -.. |P126_type| replace:: :cyan:`Output` -.. |P126_typename| replace:: :cyan:`Output - Shift registers (74HC595)` -.. |P126_porttype| replace:: `.` -.. |P126_status| replace:: :yellow:`COLLECTION E` -.. |P126_github| replace:: P126_74HC595.ino -.. _P126_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P126_74HC595.ino -.. |P126_usedby| replace:: `.` -.. |P126_shortinfo| replace:: `Output shift registers` -.. |P126_maintainer| replace:: `tonhuisman` -.. |P126_compileinfo| replace:: `.` -.. |P126_usedlibraries| replace:: `https://timodenk.com/blog/shift-register-arduino-library/ (Un-Templated, modified local copy)` - -.. |P127_name| replace:: :cyan:`CO2 CDM7160` -.. |P127_type| replace:: :cyan:`Gases` -.. |P127_typename| replace:: :cyan:`Gases - CO2 CDM7160` -.. |P127_porttype| replace:: `.` -.. |P127_status| replace:: :yellow:`COLLECTION D` :yellow:`CLIMATE` -.. |P127_github| replace:: P127_CDM7160.ino -.. _P127_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P127_CDM7160.ino -.. |P127_usedby| replace:: `.` -.. |P127_shortinfo| replace:: `CO2 sensor` -.. |P127_maintainer| replace:: `tonhuisman, V0JT4` -.. |P127_compileinfo| replace:: `.` -.. |P127_usedlibraries| replace:: `.` - -.. |P128_name| replace:: :cyan:`NeoPixel (BusFX)` -.. |P128_type| replace:: :cyan:`Output` -.. |P128_typename| replace:: :cyan:`Output - NeoPixel (BusFX)` -.. |P128_porttype| replace:: `.` -.. |P128_status| replace:: :yellow:`NEOPIXEL` -.. |P128_github| replace:: P128_NeoPixelBusFX.ino -.. _P128_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P128_NeoPixelBusFX.ino -.. |P128_usedby| replace:: `.` -.. |P128_shortinfo| replace:: `NeoPixel special effects library BusFX` -.. |P128_maintainer| replace:: `tonhuisman` -.. |P128_compileinfo| replace:: `.` -.. |P128_usedlibraries| replace:: `https://github.com/Makuna/NeoPixelBus and https://github.com/djcysmic/NeopixelBusFX (forked from ESPEasyPluginPlayground)` - -.. |P129_name| replace:: :cyan:`Shift registers (74HC165)` -.. |P129_type| replace:: :cyan:`Input` -.. |P129_typename| replace:: :cyan:`Input - Shift registers (74HC165)` -.. |P129_porttype| replace:: `.` -.. |P129_status| replace:: :yellow:`COLLECTION E` -.. |P129_github| replace:: P129_74HC165.ino -.. _P129_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P129_74HC165.ino -.. |P129_usedby| replace:: `.` -.. |P129_shortinfo| replace:: `Parallel - serial input shift registers` -.. |P129_maintainer| replace:: `tonhuisman` -.. |P129_compileinfo| replace:: `.` -.. |P129_usedlibraries| replace:: `.` +.. |P120_name| replace:: :cyan:`ADXL345 I2C` +.. |P120_type| replace:: :cyan:`Acceleration` +.. |P120_typename| replace:: :cyan:`Acceleration - ADXL345 (I2C)` +.. |P120_porttype| replace:: `.` +.. |P120_status| replace:: :yellow:`COLLECTION E` +.. |P120_github| replace:: P120_ADXL345_Accelerometer.ino +.. _P120_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P120_ADXL345_Accelerometer.ino +.. |P120_usedby| replace:: `.` +.. |P120_shortinfo| replace:: `Acceleration X/Y/X sensor` +.. |P120_maintainer| replace:: `tonhuisman` +.. |P120_compileinfo| replace:: `.` +.. |P120_usedlibraries| replace:: `https://github.com/sparkfun/SparkFun_ADXL345_Arduino_Library (local copy)` + +.. |P121_name| replace:: :cyan:`HCM5883L` +.. |P121_type| replace:: :cyan:`Position` +.. |P121_typename| replace:: :cyan:`Position - HMC5883L` +.. |P121_porttype| replace:: `.` +.. |P121_status| replace:: :yellow:`COLLECTION E` +.. |P121_github| replace:: P121_HMC5883L.ino +.. _P121_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P121_HMC5883L.ino +.. |P121_usedby| replace:: `.` +.. |P121_shortinfo| replace:: `Adafruit HMC5883L Breakout - Triple-Axis Magnetometer Compass Sensor` +.. |P121_maintainer| replace:: `svn2208` +.. |P121_compileinfo| replace:: `.` +.. |P121_usedlibraries| replace:: `https://github.com/adafruit/Adafruit_HMC5883_Unified (modified local copy)` + +.. |P122_name| replace:: :cyan:`SHT2x` +.. |P122_type| replace:: :cyan:`Environment` +.. |P122_typename| replace:: :cyan:`Environment - SHT2x` +.. |P122_porttype| replace:: `.` +.. |P122_status| replace:: :yellow:`COLLECTION F` +.. |P122_github| replace:: P122_SHT2x.ino +.. _P122_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P122_SHT2x.ino +.. |P122_usedby| replace:: `.` +.. |P122_shortinfo| replace:: `SHT2x series Temperature/Humidity` +.. |P122_maintainer| replace:: `flashmark` +.. |P122_compileinfo| replace:: `.` +.. |P122_usedlibraries| replace:: `I2C` + +.. |P123_name| replace:: :cyan:`I2C Touchscreens` +.. |P123_type| replace:: :cyan:`Touch` +.. |P123_typename| replace:: :cyan:`Touch - I2C Touchscreens` +.. |P123_porttype| replace:: `.` +.. |P123_status| replace:: :yellow:`DISPLAY` +.. |P123_github| replace:: P123_I2CTouch.ino +.. _P123_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P123_I2CTouch.ino +.. |P123_usedby| replace:: `.` +.. |P123_shortinfo| replace:: `I2C capacitive touchscreen overlays` +.. |P123_maintainer| replace:: `tonhuisman` +.. |P123_compileinfo| replace:: `.` +.. |P123_usedlibraries| replace:: `BitBank bb_captouch library (modified)` + +.. |P124_name| replace:: :cyan:`MultiRelay` +.. |P124_type| replace:: :cyan:`Output` +.. |P124_typename| replace:: :cyan:`Output - I2C Multi Relay` +.. |P124_porttype| replace:: `.` +.. |P124_status| replace:: :yellow:`COLLECTION D` +.. |P124_github| replace:: P124_MultiTelay.ino +.. _P124_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P124_MyltiRelay.ino +.. |P124_usedby| replace:: `.` +.. |P124_shortinfo| replace:: `Seeed Studio I2C Multi Relay boards` +.. |P124_maintainer| replace:: `tonhuisman` +.. |P124_compileinfo| replace:: `.` +.. |P124_usedlibraries| replace:: `https://github.com/Seeed-Studio/Multi_Channel_Relay_Arduino_Library (modified local copy)` + +.. |P125_name| replace:: :cyan:`ADXL345 SPI` +.. |P125_type| replace:: :cyan:`Acceleration` +.. |P125_typename| replace:: :cyan:`Acceleration - ADXL345 (SPI)` +.. |P125_porttype| replace:: `.` +.. |P125_status| replace:: :yellow:`COLLECTION E` +.. |P125_github| replace:: P125_ADXL345_SPI.ino +.. _P125_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P125_ADXL345_SPI.ino +.. |P125_usedby| replace:: `.` +.. |P125_shortinfo| replace:: `Acceleration X/Y/X sensor` +.. |P125_maintainer| replace:: `tonhuisman` +.. |P125_compileinfo| replace:: `.` +.. |P125_usedlibraries| replace:: `https://github.com/sparkfun/SparkFun_ADXL345_Arduino_Library (local copy)` + +.. |P126_name| replace:: :cyan:`Shift registers (74HC595)` +.. |P126_type| replace:: :cyan:`Output` +.. |P126_typename| replace:: :cyan:`Output - Shift registers (74HC595)` +.. |P126_porttype| replace:: `.` +.. |P126_status| replace:: :yellow:`COLLECTION E` +.. |P126_github| replace:: P126_74HC595.ino +.. _P126_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P126_74HC595.ino +.. |P126_usedby| replace:: `.` +.. |P126_shortinfo| replace:: `Output shift registers` +.. |P126_maintainer| replace:: `tonhuisman` +.. |P126_compileinfo| replace:: `.` +.. |P126_usedlibraries| replace:: `https://timodenk.com/blog/shift-register-arduino-library/ (Un-Templated, modified local copy)` + +.. |P127_name| replace:: :cyan:`CO2 CDM7160` +.. |P127_type| replace:: :cyan:`Gases` +.. |P127_typename| replace:: :cyan:`Gases - CO2 CDM7160` +.. |P127_porttype| replace:: `.` +.. |P127_status| replace:: :yellow:`COLLECTION D` :yellow:`CLIMATE` +.. |P127_github| replace:: P127_CDM7160.ino +.. _P127_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P127_CDM7160.ino +.. |P127_usedby| replace:: `.` +.. |P127_shortinfo| replace:: `CO2 sensor` +.. |P127_maintainer| replace:: `tonhuisman, V0JT4` +.. |P127_compileinfo| replace:: `.` +.. |P127_usedlibraries| replace:: `.` + +.. |P128_name| replace:: :cyan:`NeoPixel (BusFX)` +.. |P128_type| replace:: :cyan:`Output` +.. |P128_typename| replace:: :cyan:`Output - NeoPixel (BusFX)` +.. |P128_porttype| replace:: `.` +.. |P128_status| replace:: :yellow:`NEOPIXEL` +.. |P128_github| replace:: P128_NeoPixelBusFX.ino +.. _P128_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P128_NeoPixelBusFX.ino +.. |P128_usedby| replace:: `.` +.. |P128_shortinfo| replace:: `NeoPixel special effects library BusFX` +.. |P128_maintainer| replace:: `tonhuisman` +.. |P128_compileinfo| replace:: `.` +.. |P128_usedlibraries| replace:: `https://github.com/Makuna/NeoPixelBus and https://github.com/djcysmic/NeopixelBusFX (forked from ESPEasyPluginPlayground)` + +.. |P129_name| replace:: :cyan:`Shift registers (74HC165)` +.. |P129_type| replace:: :cyan:`Input` +.. |P129_typename| replace:: :cyan:`Input - Shift registers (74HC165)` +.. |P129_porttype| replace:: `.` +.. |P129_status| replace:: :yellow:`COLLECTION E` +.. |P129_github| replace:: P129_74HC165.ino +.. _P129_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P129_74HC165.ino +.. |P129_usedby| replace:: `.` +.. |P129_shortinfo| replace:: `Parallel - serial input shift registers` +.. |P129_maintainer| replace:: `tonhuisman` +.. |P129_compileinfo| replace:: `.` +.. |P129_usedlibraries| replace:: `.` diff --git a/docs/source/Plugin/_plugin_substitutions_p15x.repl b/docs/source/Plugin/_plugin_substitutions_p15x.repl index d964183ac..3781633ef 100644 --- a/docs/source/Plugin/_plugin_substitutions_p15x.repl +++ b/docs/source/Plugin/_plugin_substitutions_p15x.repl @@ -50,9 +50,9 @@ .. |P153_compileinfo| replace:: `.` .. |P153_usedlibraries| replace:: `.` -.. |P154_name| replace:: :cyan:`BMP3xx` +.. |P154_name| replace:: :cyan:`BMP3xx (I2C)` .. |P154_type| replace:: :cyan:`Environment` -.. |P154_typename| replace:: :cyan:`Environment - BMP3xx` +.. |P154_typename| replace:: :cyan:`Environment - BMP3xx (I2C)` .. |P154_porttype| replace:: `.` .. |P154_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` .. |P154_github| replace:: P154_BMP3xx.ino diff --git a/docs/source/Plugin/_plugin_substitutions_p16x.repl b/docs/source/Plugin/_plugin_substitutions_p16x.repl new file mode 100644 index 000000000..4caabb115 --- /dev/null +++ b/docs/source/Plugin/_plugin_substitutions_p16x.repl @@ -0,0 +1,77 @@ +.. |P162_name| replace:: :cyan:`MCP42xxx Digipot` +.. |P162_type| replace:: :cyan:`Output` +.. |P162_typename| replace:: :cyan:`Output - MCP42xxx Digipot` +.. |P162_porttype| replace:: `.` +.. |P162_status| replace:: :yellow:`COLLECTION G` +.. |P162_github| replace:: P162_MCP42xxx.ino +.. _P162_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P162_MCP42xxx.ino +.. |P162_usedby| replace:: `.` +.. |P162_shortinfo| replace:: `MCP42xxx/MCP41xxx Digital potentiometers` +.. |P162_maintainer| replace:: `tonhuisman` +.. |P162_compileinfo| replace:: `.` +.. |P162_usedlibraries| replace:: `SPI` + +.. |P164_name| replace:: :cyan:`ENS16x TVOC/eCO2` +.. |P164_type| replace:: :cyan:`Gases` +.. |P164_typename| replace:: :cyan:`Gases - ENS16x TVOC/eCO2` +.. |P164_porttype| replace:: `.` +.. |P164_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P164_github| replace:: P164_gases_ens160.ino +.. _P164_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P164_gases_ens160.ino +.. |P164_usedby| replace:: `.` +.. |P164_shortinfo| replace:: `ENS16x TVOC/eCO2 sensors` +.. |P164_maintainer| replace:: `flashmark` +.. |P164_compileinfo| replace:: `.` +.. |P164_usedlibraries| replace:: `I2C` + +.. |P166_name| replace:: :cyan:`GP8403 Dual channel DAC 0-10V` +.. |P166_type| replace:: :cyan:`Output` +.. |P166_typename| replace:: :cyan:`Output - GP8403 Dual channel DAC 0-10V` +.. |P166_porttype| replace:: `.` +.. |P166_status| replace:: :yellow:`COLLECTION G` +.. |P166_github| replace:: P166_GP8403.ino +.. _P166_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P166_GP8403.ino +.. |P166_usedby| replace:: `.` +.. |P166_shortinfo| replace:: `GP8403 Dual channel DAC 0-10V` +.. |P166_maintainer| replace:: `tonhuisman` +.. |P166_compileinfo| replace:: `.` +.. |P166_usedlibraries| replace:: `modified version of DFRobot_GP8403` + +.. |P167_name| replace:: :cyan:`Sensirion SEN5x (IKEA Vindstyrka)` +.. |P167_type| replace:: :cyan:`Environment` +.. |P167_typename| replace:: :cyan:`Environment - Sensirion SEN5x (IKEA Vindstyrka)` +.. |P167_porttype| replace:: `.` +.. |P167_status| replace:: :yellow:`CLIMATE` +.. |P167_github| replace:: P167_Vindstyrka.ino +.. _P167_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P167_Vindstyrka.ino +.. |P167_usedby| replace:: `.` +.. |P167_shortinfo| replace:: `Sensirion SEN5x (IKEA Vindstyrka)` +.. |P167_maintainer| replace:: `AndiBaciu, tonhuisman` +.. |P167_compileinfo| replace:: `.` +.. |P167_usedlibraries| replace:: `.` + +.. |P168_name| replace:: :cyan:`VEML6030/VEML7700` +.. |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_github| replace:: P168_VEML6030_7700.ino +.. _P168_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P168_VEML6030_7700.ino +.. |P168_usedby| replace:: `.` +.. |P168_shortinfo| replace:: `VEML6030/VEML7700 Light/Lux sensor` +.. |P168_maintainer| replace:: `tonhuisman` +.. |P168_compileinfo| replace:: `.` +.. |P168_usedlibraries| replace:: `modified version of Adafruit_VEML7700` + +.. |P169_name| replace:: :cyan:`AS3935 Lightning Detector` +.. |P169_type| replace:: :cyan:`Environment` +.. |P169_typename| replace:: :cyan:`Environment - AS3935 Lightning Detector` +.. |P169_porttype| replace:: `.` +.. |P169_status| replace:: :yellow:`CLIMATE` +.. |P169_github| replace:: P169_AS3935_LightningDetector.ino +.. _P169_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P169_AS3935_LightningDetector.ino +.. |P169_usedby| replace:: `.` +.. |P169_shortinfo| replace:: `AS3935 Franklin Lightning Sensor IC / Lightning Detector` +.. |P169_maintainer| replace:: `TD-er` +.. |P169_compileinfo| replace:: `.` +.. |P169_usedlibraries| replace:: `https://bitbucket.org/christandlg/as3935mi/src/master/` diff --git a/docs/source/Plugin/_plugin_substitutions_p17x.repl b/docs/source/Plugin/_plugin_substitutions_p17x.repl new file mode 100644 index 000000000..7aca8d741 --- /dev/null +++ b/docs/source/Plugin/_plugin_substitutions_p17x.repl @@ -0,0 +1,25 @@ +.. |P170_name| replace:: :cyan:`I2C Liquid level sensor` +.. |P170_type| replace:: :cyan:`Input` +.. |P170_typename| replace:: :cyan:`Input - I2C Liquid level sensor` +.. |P170_porttype| replace:: `.` +.. |P170_status| replace:: :yellow:`COLLECTION G` +.. |P170_github| replace:: P170_Waterlevel.ino +.. _P170_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P170_Waterlevel.ino +.. |P170_usedby| replace:: `.` +.. |P170_shortinfo| replace:: `Seeed Studio I2C Liquid level sensor` +.. |P170_maintainer| replace:: `tonhuisman` +.. |P170_compileinfo| replace:: `.` +.. |P170_usedlibraries| replace:: `.` + +.. |P172_name| replace:: :cyan:`BMP3xx (SPI)` +.. |P172_type| replace:: :cyan:`Environment` +.. |P172_typename| replace:: :cyan:`Environment - BMP3xx (SPI)` +.. |P172_porttype| replace:: `.` +.. |P172_status| replace:: :yellow:`COLLECTION G` :yellow:`CLIMATE` +.. |P172_github| replace:: P172_BMP3xx_SPI.ino +.. _P172_github: https://github.com/letscontrolit/ESPEasy/blob/mega/src/_P172_BMP3xx_SPI.ino +.. |P172_usedby| replace:: `.` +.. |P172_shortinfo| replace:: `BMP3xx Temperature and Pressure sensors` +.. |P172_maintainer| replace:: `TD-er, tonhuisman` +.. |P172_compileinfo| replace:: `.` +.. |P172_usedlibraries| replace:: `Adafruit BMP3XX Library` diff --git a/docs/source/Reference/Command.rst b/docs/source/Reference/Command.rst index 699a611d8..d667bf64f 100644 --- a/docs/source/Reference/Command.rst +++ b/docs/source/Reference/Command.rst @@ -1,797 +1,833 @@ -.. include:: ../Plugin/_plugin_substitutions.repl - -Command Reference -***************** - -ESP Easy offers a set of commands to control hardware devices and provide some basic local control using rules. There are several ways to launch commands on ESP Easy: - -.. csv-table:: - :header: "Protocol", "Syntax", "Extra information" - :widths: 8, 15, 15 - - " - HTTP - "," - **http:///control?cmd=** ```` - "," - Send commands over the HTTP protocol. - " - " - MQTT - "," - **/cmd** with payload: ```` - "," - Send commands over the MQTT protocol. - " - " - Serial (TTL) - "," - ```` - "," - Send commands using serial (RX/TX). Just type the ```` - " - " - UDP - "," - **SendTo,,** ```` - "," - Send commands from one ESP Easy unit to another. Setup UDP ESP Easy peer-2-peer controller first. - " - " - Rules - "," - ```` - "," - Internally within ESP Easy. Just enter the ```` within an event block or conditional block. - " - -Commands are divided into several classes: - -:red:`Internal` Commands not related to plugins, controllers or notifications. Can be run from serial and rules engine - -:green:`Rules` Related to rules processing. Can be run from serial and rules engine - -:cyan:`Plugin` Commands specific for a plugin. Can be run from serial, rules engine, HTTP, MQTT - -:blue:`Special` can be used from any source - -Command a specific task for multiple instances of a plugin ----------------------------------------------------------- - -When multiple tasks are assigned to run the same plugin, one may want to address a specific task to run the command. - -In order to do so, prefix the command with the intended task name. -Either by using ``[].`` or ``[].`` (square brackets are optional) to address a specific instance. - -N.B. This requires the plugin names to be unique (if the TaskName variant is used). - -Examples: - -``[Display1].oledframedcmd,3,'Hello World'`` - -``[Display2].oledframedcmd,4,'From the other side'`` - -This will display 'Hello World' on the 3rd line of the display with name 'Display1', and 'From the other side' on line 4 of the display named 'Display2'. - - -``[AC1].irsendac,{}`` - -``[AC2].irsendac,{}`` - -This allows to control multiple IR controlled AC's from one ESP. - - -Internal Commands ------------------ - -Commands handled by ESPEasy core. -These are not part of a plugin. - -.. include:: ../Plugin/P000_commands.repl - - -GPIO Commands -------------- - -Internal GPIO -~~~~~~~~~~~~~ - -.. include:: ../Plugin/P001_commands_GPIO.repl - -External MCPGPIO -~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P009_commands.repl - -External PCFGPIO -~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P019_commands.repl - - -Ringtone Internal GPIO -~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P001_commands_RTTTL.repl - -Task Value Stats Commands -~~~~~~~~~~~~~~~~~~~~~~~~~ - -(Added: 2022/07/11) -For task values with "Stats" enabled, one can call commands on this statistical data. - -Commands on "Stats" data: - -* ``bme.resetpeaks`` Reset the recorded "max" and "min" value of all task values of the task called "bme". -* ``bme.clearsamples`` Clear the recorded historic samples of all task values of the task called "bme". - - - - -Plugin based commands ---------------------- - -Besides the internal commands there's also plugin specific commands. - -These can only be handled when the specific plugin is included in the ESPEasy build (and the plugin is assigned to an enabled task) - -.. P001 :ref:`P001_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P001_commands.repl - - -.. P002 :ref:`P002_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P002_commands.repl - - -P003 :ref:`P003_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P003_commands.repl - - -.. P004 :ref:`P004_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P004_commands.repl - - -.. P005 :ref:`P005_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P005_commands.repl - - -.. P006 :ref:`P006_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P006_commands.repl - - -P007 :ref:`P007_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P007_commands.repl - - -.. P008 :ref:`P008_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P008_commands.repl - - -P009 :ref:`P009_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P009_commands.repl - - -.. P010 :ref:`P010_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P010_commands.repl - - -.. P011 :ref:`P011_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P011_commands.repl - - -P012 :ref:`P012_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P012_commands.repl - - -.. P013 :ref:`P013_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P013_commands.repl - - -.. P014 :ref:`P014_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P014_commands.repl - - -.. P015 :ref:`P015_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P015_commands.repl - - -.. P016 :ref:`P016_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P016_commands.repl - - -.. P017 :ref:`P017_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P017_commands.repl - - -.. P018 :ref:`P018_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P018_commands.repl - - -P019 :ref:`P019_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P019_commands.repl - - -.. P020 :ref:`P020_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P020_commands.repl - - -P021 :ref:`P021_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P021_commands.repl - - -P022 :ref:`P022_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P022_commands.repl - - -P023 :ref:`P023_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P023_commands.repl - - -.. P024 :ref:`P024_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P024_commands.repl - - -.. P025 :ref:`P025_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P025_commands.repl - - -.. P026 :ref:`P026_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P026_commands.repl - - -.. P027 :ref:`P027_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P027_commands.repl - - -.. P028 :ref:`P028_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P028_commands.repl - - -.. P029 :ref:`P029_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P029_commands.repl - - -.. P030 :ref:`P030_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P030_commands.repl - - -.. P031 :ref:`P031_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P031_commands.repl - - -.. P032 :ref:`P032_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P032_commands.repl - - -.. P033 :ref:`P033_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P033_commands.repl - - -.. P034 :ref:`P034_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P034_commands.repl - - -P035 :ref:`P035_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P035_commands.repl - - -P036 :ref:`P036_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P036_commands.repl - - -.. P037 :ref:`P037_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P037_commands.repl - - -P038 :ref:`P038_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P038_commands.repl - - -.. P039 :ref:`P039_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P039_commands.repl - - -.. P040 :ref:`P040_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P040_commands.repl - - -.. P041 :ref:`P041_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P041_commands.repl - - -.. P042 :ref:`P042_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P042_commands.repl - - -.. P043 :ref:`P043_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P043_commands.repl - - -.. P044 :ref:`P044_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P044_commands.repl - - -.. P045 :ref:`P045_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P045_commands.repl - - -.. P046 :ref:`P046_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P046_commands.repl - - -.. P047 :ref:`P047_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P047_commands.repl - - -P048 :ref:`P048_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P048_commands.repl - - -.. P049 :ref:`P049_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P049_commands.repl - - -.. P050 :ref:`P050_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P050_commands.repl - - -.. P051 :ref:`P051_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P051_commands.repl - - -P052 :ref:`P052_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P052_commands.repl - - -P053 :ref:`P053_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P053_commands.repl - - -.. P054 :ref:`P054_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P054_commands.repl - - -.. P055 :ref:`P055_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P055_commands.repl - - -.. P056 :ref:`P056_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P056_commands.repl - - -.. P057 :ref:`P057_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P057_commands.repl - - -.. P058 :ref:`P058_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P058_commands.repl - - -P059 :ref:`P059_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P059_commands.repl - - -.. P060 :ref:`P060_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P060_commands.repl - - -.. P061 :ref:`P061_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P061_commands.repl - - -.. P062 :ref:`P062_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P062_commands.repl - - -.. P063 :ref:`P063_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P063_commands.repl - - -.. P064 :ref:`P064_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P064_commands.repl - - -P065 :ref:`P065_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P065_commands.repl - - -.. P066 :ref:`P066_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P066_commands.repl - - -P067 :ref:`P067_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P067_commands.repl - - -.. P068 :ref:`P068_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P068_commands.repl - - -.. P069 :ref:`P069_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P069_commands.repl - - -.. P070 :ref:`P070_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P070_commands.repl - - -.. P071 :ref:`P071_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P071_commands.repl - - -.. P072 :ref:`P072_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P072_commands.repl - - -P073 :ref:`P073_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P073_commands.repl - - -.. P074 :ref:`P074_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P074_commands.repl - - -P075 :ref:`P075_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P075_commands.repl - - -P076 :ref:`P076_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P076_commands.repl - - -P077 :ref:`P077_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P077_commands.repl - - -.. P078 :ref:`P078_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P078_commands.repl - - -P079 :ref:`P079_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P079_commands.repl - - -.. P080 :ref:`P080_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P080_commands.repl - - -.. P081 :ref:`P081_page` -.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. .. include:: ../Plugin/P081_commands.repl - - -P082 :ref:`P082_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P082_commands.repl - -P087 :ref:`P087_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P087_commands.repl - -P088 :ref:`P088_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P088_commands.repl - - -P091 :ref:`P091_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P091_commands.repl - - -P093 :ref:`P093_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P093_commands.repl - -P094 :ref:`P094_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P094_commands.repl - -P095 :ref:`P095_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P095_commands.repl - -See also the :ref:`AdafruitGFX Helper commands `, below. - -P098 :ref:`P098_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P098_commands.repl - - -P099 :ref:`P099_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P099_commands.repl - - -P101 :ref:`P101_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P101_commands.repl - - -P104 :ref:`P104_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P104_commands.repl - -P109 :ref:`P109_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P109_commands.repl - -P115 :ref:`P115_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P115_commands.repl - -P116 :ref:`P116_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P116_commands.repl - -See also the :ref:`AdafruitGFX Helper commands `, below. - -P117 :ref:`P117_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P117_commands.repl - -P118 :ref:`P118_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P118_commands.repl - -P124 :ref:`P124_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P124_commands.repl - -P126 :ref:`P126_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P126_commands.repl - -P127 :ref:`P127_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P127_commands.repl - -P128 :ref:`P128_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P128_commands.repl - -P129 :ref:`P129_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P129_commands.repl - -P131 :ref:`P131_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P131_commands.repl - -See also the :ref:`AdafruitGFX Helper commands `, below. - -P135 :ref:`P135_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P135_commands.repl - -P137 :ref:`P137_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P137_commands.repl - -P141 :ref:`P141_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P141_commands.repl - -See also the :ref:`AdafruitGFX Helper commands `, below. - -P143 :ref:`P143_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P143_commands.repl - -P146 :ref:`P146_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P146_commands.repl - -P148 :ref:`P148_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P148_commands.repl - -P152 :ref:`P152_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P152_commands.repl - -P153 :ref:`P153_page` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../Plugin/P153_commands.repl - -.. .. *** Insert regular plugin commands above this remark! *** - -.. _AdafruitGFX Helper commands: - -AdafruitGFX Helper commands ---------------------------- - -For all displays that use the AdafruitGFX Helper, these commands are available in addition to the display-specific commands: - -.. include:: ../Plugin/AdaGFX_commands.repl - +.. include:: ../Plugin/_plugin_substitutions.repl + +Command Reference +***************** + +ESP Easy offers a set of commands to control hardware devices and provide some basic local control using rules. There are several ways to launch commands on ESP Easy: + +.. csv-table:: + :header: "Protocol", "Syntax", "Extra information" + :widths: 8, 15, 15 + + " + HTTP + "," + **http:///control?cmd=** ```` + "," + Send commands over the HTTP protocol. + " + " + MQTT + "," + **/cmd** with payload: ```` + "," + Send commands over the MQTT protocol. + " + " + Serial (TTL) + "," + ```` + "," + Send commands using serial (RX/TX). Just type the ```` + " + " + UDP + "," + **SendTo,,** ```` + "," + Send commands from one ESP Easy unit to another. Setup UDP ESP Easy peer-2-peer controller first. + " + " + Rules + "," + ```` + "," + Internally within ESP Easy. Just enter the ```` within an event block or conditional block. + " + +Commands are divided into several classes: + +:red:`Internal` Commands not related to plugins, controllers or notifications. Can be run from serial and rules engine + +:green:`Rules` Related to rules processing. Can be run from serial and rules engine + +:cyan:`Plugin` Commands specific for a plugin. Can be run from serial, rules engine, HTTP, MQTT + +:blue:`Special` can be used from any source + +Command a specific task for multiple instances of a plugin +---------------------------------------------------------- + +When multiple tasks are assigned to run the same plugin, one may want to address a specific task to run the command. + +In order to do so, prefix the command with the intended task name. +Either by using ``[].`` or ``[].`` (square brackets are optional) to address a specific instance. + +N.B. This requires the plugin names to be unique (if the TaskName variant is used). + +Examples: + +``[Display1].oledframedcmd,3,'Hello World'`` + +``[Display2].oledframedcmd,4,'From the other side'`` + +This will display 'Hello World' on the 3rd line of the display with name 'Display1', and 'From the other side' on line 4 of the display named 'Display2'. + + +``[AC1].irsendac,{}`` + +``[AC2].irsendac,{}`` + +This allows to control multiple IR controlled AC's from one ESP. + + +Internal Commands +----------------- + +Commands handled by ESPEasy core. +These are not part of a plugin. + +.. include:: ../Plugin/P000_commands.repl + + +GPIO Commands +------------- + +Internal GPIO +~~~~~~~~~~~~~ + +.. include:: ../Plugin/P001_commands_GPIO.repl + +External MCPGPIO +~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P009_commands.repl + +External PCFGPIO +~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P019_commands.repl + + +Ringtone Internal GPIO +~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P001_commands_RTTTL.repl + +Task Value Stats Commands +~~~~~~~~~~~~~~~~~~~~~~~~~ + +(Added: 2022/07/11) +For task values with "Stats" enabled, one can call commands on this statistical data. + +Commands on "Stats" data: + +* ``bme.resetpeaks`` Reset the recorded "max" and "min" value of all task values of the task called "bme". +* ``bme.clearsamples`` Clear the recorded historic samples of all task values of the task called "bme". + + + + +Plugin based commands +--------------------- + +Besides the internal commands there's also plugin specific commands. + +These can only be handled when the specific plugin is included in the ESPEasy build (and the plugin is assigned to an enabled task) + +.. P001 :ref:`P001_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P001_commands.repl + + +.. P002 :ref:`P002_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P002_commands.repl + + +P003 :ref:`P003_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P003_commands.repl + + +.. P004 :ref:`P004_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P004_commands.repl + + +.. P005 :ref:`P005_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P005_commands.repl + + +.. P006 :ref:`P006_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P006_commands.repl + + +P007 :ref:`P007_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P007_commands.repl + + +.. P008 :ref:`P008_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P008_commands.repl + + +P009 :ref:`P009_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P009_commands.repl + + +.. P010 :ref:`P010_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P010_commands.repl + + +P011 :ref:`P011_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P011_commands.repl + + +P012 :ref:`P012_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P012_commands.repl + + +.. P013 :ref:`P013_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P013_commands.repl + + +.. P014 :ref:`P014_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P014_commands.repl + + +.. P015 :ref:`P015_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P015_commands.repl + + +.. P016 :ref:`P016_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P016_commands.repl + + +.. P017 :ref:`P017_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P017_commands.repl + + +.. P018 :ref:`P018_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P018_commands.repl + + +P019 :ref:`P019_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P019_commands.repl + + +P020 :ref:`P020_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P020_commands.repl + + +P021 :ref:`P021_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P021_commands.repl + + +P022 :ref:`P022_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P022_commands.repl + + +P023 :ref:`P023_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P023_commands.repl + + +.. P024 :ref:`P024_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P024_commands.repl + + +.. P025 :ref:`P025_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P025_commands.repl + + +.. P026 :ref:`P026_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P026_commands.repl + + +.. P027 :ref:`P027_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P027_commands.repl + + +.. P028 :ref:`P028_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P028_commands.repl + + +.. P029 :ref:`P029_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P029_commands.repl + + +.. P030 :ref:`P030_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P030_commands.repl + + +.. P031 :ref:`P031_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P031_commands.repl + + +.. P032 :ref:`P032_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P032_commands.repl + + +.. P033 :ref:`P033_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P033_commands.repl + + +.. P034 :ref:`P034_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P034_commands.repl + + +P035 :ref:`P035_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P035_commands.repl + + +P036 :ref:`P036_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P036_commands.repl + + +.. P037 :ref:`P037_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P037_commands.repl + + +P038 :ref:`P038_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P038_commands.repl + + +.. P039 :ref:`P039_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P039_commands.repl + + +.. P040 :ref:`P040_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P040_commands.repl + + +.. P041 :ref:`P041_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P041_commands.repl + + +.. P042 :ref:`P042_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P042_commands.repl + + +P043 :ref:`P043_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P043_commands.repl + + +.. P044 :ref:`P044_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P044_commands.repl + + +.. P045 :ref:`P045_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P045_commands.repl + + +.. P046 :ref:`P046_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P046_commands.repl + + +.. P047 :ref:`P047_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P047_commands.repl + + +P048 :ref:`P048_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P048_commands.repl + + +.. P049 :ref:`P049_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P049_commands.repl + + +.. P050 :ref:`P050_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P050_commands.repl + + +.. P051 :ref:`P051_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P051_commands.repl + + +P052 :ref:`P052_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P052_commands.repl + + +P053 :ref:`P053_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P053_commands.repl + + +.. P054 :ref:`P054_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P054_commands.repl + + +.. P055 :ref:`P055_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P055_commands.repl + + +.. P056 :ref:`P056_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P056_commands.repl + + +.. P057 :ref:`P057_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P057_commands.repl + + +.. P058 :ref:`P058_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P058_commands.repl + + +P059 :ref:`P059_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P059_commands.repl + + +.. P060 :ref:`P060_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P060_commands.repl + + +.. P061 :ref:`P061_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P061_commands.repl + + +.. P062 :ref:`P062_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P062_commands.repl + + +.. P063 :ref:`P063_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P063_commands.repl + + +.. P064 :ref:`P064_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P064_commands.repl + + +P065 :ref:`P065_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P065_commands.repl + + +.. P066 :ref:`P066_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P066_commands.repl + + +P067 :ref:`P067_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P067_commands.repl + + +.. P068 :ref:`P068_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P068_commands.repl + + +.. P069 :ref:`P069_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P069_commands.repl + + +.. P070 :ref:`P070_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P070_commands.repl + + +.. P071 :ref:`P071_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P071_commands.repl + + +.. P072 :ref:`P072_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P072_commands.repl + + +P073 :ref:`P073_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P073_commands.repl + + +.. P074 :ref:`P074_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P074_commands.repl + + +P075 :ref:`P075_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P075_commands.repl + + +P076 :ref:`P076_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P076_commands.repl + + +P077 :ref:`P077_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P077_commands.repl + + +P078 :ref:`P078_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P078_commands.repl + + +P079 :ref:`P079_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P079_commands.repl + + +.. P080 :ref:`P080_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P080_commands.repl + + +.. P081 :ref:`P081_page` +.. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. .. include:: ../Plugin/P081_commands.repl + + +P082 :ref:`P082_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P082_commands.repl + +P087 :ref:`P087_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P087_commands.repl + +P088 :ref:`P088_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P088_commands.repl + + +P089 :ref:`P089_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P089_commands.repl + + +P091 :ref:`P091_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P091_commands.repl + + +P093 :ref:`P093_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P093_commands.repl + +P094 :ref:`P094_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P094_commands.repl + +P095 :ref:`P095_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P095_commands.repl + +See also the :ref:`AdafruitGFX Helper commands `, below. + +P098 :ref:`P098_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P098_commands.repl + + +P099 :ref:`P099_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P099_commands.repl + + +P101 :ref:`P101_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P101_commands.repl + + +P104 :ref:`P104_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P104_commands.repl + +P109 :ref:`P109_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P109_commands.repl + +P115 :ref:`P115_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P115_commands.repl + +P116 :ref:`P116_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P116_commands.repl + +See also the :ref:`AdafruitGFX Helper commands `, below. + +P117 :ref:`P117_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P117_commands.repl + +P118 :ref:`P118_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P118_commands.repl + +P123 :ref:`P123_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P123_commands.repl + +P124 :ref:`P124_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P124_commands.repl + +P126 :ref:`P126_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P126_commands.repl + +P127 :ref:`P127_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P127_commands.repl + +P128 :ref:`P128_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P128_commands.repl + +P129 :ref:`P129_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P129_commands.repl + +P131 :ref:`P131_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P131_commands.repl + +See also the :ref:`AdafruitGFX Helper commands `, below. + +P135 :ref:`P135_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P135_commands.repl + +P137 :ref:`P137_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P137_commands.repl + +P141 :ref:`P141_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P141_commands.repl + +See also the :ref:`AdafruitGFX Helper commands `, below. + +P143 :ref:`P143_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P143_commands.repl + +P146 :ref:`P146_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P146_commands.repl + +P148 :ref:`P148_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P148_commands.repl + +P152 :ref:`P152_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P152_commands.repl + +P153 :ref:`P153_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P153_commands.repl + +P159 :ref:`P159_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P159_commands.repl + +P162 :ref:`P162_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P162_commands.repl + +P166 :ref:`P166_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P166_commands.repl + +P167 :ref:`P167_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P167_commands.repl + +P169 :ref:`P169_page` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. include:: ../Plugin/P169_commands.repl + +.. .. *** Insert regular plugin commands above this remark! *** + +.. _AdafruitGFX Helper commands: + +AdafruitGFX Helper commands +--------------------------- + +For all displays that use the AdafruitGFX Helper, these commands are available in addition to the display-specific commands: + +.. include:: ../Plugin/AdaGFX_commands.repl + diff --git a/docs/source/Reference/Events.rst b/docs/source/Reference/Events.rst index 63c11999b..32b31a804 100644 --- a/docs/source/Reference/Events.rst +++ b/docs/source/Reference/Events.rst @@ -236,10 +236,10 @@ P036 :ref:`P036_page` .. include:: ../Plugin/P036_events.repl -.. P037 :ref:`P037_page` -.. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +P037 :ref:`P037_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. .. include:: ../Plugin/P037_events.repl +.. include:: ../Plugin/P037_events.repl .. P038 :ref:`P038_page` @@ -272,10 +272,10 @@ P036 :ref:`P036_page` .. .. include:: ../Plugin/P042_events.repl -.. P043 :ref:`P043_page` -.. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +P043 :ref:`P043_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. .. include:: ../Plugin/P043_events.repl +.. include:: ../Plugin/P043_events.repl .. P044 :ref:`P044_page` @@ -398,10 +398,10 @@ P053 :ref:`P053_page` .. .. include:: ../Plugin/P063_events.repl -.. P064 :ref:`P064_page` -.. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +P064 :ref:`P064_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. .. include:: ../Plugin/P064_events.repl +.. include:: ../Plugin/P064_events.repl .. P065 :ref:`P065_page` @@ -494,10 +494,10 @@ P053 :ref:`P053_page` .. .. include:: ../Plugin/P079_events.repl -.. P080 :ref:`P080_page` -.. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +P080 :ref:`P080_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. .. include:: ../Plugin/P080_events.repl +.. include:: ../Plugin/P080_events.repl P081 :ref:`P081_page` @@ -535,6 +535,11 @@ P115 :ref:`P115_page` .. include:: ../Plugin/P115_events.repl +P123 :ref:`P123_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: ../Plugin/P123_events.repl + P129 :ref:`P129_page` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -544,7 +549,13 @@ P138 :ref:`P138_page` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../Plugin/P138_events.repl + P143 :ref:`P143_page` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. include:: ../Plugin/P143_events.repl + +P170 :ref:`P170_page` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. include:: ../Plugin/P170_events.repl diff --git a/docs/source/Reference/GPIO.rst b/docs/source/Reference/GPIO.rst index 76cf1f270..101d62264 100644 --- a/docs/source/Reference/GPIO.rst +++ b/docs/source/Reference/GPIO.rst @@ -325,7 +325,7 @@ Typical uses in ESPEasy where an interrupt of a GPIO pin is used are: Pins used for RMII Ethernet PHY ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -.. include:: ../Reference/Ethernet_PHY_ESP32.rst +.. include:: ../Reference/RMII_Ethernet_PHY_ESP32.rst Best pins to use on ESP32-C3 diff --git a/docs/source/Reference/RMII_Ethernet_ESP32_boards.rst b/docs/source/Reference/RMII_Ethernet_ESP32_boards.rst new file mode 100644 index 000000000..48370e476 --- /dev/null +++ b/docs/source/Reference/RMII_Ethernet_ESP32_boards.rst @@ -0,0 +1,93 @@ + + +There is a number of ESP32 boards available with Ethernet connected via RMII. + +However it is not always clear which configuration should be used. + + +.. warning:: + **Important notice**: It is possible to actually damage the Ethernet chip when a wrong configuration is used. + +.. list-table:: RMII PHY Boards + :widths: 30 15 10 10 10 20 30 + :header-rows: 1 + + * - Board + - Ethernet Chip + - Addr + - MDC + - MDIO + - Power/RST + - Clock + * - Olimex ESP32 PoE + - LAN8720 + - 0 + - 23 + - 18 + - 12 + - 50MHz Inv Output GPIO17 + * - Olimex ESP32 EVB + - LAN8720 + - 0 + - 23 + - 18 + - (no power pin) + - External clock + * - Olimex ESP32 Gateway + - LAN8720 + - 0 + - 23 + - 18 + - 5 + - 50MHz Inv Output GPIO17 + * - wESP32 + - LAN8720 + - 0 + - 16 + - 17 + - (no power pin) + - External clock + * - WT32 ETH01 + - LAN8720 + - 1 + - 23 + - 18 + - ? + - External clock + * - TTGO T-Internet-POE ESP32 + - LAN8720 + - 0 + - 23 + - 18 + - 5 (RST) + - 50MHz Inv Output GPIO17 + * - TTGO T-ETH-POE-PRO + - LAN8720 + - 0 + - 23 + - 18 + - 5 (RST) + - 50MHz Output GPIO0 + * - TTGO T-INTER_COM + - LAN8720 + - 0 + - 23 + - 18 + - 4 (RST) + - 50MHz Output GPIO0 + * - TTGO T-EH-Lite-ESP32 + - RTL8201 + - 0 + - 23 + - 18 + - 12 + - External clock + +See: + +* `ESP32 datasheet `_ +* `TTGO/LilyGO Ethernet boards `_ +* `Olimex ESP32-PoE `_ +* `Olimex ESP32-POE-ISO `_ +* `Olimex ESP32-EVB `_ +* `Olimex ESP32-GATEWAY `_ diff --git a/docs/source/Reference/Ethernet_PHY_ESP32.rst b/docs/source/Reference/RMII_Ethernet_PHY_ESP32.rst similarity index 74% rename from docs/source/Reference/Ethernet_PHY_ESP32.rst rename to docs/source/Reference/RMII_Ethernet_PHY_ESP32.rst index cdce3b01b..99b3d84c9 100644 --- a/docs/source/Reference/Ethernet_PHY_ESP32.rst +++ b/docs/source/Reference/RMII_Ethernet_PHY_ESP32.rst @@ -10,10 +10,18 @@ The following PHY connections are required for RMII PHY data connections: - RMII Signal - ESP32 EMAC Function - Notes - * - 0 + * - 0 / 16 / 17 - REF_CLK - EMAC_TX_CLK - See desciption about the clock + * - 18 + - MDIO + - EMAC_MDIO + - In theory user configurable, but almost always used as MDIO pin + * - 23 + - MDC + - EMAC_MDC + - In theory user configurable, but almost always used as MDC pin * - 21 - TX_EN - EMAC_TX_EN diff --git a/docs/source/Reference/SPI_Ethernet_ESP32_boards.rst b/docs/source/Reference/SPI_Ethernet_ESP32_boards.rst new file mode 100644 index 000000000..516ed1ef1 --- /dev/null +++ b/docs/source/Reference/SPI_Ethernet_ESP32_boards.rst @@ -0,0 +1,149 @@ + +There is a number of ESP32 boards available with Ethernet connected via SPI + +However it is not always clear which configuration should be used. + +.. note:: SPI Ethernet is only supported on builds based on ESP-IDF 5.1 or newer. Thus only on ESP32-builds with LittleFS support made after 2024/02. + + +N.B. As there is not yet support in ESPEasy for multiple SPI busses, it cannot be configured. + When later versions of ESPEasy will support multiple SPI busses, the SPI bus information may be needed for the configuration. + Therefore it is included in the table below. + +.. list-table:: SPI Ethernet Boards + :widths: 30 15 15 10 10 10 10 10 10 10 15 + :header-rows: 1 + + * - Board + - ESP chip + - Ethernet Chip + - Addr + - CS + - IRQ/INT + - RST + - SPI Clock + - SPI MISO + - SPI MOSI + - SPI bus + * - ETH01-EVO + - ESP32-C3 + - DM9051 + - 1 + - 9 + - 8 + - 6 + - 7 + - 3 + - 10 + - SPI2_HOST + * - M5Stack PoECAM + - ESP32-classic + - W5500 + - + - 4 + - + - + - 23 + - 38 + - 13 + - SPI2_HOST + * - M5Stack Atom PoE Kit (ATOM LITE) + - ESP32-classic + - W5500 + - + - 19 + - + - + - 22 + - 23 + - 33 + - SPI2_HOST + * - M5Stack Atom PoE Kit (AtomS3) + - ESP32-S3 + - W5500 + - + - 6 + - + - + - 5 + - 7 + - 8 + - SPI2_HOST + * - M5Stack Base LAN (End-of-life) + - M5Core + - W5500 + - + - 26 + - 34 + - 13 + - 18 + - 19 + - 23 + - SPI2_HOST + * - M5Stack LAN (PoE) BASE V12 + - M5Core + - W5500 + - + - 26 + - 34 + - 13 + - 18 + - 19 + - 23 + - SPI2_HOST + * - M5Stack LAN Module 13.2 + - M5Core + - W5500 + - + - 5/15 + - 35/34 + - 0/13 + - 18 + - 19 + - 23 + - SPI2_HOST + * - M5Stack LAN Module 13.2 + - M5Core2 + - W5500 + - + - 33/2 + - 35/34 + - 0/19 + - 18 + - 38 + - 23 + - SPI2_HOST + * - M5Stack LAN Module 13.2 + - M5CoreS3 + - W5500 + - + - 1/13 + - 10/14 + - 0/7 + - 36 + - 35 + - 37 + - SPI2_HOST + * - T-ETH-Lite-ESP32S3 + - ESP32-S3 + - W5500 + - 1 + - 9 + - 13 + - 14 + - 10 + - 11 + - 12 + - SPI2_HOST + + +See: + +* `M5 Stack Base LAN `_ +* `M5 Stack LAN Base V12 `_ +* `M5 Stack PoECAM `_ +* `M5 Stack LAN PoE Base V12 `_ +* `M5 Stack LAN Module V13.2 `_ +* `M5 Stack ATOM PoE `_ `ATOM Lite `_ `AtomS3 `_ +* `M5 Stack Base PoE `_ +* `TTGO/LilyGO Ethernet boards `_ diff --git a/docs/source/Reference/Safety.rst b/docs/source/Reference/Safety.rst index cc4c291ed..fbdf0323c 100644 --- a/docs/source/Reference/Safety.rst +++ b/docs/source/Reference/Safety.rst @@ -23,12 +23,12 @@ So these have the same hole on the side used on the TH10/16 to connect sensors. This hole may seem like an invitation to connect some external sensor to these devices, but don't be tempted to do so. -HWL8012 & CSE7766 +HLW8012 & CSE7766 ================= Some ESP8266 and ESP32 powered devices have an energy monitoring sensor on board. Well known examples are the Sonoff POW, POW r2, POW R3xx(D) and Shelly PLUG S, but there are many others. -Almost all use either the HWL8012 or CSE7766 chip. +Almost all use either the HLW8012 or CSE7766 chip. These chips are not isolated from mains power, which means all electronics connected to them does also have a direct connection to mains power lines. diff --git a/docs/source/Reference/SystemVariable.rst b/docs/source/Reference/SystemVariable.rst index c32b51bbe..4b0629a8a 100644 --- a/docs/source/Reference/SystemVariable.rst +++ b/docs/source/Reference/SystemVariable.rst @@ -139,6 +139,10 @@ More uses of these system variables can be seen in the rules section and formula - 5 (05) - Current second (ss). ``%syssec%`` omits leading zeros. - Yes + * - ``%syssec_d%`` + - 83682 + - Seconds since midnight. + - * - ``%sysday%`` (``%sysday_0%``) - 7 (07) - Current day of month (DD). ``%sysday%`` omits leading zeros. @@ -366,6 +370,48 @@ The conversion always outputs a string, but not all of these can be converted ba - Convert a (known) unit number to its IP Address. (Added: 2020/11/08) f_opt: for invalid IP: 0 = ``(IP unset)`` 1 = (empty string) 2 = ``0`` + * - Unit to Name: ``%c_uname%(%unit%)`` + - Unit to Name: ``ESP32DualR3`` + - Convert to the name of the remote unit. (Added: 2024/04/21) + * - Unit to Age: ``%c_uage%(%unit%)`` + - Unit to Age: ``11`` + - Convert to the age (last received update via P2P) of the remote unit in seconds. (Added: 2024/04/21) + + If the unit is not in the list of known nodes, then ``-1`` is returned. + * - Unit to Build: ``%c_ubuild%(%unit%)`` + - Unit to Build: ``20812`` + - Convert to the buildnr of the remote unit. (Added: 2024/04/21) + * - Unit to Build-string: ``%c_ubuildstr%(%unit%)`` + - Unit to Build-string: ``20240421`` + - Convert to the buildnr converted to date-format of the remote unit. (Added: 2024/04/21) + + The date-format for buildnrs is available since build 20200, introduced on 2022-08-18. For older builds, the actual buildnumber is returned, f.e. 20117. + * - Unit to Load: ``%c_uload%(%unit%)`` + - Unit to Load: ``27.34`` + - Convert to the load percentage of the remote unit. (Added: 2024/04/21) + * - Unit to ESP-Type: ``%c_utype%(%unit%)`` + - Unit to ESP-Type: ``33`` + - Convert to the ESP-Type (numeric) of the remote unit. (Added: 2024/04/21) + + This is the list of recognized types: + + * 1 : ESP Easy (ESP8266) + * 17 : ESP Easy Mega (ESP8266) + * 33 : ESP Easy 32 + * 34 : ESP Easy 32-S2 + * 35 : ESP Easy 32-C3 + * 36 : ESP Easy 32-S3 + * 37 : ESP Easy 32-C2 + * 38 : ESP Easy 32-H2 + * 39 : ESP Easy 32-C6 + * 5 : RPI Easy + * 65 : Arduino Easy + * 81 : Nano Easy + * - Unit to ESP-Type-string: ``%c_utypestr%(%unit%)`` + - Unit to ESP-Type-string: ``ESP Easy 32`` + - Convert to the ESP-Type (string) of the remote unit. (Added: 2024/04/21) + + See ``%c_utype%()`` for the names and numbers used. Task Formulas diff --git a/docs/source/Reference/URLs.rst b/docs/source/Reference/URLs.rst index 682c77977..8c686fe5e 100644 --- a/docs/source/Reference/URLs.rst +++ b/docs/source/Reference/URLs.rst @@ -1,89 +1,97 @@ -URLs -**** - - -JSON ----- - -A lot of information can be fetched in JSON format via the ``http:///json`` url. - -At the root of the JSON output is a value names ``TTL`` which reflects the lowest task interval of all tasks included in the output. - - -.. csv-table:: - :header: "URL", "Description" - :widths: 15, 30 - - " - ``http:///json`` - "," - Most elaborate dump of information including: - - * System - System information - * WiFi - Network/WiFi related information - * Ethernet - Network/Ethernet related information. (only when ethernet support is included in the build) - * nodes - List of known other nodes in the network (all with the same UDP port for ESPEasy p2p) - * Sensors - List of all tasks with their configured controllers, task interval and task values with their names, settings, etc. - - " - " - ``http:///json?view=sensorupdate`` - "," - All task values of all tasks and needed information to format the values. - " - " - ``http:///json?view=sensorupdate&tasknr=2`` - "," - All task values of a specific task nr and needed information to format the values. - - N.B. task nr starts at 1. - " - - - -CSV ---- - -Task values and their names can be fetched in simple CSV format via a GET url. - -N.B. task number and variable number do count starting at 0. - -.. csv-table:: - :header: "URL", "Description" - :widths: 15, 30 - - " - ``http:///csv?tasknr=1`` - "," - All values of a task with header. - - .. code-block:: html - - T;H;P - 26.08;43.10;1012.50 - " - " - ``http:///csv?tasknr=1&valnr=0`` - "," - A single value of a task with header. - - .. code-block:: html - - T; - 26.08; - " - " - ``http:///csv?tasknr=1&valnr=0&header=0`` - "," - A single value of a task without header. - - .. code-block:: html - - 26.08; - " - - - - -Control -------- +URLs +**** + + +JSON +---- + +A lot of information can be fetched in JSON format via the ``http:///json`` url. + +At the root of the JSON output is a value names ``TTL`` which reflects the lowest task interval of all tasks included in the output. + + +.. csv-table:: + :header: "URL", "Description" + :widths: 15, 30 + + " + ``http:///json`` + "," + Most elaborate dump of information including: + + * System - System information + * WiFi - Network/WiFi related information + * Ethernet - Network/Ethernet related information. (only when ethernet support is included in the build) + * nodes - List of known other nodes in the network (all with the same UDP port for ESPEasy p2p) + * Sensors - List of all tasks with their configured controllers, task interval and task values with their names, settings, etc. + + " + " + ``http:///json?view=sensorupdate`` + "," + All task values of all tasks and needed information to format the values. + " + " + ``http:///json?view=sensorupdate&tasknr=2`` + "," + All task values of a specific task nr and needed information to format the values. + + N.B. task nr starts at 1. + " + " + ``http:///json?view=sensorupdate&tasknr=1&showpluginstats=1`` + "," + All task values of a specific task nr and needed information to format the values, including all info needed to create ChartJS charts when ``stats`` has been enabled for that task. + + N.B. task nr starts at 1. + " + + + +CSV +--- + +Task values and their names can be fetched in simple CSV format via a GET url. + +N.B. task number and variable number do count starting at 0. + +.. csv-table:: + :header: "URL", "Description" + :widths: 15, 30 + + " + ``http:///csv?tasknr=1`` + "," + All values of a task with header. + + .. code-block:: html + + T;H;P + 26.08;43.10;1012.50 + " + " + ``http:///csv?tasknr=1&valnr=0`` + "," + A single value of a task with header. + + .. code-block:: html + + T; + 26.08; + " + " + ``http:///csv?tasknr=1&valnr=0&header=0`` + "," + A single value of a task without header. + + .. code-block:: html + + 26.08; + " + + + + +Control +------- + diff --git a/docs/source/Rules/Rules.rst b/docs/source/Rules/Rules.rst index c4db4c633..9ba78dee4 100644 --- a/docs/source/Rules/Rules.rst +++ b/docs/source/Rules/Rules.rst @@ -1969,6 +1969,65 @@ Added: 2022/07/23 * Host name can contain user credentials. For example: ``http://username:pass@hostname:portnr/foo.html`` * HTTP user credentials now can handle Basic Auth and Digest Auth. +Added: 2023/10/26 + +* ``SendToHTTP`` now generates an event with the response of a thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & // https://de.mathworks.com/help/thingspeak/readdata.html) +* There are two options: + + 1. Get the value of a single field: + + - Example command: + ``SendToHTTP,api.thingspeak.com,80,/channels/143789/fields/5/last.csv`` + - Example of the resulting event: + ``"EVENT: ThingspeakReply=143789,5,9.65"`` + + | channel number = ``%eventvalue1%`` + | field number = ``%eventvalue2%`` + | value = ``%eventvalue3%`` + + 2. Get the values of all fields: + + - Example command: + ``SendToHTTP,api.thingspeak.com,80,/channels/143789/feeds/last.csv`` + - Example of the resulting event: + ``"EVENT: ThingspeakReply=143789,11.12,9.46,9.55,16.32,9.65,8.81,-1.23,14.76"`` + + | channel number = ``%eventvalue1%`` + | values = ``%eventvalue2%`` to ``%eventvalue9%`` + + .. note:: + ``last.csv`` is mandatory! + + .. warning:: When using the command for all fields, the reply can become extremely big and can lead to memory issues which results in instabilities of your device (especially when all eight fields are filled with very big numbers) + +* Rules example: + + .. code:: none + + On System#Boot Do + SendToHTTP,api.thingspeak.com,80,/channels/143789/feeds/last.csv + Endon + + On ThinkspeakReply Do + LogEntry,'The channel number is: %eventvalue1%' + LogEntry,'%eventvalue6%°C in Berlin' + LogEntry,'%eventvalue7%°C in Paris' + Endon + +Added 2024/02/05 + +* Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) + +* Examples: + + Single channel: ``SendToHTTP,api.thingspeak.com,80,channels/143789/fields/1.csv?end=2024-01-01%2023:59:00&results=1`` + => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 143789 + + All channels: ``SendToHTTP,api.thingspeak.com,80,channels/143789/feeds.csv?end=2024-01-01%2023:59:00&results=1`` + => gets the value of each field of the channel 143789 at (or the last entry before) 23:59:00 + + .. note:: + ``csv`` and ``results=1`` are mandatory! Convert curl POST command to PostToHTTP --------------------------------------- @@ -2404,3 +2463,113 @@ This rule can be used to calculate the moving average for, f.e., a temperature s This assumes that a Controller has been configured, and the Dummy task is configured to send out its values via the controller. +Register daily working time +--------------------------- + +To register the daily time in seconds that a device is active, these rules have been developed (from the forum). + +Required device tasks: + +* Sensor (temperature in the example) +* Dummy device (named ``Dummy`` in this example, minimal 2 values, ``LoggingON`` and ``LoggingOFF``), Interval can be set to 0 + +.. code-block:: none + + On System#Boot Do + TaskValueSet,Dummy,LoggingON,1 // Make sure timer is started and Heater ON message is sent + Endon + + On DS1#Temperature Do // Check tmeperature + If %eventvalue1% < 40 + GPIO,5,0 + AsyncEvent,HeaterON=%eventvalue1% + Endif + If %eventvalue1% > 55 + GPIO,5,1 + AsyncEvent,HeaterOFF=%eventvalue1% + Endif + Endon + + On HeaterON Do // Optional 1st argument is the temperature, defaults to the value of DS1#Temperature if not provided + If [Dummy#LoggingON] = 1 + Let,1,%syssec_d% // Store current nr of seconds of today in var#1 + PostToHTTP,192.168.1.20,8080,/receiver.php,'','%lcltime% !!! Temp = %eventvalue1|[DS1#Temperature]% -> Heater ON' + TaskValueSet,Dummy,LoggingON,0 + TaskValueSet,Dummy,LoggingOFF,1 + TaskRun,Dummy + Endif + Endon + + On HeaterOFF Do // Optional 1st argument is the temperature, defaults to the value of DS1#Temperature if not provided + If [Dummy#LoggingOFF] = 1 + Let,2,[int#2]+%syssec_d%-[int#1] // Add run time to var#2 + PostToHTTP,192.168.1.20,8080,/receiver.php,'','%lcltime% !!! Temp = %eventvalue1|[DS1#Temperature]% -> Heater OFF' + TaskValueSet,Dummy,LoggingON,1 + TaskValueSet,Dummy,LoggingOFF,0 + TaskRun,Dummy + Endif + Endon + + On Clock#Time=All,00:00 Do // At midnight + // Send value of [int#2] to wherever you need it + PostToHTTP,192.168.1.20,8080,/receiver.php,'','%lcltime% !!! Total RunningTime = [int#2] Seconds' + Let,1,0 // Reset start time + Let,2,0 // Reset total counter + Endon + + +Register power used for a heater +-------------------------------- + +As a variation on the running time, we can also measure the time and calculate the total power used, as long as the used device-power is known. Parts from the above example have been re-used. + +This example uses a ``Generic - Dummy Device``, so the values can also be viewed on the Devices page. This has name: Power, output data type: Dual (or Triple or Quad, must be able to store decimals!), value names: Seconds (0 decimmals) and PowerUsed (4 decimals). + +The time is counted while GPIO-14 (D5 on a Wemos or NodeMCU ESP8266) has a low state, and power is calculated once the power goes off. The not-On state will need a pull-up resistor to pull the level to 3V3! + +After loading this code, either reboot the ESP, or run the command ``event,system#boot`` to set up the GPIO monitoring and wattage of the device. + +.. code-block:: none + + // Used variables: 1,3,4,5 + + On GPIO#14 Do // GPIO-14 = D5 on Wemos/NodeMCU ESP8266 boards + If %eventvalue1%=0 // On state + Let,1,%syssec_d% // Store current nr of seconds of today in var#1 + Else // Off state + Event,CalcPower // Don't queue + Event,TransmitPower // Send out to receiver + Endif + Let,5,!%eventvalue1% // 0 = On, to invert on/off state change to: Let,5,%eventvalue1% + LogEntry,"Power [int#5#O#C], measured: [Power#Seconds] sec. [Power#PowerUsed#d.4] kWh" + Endon + + On CalcPower Do + TaskValueSet,Power,Seconds,[Power#Seconds]+%syssec_d%-[int#1] // Add run time to Power#Seconds + Let,4,[Power#Seconds]*[var#3] // Wattseconds + If [var#4]>0 + TaskValueSet,Power,PowerUsed,[var#4]/3600000 // Wattseconds to kWh + Endif + TaskRun,Power + Endon + + On TransmitPower Do + // Send value of [Power#Seconds] and [Power#PowerUsed] to wherever you need it, adjust as needed + PostToHTTP,192.168.1.20,8080,/receiver.php,'','%lcltime% !!! Total RunningTime = [Power#Seconds] Seconds, PowerUsed = [Power#PowerUsed] kWh' + Endon + + On Clock#Time=All,00:00 Do // At midnight + // Include power used until midnight + If [Plugin#GPIO#PinState#14]=0 // Still on? + Event,CalcPower // Don't queue + Endif + Let,1,0 // Reset start time + Event,TransmitPower // Send out remainder of the day + TaskValueSet,Power,Seconds,0 // Reset total counter + TaskValueSet,Power,PowerUsed,0 // Reset total power + Endon + + On System#Boot Do + Monitor,gpio,14 // Generate an event when the GPIO state changes + Let,3,250 // Wattage of the load, adjust as needed + Endon diff --git a/docs/source/Tools/Tools.rst b/docs/source/Tools/Tools.rst index 8f4a415fa..3d90d4138 100644 --- a/docs/source/Tools/Tools.rst +++ b/docs/source/Tools/Tools.rst @@ -233,9 +233,7 @@ Rules Settings -------------- * Rules - Check to enable rules functionality (on next page load, extra Rules tab will appear) -* Old Engine - Default checked. * Enable Rules Cache - Rules cache will keep track of where in the rules files each ``on ... do`` block is located. This significantly improves the time it takes to handle events. (Enabled by default, Added 2022/04/17) -* Allow Rules Event Reorder - It is best to have the rules blocks for the most frequently occuring events placed at the top of the first rules file. (also for frequently happening events, which you don't want to act on) The cached event positions can be reordered in memory based on how often an event was matched. (Enabled by default, Added 2022/04/17, disabled 2022/06/24) * Tolerant last parameter - When checked, the last parameter of a command will have less strict parsing. * SendToHTTP wait for ack - When checked, the command SendToHTTP will wait for an acknowledgement from the server. * SendToHTTP Follow Redirects - When checked, HTTP calls may follow redirects. Strict RFC2616, only requests using GET or HEAD methods will be redirected (using the same method), since the RFC requires end-user confirmation in other cases. @@ -469,6 +467,19 @@ Added: 2023-07-20 When Rules auto-completion, also including syntax highlighting, is available in the build, some users have difficulty working with the auto-completion. This option disables the auto-completion, and that also inhibits the syntax highlighting as these 2 features are closely integrated. +Disable Save Config as .tar +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Added: 2023-08-25 + +Only available in builds that have .tar support included! + +By default, using the Tools/Save button, the complete configuration will be downloaded as a single .tar archive, that includes all configuration files (``config.dat``, ``security.dat``, ``provisioning.dat``, ``notification.dat``, ``rules1.txt`` .. ``rules4.txt`` and any task-specific CustomSettings ``extcfg.dat``). + +Enabling this option allows to download *only* the ``config.dat`` file (renamed to include unit name, unit number, buildnumber and current date/time), to accommodate external systems/scripts that expect only the .dat file. + +The Tools/Backup files feature will still download all files stored on the Flash file system, independent of this setting, and also the Tools/Load and Tools/File browser/Upload buttons will extract the included files from an uploaded .tar file. + Deep Sleep Alternative ^^^^^^^^^^^^^^^^^^^^^^ @@ -888,6 +899,8 @@ Then it does not make sense to have the client timeout of that controller set to System Variables ================ +Interfaces +========== I2C Scan ======== @@ -910,6 +923,33 @@ Example scan using an I2C multiplexer, showing multiple devices across multiple .. note:: On builds that have ``LIMIT_BUILD_SIZE`` set, like the ESP8266 Collection and Display builds, the names of the supported devices and plugins are **not** included in the output, only the address(es) are listed. +Settings +======== + +The :cyan:`Load` button will allow to load files onto the Flash file system. If you want to restore a previously saved ``config.dat``, the downloaded file has to be renamed to exactly ``config.dat`` and uploaded. + +Since 2023-08-25, .tar archive support has been added and made available in most builds, allowing to download and upload the complete configuration, and even all files on the flash file system, as a single archive, for backup and restore/clone purposes. This makes it possible to more easily deploy a unit using a pre-configured configuration. + +Uploading an earlier created backup as a .tar file, will unpack all files in the root of the archive to the flash file system, *overwriting* any files that already exist. If the archive includes ``config.dat`` and the Extended CustomTaskSettings feature is available, any already existing ``extcfg.dat`` file that's not included in the archive will be removed, as that is part of the configuration, and these files can not be deleted manually. + +Any files in subdirectories in the archive will be ignored, as directories are not supported on the flash filesystem. + +The :cyan:`Save` button offers to download the configuration of the unit. If .tar file support is included in the build, by default all configuration files (``config.dat``, ``security.dat``, ``provisioning.dat``, ``notification.dat``, ``rules1.txt`` .. ``rules4.txt`` and any task-specific CustomSettings ``extcfg.dat``) will be included, if they exist, in the .tar archive that can be downloaded. + +If .tar file support is not included, or the Tools/Advanced option **Disable Save Config as .tar** is enabled, only the ``config.dat`` file will be downloaded. + +The :cyan:`Backup files` button is only available if .tar file support is included in the build, and offers to download a .tar archive containing all files on the flash file system. These can be stored as a backup and restored in case of some configuration or system failure, or used to create 1 or multiple clones of the unit for multi-deployment. Uploading can also be started from an automation system or script, POST-ing the .tar archive from an external source. + +Firmware update +=============== + +Via the :cyan:`Update Firmware` button, you can browse for an updated firmware, downloaded from the Releases page, an Actions run, or self-built, and install that. When using the same flash configuration (``4M1M``, ``4M316k``, ``8M1M``, etc.) all settings will be preserved. When uncertain, the configuration can be saved using either the Save (or Backup files if available) button above. + +File system +=========== + +Via :cyan:`File browser` you can browse the files on the flash file system, download them separately, upload additional files, or delete any non-system files. + Factory Reset ============= diff --git a/docs/source/_static/css/custom.css b/docs/source/_static/css/custom.css index 1f3858aaf..963a46bd5 100644 --- a/docs/source/_static/css/custom.css +++ b/docs/source/_static/css/custom.css @@ -63,6 +63,10 @@ .menuselection { font-weight: bold; } +summary { + font-weight: bold; + cursor: pointer; +} img { max-width: 100%; height: auto; @@ -100,7 +104,7 @@ h4 { color: #FFFFFF; } td, th { - border-bottom: : 1px solid #ddd; + border-bottom: 1px solid #ddd; padding: 8px; } tr:nth-child(even){background-color: #F2F2F2;} diff --git a/docs/source/conf.py b/docs/source/conf.py index 20f1bd87d..077093241 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-2023, ESP Easy' +copyright = u'2018-2024, ESP Easy' author = u'Grovkillen, TD-er & Friends' # The short X.Y version @@ -45,7 +45,8 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.imgmath', 'sphinx.ext.imgconverter', - 'recommonmark' + 'recommonmark', + 'sphinx_toolbox.collapse', ] # Add any paths that contain templates here, relative to this directory. @@ -69,7 +70,7 @@ master_doc = 'index' # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. @@ -179,7 +180,10 @@ html_theme_options = { # Choose Bootstrap version. # Values: "3" (default) or "2" (in quotes) - 'bootstrap_version': "3", + 'bootstrap_version': "5", + + # Disable showing the sidebar. Defaults to 'false' + 'nosidebar': True, } # Add any paths that contain custom static files (such as style sheets) here, @@ -278,7 +282,7 @@ epub_exclude_files = ['search.html'] # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'https://docs.python.org/3/': None} +intersphinx_mapping = { 'ESPEasy': ('https://docs.python.org/3/', None) } # -- Options for todo extension ---------------------------------------------- diff --git a/lib/AS3935MI/README.md b/lib/AS3935MI/README.md new file mode 100644 index 000000000..05428435c --- /dev/null +++ b/lib/AS3935MI/README.md @@ -0,0 +1,84 @@ +# Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +home: https://bitbucket.org/christandlg/as3935mi +sensor: https://ams.com/as3935 + +## Features: + - Supports I2C and SPI via the Wire and SPI libraries, respectively + - Supports I2C and SPI interfaces via other libraries (e.g. Software I2C) by inheritance + - Automatic antenna tuning + +## Changelog: +- 1.3.5 + - fixed #50 + - implemented a more robust, interrupt based calibration procedure that is also faster. thanks to @td-er for reporting and impementing this. + - updated increase / decrease function signatures (should be backwards compatible) + - updated examples + +- 1.3.4 + - partially fixed https://bitbucket.org/christandlg/as3935mi/issues/50/resonance-frequency-calibration-inaccurate : fixed a bug where occasionally I2C comms will silently fail during oscillator calibration - thanks to @td-er for reporting and fixing this issue + +- 1.3.3 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/49/class-spiclass-has-no-member-named + +- 1.3.2 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/47/need-help-using-as3935spiclass + +- 1.3.1 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/48/clear-statistics-function-to-be-added + +- 1.3.0 + - fixed https://bitbucket.org/christandlg/as3935mi/issues/12/autocalibrate-no-longer-working + +- 1.2.1 + - Merged PR by Hernán Freschi https://bitbucket.org/christandlg/as3935mi/pull-requests/2 + +- 1.2.0 + - extended examples to include increasing sensitivity if no disturbances are detected. + +- 1.1.1 + - fixed an issue where ESP8266 would crash with message "ISR not in IRAM" + +- 1.1.0 + - extended function calibrateResonanceFrequency() to return the resonance frequency of the antenna + +- 1.0.0 + - added more values for watchdog threshold and spike rejection ratio settings + - fixed an incorrect function name + - added missing function names to keywords.txt + +- 0.5.0 + - added new classes AS3935TwoWire and AS3935SPIClass for TwoWire and SPIClass interfaces + - moved AS3935I2C and AS3935SPI classes into their own respecitve source files, further separating data processing from communications + - when updating from an earlier version and using the AS3935I2C or AS3935SPI classes, change ```#include ``` to ```#include ``` or ```#include ```, respectively + - added examples for AS3935TwoWire and AS3935SPIClass + +- 0.4.1 + - fixed an issue where checkIRQ() causes a deadlock on Arduino Nano + +- 0.4.0 + - added functions to increase / decrease noise floor threshold + - added functions to increase / decrease watchdog threshold + - added functions to increase / decrease spike rejection ratio + - added function to check communication to sensor + - added function to check IRQ pin assignment + +- 0.3.0 + - derived classes must now implement function beginInterface() instead of begin() + +- 0.2.0 + - split code into 3 classes - AS3935MI, AS3935I2C, AS3935SPI + - users can now implement classes derived from AS3935MI easily + - writeRegister is nur used to send direct commands + - updated examples + - added arduino due I2C issue workaround + - minor fixes + - derived + +- 0.1.2 + - renamed library + +- 0.1.1 + - added license information + +- 0.1.0 + - initial release \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino new file mode 100644 index 000000000..9a300fdbf --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_I2C/AS3935MI_LightningDetector_I2C.ino @@ -0,0 +1,264 @@ +// AS3935MI_LightningDetector_I2C.ino +// +// shows how to use the AS3935MI library with the lightning sensor connected using I2C. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA ----- MOSI +// SCL ----- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#ifdef D1 +#define PIN_IRQ D1 +#else +#define PIN_IRQ 2 +#endif + +//create an AS3935 object using the I2C interface, I2C address 0x01 and IRQ pin number 2 +AS3935I2C as3935(AS3935I2C::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + +#if defined(ESP8266) + Wire.begin(D2, D3); +#else + Wire.begin(); //for Arduino boards +#endif + + //begin() checks the Interface and I2C Address passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935I2C constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + uint8_t division_ratio = AS3935MI::AS3935_DR_16; + if (F_CPU < 48000000) //fixes https://bitbucket.org/christandlg/as3935mi/issues/12/autocalibrate-no-longer-working + division_ratio = AS3935MI::AS3935_DR_64; + + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency, division_ratio)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + // while (1); + } + else + { + Serial.println("Resonance Frequency Calibration passed. Resonance Frequency is "); + Serial.print(frequency); + Serial.println(" Hz"); + } + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCO Calibration failed. "); + while (1); + } + else + Serial.println("RCP Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino new file mode 100644 index 000000000..b030c6192 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPI/AS3935MI_LightningDetector_SPI.ino @@ -0,0 +1,251 @@ +// AS3935_LightningDetector_SPI.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using SPI. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// MOSI ---- MOSI +// MISO ---- MISO +// SCK ----- SCK +// GND ----- SI (activates SPI for the AS3935) +// D3 ------ CS chip select pin for AS3935 +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 3 +#define PIN_CS 4 + +//create an AS3935 object using the SPI interface, chip select pin 4 and IRQ pin number 3 +AS3935SPI as3935(PIN_CS, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + SPI.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. check your AS3935 Interface setting."); + while (1); + } + + //check SPI connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your SPI connection and SPI chip select pin. "); + while (1); + } + else + Serial.println("SPI connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935SPI constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino new file mode 100644 index 000000000..5198d02f0 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_SPIClass/AS3935MI_LightningDetector_SPIClass.ino @@ -0,0 +1,252 @@ +// AS3935_LightningDetector_SPIClass.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using an interface that inherits from SPIClass. +// in this example, the SPI object is used, but other objects that inherit from SPIClass may be used instead. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// MOSI ---- MOSI +// MISO ---- MISO +// SCK ----- SCK +// GND ----- SI (activates SPI for the AS3935) +// D3 ------ CS chip select pin for AS3935 +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 3 +#define PIN_CS 4 + +//create an AS3935 object using the SPI interface, chip select pin 4 and IRQ pin number 3 +//the SPIClass object is passed by reference +AS3935SPIClass as3935(&SPI, PIN_CS, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + SPI.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. check your AS3935 Interface setting."); + while (1); + } + + //check SPI connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your SPI connection and SPI chip select pin. "); + while (1); + } + else + Serial.println("SPI connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935SPI constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino new file mode 100644 index 000000000..108a654c6 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_TwoWire/AS3935MI_LightningDetector_TwoWire.ino @@ -0,0 +1,249 @@ +// AS3935MI_LightningDetector_TwoWire.ino +// +// shows how to use the AS3935 library with the lightning sensor connected using an interface that inherits from TwoWire. +// in this example, the Wire object is used, but other objects that inherit from TwoWire may be used instead. +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino like this: +// +// Arduino - AS3935 +// 5V ------ VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA ----- MOSI +// SCL ----- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include +#include + +#include + +#define PIN_IRQ 2 + +//create an AS3935 object using the I2C interface, I2C address 0x01 and IRQ pin number 2 +AS3935TwoWire as3935(&Wire, AS3935TwoWire::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + Wire.begin(); + + //begin() checks the Interface and I2C Address passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935I2C constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCO Calibration failed. "); + while (1); + } + else + Serial.println("RCP Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino b/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino new file mode 100644 index 000000000..bc1c1a633 --- /dev/null +++ b/lib/AS3935MI/examples/AS3935MI_LightningDetector_otherInterfaces/AS3935MI_LightningDetector_otherInterfaces.ino @@ -0,0 +1,327 @@ +// AS3935_LightningDetector_otherInterfaces.ino +// +// shows how to use the AS3935 library with interfaces that are not derived from TwoWire or SPIClass. +// here, the second I2C port of an Arduino Due is used (Wire1) +// +// Copyright (c) 2018-2019 Gregor Christandl +// +// connect the AS3935 to the Arduino Due like this: +// +// Arduino - AS3935 +// 3.3V ---- VCC +// GND ----- GND +// D2 ------ IRQ must be a pin supporting external interrupts, e.g. D2 or D3 on an Arduino Uno. +// SDA1 ---- MOSI +// SCL1 ---- SCL +// 5V ------ SI (activates I2C for the AS3935) +// 5V ------ A0 (sets the AS3935' I2C address to 0x01) +// GND ----- A1 (sets the AS3935' I2C address to 0x01) +// 5V ------ EN_VREG !IMPORTANT when using 5V Arduinos (Uno, Mega2560, ...) +// other pins can be left unconnected. + +#include + +#include + +#include + +#define PIN_IRQ 2 + +//class derived from AS3935MI that implements communication via an interface other than native I2C or SPI. +class AS3935Wire1 : public AS3935MI +{ + public: + enum I2C_address_t : uint8_t + { + AS3935I2C_A01 = 0b01, + AS3935I2C_A10 = 0b10, + AS3935I2C_A11 = 0b11 + }; + + //constructor of the derived class. in this case, only 2 parameters are needed + //@param address i2c address of the sensor. + //@param irq input pin the sensors irq pin is connected to. this parameter is passed to the constructor of the parent class (AS3935MI) + AS3935Wire1(uint8_t address, uint8_t irq) : + AS3935MI(irq), //AS3935MI does not have a default constructor therefore the constructor must be called explicitly. it takes the irq pin number as an argument. + address_(address) //initialize the AS3935Wire1 classes private member address_ to the i2c address provided + { + //nothing else to do here... + } + + //this function must be implemented by derived classes. it is used to initialize the interface. + //@return true if the interface was initializes successfully, false otherwise. + bool beginInterface() + { + //check if a valid i2c address for AS3935 lightning sensors has been provided. + switch (address_) + { + case 0x01: + case 0x02: + case 0x03: + break; //exit the switch statement + default: + //return false if an invalid I2C address was given. + return false; + } + + return true; + } + + private: + //this function must be implemented by derived classes. this function is responsible for reading data from the sensor. + //@param reg register to read. + //@return read data (1 byte). + uint8_t readRegister(uint8_t reg) + { + #if defined(ARDUINO_SAM_DUE) + //workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this + //undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560) + //see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0 + Wire1.requestFrom(address_, 1, reg, 1, true); + #else + Wire1.beginTransmission(address_); + Wire1.write(reg); + Wire1.endTransmission(false); + Wire1.requestFrom(address_, static_cast(1)); + #endif + + return Wire1.read(); + } + + //this function must be implemented by derived classes. this function is responsible for sending data to the sensor. + //@param reg register to write to. + //@param data data to write to register. + void writeRegister(uint8_t reg, uint8_t data) + { + Wire1.beginTransmission(address_); + Wire1.write(reg); + Wire1.write(data); + Wire1.endTransmission(); + } + + uint8_t address_; //i2c address of sensor +}; + +//create an AS3935 object using the Wire1 interface, I2C address 0x01 and IRQ pin number 2 +AS3935Wire1 as3935(AS3935Wire1::AS3935I2C_A01, PIN_IRQ); + +//this value will be set to true by the AS3935 interrupt service routine. +volatile bool interrupt_ = false; + +constexpr uint32_t SENSE_INCREASE_INTERVAL = 15000; //15 s sensitivity increase interval +uint32_t sense_adj_last_ = 0L; //time of last sensitivity adjustment + +void setup() { + // put your setup code here, to run once: + Serial.begin(9600); + + //wait for serial connection to open (only necessary on some boards) + while (!Serial); + + //set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + //high if an event is registered. + pinMode(PIN_IRQ, INPUT); + + Wire1.begin(); + + //begin() checks the Interface passed to the constructor and resets the AS3935 to + //default values. + if (!as3935.begin()) + { + Serial.println("begin() failed. Check the I2C address passed to the AS3935I2C constructor. "); + while (1); + } + + //check I2C connection. + if (!as3935.checkConnection()) + { + Serial.println("checkConnection() failed. check your I2C connection and I2C Address. "); + while (1); + } + else + Serial.println("I2C connection check passed. "); + + //check the IRQ pin connection. + if (!as3935.checkIRQ()) + { + Serial.println("checkIRQ() failed. check if the correct IRQ pin was passed to the AS3935Wire1 constructor. "); + while (1); + } + else + Serial.println("IRQ pin connection check passed. "); + + //calibrate the resonance frequency. failing the resonance frequency could indicate an issue + //of the sensor. resonance frequency calibration will take about 1.7 seconds to complete. + int32_t frequency = 0; + if (!as3935.calibrateResonanceFrequency(frequency)) + { + Serial.print("Resonance Frequency Calibration failed: is "); + Serial.print(frequency); + Serial.println(" Hz, should be 482500 Hz - 517500 Hz"); + //while (1); + } + else + Serial.println("Resonance Frequency Calibration passed. "); + + Serial.print("Resonance Frequency is "); Serial.print(frequency); Serial.println(" Hz"); + + + //calibrate the RCO. + if (!as3935.calibrateRCO()) + { + Serial.println("RCP Calibration failed. "); + while (1); + } + else + Serial.println("RCO Calibration passed. "); + + //set the analog front end to 'indoors' + as3935.writeAFE(AS3935MI::AS3935_INDOORS); + + //set default value for noise floor threshold + as3935.writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + + //set the default Watchdog Threshold + as3935.writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + + //set the default Spike Rejection + as3935.writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + //write default value for minimum lightnings (1) + as3935.writeMinLightnings(AS3935MI::AS3935_MNL_1); + + //do not mask disturbers + as3935.writeMaskDisturbers(false); + + //the AS3935 will pull the interrupt pin HIGH when an event is registered and will keep it + //pulled high until the event register is read. + attachInterrupt(digitalPinToInterrupt(PIN_IRQ), AS3935ISR, RISING); + + Serial.println("Initialization complete, waiting for events..."); +} + +void loop() { + // put your main code here, to run repeatedly: + + if (interrupt_) + { + //the Arduino should wait at least 2ms after the IRQ pin has been pulled high + delay(2); + + //reset the interrupt variable + interrupt_ = false; + + //query the interrupt source from the AS3935 + uint8_t event = as3935.readInterruptSource(); + + //send a report if the noise floor is too high. + if (event == AS3935MI::AS3935_INT_NH) + { + Serial.println("Noise floor too high. attempting to increase noise floor threshold. "); + + //if the noise floor threshold setting is not yet maxed out, increase the setting. + //note that noise floor threshold events can also be triggered by an incorrect + //analog front end setting. + if (as3935.increaseNoiseFloorThreshold() == AS3935MI::AS3935_NFL_0) + Serial.println("noise floor threshold already at maximum"); + else + Serial.println("increased noise floor threshold"); + } + + //send a report if a disturber was detected. if disturbers are masked with as3935.writeMaskDisturbers(true); + //this event will never be reported. + else if (event == AS3935MI::AS3935_INT_D) + { + Serial.println("Disturber detected, attempting to increase noise floor threshold. "); + + //increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + //against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth < AS3935MI::AS3935_WDTH_10) || (srej < AS3935MI::AS3935_SREJ_10)) + { + sense_adj_last_ = millis(); + + //alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (as3935.increaseSpikeRejection() == AS3935MI::AS3935_SREJ_0) + Serial.println("spike rejection ratio already at maximum"); + else + Serial.println("increased spike rejection ratio"); + } + else + { + if (as3935.increaseWatchdogThreshold() == AS3935MI::AS3935_WDTH_0) + Serial.println("watchdog threshold already at maximum"); + else + Serial.println("increased watchdog threshold"); + } + } + else + { + Serial.println("error: Watchdog Threshold and Spike Rejection settings are already maxed out."); + } + } + + else if (event == AS3935MI::AS3935_INT_L) + { + Serial.print("Lightning detected! Storm Front is "); + Serial.print(as3935.readStormDistance()); + Serial.println("km away."); + } + } + + //increase sensor sensitivity every once in a while. SENSE_INCREASE_INTERVAL controls how quickly the code + //attempts to increase sensitivity. + if (millis() - sense_adj_last_ > SENSE_INCREASE_INTERVAL) + { + sense_adj_last_ = millis(); + + Serial.println("No disturber detected, attempting to decrease noise floor threshold. "); + + uint8_t wdth = as3935.readWatchdogThreshold(); + uint8_t srej = as3935.readSpikeRejection(); + + if ((wdth > AS3935MI::AS3935_WDTH_0) || (srej > AS3935MI::AS3935_SREJ_0)) + { + + //alternatively derease spike rejection and watchdog threshold + if (srej > wdth) + { + if (as3935.decreaseSpikeRejection()) + Serial.println("decreased spike rejection ratio"); + else + Serial.println("spike rejection ratio already at minimum"); + } + else + { + if (as3935.decreaseWatchdogThreshold()) + Serial.println("decreased watchdog threshold"); + else + Serial.println("watchdog threshold already at minimum"); + } + } + } +} + + +//interrupt service routine. this function is called each time the AS3935 reports an event by pulling +//the IRQ pin high. +#if defined(ESP32) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#elif defined(ESP8266) +ICACHE_RAM_ATTR void AS3935ISR() +{ + interrupt_ = true; +} +#else +void AS3935ISR() +{ + interrupt_ = true; +} +#endif \ No newline at end of file diff --git a/lib/AS3935MI/keywords.txt b/lib/AS3935MI/keywords.txt new file mode 100644 index 000000000..6391acd28 --- /dev/null +++ b/lib/AS3935MI/keywords.txt @@ -0,0 +1,128 @@ +####################################### +# Syntax Coloring Map For ExampleLibrary +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +AS3935MI KEYWORD1 +AS3935I2C KEYWORD1 +AS3935SPI KEYWORD1 +AS3935TwoWire KEYWORD1 +AS3935SPIClass KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +checkConnection KEYWORD2 +checkIRQ KEYWORD2 +clearstatistics KEYWORD2 +readStormDistance KEYWORD2 +readInterruptSource KEYWORD2 +readPowerDown KEYWORD2 +writePowerDown KEYWORD2 +readMaskDisturbers KEYWORD2 +writeMaskDisturbers KEYWORD2 +readAFE KEYWORD2 +writeAFE KEYWORD2 +readNoiseFloorThreshold KEYWORD2 +writeNoiseFloorThreshold KEYWORD2 +increaseNoiseFloorThreshold KEYWORD2 +decreaseNoiseFloorThreshold KEYWORD2 +readWatchdogThreshold KEYWORD2 +writeWatchdogThreshold KEYWORD2 +increaseWatchdogThreshold KEYWORD2 +decreaseWatchdogThreshold KEYWORD2 +readSpikeRejection KEYWORD2 +writeSpikeRejection KEYWORD2 +increaseSpikeRejection KEYWORD2 +decreaseSpikeRejection KEYWORD2 +readEnergy KEYWORD2 +readAntennaTuning KEYWORD2 +writeAntennaTuning KEYWORD2 +readDivisionRatio KEYWORD2 +writeDivisionRatio KEYWORD2 +readMinLightnings KEYWORD2 +writeMinLightnings KEYWORD2 +resetToDefaults KEYWORD2 +calibrateRCO KEYWORD2 +calibrateResonanceFrequency KEYWORD2 +readRegister KEYWORD2 +writeRegister KEYWORD2 + +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) + +AS3935_INDOORS LITERAL1 +AS3935_OUTDOORS LITERAL1 + +AS3935_INT_NH LITERAL1 +AS3935_INT_D LITERAL1 +AS3935_INT_L LITERAL1 + +AS3935_WDTH_0 LITERAL1 +AS3935_WDTH_1 LITERAL1 +AS3935_WDTH_2 LITERAL1 +AS3935_WDTH_3 LITERAL1 +AS3935_WDTH_4 LITERAL1 +AS3935_WDTH_5 LITERAL1 +AS3935_WDTH_6 LITERAL1 +AS3935_WDTH_7 LITERAL1 +AS3935_WDTH_8 LITERAL1 +AS3935_WDTH_9 LITERAL1 +AS3935_WDTH_10 LITERAL1 +AS3935_WDTH_11 LITERAL1 +AS3935_WDTH_12 LITERAL1 +AS3935_WDTH_13 LITERAL1 +AS3935_WDTH_14 LITERAL1 +AS3935_WDTH_15 LITERAL1 + +AS3935_SREJ_0 LITERAL1 +AS3935_SREJ_1 LITERAL1 +AS3935_SREJ_2 LITERAL1 +AS3935_SREJ_3 LITERAL1 +AS3935_SREJ_4 LITERAL1 +AS3935_SREJ_5 LITERAL1 +AS3935_SREJ_6 LITERAL1 +AS3935_SREJ_7 LITERAL1 +AS3935_SREJ_8 LITERAL1 +AS3935_SREJ_9 LITERAL1 +AS3935_SREJ_10 LITERAL1 +AS3935_SREJ_11 LITERAL1 +AS3935_SREJ_12 LITERAL1 +AS3935_SREJ_13 LITERAL1 +AS3935_SREJ_14 LITERAL1 +AS3935_SREJ_15 LITERAL1 + +AS3935_NFL_0 LITERAL1 +AS3935_NFL_1 LITERAL1 +AS3935_NFL_2 LITERAL1 +AS3935_NFL_3 LITERAL1 +AS3935_NFL_4 LITERAL1 +AS3935_NFL_5 LITERAL1 +AS3935_NFL_6 LITERAL1 +AS3935_NFL_7 LITERAL1 + +AS3935_MNL_1 LITERAL1 +AS3935_MNL_5 LITERAL1 +AS3935_MNL_9 LITERAL1 +AS3935_MNL_16 LITERAL1 + +AS3935_DR_16 LITERAL1 +AS3935_DR_32 LITERAL1 +AS3935_DR_64 LITERAL1 +AS3935_DR_128 LITERAL1 + +AS3935I2C_A01 LITERAL1 +AS3935I2C_A10 LITERAL1 +AS3935I2C_A11 LITERAL1 + +AS3935_DST_OOR LITERAL1 +####################################### \ No newline at end of file diff --git a/lib/AS3935MI/library.json b/lib/AS3935MI/library.json new file mode 100644 index 000000000..f15df8c85 --- /dev/null +++ b/lib/AS3935MI/library.json @@ -0,0 +1,19 @@ +{ + "name": "AS3935MI", + "version": "1.3.5", + "keywords": "AS3935", + "description": "A library for the ams AS3935 lightning sensor. The library supports both the SPI (via the SPI Library) and I2C (via the Wire Library) interfaces. Use of other I2C / SPI libraries (e.g. software I2C) is supported by inheritance. ", + "authors": + { + "name": "Gregor Christandl", + "email": "christandlg@yahoo.com", + "url": "https://bitbucket.org/christandlg/as3935mi" + }, + "repository": + { + "type": "git", + "url": "https://bitbucket.org/christandlg/as3935mi.git" + }, + "frameworks": "arduino", + "platforms": "*" +} \ No newline at end of file diff --git a/lib/AS3935MI/library.properties b/lib/AS3935MI/library.properties new file mode 100644 index 000000000..9a04cf66a --- /dev/null +++ b/lib/AS3935MI/library.properties @@ -0,0 +1,10 @@ +name=AS3935MI +version=1.3.5 +author=Gregor Christandl +maintainer=Gregor Christandl +sentence=A library for the Austria Microsystems AS3935 Franklin Lightning Detector, supporting I2C and SPI interfaces. +paragraph=The library supports both the SPI (via the SPI Library) and I2C (via the Wire Library) interfaces. Use of other I2C / SPI libraries (e.g. software I2C) is supported by inheritance. +category=Sensors +url=https://bitbucket.org/christandlg/as3935mi/ +architectures=* +includes= \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935I2C.cpp b/lib/AS3935MI/src/AS3935I2C.cpp new file mode 100644 index 000000000..e071e00c6 --- /dev/null +++ b/lib/AS3935MI/src/AS3935I2C.cpp @@ -0,0 +1,28 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935I2C.h" + +AS3935I2C::AS3935I2C(uint8_t address, uint8_t irq) : + AS3935TwoWire(&Wire, address, irq) +{ +} + +AS3935I2C::~AS3935I2C() +{ +} diff --git a/lib/AS3935MI/src/AS3935I2C.h b/lib/AS3935MI/src/AS3935I2C.h new file mode 100644 index 000000000..8c42899f9 --- /dev/null +++ b/lib/AS3935MI/src/AS3935I2C.h @@ -0,0 +1,32 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935I2C_H_ +#define AS3935I2C_H_ + +#include "AS3935TwoWire.h" + +class AS3935I2C : + public AS3935TwoWire +{ +public: + AS3935I2C(uint8_t address, uint8_t irq); + virtual ~AS3935I2C(); +}; + +#endif /* AS3935I2C_H_ */ diff --git a/lib/AS3935MI/src/AS3935MI.cpp b/lib/AS3935MI/src/AS3935MI.cpp new file mode 100644 index 000000000..4358460c7 --- /dev/null +++ b/lib/AS3935MI/src/AS3935MI.cpp @@ -0,0 +1,877 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935 +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. + +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935MI.h" + + +#ifdef ESP8266 +#define getMicros64 micros64 +#elif defined(ESP32) +#define getMicros64 esp_timer_get_time +#else +#define getMicros64 micros +#endif + + +// When we can't use attachInterruptArg to directly access volatile members, +// we must use static variables in the .cpp file +#ifndef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + AS3935MI_VOLATILE_TYPE interrupt_timestamp_ = 0; + AS3935MI_VOLATILE_TYPE interrupt_count_ = 0; + + // Store the time micros as 32-bit int so it can be stored and comprared as an atomic operation. + // Expected duration will be much less than 2^32 usec, thus overflow isn't an issue here + AS3935MI_VOLATILE_TYPE calibration_start_micros_ = 0; + AS3935MI_VOLATILE_TYPE calibration_end_micros_ = 0; + + uint32_t nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; +#endif + +AS3935MI::AS3935MI(uint8_t irq) : + irq_(irq), + tuning_cap_cache_(0), + mode_(AS3935MI::AS3935_INTERRUPT_UNINITIALIZED), + calibration_mode_edgetrigger_trigger_(AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER), + calibration_mode_division_ratio_(AS3935MI_LCO_DIVISION_RATIO), + calibrated_ant_cap_(-1), + calibrate_all_ant_cap_(true) +{ + // Setup these in the constructor body as these might not be a member + // if AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION is not defined. + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + + calibration_start_micros_ = 0; + calibration_end_micros_ = 0; + + nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; + + pinMode(irq_, INPUT); +} + +AS3935MI::~AS3935MI() +{ + if (mode_ == AS3935MI::AS3935_INTERRUPT_NORMAL || + mode_ == AS3935MI::AS3935_INTERRUPT_CALIBRATION) { + detachInterrupt(irq_); + } +} + +bool AS3935MI::begin() +{ + if (!beginInterface()) + return false; + + writePowerDown(false); + + setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + resetToDefaults(); + + return true; +} + +uint8_t AS3935MI::readStormDistance() +{ + return readRegisterValue(AS3935_REGISTER_DISTANCE, AS3935_MASK_DISTANCE); +} + +uint8_t AS3935MI::readInterruptSource() +{ + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + return readRegisterValue(AS3935_REGISTER_INT, AS3935_MASK_INT); +} + +bool AS3935MI::readPowerDown() +{ + return (readRegisterValue(AS3935_REGISTER_PWD, AS3935_MASK_PWD) == 1 ? true : false); +} + +void AS3935MI::writePowerDown(bool enabled) +{ + writeRegisterValue(AS3935_REGISTER_PWD, AS3935_MASK_PWD, enabled ? 1 : 0); + if (!enabled) { + delayMicroseconds(AS3935_TIMEOUT); + } +} + +bool AS3935MI::readMaskDisturbers() +{ + return (readRegisterValue(AS3935_REGISTER_MASK_DIST, AS3935_MASK_MASK_DIST) == 1 ? true : false); +} + +void AS3935MI::writeMaskDisturbers(bool enabled) +{ + writeRegisterValue(AS3935_REGISTER_MASK_DIST, AS3935_MASK_MASK_DIST, enabled ? 1 : 0); +} + +uint8_t AS3935MI::readAFE() +{ + return readRegisterValue(AS3935_REGISTER_AFE_GB, AS3935_MASK_AFE_GB); +} + +void AS3935MI::writeAFE(uint8_t afe_setting) +{ + writeRegisterValue(AS3935_REGISTER_AFE_GB, AS3935_MASK_AFE_GB, afe_setting); +} + +uint8_t AS3935MI::readNoiseFloorThreshold() +{ + return readRegisterValue(AS3935_REGISTER_NF_LEV, AS3935_MASK_NF_LEV); +} + +void AS3935MI::writeNoiseFloorThreshold(uint8_t threshold) +{ + if (threshold > AS3935_NFL_7) + return; + + writeRegisterValue(AS3935_REGISTER_NF_LEV, AS3935_MASK_NF_LEV, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint8_t AS3935MI::readWatchdogThreshold() +{ + return readRegisterValue(AS3935_REGISTER_WDTH, AS3935_MASK_WDTH); +} + +void AS3935MI::writeWatchdogThreshold(uint8_t threshold) +{ + if (threshold > AS3935_WDTH_15) + return; + + writeRegisterValue(AS3935_REGISTER_WDTH, AS3935_MASK_WDTH, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint8_t AS3935MI::readSpikeRejection() +{ + return readRegisterValue(AS3935_REGISTER_SREJ, AS3935_MASK_SREJ); +} + +void AS3935MI::writeSpikeRejection(uint8_t threshold) +{ + if (threshold > AS3935_SREJ_15) + return; + + writeRegisterValue(AS3935_REGISTER_SREJ, AS3935_MASK_SREJ, threshold); + + delayMicroseconds(AS3935_TIMEOUT); +} + +uint32_t AS3935MI::readEnergy() +{ + uint32_t energy = 0; + //from https://www.eevblog.com/forum/microcontrollers/define-mmsbyte-for-as3935-lightning-detector/ + //Reg 0x04: Energy word, bits 0 : 7 + //Reg 0x05 : Energy word, bits 8 : 15 + //Reg 0x06 : Energy word, bits 16 : 20 + //energy |= LSB + //energy |= (MSB << 8) + //energy |= (MMSB << 16) + energy |= static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_L, AS3935_MASK_S_LIG_L)); + energy |= (static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_M, AS3935_MASK_S_LIG_M)) << 8); + energy |= (static_cast(readRegisterValue(AS3935_REGISTER_S_LIG_MM, AS3935_MASK_S_LIG_MM)) << 16); + + return energy; +} + +uint8_t AS3935MI::readAntennaTuning() +{ + // Do not call readRegisterValue(AS3935_REGISTER_TUN_CAP, AS3935_MASK_TUN_CAP) + // here as we need to be able to detect read errors. + const uint8_t return_value = readRegister(AS3935_REGISTER_TUN_CAP); + if (return_value != static_cast(-1)) { + // No read error, so update the tuning_cap_cache_ + tuning_cap_cache_ = return_value & AS3935_MASK_TUN_CAP; + } else { + return tuning_cap_cache_ & AS3935_MASK_TUN_CAP; + } + + return return_value & AS3935_MASK_TUN_CAP; +} + +bool AS3935MI::writeAntennaTuning(uint8_t tuning) +{ + if ((tuning & ~AS3935_MASK_TUN_CAP) != 0) { + return false; + } + tuning_cap_cache_ = tuning; + writeRegisterValue(AS3935_REGISTER_TUN_CAP, AS3935_MASK_TUN_CAP, tuning); + return true; +} + +uint8_t AS3935MI::readDivisionRatio() +{ + return readRegisterValue(AS3935_REGISTER_LCO_FDIV, AS3935_MASK_LCO_FDIV); +} + +void AS3935MI::writeDivisionRatio(uint8_t ratio) +{ + writeRegisterValue(AS3935_REGISTER_LCO_FDIV, AS3935_MASK_LCO_FDIV, ratio); +} + +uint8_t AS3935MI::readMinLightnings() +{ + return readRegisterValue(AS3935_REGISTER_MIN_NUM_LIGH, AS3935_MASK_MIN_NUM_LIGH); +} + +void AS3935MI::writeMinLightnings(uint8_t number) +{ + writeRegisterValue(AS3935_REGISTER_MIN_NUM_LIGH, AS3935_MASK_MIN_NUM_LIGH, number); +} + +void AS3935MI::resetToDefaults() +{ + writeRegister(AS3935_REGISTER_PRESET_DEFAULT, AS3935_DIRECT_CMD); + + delayMicroseconds(AS3935_TIMEOUT); +} + +bool AS3935MI::calibrateRCO() +{ + //cannot calibrate if in power down mode. + if (readPowerDown()) + return false; + + //issue calibration command + writeRegister(AS3935_REGISTER_CALIB_RCO, AS3935_DIRECT_CMD); + + //expose 1.1 MHz SRCO clock on IRQ pin + displaySrcoOnIrq(true); + + //wait for calibration to finish... + delayMicroseconds(AS3935_TIMEOUT); + + //stop exposing clock on IRQ pin + displaySrcoOnIrq(false); + + //check calibration results. bits will be set if calibration failed. + bool success_TRCO = (readRegisterValue(AS3935_REGISTER_TRCO_CALIB_NOK, AS3935_MASK_TRCO_CALIB_ALL) == 0b10); + bool success_SRCO = (readRegisterValue(AS3935_REGISTER_SRCO_CALIB_NOK, AS3935_MASK_SRCO_CALIB_ALL) == 0b10); + + return (success_TRCO && success_SRCO); +} + +void AS3935MI::setFrequencyMeasureNrSamples(uint32_t nrSamples) +{ + nr_calibration_samples_ = nrSamples; +} + +void AS3935MI::setFrequencyMeasureEdgeChange(bool triggerRisingAndFalling) +{ + calibration_mode_edgetrigger_trigger_ = triggerRisingAndFalling ? CHANGE : RISING; +} + +void AS3935MI::setCalibrationDivisionRatio(uint8_t division_ratio) +{ + if (division_ratio <= AS3935MI::division_ratio_t::AS3935_DR_128) { + calibration_mode_division_ratio_ = static_cast(division_ratio); + } else { + calibration_mode_division_ratio_ = AS3935MI_LCO_DIVISION_RATIO; + } +} + +bool AS3935MI::calibrateResonanceFrequency(int32_t& frequency, uint8_t division_ratio) +{ + if (readPowerDown()) + return false; + + setCalibrationDivisionRatio(division_ratio); + + // Check for allowed deviation + constexpr uint32_t allowedDeviation = 500000 * AS3935MI_ALLOWED_DEVIATION; + const uint32_t cur_nr_samples = nr_calibration_samples_; + + calibrated_ant_cap_ = -1; + + uint32_t best_diff = 500000; + int8_t best_i = -1; + + frequency = 0; + + // Clear previous calibration results + for (uint8_t i = 0; i < 16; i++) + { + calibration_frequencies_[i] = 0.0f; + } + + // When set to calibrate all ant_cap, the + uint8_t attempt = calibrate_all_ant_cap_ ? 0 : 2; + uint8_t lowest_cap = 0; + uint8_t highest_cap = 15; + + // Find upper and lower bound of ant_caps to test using more samples + while (attempt > 0) { + --attempt; + const int32_t freq_0 = measureResonanceFrequency( + display_frequency_source_t::LCO, 0); + const int32_t freq_15 = measureResonanceFrequency( + display_frequency_source_t::LCO, 15); + + if ((freq_0 == 0 || freq_15 == 0) || (freq_0 == freq_15)) { + setFrequencyMeasureNrSamples(nr_calibration_samples_ * 2); + } else { + const int estimated_cap = map(500000, freq_0, freq_15, 0, 15); + if (estimated_cap <= 0) { + highest_cap = 1; + } else if (estimated_cap >= 15) { + lowest_cap = 14; + } else { + lowest_cap = estimated_cap - 1; + highest_cap = estimated_cap + 1; + } + attempt = 0; + } + } + + // Now test with higher number of samples to get better accuracy + if (nr_calibration_samples_ < AS3935MI_NR_CALIBRATION_SAMPLES) { + setFrequencyMeasureNrSamples(AS3935MI_NR_CALIBRATION_SAMPLES); + } + for (uint8_t i = lowest_cap; i <= highest_cap; i++) + { + const int32_t freq = measureResonanceFrequency( + display_frequency_source_t::LCO, i); + + if (freq == 0) { + // restore nr of samples set by user + setFrequencyMeasureNrSamples(cur_nr_samples); + return false; + } + const uint32_t freq_diff = abs(500000 - freq); + + if (freq_diff < best_diff) { + best_diff = freq_diff; + best_i = i; + frequency = freq; + } + } + + // restore nr of samples set by user + setFrequencyMeasureNrSamples(cur_nr_samples); + + if (best_i < 0) { + frequency = 0; + return false; + } + + calibrated_ant_cap_ = best_i; + + writeAntennaTuning(calibrated_ant_cap_); + + return best_diff < allowedDeviation; +} + +bool AS3935MI::calibrateResonanceFrequency(int32_t& frequency) +{ + return calibrateResonanceFrequency(frequency, calibration_mode_division_ratio_); +} + +bool AS3935MI::calibrateResonanceFrequency() +{ + int32_t frequency = 0; + return calibrateResonanceFrequency(frequency, calibration_mode_division_ratio_); +} + +bool AS3935MI::checkConnection() +{ + uint8_t afe = readAFE(); + + return ((afe == AS3935_INDOORS) || (afe == AS3935_OUTDOORS)); +} + +bool AS3935MI::checkIRQ() +{ + // Only need a quick check, so set nr of samples low as we're not yet interested in an accurate measurement + const uint32_t cur_nr_samples = nr_calibration_samples_; + setFrequencyMeasureNrSamples(128); + const uint32_t freq = measureResonanceFrequency(display_frequency_source_t::LCO); + setFrequencyMeasureNrSamples(cur_nr_samples); + + // Expected LCO frequency is several kHz, so we should see at the very least see 1 kHz + return freq > 1000; +} + +void AS3935MI::clearStatistics() +{ + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 1); + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 0); + writeRegisterValue(AS3935_REGISTER_CL_STAT, AS3935_MASK_CL_STAT, 1); +} + +bool AS3935MI::decreaseNoiseFloorThreshold() { + uint8_t nf_lev{}; + return decreaseNoiseFloorThreshold(nf_lev); +} + +bool AS3935MI::decreaseNoiseFloorThreshold(uint8_t& nf_lev) +{ + nf_lev = readNoiseFloorThreshold(); + + if (nf_lev == AS3935_NFL_0) + return false; + + writeNoiseFloorThreshold(--nf_lev); + + return true; +} + +bool AS3935MI::increaseNoiseFloorThreshold() { + uint8_t nf_lev{}; + return increaseNoiseFloorThreshold(nf_lev); +} + +bool AS3935MI::increaseNoiseFloorThreshold(uint8_t& nf_lev) +{ + nf_lev = readNoiseFloorThreshold(); + + if (nf_lev >= AS3935_NFL_7) + return false; + + writeNoiseFloorThreshold(++nf_lev); + + return true; +} + +bool AS3935MI::decreaseWatchdogThreshold() { + uint8_t wdth{}; + return decreaseWatchdogThreshold(wdth); +} + +bool AS3935MI::decreaseWatchdogThreshold(uint8_t& wdth) +{ + wdth = readWatchdogThreshold(); + + if (wdth == AS3935_WDTH_0) + return false; + + writeWatchdogThreshold(--wdth); + + return true; +} + +bool AS3935MI::increaseWatchdogThreshold() { + uint8_t wdth{}; + return increaseWatchdogThreshold(wdth); +} + +bool AS3935MI::increaseWatchdogThreshold(uint8_t& wdth) +{ + wdth = readWatchdogThreshold(); + + if (wdth >= AS3935_WDTH_15) + return false; + + writeWatchdogThreshold(++wdth); + + return true; +} + + +bool AS3935MI::decreaseSpikeRejection() +{ + uint8_t srej{}; + return decreaseSpikeRejection(srej); +} + +bool AS3935MI::decreaseSpikeRejection(uint8_t& srej) +{ + srej = readSpikeRejection(); + + if (srej == AS3935_SREJ_0) + return false; + + writeSpikeRejection(--srej); + + return true; +} + +bool AS3935MI::increaseSpikeRejection() +{ + uint8_t srej{}; + return increaseSpikeRejection(srej); +} + +bool AS3935MI::increaseSpikeRejection(uint8_t& srej) +{ + srej = readSpikeRejection(); + + if (srej >= AS3935_SREJ_15) + return false; + + writeSpikeRejection(++srej); + + return true; +} + +void AS3935MI::displayLcoOnIrq(bool enable) +{ + // With display of any frequency, the device may sometimes report NAK when reading registers + // So for this reason we're now writing directly and not try to read first, patch bits, write + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_LCO; + } + writeRegister(AS3935_REGISTER_DISP_LCO, value); +} + +void AS3935MI::displaySrcoOnIrq(bool enable) +{ + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_SRCO; + } + writeRegister(AS3935_REGISTER_DISP_SRCO, value); +} + + +void AS3935MI::displayTrcoOnIrq(bool enable) +{ + uint8_t value = tuning_cap_cache_; + if (enable) { + value |= AS3935_MASK_DISP_TRCO; + } + writeRegister(AS3935_REGISTER_DISP_TRCO, value); +} + + +bool AS3935MI::validateCurrentResonanceFrequency(int32_t& frequency) +{ + frequency = measureResonanceFrequency( + display_frequency_source_t::LCO, + readAntennaTuning()); + + // Check for allowed deviation + constexpr int allowedDeviation = 500000 * AS3935MI_ALLOWED_DEVIATION; + + return abs(500000 - frequency) < allowedDeviation; +} + +int32_t AS3935MI::measureResonanceFrequency(display_frequency_source_t source) +{ + return measureResonanceFrequency( + source, + readAntennaTuning()); +} + + +uint8_t AS3935MI::getMaskShift(uint8_t mask) +{ + uint8_t return_value = 0; + + //count how many times the mask must be shifted right until the lowest bit is set + if (mask != 0) + { + while (!(mask & 1)) + { + return_value++; + mask >>= 1; + } + } + + return return_value; +} + +uint8_t AS3935MI::getMaskedBits(uint8_t reg, uint8_t mask) +{ + //extract masked bits + return ((reg & mask) >> getMaskShift(mask)); +} + +uint8_t AS3935MI::setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value) +{ + //clear mask bits in register + reg &= (~mask); + + //set masked bits in register according to value + return ((value << getMaskShift(mask)) & mask) | reg; +} + +uint8_t AS3935MI::readRegisterValue(uint8_t reg, uint8_t mask) +{ + return getMaskedBits(readRegister(reg), mask); +} + +void AS3935MI::writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value) +{ + uint8_t reg_val = readRegister(reg); + writeRegister(reg, setMaskedBits(reg_val, mask, value)); +} + + +uint32_t AS3935MI::computeCalibratedFrequency(int32_t divider) +{ + switch (divider) + { + case AS3935_DIVIDER_1: + case AS3935_DIVIDER_16: + case AS3935_DIVIDER_32: + case AS3935_DIVIDER_64: + case AS3935_DIVIDER_128: + break; + default: + return 0ul; + } + + // Need to copy the timestamps first as they are volatile + const uint32_t start = calibration_start_micros_; + const uint32_t end = calibration_end_micros_; + + if ((start == 0ul) || (end == 0ul)) { + return 0ul; + } + + const int32_t duration_usec = (int32_t) (end - start); + + if (duration_usec <= 0l) { + return 0ul; + } + + // Compute measured frequency + // we have duration of nr_calibration_samples_ pulses in usec, thus measured frequency is: + // (nr_calibration_samples_ * 1000'000) / duration in usec. + // Actual frequency should take the division ratio into account. + uint64_t freq = (static_cast(divider) * 1000000ull * (nr_calibration_samples_ + 1)); + if (calibration_mode_edgetrigger_trigger_ == CHANGE) { + // Counting on both rising and falling edge, so actual frequency is half + freq /= 2ull; + } + + freq /= duration_usec; + + return static_cast(freq); +} + + +uint32_t AS3935MI::measureResonanceFrequency(display_frequency_source_t source, uint8_t tuningCapacitance) +{ + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_DETACHED); + +// delayMicroseconds(AS3935_TIMEOUT); + + unsigned sourceFreq_kHz = 500; + int32_t divider = 1; + + // display LCO on IRQ + switch (source) { + case display_frequency_source_t::LCO: + // set tuning capacitors + if (!writeAntennaTuning(tuningCapacitance)) { + return 0u; + } + displayLcoOnIrq(true); + writeDivisionRatio(calibration_mode_division_ratio_); + divider = 16 << static_cast(calibration_mode_division_ratio_); + sourceFreq_kHz = 500; + break; + + // TD-er: Do not try to measure the 1.1 MHz signal as the ESP32 will not be able to keep up with all the interrupts. + case display_frequency_source_t::SRCO: + displaySrcoOnIrq(true); + sourceFreq_kHz = 1100; + break; + case display_frequency_source_t::TRCO: + displayTrcoOnIrq(true); + sourceFreq_kHz = 33; + break; + } + + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_CALIBRATION); + + // Need to give enough time for the sensor to set the LCO signal on the IRQ pin + delayMicroseconds(AS3935_TIMEOUT); + calibration_end_micros_ = 0ul; + interrupt_count_ = 0ul; + calibration_start_micros_ = static_cast(getMicros64()); + + // Wait for the amount of samples to be counted (or timeout) + // Typically this takes 32 msec for the 500 kHz LCO when taking 1000 samples + unsigned expectedDuration = (divider * nr_calibration_samples_) / sourceFreq_kHz; + if (expectedDuration < 10) { + // For low nr of samples, we should still keep some minimum timeout of 10 msec. + expectedDuration = 10; + } + + const uint32_t timeout = millis() + (2 * expectedDuration); + uint32_t freq = 0; + + while (freq == 0 && (((int32_t)(millis() - timeout)) < 0)) { + delay(1); + freq = computeCalibratedFrequency(divider); + } + + // Need to disable interrupts first or else sending I2C commands may fail + setInterruptMode(interrupt_mode_t::AS3935_INTERRUPT_DETACHED); + + // stop displaying LCO on IRQ + displayLcoOnIrq(false); + + if (source == display_frequency_source_t::LCO) { + calibration_frequencies_[tuningCapacitance] = freq; + } + + return freq; +} + +uint32_t AS3935MI::getInterruptTimestamp() const { + return interrupt_timestamp_; +} + +uint32_t AS3935MI::getInterruptCount() const { + return interrupt_count_; +} + +void AS3935MI::setInterruptMode(interrupt_mode_t mode) { + if (mode_ == mode) { + return; + } + + if (mode_ == AS3935MI::AS3935_INTERRUPT_NORMAL || + mode_ == AS3935MI::AS3935_INTERRUPT_CALIBRATION) { + detachInterrupt(irq_); + } + + // set the IRQ pin as an input pin. do not use INPUT_PULLUP - the AS3935 will pull the pin + // high if an event is registered. + pinMode(irq_, INPUT); + + interrupt_timestamp_ = 0; + interrupt_count_ = 0; + mode_ = mode; + + switch (mode) { + case interrupt_mode_t::AS3935_INTERRUPT_UNINITIALIZED: + case interrupt_mode_t::AS3935_INTERRUPT_DETACHED: + break; + case interrupt_mode_t::AS3935_INTERRUPT_NORMAL: +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + attachInterruptArg(digitalPinToInterrupt(irq_), + reinterpret_cast(interruptISR), + this, + RISING); +#else + attachInterrupt(digitalPinToInterrupt(irq_), + interruptISR, + RISING); +#endif + break; + case interrupt_mode_t::AS3935_INTERRUPT_CALIBRATION: + calibration_start_micros_ = 0; + calibration_end_micros_ = 0; +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + attachInterruptArg(digitalPinToInterrupt(irq_), + reinterpret_cast(calibrateISR), + this, + calibration_mode_edgetrigger_trigger_); +#else + attachInterrupt(digitalPinToInterrupt(irq_), + calibrateISR, + calibration_mode_edgetrigger_trigger_); +#endif + break; + } +} + +bool AS3935MI::checkProperlySetToListenMode() { + if (mode_ != AS3935MI::AS3935_INTERRUPT_NORMAL) { + // Nothing to check here, so just return all OK + return true; + } + + if (interrupt_timestamp_ == 0 || interrupt_count_ == 0) { + // No interrupts since last clearing of these interrupt variables. + return true; + } + + int retries = 2; + while (retries > 0 && interrupt_count_ < 2) { + // The possible displayed frequencies are several kHz, + // so time since last interrupt should be less than a msec since + // last trigger if these frequencies are still present. + // + // In 'normal' mode, the interrupt count should never be more than 1 + // as the IRQ pin will be kept high until the interrupt source is read. + // + // N.B. Use the volatile variable here as those may be updated inbetween. + const int32_t msec_passed_since = (int32_t)(millis() - interrupt_timestamp_); + if (msec_passed_since > 2) { + return true; + } + --retries; + delay(1); + } + + // Apparently there is still some frequency being displayed. + setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + delayMicroseconds(AS3935_TIMEOUT); + + // stop displaying LCO on IRQ + displayLcoOnIrq(false); + delayMicroseconds(AS3935_TIMEOUT); + + // restore normal operation + setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + + // It wasn't how it should be so return false + return false; +} + + +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION +void AS3935MI_IRAM_ATTR AS3935MI::interruptISR(AS3935MI *self) { + self->interrupt_timestamp_ = millis(); + ++(self->interrupt_count_); +} + +void AS3935MI_IRAM_ATTR AS3935MI::calibrateISR(AS3935MI *self) { + // interrupt_count_ is volatile, so we can miss when testing for exactly nr_calibration_samples_ + if (self->interrupt_count_ < self->nr_calibration_samples_) { + ++self->interrupt_count_; + } + else if (self->calibration_end_micros_ == 0ul) { + self->calibration_end_micros_ = static_cast(getMicros64()); + } +} +#else +void AS3935MI_IRAM_ATTR AS3935MI::interruptISR() { + interrupt_timestamp_ = millis(); + ++interrupt_count_; +} + +void AS3935MI_IRAM_ATTR AS3935MI::calibrateISR() { + // interrupt_count_ is volatile, so we can miss when testing for exactly nr_calibration_samples_ + if (interrupt_count_ < nr_calibration_samples_) { + ++interrupt_count_; + } + else if (calibration_end_micros_ == 0ul) { + calibration_end_micros_ = static_cast(getMicros64()); + } +} +#endif + +int32_t AS3935MI::getAntCapFrequency(uint8_t tuningCapacitance) const +{ + constexpr unsigned int nrElements = sizeof(calibration_frequencies_) / sizeof(calibration_frequencies_[0]); + if (tuningCapacitance < nrElements) { + return calibration_frequencies_[tuningCapacitance]; + } + return -1; +} \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935MI.h b/lib/AS3935MI/src/AS3935MI.h new file mode 100644 index 000000000..090ff99c3 --- /dev/null +++ b/lib/AS3935MI/src/AS3935MI.h @@ -0,0 +1,574 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935MI_H_ +#define AS3935MI_H_ + +#include + +#if defined(ESP8266) || defined(ESP32) +// When we can't use attachInterruptArg to directly access volatile members, +// we must use static variables in the .cpp file +// This means only a single instance of this class can be used. +// +// When we can use attachInterruptArg, we can use volatile members +// and thus have multiple instances of this class without jumping through hoops +// to avoid issues sharing volatile variables + +#define AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + +#define AS3935MI_IRAM_ATTR IRAM_ATTR +#endif + +#if ESP_IDF_VERSION_MAJOR >= 5 +# include +#endif + +#ifndef AS3935MI_IRAM_ATTR +// Define this attribute as empty for platforms that don't need a special +// IRAM_ATTR for ISR callback functions +#define AS3935MI_IRAM_ATTR +#endif + +// Allow for 3.5% deviation +# define AS3935MI_ALLOWED_DEVIATION 0.035f + +// Division ratio and nr of samples chosen so we expect a +// 500 kHz LCO measurement to take about 18 msec on ESP32 +// On others it will take about 32 msec. +// ESP8266 can't handle > 20 kHz interrupt calls very well, +// therefore set to DR_32 and edge trigger to "RISING" +# ifdef ESP32 + +// Expected LCO frequency for DR_16 = 31250 Hz +# define AS3935MI_LCO_DIVISION_RATIO AS3935MI::AS3935_DR_16 +# define AS3935MI_NR_CALIBRATION_SAMPLES 1000ul +# define AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER CHANGE +# else // ifdef ESP32 + +// Expected LCO frequency for DR_32 = 15625 Hz +# define AS3935MI_LCO_DIVISION_RATIO AS3935MI::AS3935_DR_32 +# define AS3935MI_NR_CALIBRATION_SAMPLES 500ul +# define AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER RISING +# endif // ifdef ESP32 + +class AS3935MI +{ +public: + enum afe_setting_t : uint8_t + { + AS3935_INDOORS = 0b10010, + AS3935_OUTDOORS = 0b01110 + }; + + enum interrupt_name_t : uint8_t + { + AS3935_INT_DUPDATE = 0b0000,//distance estimation has changed due to purging of old events in the statistics, based on the lightning distance estimation algorithm. + AS3935_INT_NH = 0b0001, //noise level too high + AS3935_INT_D = 0b0100, //disturber detected + AS3935_INT_L = 0b1000 //lightning interrupt + }; + + enum wdth_setting_t : uint8_t + { + AS3935_WDTH_0 = 0b0000, + AS3935_WDTH_1 = 0b0001, + AS3935_WDTH_2 = 0b0010, + AS3935_WDTH_3 = 0b0011, + AS3935_WDTH_4 = 0b0100, + AS3935_WDTH_5 = 0b0101, + AS3935_WDTH_6 = 0b0110, + AS3935_WDTH_7 = 0b0111, + AS3935_WDTH_8 = 0b1000, + AS3935_WDTH_9 = 0b1001, + AS3935_WDTH_10 = 0b1010, + AS3935_WDTH_11 = 0b1011, + AS3935_WDTH_12 = 0b1100, + AS3935_WDTH_13 = 0b1101, + AS3935_WDTH_14 = 0b1110, + AS3935_WDTH_15 = 0b1111 + }; + + enum srej_setting_t : uint8_t + { + AS3935_SREJ_0 = 0b0000, + AS3935_SREJ_1 = 0b0001, + AS3935_SREJ_2 = 0b0010, + AS3935_SREJ_3 = 0b0011, + AS3935_SREJ_4 = 0b0100, + AS3935_SREJ_5 = 0b0101, + AS3935_SREJ_6 = 0b0110, + AS3935_SREJ_7 = 0b0111, + AS3935_SREJ_8 = 0b1000, + AS3935_SREJ_9 = 0b1001, + AS3935_SREJ_10 = 0b1010, + AS3935_SREJ_11 = 0b1011, + AS3935_SREJ_12 = 0b1100, + AS3935_SREJ_13 = 0b1101, + AS3935_SREJ_14 = 0b1110, + AS3935_SREJ_15 = 0b1111 + }; + + enum noise_floor_threshold_t : uint8_t + { + AS3935_NFL_0 = 0b000, + AS3935_NFL_1 = 0b001, + AS3935_NFL_2 = 0b010, //default + AS3935_NFL_3 = 0b011, + AS3935_NFL_4 = 0b100, + AS3935_NFL_5 = 0b101, + AS3935_NFL_6 = 0b110, + AS3935_NFL_7 = 0b111, + }; + + enum min_num_lightnings_t : uint8_t + { + AS3935_MNL_1 = 0b00, //minimum number of lightnings: 1 + AS3935_MNL_5 = 0b01, //minimum number of lightnings: 5 + AS3935_MNL_9 = 0b10, //minimum number of lightnings: 9 + AS3935_MNL_16 = 0b11, //minimum number of lightnings: 16 + }; + + enum division_ratio_t : uint8_t + { + AS3935_DR_16 = 0b00, + AS3935_DR_32 = 0b01, + AS3935_DR_64 = 0b10, + AS3935_DR_128 = 0b11 + }; + + + enum class display_frequency_source_t { + LCO, // 500 kHz resonance freq + SRCO, // 1.1 MHz signal + TRCO // 32768 Hz signal + }; + + static const uint8_t AS3935_DST_OOR = 0b111111; //detected lightning was out of range + + AS3935MI(uint8_t irq); + virtual ~AS3935MI(); + + bool begin(); + + /* + @return storm distance in km. */ + uint8_t readStormDistance(); + + /* + @return interrupt source as AS9395::interrupt_name_t. */ + uint8_t readInterruptSource(); + + /* + @return true: powered down, false: powered up. */ + bool readPowerDown(); + + /* + @param enabled: true to power down, false to power up. */ + void writePowerDown(bool enabled); + + /* + @return true if disturbers are masked, false otherwise. */ + bool readMaskDisturbers(); + + /* + @param enabled true to mask disturbers, false otherwise. */ + void writeMaskDisturbers(bool enabled); + + /* + @return AFE setting as afe_setting_t. */ + uint8_t readAFE(); + + /* + @param afe_setting AFE setting as one if afe_setting_t. */ + void writeAFE(uint8_t afe_setting); + + /* + @return current noise floor. */ + uint8_t readNoiseFloorThreshold(); + + /* + writes a noise floor threshold setting to the sensor. + @param threshold as noise_floor_threshold_t*/ + void writeNoiseFloorThreshold(uint8_t threshold); + + /* + @return current noise floor threshold. */ + uint8_t readWatchdogThreshold(); + + /* + @param noise floor threshold setting. */ + void writeWatchdogThreshold(uint8_t noise_floor); + + /* + @return current spike rejection setting as srej_setting_t. */ + uint8_t readSpikeRejection(); + + /* + @param spike rejection setting as srej_setting_t. */ + void writeSpikeRejection(uint8_t threshold); + + /* + @return lightning energy. no physical meaning. */ + uint32_t readEnergy(); + + /* + @return antenna tuning*/ + uint8_t readAntennaTuning(); + + /* + writes an antenna tuning setting to the sensor. */ + bool writeAntennaTuning(uint8_t tuning); + + /* + read the currently set antenna tuning division ratio from the sensor. */ + uint8_t readDivisionRatio(); + + /* + writes an antenna tuning division ratio setting to the sensor. */ + void writeDivisionRatio(uint8_t ratio); + + /* + get the currently set minimum number of lightnings in the last 15 minues before lightning interrupts are issued, as min_num_lightnings_t. */ + uint8_t readMinLightnings(); + + /* + @param minimum number of lightnings in the last 15 minues before lightning interrupts are issued, as min_num_lightnings_t. */ + void writeMinLightnings(uint8_t number); + + /* + resets all registers to default values. */ + void resetToDefaults(); + + /* + calibrates the AS3935 TCRO accordingto procedure in AS3935 datasheet p36. must be done *after* calibrating the resonance frequency. + @return true on success, false otherwise. */ + bool calibrateRCO(); + + // Set the number of samples counted during frequency measurements. + void setFrequencyMeasureNrSamples(uint32_t nrSamples); + + // Set the edge mode trigger for any frequency measurement to either RISING or CHANGE + void setFrequencyMeasureEdgeChange(bool triggerRisingAndFalling); + + // Set the division ratio, only used when measuring LCO (thus only during calibration) + void setCalibrationDivisionRatio(uint8_t division_ratio); + + /* + calibrates the AS3935 antenna's resonance frequency. + @param (by reference, write only) frequency: after return, will hold the frequency the AS3935 + has been calibrated to. + @return true on success, false on failure or if the resonance frequency could not be tuned + to within +-3.5% of 500kHz. */ + bool calibrateResonanceFrequency( + int32_t& frequency, + uint8_t division_ratio); + bool calibrateResonanceFrequency( + int32_t& frequency); + bool calibrateResonanceFrequency(); + + /* + checks if the sensor is connected by attempting to read the AFE gain boost setting. + @return true if the AFE gain boost setting is 0b10010 or 0b01110, false otherwise. */ + bool checkConnection(); + + /* + checks the IRQ pin by instructing the AS3935 to display the antenna's resonance frequency on the IRQ pin + and monitoring the pin for changing levels. IRQ pin interrupt must not be enabled during this test. + The test takes approximately 14ms. the test is considered successful if more than 100 transitions have + been detected (to prevent false positives). + @return true if more than 100 changes in IRQ pin logic level were detected, false otherwise. */ + bool checkIRQ(); + + /* + * clears lightning distance estimation statistics + */ + void clearStatistics(); + + /* + increases the noise floor threshold setting, if possible. + @return true on success, false otherwise. */ + bool decreaseNoiseFloorThreshold(); + bool decreaseNoiseFloorThreshold(uint8_t& nf_lev); + + /* + increases the noise floor threshold setting, if possible. + @return new value on success, 0 otherwise. */ + bool increaseNoiseFloorThreshold(); + bool increaseNoiseFloorThreshold(uint8_t& nf_lev); + + /* + increases the watchdog threshold setting, if possible. + @return true on success, false otherwise. */ + bool decreaseWatchdogThreshold(); + bool decreaseWatchdogThreshold(uint8_t& wdth); + + /* + increases the watchdog threshold setting, if possible. + @return true on success, false otherwise. */ + bool increaseWatchdogThreshold(); + bool increaseWatchdogThreshold(uint8_t& wdth); + + /* + increases the spike rejection setting, if possible. + @return true on success, false otherwise. */ + bool decreaseSpikeRejection(); + bool decreaseSpikeRejection(uint8_t& srej); + + /* + increases the spike rejection setting, if possible. + @return true on success, false otherwise. */ + bool increaseSpikeRejection(); + bool increaseSpikeRejection(uint8_t& srej); + + // Ideally 500 kHz signal divided by the set division ratio + void displayLcoOnIrq(bool enable); + + // Ideally 1.1 MHz signal + void displaySrcoOnIrq(bool enable); + + // Ideally 32.768 kHz signal + void displayTrcoOnIrq(bool enable); + + + bool validateCurrentResonanceFrequency(int32_t& frequency); + + int32_t measureResonanceFrequency(display_frequency_source_t source); + + +private: + enum AS3935_registers_t : uint8_t + { + AS3935_REGISTER_AFE_GB = 0x00, //Analog Frontend Gain Boost + AS3935_REGISTER_PWD = 0x00, //Power Down + AS3935_REGISTER_NF_LEV = 0x01, //Noise Floor Level + AS3935_REGISTER_WDTH = 0x01, //Watchdog threshold + AS3935_REGISTER_CL_STAT = 0x02, //Clear statistics + AS3935_REGISTER_MIN_NUM_LIGH = 0x02, //Minimum number of lightnings + AS3935_REGISTER_SREJ = 0x02, //Spike rejection + AS3935_REGISTER_LCO_FDIV = 0x03, //Frequency division ratio for antenna tuning + AS3935_REGISTER_MASK_DIST = 0x03, //Mask Disturber + AS3935_REGISTER_INT = 0x03, //Interrupt + AS3935_REGISTER_S_LIG_L = 0x04, //Energy of the Single Lightning LSBYTE + AS3935_REGISTER_S_LIG_M = 0x05, //Energy of the Single Lightning MSBYTE + AS3935_REGISTER_S_LIG_MM = 0x06, //Energy of the Single Lightning MMSBYTE + AS3935_REGISTER_DISTANCE = 0x07, //Distance estimation + AS3935_REGISTER_DISP_LCO = 0x08, //Display LCO on IRQ pin + AS3935_REGISTER_DISP_SRCO = 0x08, //Display SRCO on IRQ pin + AS3935_REGISTER_DISP_TRCO = 0x08, //Display TRCO on IRQ pin + AS3935_REGISTER_TUN_CAP = 0x08, //Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + AS3935_REGISTER_TRCO_CALIB_DONE = 0x3A, //Calibration of TRCO done (1=successful) + AS3935_REGISTER_TRCO_CALIB_NOK = 0x3A, //Calibration of TRCO unsuccessful (1 = not successful) + AS3935_REGISTER_SRCO_CALIB_DONE = 0x3B, //Calibration of SRCO done (1=successful) + AS3935_REGISTER_SRCO_CALIB_NOK = 0x3B, //Calibration of SRCO unsuccessful (1 = not successful) + AS3935_REGISTER_PRESET_DEFAULT = 0x3C, //Sets all registers in default mode + AS3935_REGISTER_CALIB_RCO = 0x3D //Sets all registers in default mode + }; + + enum AS3935_register_mask_t : uint8_t + { + AS3935_MASK_AFE_GB = 0b00111110, //Analog Frontend Gain Boost + AS3935_MASK_PWD = 0b00000001, //Power Down + AS3935_MASK_NF_LEV = 0b01110000, //Noise Floor Level + AS3935_MASK_WDTH = 0b00001111, //Watchdog threshold + AS3935_MASK_CL_STAT = 0b01000000, //Clear statistics + AS3935_MASK_MIN_NUM_LIGH = 0b00110000, //Minimum number of lightnings + AS3935_MASK_SREJ = 0b00001111, //Spike rejection + AS3935_MASK_LCO_FDIV = 0b11000000, //Frequency division ratio for antenna tuning + AS3935_MASK_MASK_DIST = 0b00100000, //Mask Disturber + AS3935_MASK_INT = 0b00001111, //Interrupt + AS3935_MASK_S_LIG_L = 0b11111111, //Energy of the Single Lightning LSBYTE + AS3935_MASK_S_LIG_M = 0b11111111, //Energy of the Single Lightning MSBYTE + AS3935_MASK_S_LIG_MM = 0b00001111, //Energy of the Single Lightning MMSBYTE + AS3935_MASK_DISTANCE = 0b00111111, //Distance estimation + AS3935_MASK_DISP_LCO = 0b10000000, //Display LCO on IRQ pin + AS3935_MASK_DISP_SRCO = 0b01000000, //Display SRCO on IRQ pin + AS3935_MASK_DISP_TRCO = 0b00100000, //Display TRCO on IRQ pin + AS3935_MASK_TUN_CAP = 0b00001111, //Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + AS3935_MASK_TRCO_CALIB_DONE = 0b10000000, //Calibration of TRCO done (1=successful) + AS3935_MASK_TRCO_CALIB_NOK = 0b01000000, //Calibration of TRCO unsuccessful (1 = not successful) + AS3935_MASK_TRCO_CALIB_ALL = 0b11000000, //Calibration of TRCO done (0b10 = successful) + AS3935_MASK_SRCO_CALIB_DONE = 0b10000000, //Calibration of SRCO done (1=successful) + AS3935_MASK_SRCO_CALIB_NOK = 0b01000000, //Calibration of SRCO unsuccessful (1 = not successful) + AS3935_MASK_SRCO_CALIB_ALL = 0b11000000, //Calibration of SRCO done (0b10 = successful) + AS3935_MASK_PRESET_DEFAULT = 0b11111111, //Sets all registers in default mode + AS3935_MASK_CALIB_RCO = 0b11111111 //Sets all registers in default mode + }; + + enum co_divider_t + { + AS3935_DIVIDER_1 = 1, + AS3935_DIVIDER_16 = 16, + AS3935_DIVIDER_32 = 32, + AS3935_DIVIDER_64 = 64, + AS3935_DIVIDER_128 = 128, + }; + + + virtual bool beginInterface() = 0; + + /* + @param mask + @return number of bits to shift value so it fits into mask. */ + uint8_t getMaskShift(uint8_t mask); + + /* + @param register value of register. + @param mask mask of value in register + @return value of masked bits. */ + uint8_t getMaskedBits(uint8_t reg, uint8_t mask); + + /* + @param register value of register + @param mask mask of value in register + @param value value to write into masked area + @param register value with masked bits set to value. */ + uint8_t setMaskedBits(uint8_t reg, uint8_t mask, uint8_t value); + + /* + reads the masked value from the register. + @param reg register to read. + @param mask mask of value. + @return masked value in register. */ + uint8_t readRegisterValue(uint8_t reg, uint8_t mask); + + /* + sets values in a register. + @param reg register to set values in + @param mask bits of register to set value in + @param value value to set */ + void writeRegisterValue(uint8_t reg, uint8_t mask, uint8_t value); + + /* + reads a register from the sensor. must be overwritten by derived classes. + @param reg register to read. + @return register content*/ + virtual uint8_t readRegister(uint8_t reg) = 0; + + /* + writes a register to the sensor. must be overwritten by derived classes. + this function is also used to send direct commands. + @param reg register to write to. + @param value value writeRegister write to register. */ + virtual void writeRegister(uint8_t reg, uint8_t value) = 0; + + + uint32_t computeCalibratedFrequency(int32_t divider); + +public: + + // Internal Tuning Capacitors (from 0 to 120pF in steps of 8pF) + uint32_t measureResonanceFrequency(display_frequency_source_t source, uint8_t tuningCapacitance); + + + enum interrupt_mode_t { + AS3935_INTERRUPT_UNINITIALIZED, + AS3935_INTERRUPT_DETACHED, + AS3935_INTERRUPT_NORMAL, + AS3935_INTERRUPT_CALIBRATION + }; + + interrupt_mode_t getInterruptMode() const { return mode_; } + + uint32_t getInterruptTimestamp() const; + + uint32_t getInterruptCount() const; + + void setInterruptMode(interrupt_mode_t mode); + + // For unknown reasons, the sensor may not have turned off any of the + // displayed frequencies and thus those may still cause lots of unwanted interrupts to be triggered. + bool checkProperlySetToListenMode(); + +private: + static const uint8_t AS3935_DIRECT_CMD = 0x96; + + static const uint32_t AS3935_TIMEOUT = 2000; + + uint8_t irq_; //interrupt pin + + // Tuning cap value is located in the same register as the display LCO/SRCO/TRCO flags + // When those are active the device may not give an ACK when trying to read + // (via I2C) the register to update those display flags + // To overcome this issue, we keep a cache of the tuning cap parameter + // and write directly to the register instead of read/set bits/write. + uint8_t tuning_cap_cache_ = 0; + + AS3935MI::interrupt_mode_t mode_ = AS3935MI::AS3935_INTERRUPT_UNINITIALIZED; + + int calibration_mode_edgetrigger_trigger_ = AS3935MI_CALIBRATION_MODE_EDGE_TRIGGER; + AS3935MI::division_ratio_t calibration_mode_division_ratio_ = AS3935MI_LCO_DIVISION_RATIO; + + +#if ESP_IDF_VERSION_MAJOR >= 5 +#define AS3935MI_VOLATILE_TYPE std::atomic +#else +#define AS3935MI_VOLATILE_TYPE volatile uint32_t +#endif + + +#ifdef AS3935MI_HAS_ATTACHINTERRUPTARG_FUNCTION + static void AS3935MI_IRAM_ATTR interruptISR(AS3935MI *self); + static void AS3935MI_IRAM_ATTR calibrateISR(AS3935MI *self); + + AS3935MI_VOLATILE_TYPE interrupt_timestamp_ = 0; + AS3935MI_VOLATILE_TYPE interrupt_count_ = 0; + + // Store the time micros as 32-bit int so it can be stored and comprared as an atomic operation. + // Expected duration will be much less than 2^32 usec, thus overflow isn't an issue here + AS3935MI_VOLATILE_TYPE calibration_start_micros_ = 0; + AS3935MI_VOLATILE_TYPE calibration_end_micros_ = 0; + + uint32_t nr_calibration_samples_ = AS3935MI_NR_CALIBRATION_SAMPLES; + +#else + static void AS3935MI_IRAM_ATTR interruptISR(); + static void AS3935MI_IRAM_ATTR calibrateISR(); +#endif + + +public: + // Return the result of the last frequency measurement of the given tuning cap index + // @retval -1 when tuningCapacitance is out of range + int32_t getAntCapFrequency(uint8_t tuningCapacitance) const; + + // Return the best ant_cap found during last LCO calibration + // @retval -1 when no LCO calibration was performed + int8_t getCalibratedAntCap() const { + return calibrated_ant_cap_; + } + + // When set to calibrate all ant_cap indices, the LCO calibration is + // effectively set to perform a 'slow' calibration. + // All caps will be tried and also using more samples. + void setCalibrateAllAntCap(bool calibrate_all) { + calibrate_all_ant_cap_ = calibrate_all; + } + + bool getCalibrateAllAntCap() const { + return calibrate_all_ant_cap_; + } + +private: + int32_t calibration_frequencies_[16]{}; + int8_t calibrated_ant_cap_ = -1; + bool calibrate_all_ant_cap_ = true; + +}; + +#endif /* AS3935_H_ */ \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935SPI.cpp b/lib/AS3935MI/src/AS3935SPI.cpp new file mode 100644 index 000000000..5f1d81fbc --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPI.cpp @@ -0,0 +1,28 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935SPI.h" + +AS3935SPI::AS3935SPI(uint8_t cs, uint8_t irq) : + AS3935SPIClass(&SPI, cs, irq) +{ +} + +AS3935SPI::~AS3935SPI() +{ +} diff --git a/lib/AS3935MI/src/AS3935SPI.h b/lib/AS3935MI/src/AS3935SPI.h new file mode 100644 index 000000000..20daa973e --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPI.h @@ -0,0 +1,32 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935SPI_H_ +#define AS3935SPI_H_ + +#include "AS3935SPIClass.h" + +class AS3935SPI : + public AS3935SPIClass +{ +public: + AS3935SPI(uint8_t cs, uint8_t irq); + virtual ~AS3935SPI(); +}; + +#endif /* AS3935SPI_H_ */ diff --git a/lib/AS3935MI/src/AS3935SPIClass.cpp b/lib/AS3935MI/src/AS3935SPIClass.cpp new file mode 100644 index 000000000..dc31019e7 --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPIClass.cpp @@ -0,0 +1,103 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935SPIClass.h" + +#ifndef ESP32 +SPISettings AS3935SPIClass::spi_settings_ = SPISettings(1000000, MSBFIRST, SPI_MODE1); +#endif + +AS3935SPIClass::AS3935SPIClass(SPIClass *spi, uint8_t cs, uint8_t irq) : + AS3935MI(irq), + spi_(spi), + cs_(cs) +{ +} + +AS3935SPIClass::~AS3935SPIClass() +{ + spi_ = nullptr; +} + +bool AS3935SPIClass::beginInterface() +{ + if (!spi_) + return false; + + pinMode(cs_, OUTPUT); + digitalWrite(cs_, HIGH); //deselect + + return true; +} + +uint8_t AS3935SPIClass::readRegister(uint8_t reg) +{ + if (!spi_) + return 0; + + uint8_t return_value = 0; + +#ifdef ESP32 + spi_->setBitOrder(MSBFIRST); + spi_->setDataMode(SPI_MODE1); + spi_->setFrequency(1000000); + //spi_->setClockDivider(SPI_CLOCK_DIV16); +#else + spi_->beginTransaction(spi_settings_); +#endif + + digitalWrite(cs_, LOW); //select sensor + + spi_->transfer((reg & 0x3F) | 0x40); //select register and set pin 7 (indicates read) + + return_value = spi_->transfer(0); + + digitalWrite(cs_, HIGH); //deselect sensor + +#ifndef ESP32 + spi_->endTransaction(); +#endif + + return return_value; +} + +void AS3935SPIClass::writeRegister(uint8_t reg, uint8_t value) +{ + if (!spi_) + return; + +#ifdef ESP32 + spi_->setBitOrder(MSBFIRST); + spi_->setDataMode(SPI_MODE1); + spi_->setFrequency(1000000); + //spi_->setClockDivider(SPI_CLOCK_DIV16); +#else + spi_->beginTransaction(spi_settings_); +#endif + + digitalWrite(cs_, LOW); //select sensor + + spi_->transfer((reg & 0x3F)); //select regsiter + spi_->transfer(value); + + digitalWrite(cs_, HIGH); //deselect sensor + +#ifndef ESP32 + spi_->endTransaction(); +#endif +} diff --git a/lib/AS3935MI/src/AS3935SPIClass.h b/lib/AS3935MI/src/AS3935SPIClass.h new file mode 100644 index 000000000..55041541d --- /dev/null +++ b/lib/AS3935MI/src/AS3935SPIClass.h @@ -0,0 +1,50 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935SPICLASS_H_ +#define AS3935SPICLASS_H_ + +#include "AS3935MI.h" + +#include + +#include + +class AS3935SPIClass : + public AS3935MI +{ +public: + AS3935SPIClass(SPIClass *spi, uint8_t cs, uint8_t irq); + virtual ~AS3935SPIClass(); + +protected: + SPIClass *spi_; + + uint8_t cs_; + + static SPISettings spi_settings_; //spi settings object. is the same for all AS3935 sensors + +private: + virtual bool beginInterface(); + + virtual uint8_t readRegister(uint8_t reg); + + virtual void writeRegister(uint8_t reg, uint8_t value); +}; + +#endif /* AS3935SPICLASS_H_ */ diff --git a/lib/AS3935MI/src/AS3935TwoWire.cpp b/lib/AS3935MI/src/AS3935TwoWire.cpp new file mode 100644 index 000000000..c1267ea23 --- /dev/null +++ b/lib/AS3935MI/src/AS3935TwoWire.cpp @@ -0,0 +1,81 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "AS3935TwoWire.h" + +AS3935TwoWire::AS3935TwoWire(TwoWire *wire, uint8_t address, uint8_t irq) : + AS3935MI(irq), + wire_(wire), + address_(address) +{ +} + +AS3935TwoWire::~AS3935TwoWire() +{ + wire_ = nullptr; +} + +bool AS3935TwoWire::beginInterface() +{ + if (!wire_) + return false; + + switch (address_) + { + case AS3935I2C_A01: + case AS3935I2C_A10: + case AS3935I2C_A11: + break; + default: + //return false if an invalid I2C address was given. + return false; + } + + return true; +} + +uint8_t AS3935TwoWire::readRegister(uint8_t reg) +{ + if (!wire_) + return 0; + +#if defined(ARDUINO_SAM_DUE) + //workaround for Arduino Due. The Due seems not to send a repeated start with the code below, so this + //undocumented feature of Wire::requestFrom() is used. can be used on other Arduinos too (tested on Mega2560) + //see this thread for more info: https://forum.arduino.cc/index.php?topic=385377.0 + wire_->requestFrom(address_, 1, reg, 1, true); +#else + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->endTransmission(false); + wire_->requestFrom(address_, static_cast(1)); +#endif + + return wire_->read(); +} + +void AS3935TwoWire::writeRegister(uint8_t reg, uint8_t value) +{ + if (!wire_) + return; + + wire_->beginTransmission(address_); + wire_->write(reg); + wire_->write(value); + wire_->endTransmission(); +} \ No newline at end of file diff --git a/lib/AS3935MI/src/AS3935TwoWire.h b/lib/AS3935MI/src/AS3935TwoWire.h new file mode 100644 index 000000000..1395288be --- /dev/null +++ b/lib/AS3935MI/src/AS3935TwoWire.h @@ -0,0 +1,55 @@ +//Yet Another Arduino ams AS3935 'Franklin' lightning sensor library +// Copyright (c) 2018-2019 Gregor Christandl +// home: https://bitbucket.org/christandlg/as3935mi +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#ifndef AS3935TWOWIRE_H_ +#define AS3935TWOWIRE_H_ + +#include "AS3935MI.h" + +#include + +#include + +class AS3935TwoWire : + public AS3935MI +{ +public: + enum I2C_address_t : uint8_t + { + AS3935I2C_A01 = 0b01, + AS3935I2C_A10 = 0b10, + AS3935I2C_A11 = 0b11 + }; + + AS3935TwoWire(TwoWire *wire, uint8_t address, uint8_t irq); + virtual ~AS3935TwoWire(); + +protected: + TwoWire *wire_; + + uint8_t address_; + +private: + virtual bool beginInterface(); + + virtual uint8_t readRegister(uint8_t reg); + + virtual void writeRegister(uint8_t reg, uint8_t value); +}; + +#endif /* AS3935TWOWIRE_H_ */ diff --git a/lib/Adafruit_GFX_Library/Fonts/TomThumb.h b/lib/Adafruit_GFX_Library/Fonts/TomThumb.h index 08b20800e..e65f7122f 100644 --- a/lib/Adafruit_GFX_Library/Fonts/TomThumb.h +++ b/lib/Adafruit_GFX_Library/Fonts/TomThumb.h @@ -1,474 +1,476 @@ -/** -** The original 3x5 font is licensed under the 3-clause BSD license: -** -** Copyright 1999 Brian J. Swetland -** Copyright 1999 Vassilii Khachaturov -** Portions (of vt100.c/vt100.h) copyright Dan Marks -** -** All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions, and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions, and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. The name of the authors may not be used to endorse or promote products -** derived from this software without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -** -** Modifications to Tom Thumb for improved readability are from Robey Pointer, -** see: -** http://robey.lag.net/2010/01/23/tiny-monospace-font.html -** -** The original author does not have any objection to relicensing of Robey -** Pointer's modifications (in this file) in a more permissive license. See -** the discussion at the above blog, and also here: -** http://opengameart.org/forumtopic/how-to-submit-art-using-the-3-clause-bsd-license -** -** Feb 21, 2016: Conversion from Linux BDF --> Adafruit GFX font, -** with the help of this Python script: -** https://gist.github.com/skelliam/322d421f028545f16f6d -** William Skellenger (williamj@skellenger.net) -** Twitter: @skelliam -** -** Jan 09, 2020: Bitmaps now compressed, to fix the bounding box problem, -** because non-compressed the calculated text width were wrong. -** Andreas Merkle (web@blue-andi.de) -*/ - -#define TOMTHUMB_USE_EXTENDED 0 - -const uint8_t TomThumbBitmaps[] PROGMEM = { - 0x00, /* 0x20 space */ - 0xE8, /* 0x21 exclam */ - 0xB4, /* 0x22 quotedbl */ - 0xBE, 0xFA, /* 0x23 numbersign */ - 0x79, 0xE4, /* 0x24 dollar */ - 0x85, 0x42, /* 0x25 percent */ - 0xDB, 0xD6, /* 0x26 ampersand */ - 0xC0, /* 0x27 quotesingle */ - 0x6A, 0x40, /* 0x28 parenleft */ - 0x95, 0x80, /* 0x29 parenright */ - 0xAA, 0x80, /* 0x2A asterisk */ - 0x5D, 0x00, /* 0x2B plus */ - 0x60, /* 0x2C comma */ - 0xE0, /* 0x2D hyphen */ - 0x80, /* 0x2E period */ - 0x25, 0x48, /* 0x2F slash */ - 0x76, 0xDC, /* 0x30 zero */ - 0x75, 0x40, /* 0x31 one */ - 0xC5, 0x4E, /* 0x32 two */ - 0xC5, 0x1C, /* 0x33 three */ - 0xB7, 0x92, /* 0x34 four */ - 0xF3, 0x1C, /* 0x35 five */ - 0x73, 0xDE, /* 0x36 six */ - 0xE5, 0x48, /* 0x37 seven */ - 0xF7, 0xDE, /* 0x38 eight */ - 0xF7, 0x9C, /* 0x39 nine */ - 0xA0, /* 0x3A colon */ - 0x46, /* 0x3B semicolon */ - 0x2A, 0x22, /* 0x3C less */ - 0xE3, 0x80, /* 0x3D equal */ - 0x88, 0xA8, /* 0x3E greater */ - 0xE5, 0x04, /* 0x3F question */ - 0x57, 0xC6, /* 0x40 at */ - 0x57, 0xDA, /* 0x41 A */ - 0xD7, 0x5C, /* 0x42 B */ - 0x72, 0x46, /* 0x43 C */ - 0xD6, 0xDC, /* 0x44 D */ - 0xF3, 0xCE, /* 0x45 E */ - 0xF3, 0xC8, /* 0x46 F */ - 0x73, 0xD6, /* 0x47 G */ - 0xB7, 0xDA, /* 0x48 H */ - 0xE9, 0x2E, /* 0x49 I */ - 0x24, 0xD4, /* 0x4A J */ - 0xB7, 0x5A, /* 0x4B K */ - 0x92, 0x4E, /* 0x4C L */ - 0xBF, 0xDA, /* 0x4D M */ - 0xBF, 0xFA, /* 0x4E N */ - 0x56, 0xD4, /* 0x4F O */ - 0xD7, 0x48, /* 0x50 P */ - 0x56, 0xF6, /* 0x51 Q */ - 0xD7, 0xEA, /* 0x52 R */ - 0x71, 0x1C, /* 0x53 S */ - 0xE9, 0x24, /* 0x54 T */ - 0xB6, 0xD6, /* 0x55 U */ - 0xB6, 0xA4, /* 0x56 V */ - 0xB7, 0xFA, /* 0x57 W */ - 0xB5, 0x5A, /* 0x58 X */ - 0xB5, 0x24, /* 0x59 Y */ - 0xE5, 0x4E, /* 0x5A Z */ - 0xF2, 0x4E, /* 0x5B bracketleft */ - 0x88, 0x80, /* 0x5C backslash */ - 0xE4, 0x9E, /* 0x5D bracketright */ - 0x54, /* 0x5E asciicircum */ - 0xE0, /* 0x5F underscore */ - 0x90, /* 0x60 grave */ - 0xCE, 0xF0, /* 0x61 a */ - 0x9A, 0xDC, /* 0x62 b */ - 0x72, 0x30, /* 0x63 c */ - 0x2E, 0xD6, /* 0x64 d */ - 0x77, 0x30, /* 0x65 e */ - 0x2B, 0xA4, /* 0x66 f */ - 0x77, 0x94, /* 0x67 g */ - 0x9A, 0xDA, /* 0x68 h */ - 0xB8, /* 0x69 i */ - 0x20, 0x9A, 0x80, /* 0x6A j */ - 0x97, 0x6A, /* 0x6B k */ - 0xC9, 0x2E, /* 0x6C l */ - 0xFF, 0xD0, /* 0x6D m */ - 0xD6, 0xD0, /* 0x6E n */ - 0x56, 0xA0, /* 0x6F o */ - 0xD6, 0xE8, /* 0x70 p */ - 0x76, 0xB2, /* 0x71 q */ - 0x72, 0x40, /* 0x72 r */ - 0x79, 0xE0, /* 0x73 s */ - 0x5D, 0x26, /* 0x74 t */ - 0xB6, 0xB0, /* 0x75 u */ - 0xB7, 0xA0, /* 0x76 v */ - 0xBF, 0xF0, /* 0x77 w */ - 0xA9, 0x50, /* 0x78 x */ - 0xB5, 0x94, /* 0x79 y */ - 0xEF, 0x70, /* 0x7A z */ - 0x6A, 0x26, /* 0x7B braceleft */ - 0xD8, /* 0x7C bar */ - 0xC8, 0xAC, /* 0x7D braceright */ - 0x78, /* 0x7E asciitilde */ -#if (TOMTHUMB_USE_EXTENDED) - 0xB8, /* 0xA1 exclamdown */ - 0x5E, 0x74, /* 0xA2 cent */ - 0x6B, 0xAE, /* 0xA3 sterling */ - 0xAB, 0xAA, /* 0xA4 currency */ - 0xB5, 0x74, /* 0xA5 yen */ - 0xD8, /* 0xA6 brokenbar */ - 0x6A, 0xAC, /* 0xA7 section */ - 0xA0, /* 0xA8 dieresis */ - 0x71, 0x80, /* 0xA9 copyright */ - 0x77, 0x8E, /* 0xAA ordfeminine */ - 0x64, /* 0xAB guillemotleft */ - 0xE4, /* 0xAC logicalnot */ - 0xC0, /* 0xAD softhyphen */ - 0xDA, 0x80, /* 0xAE registered */ - 0xE0, /* 0xAF macron */ - 0x55, 0x00, /* 0xB0 degree */ - 0x5D, 0x0E, /* 0xB1 plusminus */ - 0xC9, 0x80, /* 0xB2 twosuperior */ - 0xEF, 0x80, /* 0xB3 threesuperior */ - 0x60, /* 0xB4 acute */ - 0xB6, 0xE8, /* 0xB5 mu */ - 0x75, 0xB6, /* 0xB6 paragraph */ - 0xFF, 0x80, /* 0xB7 periodcentered */ - 0x47, 0x00, /* 0xB8 cedilla */ - 0xE0, /* 0xB9 onesuperior */ - 0x55, 0x0E, /* 0xBA ordmasculine */ - 0x98, /* 0xBB guillemotright */ - 0x90, 0x32, /* 0xBC onequarter */ - 0x90, 0x66, /* 0xBD onehalf */ - 0xD8, 0x32, /* 0xBE threequarters */ - 0x41, 0x4E, /* 0xBF questiondown */ - 0x45, 0x7A, /* 0xC0 Agrave */ - 0x51, 0x7A, /* 0xC1 Aacute */ - 0xE1, 0x7A, /* 0xC2 Acircumflex */ - 0x79, 0x7A, /* 0xC3 Atilde */ - 0xAA, 0xFA, /* 0xC4 Adieresis */ - 0xDA, 0xFA, /* 0xC5 Aring */ - 0x7B, 0xEE, /* 0xC6 AE */ - 0x72, 0x32, 0x80, /* 0xC7 Ccedilla */ - 0x47, 0xEE, /* 0xC8 Egrave */ - 0x53, 0xEE, /* 0xC9 Eacute */ - 0xE3, 0xEE, /* 0xCA Ecircumflex */ - 0xA3, 0xEE, /* 0xCB Edieresis */ - 0x47, 0xAE, /* 0xCC Igrave */ - 0x53, 0xAE, /* 0xCD Iacute */ - 0xE3, 0xAE, /* 0xCE Icircumflex */ - 0xA3, 0xAE, /* 0xCF Idieresis */ - 0xD7, 0xDC, /* 0xD0 Eth */ - 0xCE, 0xFA, /* 0xD1 Ntilde */ - 0x47, 0xDE, /* 0xD2 Ograve */ - 0x53, 0xDE, /* 0xD3 Oacute */ - 0xE3, 0xDE, /* 0xD4 Ocircumflex */ - 0xCF, 0xDE, /* 0xD5 Otilde */ - 0xA3, 0xDE, /* 0xD6 Odieresis */ - 0xAA, 0x80, /* 0xD7 multiply */ - 0x77, 0xDC, /* 0xD8 Oslash */ - 0x8A, 0xDE, /* 0xD9 Ugrave */ - 0x2A, 0xDE, /* 0xDA Uacute */ - 0xE2, 0xDE, /* 0xDB Ucircumflex */ - 0xA2, 0xDE, /* 0xDC Udieresis */ - 0x2A, 0xF4, /* 0xDD Yacute */ - 0x9E, 0xF8, /* 0xDE Thorn */ - 0x77, 0x5D, 0x00, /* 0xDF germandbls */ - 0x45, 0xDE, /* 0xE0 agrave */ - 0x51, 0xDE, /* 0xE1 aacute */ - 0xE1, 0xDE, /* 0xE2 acircumflex */ - 0x79, 0xDE, /* 0xE3 atilde */ - 0xA1, 0xDE, /* 0xE4 adieresis */ - 0x6D, 0xDE, /* 0xE5 aring */ - 0x7F, 0xE0, /* 0xE6 ae */ - 0x71, 0x94, /* 0xE7 ccedilla */ - 0x45, 0xF6, /* 0xE8 egrave */ - 0x51, 0xF6, /* 0xE9 eacute */ - 0xE1, 0xF6, /* 0xEA ecircumflex */ - 0xA1, 0xF6, /* 0xEB edieresis */ - 0x9A, 0x80, /* 0xEC igrave */ - 0x65, 0x40, /* 0xED iacute */ - 0xE1, 0x24, /* 0xEE icircumflex */ - 0xA1, 0x24, /* 0xEF idieresis */ - 0x79, 0xD6, /* 0xF0 eth */ - 0xCF, 0x5A, /* 0xF1 ntilde */ - 0x45, 0x54, /* 0xF2 ograve */ - 0x51, 0x54, /* 0xF3 oacute */ - 0xE1, 0x54, /* 0xF4 ocircumflex */ - 0xCD, 0x54, /* 0xF5 otilde */ - 0xA1, 0x54, /* 0xF6 odieresis */ - 0x43, 0x84, /* 0xF7 divide */ - 0x7E, 0xE0, /* 0xF8 oslash */ - 0x8A, 0xD6, /* 0xF9 ugrave */ - 0x2A, 0xD6, /* 0xFA uacute */ - 0xE2, 0xD6, /* 0xFB ucircumflex */ - 0xA2, 0xD6, /* 0xFC udieresis */ - 0x2A, 0xB2, 0x80, /* 0xFD yacute */ - 0x9A, 0xE8, /* 0xFE thorn */ - 0xA2, 0xB2, 0x80, /* 0xFF ydieresis */ - 0x00, /* 0x11D gcircumflex */ - 0x7B, 0xE6, /* 0x152 OE */ - 0x7F, 0x70, /* 0x153 oe */ - 0xAF, 0x3C, /* 0x160 Scaron */ - 0xAF, 0x3C, /* 0x161 scaron */ - 0xA2, 0xA4, /* 0x178 Ydieresis */ - 0xBD, 0xEE, /* 0x17D Zcaron */ - 0xBD, 0xEE, /* 0x17E zcaron */ - 0x00, /* 0xEA4 uni0EA4 */ - 0x00, /* 0x13A0 uni13A0 */ - 0x80, /* 0x2022 bullet */ - 0xA0, /* 0x2026 ellipsis */ - 0x7F, 0xE6, /* 0x20AC Euro */ - 0xEA, 0xAA, 0xE0, /* 0xFFFD uniFFFD */ -#endif /* (TOMTHUMB_USE_EXTENDED) */ -}; - -/* {offset, width, height, advance cursor, x offset, y offset} */ -const GFXglyph TomThumbGlyphs[] PROGMEM = { - {0, 1, 1, 2, 0, -5}, /* 0x20 space */ - {1, 1, 5, 2, 0, -5}, /* 0x21 exclam */ - {2, 3, 2, 4, 0, -5}, /* 0x22 quotedbl */ - {3, 3, 5, 4, 0, -5}, /* 0x23 numbersign */ - {5, 3, 5, 4, 0, -5}, /* 0x24 dollar */ - {7, 3, 5, 4, 0, -5}, /* 0x25 percent */ - {9, 3, 5, 4, 0, -5}, /* 0x26 ampersand */ - {11, 1, 2, 2, 0, -5}, /* 0x27 quotesingle */ - {12, 2, 5, 3, 0, -5}, /* 0x28 parenleft */ - {14, 2, 5, 3, 0, -5}, /* 0x29 parenright */ - {16, 3, 3, 4, 0, -5}, /* 0x2A asterisk */ - {18, 3, 3, 4, 0, -4}, /* 0x2B plus */ - {20, 2, 2, 3, 0, -2}, /* 0x2C comma */ - {21, 3, 1, 4, 0, -3}, /* 0x2D hyphen */ - {22, 1, 1, 2, 0, -1}, /* 0x2E period */ - {23, 3, 5, 4, 0, -5}, /* 0x2F slash */ - {25, 3, 5, 4, 0, -5}, /* 0x30 zero */ - {27, 2, 5, 3, 0, -5}, /* 0x31 one */ - {29, 3, 5, 4, 0, -5}, /* 0x32 two */ - {31, 3, 5, 4, 0, -5}, /* 0x33 three */ - {33, 3, 5, 4, 0, -5}, /* 0x34 four */ - {35, 3, 5, 4, 0, -5}, /* 0x35 five */ - {37, 3, 5, 4, 0, -5}, /* 0x36 six */ - {39, 3, 5, 4, 0, -5}, /* 0x37 seven */ - {41, 3, 5, 4, 0, -5}, /* 0x38 eight */ - {43, 3, 5, 4, 0, -5}, /* 0x39 nine */ - {45, 1, 3, 2, 0, -4}, /* 0x3A colon */ - {46, 2, 4, 3, 0, -4}, /* 0x3B semicolon */ - {47, 3, 5, 4, 0, -5}, /* 0x3C less */ - {49, 3, 3, 4, 0, -4}, /* 0x3D equal */ - {51, 3, 5, 4, 0, -5}, /* 0x3E greater */ - {53, 3, 5, 4, 0, -5}, /* 0x3F question */ - {55, 3, 5, 4, 0, -5}, /* 0x40 at */ - {57, 3, 5, 4, 0, -5}, /* 0x41 A */ - {59, 3, 5, 4, 0, -5}, /* 0x42 B */ - {61, 3, 5, 4, 0, -5}, /* 0x43 C */ - {63, 3, 5, 4, 0, -5}, /* 0x44 D */ - {65, 3, 5, 4, 0, -5}, /* 0x45 E */ - {67, 3, 5, 4, 0, -5}, /* 0x46 F */ - {69, 3, 5, 4, 0, -5}, /* 0x47 G */ - {71, 3, 5, 4, 0, -5}, /* 0x48 H */ - {73, 3, 5, 4, 0, -5}, /* 0x49 I */ - {75, 3, 5, 4, 0, -5}, /* 0x4A J */ - {77, 3, 5, 4, 0, -5}, /* 0x4B K */ - {79, 3, 5, 4, 0, -5}, /* 0x4C L */ - {81, 3, 5, 4, 0, -5}, /* 0x4D M */ - {83, 3, 5, 4, 0, -5}, /* 0x4E N */ - {85, 3, 5, 4, 0, -5}, /* 0x4F O */ - {87, 3, 5, 4, 0, -5}, /* 0x50 P */ - {89, 3, 5, 4, 0, -5}, /* 0x51 Q */ - {91, 3, 5, 4, 0, -5}, /* 0x52 R */ - {93, 3, 5, 4, 0, -5}, /* 0x53 S */ - {95, 3, 5, 4, 0, -5}, /* 0x54 T */ - {97, 3, 5, 4, 0, -5}, /* 0x55 U */ - {99, 3, 5, 4, 0, -5}, /* 0x56 V */ - {101, 3, 5, 4, 0, -5}, /* 0x57 W */ - {103, 3, 5, 4, 0, -5}, /* 0x58 X */ - {105, 3, 5, 4, 0, -5}, /* 0x59 Y */ - {107, 3, 5, 4, 0, -5}, /* 0x5A Z */ - {109, 3, 5, 4, 0, -5}, /* 0x5B bracketleft */ - {111, 3, 3, 4, 0, -4}, /* 0x5C backslash */ - {113, 3, 5, 4, 0, -5}, /* 0x5D bracketright */ - {115, 3, 2, 4, 0, -5}, /* 0x5E asciicircum */ - {116, 3, 1, 4, 0, -1}, /* 0x5F underscore */ - {117, 2, 2, 3, 0, -5}, /* 0x60 grave */ - {118, 3, 4, 4, 0, -4}, /* 0x61 a */ - {120, 3, 5, 4, 0, -5}, /* 0x62 b */ - {122, 3, 4, 4, 0, -4}, /* 0x63 c */ - {124, 3, 5, 4, 0, -5}, /* 0x64 d */ - {126, 3, 4, 4, 0, -4}, /* 0x65 e */ - {128, 3, 5, 4, 0, -5}, /* 0x66 f */ - {130, 3, 5, 4, 0, -4}, /* 0x67 g */ - {132, 3, 5, 4, 0, -5}, /* 0x68 h */ - {134, 1, 5, 2, 0, -5}, /* 0x69 i */ - {135, 3, 6, 4, 0, -5}, /* 0x6A j */ - {138, 3, 5, 4, 0, -5}, /* 0x6B k */ - {140, 3, 5, 4, 0, -5}, /* 0x6C l */ - {142, 3, 4, 4, 0, -4}, /* 0x6D m */ - {144, 3, 4, 4, 0, -4}, /* 0x6E n */ - {146, 3, 4, 4, 0, -4}, /* 0x6F o */ - {148, 3, 5, 4, 0, -4}, /* 0x70 p */ - {150, 3, 5, 4, 0, -4}, /* 0x71 q */ - {152, 3, 4, 4, 0, -4}, /* 0x72 r */ - {154, 3, 4, 4, 0, -4}, /* 0x73 s */ - {156, 3, 5, 4, 0, -5}, /* 0x74 t */ - {158, 3, 4, 4, 0, -4}, /* 0x75 u */ - {160, 3, 4, 4, 0, -4}, /* 0x76 v */ - {162, 3, 4, 4, 0, -4}, /* 0x77 w */ - {164, 3, 4, 4, 0, -4}, /* 0x78 x */ - {166, 3, 5, 4, 0, -4}, /* 0x79 y */ - {168, 3, 4, 4, 0, -4}, /* 0x7A z */ - {170, 3, 5, 4, 0, -5}, /* 0x7B braceleft */ - {172, 1, 5, 2, 0, -5}, /* 0x7C bar */ - {173, 3, 5, 4, 0, -5}, /* 0x7D braceright */ - {175, 3, 2, 4, 0, -5}, /* 0x7E asciitilde */ -#if (TOMTHUMB_USE_EXTENDED) - {176, 1, 5, 2, 0, -5}, /* 0xA1 exclamdown */ - {177, 3, 5, 4, 0, -5}, /* 0xA2 cent */ - {179, 3, 5, 4, 0, -5}, /* 0xA3 sterling */ - {181, 3, 5, 4, 0, -5}, /* 0xA4 currency */ - {183, 3, 5, 4, 0, -5}, /* 0xA5 yen */ - {185, 1, 5, 2, 0, -5}, /* 0xA6 brokenbar */ - {186, 3, 5, 4, 0, -5}, /* 0xA7 section */ - {188, 3, 1, 4, 0, -5}, /* 0xA8 dieresis */ - {189, 3, 3, 4, 0, -5}, /* 0xA9 copyright */ - {191, 3, 5, 4, 0, -5}, /* 0xAA ordfeminine */ - {193, 2, 3, 3, 0, -5}, /* 0xAB guillemotleft */ - {194, 3, 2, 4, 0, -4}, /* 0xAC logicalnot */ - {195, 2, 1, 3, 0, -3}, /* 0xAD softhyphen */ - {196, 3, 3, 4, 0, -5}, /* 0xAE registered */ - {198, 3, 1, 4, 0, -5}, /* 0xAF macron */ - {199, 3, 3, 4, 0, -5}, /* 0xB0 degree */ - {201, 3, 5, 4, 0, -5}, /* 0xB1 plusminus */ - {203, 3, 3, 4, 0, -5}, /* 0xB2 twosuperior */ - {205, 3, 3, 4, 0, -5}, /* 0xB3 threesuperior */ - {207, 2, 2, 3, 0, -5}, /* 0xB4 acute */ - {208, 3, 5, 4, 0, -5}, /* 0xB5 mu */ - {210, 3, 5, 4, 0, -5}, /* 0xB6 paragraph */ - {212, 3, 3, 4, 0, -4}, /* 0xB7 periodcentered */ - {214, 3, 3, 4, 0, -3}, /* 0xB8 cedilla */ - {216, 1, 3, 2, 0, -5}, /* 0xB9 onesuperior */ - {217, 3, 5, 4, 0, -5}, /* 0xBA ordmasculine */ - {219, 2, 3, 3, 0, -5}, /* 0xBB guillemotright */ - {220, 3, 5, 4, 0, -5}, /* 0xBC onequarter */ - {222, 3, 5, 4, 0, -5}, /* 0xBD onehalf */ - {224, 3, 5, 4, 0, -5}, /* 0xBE threequarters */ - {226, 3, 5, 4, 0, -5}, /* 0xBF questiondown */ - {228, 3, 5, 4, 0, -5}, /* 0xC0 Agrave */ - {230, 3, 5, 4, 0, -5}, /* 0xC1 Aacute */ - {232, 3, 5, 4, 0, -5}, /* 0xC2 Acircumflex */ - {234, 3, 5, 4, 0, -5}, /* 0xC3 Atilde */ - {236, 3, 5, 4, 0, -5}, /* 0xC4 Adieresis */ - {238, 3, 5, 4, 0, -5}, /* 0xC5 Aring */ - {240, 3, 5, 4, 0, -5}, /* 0xC6 AE */ - {242, 3, 6, 4, 0, -5}, /* 0xC7 Ccedilla */ - {245, 3, 5, 4, 0, -5}, /* 0xC8 Egrave */ - {247, 3, 5, 4, 0, -5}, /* 0xC9 Eacute */ - {249, 3, 5, 4, 0, -5}, /* 0xCA Ecircumflex */ - {251, 3, 5, 4, 0, -5}, /* 0xCB Edieresis */ - {253, 3, 5, 4, 0, -5}, /* 0xCC Igrave */ - {255, 3, 5, 4, 0, -5}, /* 0xCD Iacute */ - {257, 3, 5, 4, 0, -5}, /* 0xCE Icircumflex */ - {259, 3, 5, 4, 0, -5}, /* 0xCF Idieresis */ - {261, 3, 5, 4, 0, -5}, /* 0xD0 Eth */ - {263, 3, 5, 4, 0, -5}, /* 0xD1 Ntilde */ - {265, 3, 5, 4, 0, -5}, /* 0xD2 Ograve */ - {267, 3, 5, 4, 0, -5}, /* 0xD3 Oacute */ - {269, 3, 5, 4, 0, -5}, /* 0xD4 Ocircumflex */ - {271, 3, 5, 4, 0, -5}, /* 0xD5 Otilde */ - {273, 3, 5, 4, 0, -5}, /* 0xD6 Odieresis */ - {275, 3, 3, 4, 0, -4}, /* 0xD7 multiply */ - {277, 3, 5, 4, 0, -5}, /* 0xD8 Oslash */ - {279, 3, 5, 4, 0, -5}, /* 0xD9 Ugrave */ - {281, 3, 5, 4, 0, -5}, /* 0xDA Uacute */ - {283, 3, 5, 4, 0, -5}, /* 0xDB Ucircumflex */ - {285, 3, 5, 4, 0, -5}, /* 0xDC Udieresis */ - {287, 3, 5, 4, 0, -5}, /* 0xDD Yacute */ - {289, 3, 5, 4, 0, -5}, /* 0xDE Thorn */ - {291, 3, 6, 4, 0, -5}, /* 0xDF germandbls */ - {294, 3, 5, 4, 0, -5}, /* 0xE0 agrave */ - {296, 3, 5, 4, 0, -5}, /* 0xE1 aacute */ - {298, 3, 5, 4, 0, -5}, /* 0xE2 acircumflex */ - {300, 3, 5, 4, 0, -5}, /* 0xE3 atilde */ - {302, 3, 5, 4, 0, -5}, /* 0xE4 adieresis */ - {304, 3, 5, 4, 0, -5}, /* 0xE5 aring */ - {306, 3, 4, 4, 0, -4}, /* 0xE6 ae */ - {308, 3, 5, 4, 0, -4}, /* 0xE7 ccedilla */ - {310, 3, 5, 4, 0, -5}, /* 0xE8 egrave */ - {312, 3, 5, 4, 0, -5}, /* 0xE9 eacute */ - {314, 3, 5, 4, 0, -5}, /* 0xEA ecircumflex */ - {316, 3, 5, 4, 0, -5}, /* 0xEB edieresis */ - {318, 2, 5, 3, 0, -5}, /* 0xEC igrave */ - {320, 2, 5, 3, 0, -5}, /* 0xED iacute */ - {322, 3, 5, 4, 0, -5}, /* 0xEE icircumflex */ - {324, 3, 5, 4, 0, -5}, /* 0xEF idieresis */ - {326, 3, 5, 4, 0, -5}, /* 0xF0 eth */ - {328, 3, 5, 4, 0, -5}, /* 0xF1 ntilde */ - {330, 3, 5, 4, 0, -5}, /* 0xF2 ograve */ - {332, 3, 5, 4, 0, -5}, /* 0xF3 oacute */ - {334, 3, 5, 4, 0, -5}, /* 0xF4 ocircumflex */ - {336, 3, 5, 4, 0, -5}, /* 0xF5 otilde */ - {338, 3, 5, 4, 0, -5}, /* 0xF6 odieresis */ - {340, 3, 5, 4, 0, -5}, /* 0xF7 divide */ - {342, 3, 4, 4, 0, -4}, /* 0xF8 oslash */ - {344, 3, 5, 4, 0, -5}, /* 0xF9 ugrave */ - {346, 3, 5, 4, 0, -5}, /* 0xFA uacute */ - {348, 3, 5, 4, 0, -5}, /* 0xFB ucircumflex */ - {350, 3, 5, 4, 0, -5}, /* 0xFC udieresis */ - {352, 3, 6, 4, 0, -5}, /* 0xFD yacute */ - {355, 3, 5, 4, 0, -4}, /* 0xFE thorn */ - {357, 3, 6, 4, 0, -5}, /* 0xFF ydieresis */ - {360, 1, 1, 2, 0, -1}, /* 0x11D gcircumflex */ - {361, 3, 5, 4, 0, -5}, /* 0x152 OE */ - {363, 3, 4, 4, 0, -4}, /* 0x153 oe */ - {365, 3, 5, 4, 0, -5}, /* 0x160 Scaron */ - {367, 3, 5, 4, 0, -5}, /* 0x161 scaron */ - {369, 3, 5, 4, 0, -5}, /* 0x178 Ydieresis */ - {371, 3, 5, 4, 0, -5}, /* 0x17D Zcaron */ - {373, 3, 5, 4, 0, -5}, /* 0x17E zcaron */ - {375, 1, 1, 2, 0, -1}, /* 0xEA4 uni0EA4 */ - {376, 1, 1, 2, 0, -1}, /* 0x13A0 uni13A0 */ - {377, 1, 1, 2, 0, -3}, /* 0x2022 bullet */ - {378, 3, 1, 4, 0, -1}, /* 0x2026 ellipsis */ - {379, 3, 5, 4, 0, -5}, /* 0x20AC Euro */ - {381, 4, 5, 5, 0, -5}, /* 0xFFFD uniFFFD */ -#endif /* (TOMTHUMB_USE_EXTENDED) */ -}; - -const GFXfont TomThumb PROGMEM = {(uint8_t *)TomThumbBitmaps, - (GFXglyph *)TomThumbGlyphs, 0x20, 0x7E, 6}; +/** +** The original 3x5 font is licensed under the 3-clause BSD license: +** +** Copyright 1999 Brian J. Swetland +** Copyright 1999 Vassilii Khachaturov +** Portions (of vt100.c/vt100.h) copyright Dan Marks +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions, and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions, and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the authors may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +** Modifications to Tom Thumb for improved readability are from Robey Pointer, +** see: +** http://robey.lag.net/2010/01/23/tiny-monospace-font.html +** +** The original author does not have any objection to relicensing of Robey +** Pointer's modifications (in this file) in a more permissive license. See +** the discussion at the above blog, and also here: +** http://opengameart.org/forumtopic/how-to-submit-art-using-the-3-clause-bsd-license +** +** Feb 21, 2016: Conversion from Linux BDF --> Adafruit GFX font, +** with the help of this Python script: +** https://gist.github.com/skelliam/322d421f028545f16f6d +** William Skellenger (williamj@skellenger.net) +** Twitter: @skelliam +** +** Jan 09, 2020: Bitmaps now compressed, to fix the bounding box problem, +** because non-compressed the calculated text width were wrong. +** Andreas Merkle (web@blue-andi.de) +*/ + +#ifndef TOMTHUMB_USE_EXTENDED +#define TOMTHUMB_USE_EXTENDED 0 +#endif + +const uint8_t TomThumbBitmaps[] PROGMEM = { + 0x00, /* 0x20 space */ + 0xE8, /* 0x21 exclam */ + 0xB4, /* 0x22 quotedbl */ + 0xBE, 0xFA, /* 0x23 numbersign */ + 0x79, 0xE4, /* 0x24 dollar */ + 0x85, 0x42, /* 0x25 percent */ + 0xDB, 0xD6, /* 0x26 ampersand */ + 0xC0, /* 0x27 quotesingle */ + 0x6A, 0x40, /* 0x28 parenleft */ + 0x95, 0x80, /* 0x29 parenright */ + 0xAA, 0x80, /* 0x2A asterisk */ + 0x5D, 0x00, /* 0x2B plus */ + 0x60, /* 0x2C comma */ + 0xE0, /* 0x2D hyphen */ + 0x80, /* 0x2E period */ + 0x25, 0x48, /* 0x2F slash */ + 0x76, 0xDC, /* 0x30 zero */ + 0x59, 0x2E, /* 0x31 one */ + 0xC5, 0x4E, /* 0x32 two */ + 0xC5, 0x1C, /* 0x33 three */ + 0xB7, 0x92, /* 0x34 four */ + 0xF3, 0x1C, /* 0x35 five */ + 0x73, 0xDE, /* 0x36 six */ + 0xE5, 0x48, /* 0x37 seven */ + 0xF7, 0xDE, /* 0x38 eight */ + 0xF7, 0x9C, /* 0x39 nine */ + 0xA0, /* 0x3A colon */ + 0x46, /* 0x3B semicolon */ + 0x2A, 0x22, /* 0x3C less */ + 0xE3, 0x80, /* 0x3D equal */ + 0x88, 0xA8, /* 0x3E greater */ + 0xE5, 0x04, /* 0x3F question */ + 0x57, 0xC6, /* 0x40 at */ + 0x57, 0xDA, /* 0x41 A */ + 0xD7, 0x5C, /* 0x42 B */ + 0x72, 0x46, /* 0x43 C */ + 0xD6, 0xDC, /* 0x44 D */ + 0xF3, 0xCE, /* 0x45 E */ + 0xF3, 0xC8, /* 0x46 F */ + 0x73, 0xD6, /* 0x47 G */ + 0xB7, 0xDA, /* 0x48 H */ + 0xE9, 0x2E, /* 0x49 I */ + 0x24, 0xD4, /* 0x4A J */ + 0xB7, 0x5A, /* 0x4B K */ + 0x92, 0x4E, /* 0x4C L */ + 0xBF, 0xDA, /* 0x4D M */ + 0xBF, 0xFA, /* 0x4E N */ + 0x56, 0xD4, /* 0x4F O */ + 0xD7, 0x48, /* 0x50 P */ + 0x56, 0xF6, /* 0x51 Q */ + 0xD7, 0xEA, /* 0x52 R */ + 0x71, 0x1C, /* 0x53 S */ + 0xE9, 0x24, /* 0x54 T */ + 0xB6, 0xD6, /* 0x55 U */ + 0xB6, 0xA4, /* 0x56 V */ + 0xB7, 0xFA, /* 0x57 W */ + 0xB5, 0x5A, /* 0x58 X */ + 0xB5, 0x24, /* 0x59 Y */ + 0xE5, 0x4E, /* 0x5A Z */ + 0xF2, 0x4E, /* 0x5B bracketleft */ + 0x88, 0x80, /* 0x5C backslash */ + 0xE4, 0x9E, /* 0x5D bracketright */ + 0x54, /* 0x5E asciicircum */ + 0xE0, /* 0x5F underscore */ + 0x90, /* 0x60 grave */ + 0xCE, 0xF0, /* 0x61 a */ + 0x9A, 0xDC, /* 0x62 b */ + 0x72, 0x30, /* 0x63 c */ + 0x2E, 0xD6, /* 0x64 d */ + 0x77, 0x30, /* 0x65 e */ + 0x2B, 0xA4, /* 0x66 f */ + 0x77, 0x94, /* 0x67 g */ + 0x9A, 0xDA, /* 0x68 h */ + 0xB8, /* 0x69 i */ + 0x20, 0x9A, 0x80, /* 0x6A j */ + 0x97, 0x6A, /* 0x6B k */ + 0xC9, 0x2E, /* 0x6C l */ + 0xFF, 0xD0, /* 0x6D m */ + 0xD6, 0xD0, /* 0x6E n */ + 0x56, 0xA0, /* 0x6F o */ + 0xD6, 0xE8, /* 0x70 p */ + 0x76, 0xB2, /* 0x71 q */ + 0x72, 0x40, /* 0x72 r */ + 0x79, 0xE0, /* 0x73 s */ + 0x5D, 0x26, /* 0x74 t */ + 0xB6, 0xB0, /* 0x75 u */ + 0xB7, 0xA0, /* 0x76 v */ + 0xBF, 0xF0, /* 0x77 w */ + 0xA9, 0x50, /* 0x78 x */ + 0xB5, 0x94, /* 0x79 y */ + 0xEF, 0x70, /* 0x7A z */ + 0x6A, 0x26, /* 0x7B braceleft */ + 0xD8, /* 0x7C bar */ + 0xC8, 0xAC, /* 0x7D braceright */ + 0x78, /* 0x7E asciitilde */ +#if (TOMTHUMB_USE_EXTENDED) + 0xB8, /* 0xA1 exclamdown */ + 0x5E, 0x74, /* 0xA2 cent */ + 0x6B, 0xAE, /* 0xA3 sterling */ + 0xAB, 0xAA, /* 0xA4 currency */ + 0xB5, 0x74, /* 0xA5 yen */ + 0xD8, /* 0xA6 brokenbar */ + 0x6A, 0xAC, /* 0xA7 section */ + 0xA0, /* 0xA8 dieresis */ + 0x71, 0x80, /* 0xA9 copyright */ + 0x77, 0x8E, /* 0xAA ordfeminine */ + 0x64, /* 0xAB guillemotleft */ + 0xE4, /* 0xAC logicalnot */ + 0xC0, /* 0xAD softhyphen */ + 0xDA, 0x80, /* 0xAE registered */ + 0xE0, /* 0xAF macron */ + 0x55, 0x00, /* 0xB0 degree */ + 0x5D, 0x0E, /* 0xB1 plusminus */ + 0xC9, 0x80, /* 0xB2 twosuperior */ + 0xEF, 0x80, /* 0xB3 threesuperior */ + 0x60, /* 0xB4 acute */ + 0xB6, 0xE8, /* 0xB5 mu */ + 0x75, 0xB6, /* 0xB6 paragraph */ + 0xFF, 0x80, /* 0xB7 periodcentered */ + 0x47, 0x00, /* 0xB8 cedilla */ + 0xE0, /* 0xB9 onesuperior */ + 0x55, 0x0E, /* 0xBA ordmasculine */ + 0x98, /* 0xBB guillemotright */ + 0x90, 0x32, /* 0xBC onequarter */ + 0x90, 0x66, /* 0xBD onehalf */ + 0xD8, 0x32, /* 0xBE threequarters */ + 0x41, 0x4E, /* 0xBF questiondown */ + 0x45, 0x7A, /* 0xC0 Agrave */ + 0x51, 0x7A, /* 0xC1 Aacute */ + 0xE1, 0x7A, /* 0xC2 Acircumflex */ + 0x79, 0x7A, /* 0xC3 Atilde */ + 0xAA, 0xFA, /* 0xC4 Adieresis */ + 0xDA, 0xFA, /* 0xC5 Aring */ + 0x7B, 0xEE, /* 0xC6 AE */ + 0x72, 0x32, 0x80, /* 0xC7 Ccedilla */ + 0x47, 0xEE, /* 0xC8 Egrave */ + 0x53, 0xEE, /* 0xC9 Eacute */ + 0xE3, 0xEE, /* 0xCA Ecircumflex */ + 0xA3, 0xEE, /* 0xCB Edieresis */ + 0x47, 0xAE, /* 0xCC Igrave */ + 0x53, 0xAE, /* 0xCD Iacute */ + 0xE3, 0xAE, /* 0xCE Icircumflex */ + 0xA3, 0xAE, /* 0xCF Idieresis */ + 0xD7, 0xDC, /* 0xD0 Eth */ + 0xCE, 0xFA, /* 0xD1 Ntilde */ + 0x47, 0xDE, /* 0xD2 Ograve */ + 0x53, 0xDE, /* 0xD3 Oacute */ + 0xE3, 0xDE, /* 0xD4 Ocircumflex */ + 0xCF, 0xDE, /* 0xD5 Otilde */ + 0xA3, 0xDE, /* 0xD6 Odieresis */ + 0xAA, 0x80, /* 0xD7 multiply */ + 0x77, 0xDC, /* 0xD8 Oslash */ + 0x8A, 0xDE, /* 0xD9 Ugrave */ + 0x2A, 0xDE, /* 0xDA Uacute */ + 0xE2, 0xDE, /* 0xDB Ucircumflex */ + 0xA2, 0xDE, /* 0xDC Udieresis */ + 0x2A, 0xF4, /* 0xDD Yacute */ + 0x9E, 0xF8, /* 0xDE Thorn */ + 0x77, 0x5D, 0x00, /* 0xDF germandbls */ + 0x45, 0xDE, /* 0xE0 agrave */ + 0x51, 0xDE, /* 0xE1 aacute */ + 0xE1, 0xDE, /* 0xE2 acircumflex */ + 0x79, 0xDE, /* 0xE3 atilde */ + 0xA1, 0xDE, /* 0xE4 adieresis */ + 0x6D, 0xDE, /* 0xE5 aring */ + 0x7F, 0xE0, /* 0xE6 ae */ + 0x71, 0x94, /* 0xE7 ccedilla */ + 0x45, 0xF6, /* 0xE8 egrave */ + 0x51, 0xF6, /* 0xE9 eacute */ + 0xE1, 0xF6, /* 0xEA ecircumflex */ + 0xA1, 0xF6, /* 0xEB edieresis */ + 0x9A, 0x80, /* 0xEC igrave */ + 0x65, 0x40, /* 0xED iacute */ + 0xE1, 0x24, /* 0xEE icircumflex */ + 0xA1, 0x24, /* 0xEF idieresis */ + 0x79, 0xD6, /* 0xF0 eth */ + 0xCF, 0x5A, /* 0xF1 ntilde */ + 0x45, 0x54, /* 0xF2 ograve */ + 0x51, 0x54, /* 0xF3 oacute */ + 0xE1, 0x54, /* 0xF4 ocircumflex */ + 0xCD, 0x54, /* 0xF5 otilde */ + 0xA1, 0x54, /* 0xF6 odieresis */ + 0x43, 0x84, /* 0xF7 divide */ + 0x7E, 0xE0, /* 0xF8 oslash */ + 0x8A, 0xD6, /* 0xF9 ugrave */ + 0x2A, 0xD6, /* 0xFA uacute */ + 0xE2, 0xD6, /* 0xFB ucircumflex */ + 0xA2, 0xD6, /* 0xFC udieresis */ + 0x2A, 0xB2, 0x80, /* 0xFD yacute */ + 0x9A, 0xE8, /* 0xFE thorn */ + 0xA2, 0xB2, 0x80, /* 0xFF ydieresis */ + 0x00, /* 0x11D gcircumflex */ + 0x7B, 0xE6, /* 0x152 OE */ + 0x7F, 0x70, /* 0x153 oe */ + 0xAF, 0x3C, /* 0x160 Scaron */ + 0xAF, 0x3C, /* 0x161 scaron */ + 0xA2, 0xA4, /* 0x178 Ydieresis */ + 0xBD, 0xEE, /* 0x17D Zcaron */ + 0xBD, 0xEE, /* 0x17E zcaron */ + 0x00, /* 0xEA4 uni0EA4 */ + 0x00, /* 0x13A0 uni13A0 */ + 0x80, /* 0x2022 bullet */ + 0xA0, /* 0x2026 ellipsis */ + 0x7F, 0xE6, /* 0x20AC Euro */ + 0xEA, 0xAA, 0xE0, /* 0xFFFD uniFFFD */ +#endif /* (TOMTHUMB_USE_EXTENDED) */ +}; + +/* {offset, width, height, advance cursor, x offset, y offset} */ +const GFXglyph TomThumbGlyphs[] PROGMEM = { + {0, 1, 1, 2, 0, -5}, /* 0x20 space */ + {1, 1, 5, 2, 0, -5}, /* 0x21 exclam */ + {2, 3, 2, 4, 0, -5}, /* 0x22 quotedbl */ + {3, 3, 5, 4, 0, -5}, /* 0x23 numbersign */ + {5, 3, 5, 4, 0, -5}, /* 0x24 dollar */ + {7, 3, 5, 4, 0, -5}, /* 0x25 percent */ + {9, 3, 5, 4, 0, -5}, /* 0x26 ampersand */ + {11, 1, 2, 2, 0, -5}, /* 0x27 quotesingle */ + {12, 2, 5, 3, 0, -5}, /* 0x28 parenleft */ + {14, 2, 5, 3, 0, -5}, /* 0x29 parenright */ + {16, 3, 3, 4, 0, -5}, /* 0x2A asterisk */ + {18, 3, 3, 4, 0, -4}, /* 0x2B plus */ + {20, 2, 2, 3, 0, -2}, /* 0x2C comma */ + {21, 3, 1, 4, 0, -3}, /* 0x2D hyphen */ + {22, 1, 1, 2, 0, -1}, /* 0x2E period */ + {23, 3, 5, 4, 0, -5}, /* 0x2F slash */ + {25, 3, 5, 4, 0, -5}, /* 0x30 zero */ + {27, 3, 5, 4, 0, -5}, /* 0x31 one */ + {29, 3, 5, 4, 0, -5}, /* 0x32 two */ + {31, 3, 5, 4, 0, -5}, /* 0x33 three */ + {33, 3, 5, 4, 0, -5}, /* 0x34 four */ + {35, 3, 5, 4, 0, -5}, /* 0x35 five */ + {37, 3, 5, 4, 0, -5}, /* 0x36 six */ + {39, 3, 5, 4, 0, -5}, /* 0x37 seven */ + {41, 3, 5, 4, 0, -5}, /* 0x38 eight */ + {43, 3, 5, 4, 0, -5}, /* 0x39 nine */ + {45, 1, 3, 2, 0, -4}, /* 0x3A colon */ + {46, 2, 4, 3, 0, -4}, /* 0x3B semicolon */ + {47, 3, 5, 4, 0, -5}, /* 0x3C less */ + {49, 3, 3, 4, 0, -4}, /* 0x3D equal */ + {51, 3, 5, 4, 0, -5}, /* 0x3E greater */ + {53, 3, 5, 4, 0, -5}, /* 0x3F question */ + {55, 3, 5, 4, 0, -5}, /* 0x40 at */ + {57, 3, 5, 4, 0, -5}, /* 0x41 A */ + {59, 3, 5, 4, 0, -5}, /* 0x42 B */ + {61, 3, 5, 4, 0, -5}, /* 0x43 C */ + {63, 3, 5, 4, 0, -5}, /* 0x44 D */ + {65, 3, 5, 4, 0, -5}, /* 0x45 E */ + {67, 3, 5, 4, 0, -5}, /* 0x46 F */ + {69, 3, 5, 4, 0, -5}, /* 0x47 G */ + {71, 3, 5, 4, 0, -5}, /* 0x48 H */ + {73, 3, 5, 4, 0, -5}, /* 0x49 I */ + {75, 3, 5, 4, 0, -5}, /* 0x4A J */ + {77, 3, 5, 4, 0, -5}, /* 0x4B K */ + {79, 3, 5, 4, 0, -5}, /* 0x4C L */ + {81, 3, 5, 4, 0, -5}, /* 0x4D M */ + {83, 3, 5, 4, 0, -5}, /* 0x4E N */ + {85, 3, 5, 4, 0, -5}, /* 0x4F O */ + {87, 3, 5, 4, 0, -5}, /* 0x50 P */ + {89, 3, 5, 4, 0, -5}, /* 0x51 Q */ + {91, 3, 5, 4, 0, -5}, /* 0x52 R */ + {93, 3, 5, 4, 0, -5}, /* 0x53 S */ + {95, 3, 5, 4, 0, -5}, /* 0x54 T */ + {97, 3, 5, 4, 0, -5}, /* 0x55 U */ + {99, 3, 5, 4, 0, -5}, /* 0x56 V */ + {101, 3, 5, 4, 0, -5}, /* 0x57 W */ + {103, 3, 5, 4, 0, -5}, /* 0x58 X */ + {105, 3, 5, 4, 0, -5}, /* 0x59 Y */ + {107, 3, 5, 4, 0, -5}, /* 0x5A Z */ + {109, 3, 5, 4, 0, -5}, /* 0x5B bracketleft */ + {111, 3, 3, 4, 0, -4}, /* 0x5C backslash */ + {113, 3, 5, 4, 0, -5}, /* 0x5D bracketright */ + {115, 3, 2, 4, 0, -5}, /* 0x5E asciicircum */ + {116, 3, 1, 4, 0, -1}, /* 0x5F underscore */ + {117, 2, 2, 3, 0, -5}, /* 0x60 grave */ + {118, 3, 4, 4, 0, -4}, /* 0x61 a */ + {120, 3, 5, 4, 0, -5}, /* 0x62 b */ + {122, 3, 4, 4, 0, -4}, /* 0x63 c */ + {124, 3, 5, 4, 0, -5}, /* 0x64 d */ + {126, 3, 4, 4, 0, -4}, /* 0x65 e */ + {128, 3, 5, 4, 0, -5}, /* 0x66 f */ + {130, 3, 5, 4, 0, -4}, /* 0x67 g */ + {132, 3, 5, 4, 0, -5}, /* 0x68 h */ + {134, 1, 5, 2, 0, -5}, /* 0x69 i */ + {135, 3, 6, 4, 0, -5}, /* 0x6A j */ + {138, 3, 5, 4, 0, -5}, /* 0x6B k */ + {140, 3, 5, 4, 0, -5}, /* 0x6C l */ + {142, 3, 4, 4, 0, -4}, /* 0x6D m */ + {144, 3, 4, 4, 0, -4}, /* 0x6E n */ + {146, 3, 4, 4, 0, -4}, /* 0x6F o */ + {148, 3, 5, 4, 0, -4}, /* 0x70 p */ + {150, 3, 5, 4, 0, -4}, /* 0x71 q */ + {152, 3, 4, 4, 0, -4}, /* 0x72 r */ + {154, 3, 4, 4, 0, -4}, /* 0x73 s */ + {156, 3, 5, 4, 0, -5}, /* 0x74 t */ + {158, 3, 4, 4, 0, -4}, /* 0x75 u */ + {160, 3, 4, 4, 0, -4}, /* 0x76 v */ + {162, 3, 4, 4, 0, -4}, /* 0x77 w */ + {164, 3, 4, 4, 0, -4}, /* 0x78 x */ + {166, 3, 5, 4, 0, -4}, /* 0x79 y */ + {168, 3, 4, 4, 0, -4}, /* 0x7A z */ + {170, 3, 5, 4, 0, -5}, /* 0x7B braceleft */ + {172, 1, 5, 2, 0, -5}, /* 0x7C bar */ + {173, 3, 5, 4, 0, -5}, /* 0x7D braceright */ + {175, 3, 2, 4, 0, -5}, /* 0x7E asciitilde */ +#if (TOMTHUMB_USE_EXTENDED) + {176, 1, 5, 2, 0, -5}, /* 0xA1 exclamdown */ + {177, 3, 5, 4, 0, -5}, /* 0xA2 cent */ + {179, 3, 5, 4, 0, -5}, /* 0xA3 sterling */ + {181, 3, 5, 4, 0, -5}, /* 0xA4 currency */ + {183, 3, 5, 4, 0, -5}, /* 0xA5 yen */ + {185, 1, 5, 2, 0, -5}, /* 0xA6 brokenbar */ + {186, 3, 5, 4, 0, -5}, /* 0xA7 section */ + {188, 3, 1, 4, 0, -5}, /* 0xA8 dieresis */ + {189, 3, 3, 4, 0, -5}, /* 0xA9 copyright */ + {191, 3, 5, 4, 0, -5}, /* 0xAA ordfeminine */ + {193, 2, 3, 3, 0, -5}, /* 0xAB guillemotleft */ + {194, 3, 2, 4, 0, -4}, /* 0xAC logicalnot */ + {195, 2, 1, 3, 0, -3}, /* 0xAD softhyphen */ + {196, 3, 3, 4, 0, -5}, /* 0xAE registered */ + {198, 3, 1, 4, 0, -5}, /* 0xAF macron */ + {199, 3, 3, 4, 0, -5}, /* 0xB0 degree */ + {201, 3, 5, 4, 0, -5}, /* 0xB1 plusminus */ + {203, 3, 3, 4, 0, -5}, /* 0xB2 twosuperior */ + {205, 3, 3, 4, 0, -5}, /* 0xB3 threesuperior */ + {207, 2, 2, 3, 0, -5}, /* 0xB4 acute */ + {208, 3, 5, 4, 0, -5}, /* 0xB5 mu */ + {210, 3, 5, 4, 0, -5}, /* 0xB6 paragraph */ + {212, 3, 3, 4, 0, -4}, /* 0xB7 periodcentered */ + {214, 3, 3, 4, 0, -3}, /* 0xB8 cedilla */ + {216, 1, 3, 2, 0, -5}, /* 0xB9 onesuperior */ + {217, 3, 5, 4, 0, -5}, /* 0xBA ordmasculine */ + {219, 2, 3, 3, 0, -5}, /* 0xBB guillemotright */ + {220, 3, 5, 4, 0, -5}, /* 0xBC onequarter */ + {222, 3, 5, 4, 0, -5}, /* 0xBD onehalf */ + {224, 3, 5, 4, 0, -5}, /* 0xBE threequarters */ + {226, 3, 5, 4, 0, -5}, /* 0xBF questiondown */ + {228, 3, 5, 4, 0, -5}, /* 0xC0 Agrave */ + {230, 3, 5, 4, 0, -5}, /* 0xC1 Aacute */ + {232, 3, 5, 4, 0, -5}, /* 0xC2 Acircumflex */ + {234, 3, 5, 4, 0, -5}, /* 0xC3 Atilde */ + {236, 3, 5, 4, 0, -5}, /* 0xC4 Adieresis */ + {238, 3, 5, 4, 0, -5}, /* 0xC5 Aring */ + {240, 3, 5, 4, 0, -5}, /* 0xC6 AE */ + {242, 3, 6, 4, 0, -5}, /* 0xC7 Ccedilla */ + {245, 3, 5, 4, 0, -5}, /* 0xC8 Egrave */ + {247, 3, 5, 4, 0, -5}, /* 0xC9 Eacute */ + {249, 3, 5, 4, 0, -5}, /* 0xCA Ecircumflex */ + {251, 3, 5, 4, 0, -5}, /* 0xCB Edieresis */ + {253, 3, 5, 4, 0, -5}, /* 0xCC Igrave */ + {255, 3, 5, 4, 0, -5}, /* 0xCD Iacute */ + {257, 3, 5, 4, 0, -5}, /* 0xCE Icircumflex */ + {259, 3, 5, 4, 0, -5}, /* 0xCF Idieresis */ + {261, 3, 5, 4, 0, -5}, /* 0xD0 Eth */ + {263, 3, 5, 4, 0, -5}, /* 0xD1 Ntilde */ + {265, 3, 5, 4, 0, -5}, /* 0xD2 Ograve */ + {267, 3, 5, 4, 0, -5}, /* 0xD3 Oacute */ + {269, 3, 5, 4, 0, -5}, /* 0xD4 Ocircumflex */ + {271, 3, 5, 4, 0, -5}, /* 0xD5 Otilde */ + {273, 3, 5, 4, 0, -5}, /* 0xD6 Odieresis */ + {275, 3, 3, 4, 0, -4}, /* 0xD7 multiply */ + {277, 3, 5, 4, 0, -5}, /* 0xD8 Oslash */ + {279, 3, 5, 4, 0, -5}, /* 0xD9 Ugrave */ + {281, 3, 5, 4, 0, -5}, /* 0xDA Uacute */ + {283, 3, 5, 4, 0, -5}, /* 0xDB Ucircumflex */ + {285, 3, 5, 4, 0, -5}, /* 0xDC Udieresis */ + {287, 3, 5, 4, 0, -5}, /* 0xDD Yacute */ + {289, 3, 5, 4, 0, -5}, /* 0xDE Thorn */ + {291, 3, 6, 4, 0, -5}, /* 0xDF germandbls */ + {294, 3, 5, 4, 0, -5}, /* 0xE0 agrave */ + {296, 3, 5, 4, 0, -5}, /* 0xE1 aacute */ + {298, 3, 5, 4, 0, -5}, /* 0xE2 acircumflex */ + {300, 3, 5, 4, 0, -5}, /* 0xE3 atilde */ + {302, 3, 5, 4, 0, -5}, /* 0xE4 adieresis */ + {304, 3, 5, 4, 0, -5}, /* 0xE5 aring */ + {306, 3, 4, 4, 0, -4}, /* 0xE6 ae */ + {308, 3, 5, 4, 0, -4}, /* 0xE7 ccedilla */ + {310, 3, 5, 4, 0, -5}, /* 0xE8 egrave */ + {312, 3, 5, 4, 0, -5}, /* 0xE9 eacute */ + {314, 3, 5, 4, 0, -5}, /* 0xEA ecircumflex */ + {316, 3, 5, 4, 0, -5}, /* 0xEB edieresis */ + {318, 2, 5, 3, 0, -5}, /* 0xEC igrave */ + {320, 2, 5, 3, 0, -5}, /* 0xED iacute */ + {322, 3, 5, 4, 0, -5}, /* 0xEE icircumflex */ + {324, 3, 5, 4, 0, -5}, /* 0xEF idieresis */ + {326, 3, 5, 4, 0, -5}, /* 0xF0 eth */ + {328, 3, 5, 4, 0, -5}, /* 0xF1 ntilde */ + {330, 3, 5, 4, 0, -5}, /* 0xF2 ograve */ + {332, 3, 5, 4, 0, -5}, /* 0xF3 oacute */ + {334, 3, 5, 4, 0, -5}, /* 0xF4 ocircumflex */ + {336, 3, 5, 4, 0, -5}, /* 0xF5 otilde */ + {338, 3, 5, 4, 0, -5}, /* 0xF6 odieresis */ + {340, 3, 5, 4, 0, -5}, /* 0xF7 divide */ + {342, 3, 4, 4, 0, -4}, /* 0xF8 oslash */ + {344, 3, 5, 4, 0, -5}, /* 0xF9 ugrave */ + {346, 3, 5, 4, 0, -5}, /* 0xFA uacute */ + {348, 3, 5, 4, 0, -5}, /* 0xFB ucircumflex */ + {350, 3, 5, 4, 0, -5}, /* 0xFC udieresis */ + {352, 3, 6, 4, 0, -5}, /* 0xFD yacute */ + {355, 3, 5, 4, 0, -4}, /* 0xFE thorn */ + {357, 3, 6, 4, 0, -5}, /* 0xFF ydieresis */ + {360, 1, 1, 2, 0, -1}, /* 0x11D gcircumflex */ + {361, 3, 5, 4, 0, -5}, /* 0x152 OE */ + {363, 3, 4, 4, 0, -4}, /* 0x153 oe */ + {365, 3, 5, 4, 0, -5}, /* 0x160 Scaron */ + {367, 3, 5, 4, 0, -5}, /* 0x161 scaron */ + {369, 3, 5, 4, 0, -5}, /* 0x178 Ydieresis */ + {371, 3, 5, 4, 0, -5}, /* 0x17D Zcaron */ + {373, 3, 5, 4, 0, -5}, /* 0x17E zcaron */ + {375, 1, 1, 2, 0, -1}, /* 0xEA4 uni0EA4 */ + {376, 1, 1, 2, 0, -1}, /* 0x13A0 uni13A0 */ + {377, 1, 1, 2, 0, -3}, /* 0x2022 bullet */ + {378, 3, 1, 4, 0, -1}, /* 0x2026 ellipsis */ + {379, 3, 5, 4, 0, -5}, /* 0x20AC Euro */ + {381, 4, 5, 5, 0, -5}, /* 0xFFFD uniFFFD */ +#endif /* (TOMTHUMB_USE_EXTENDED) */ +}; + +const GFXfont TomThumb PROGMEM = {(uint8_t *)TomThumbBitmaps, + (GFXglyph *)TomThumbGlyphs, 0x20, 0x7E, 6}; diff --git a/lib/Adafruit_ILI9341/Adafruit_ILI9341.cpp b/lib/Adafruit_ILI9341/Adafruit_ILI9341.cpp index 0a6d3a28a..2a61cf3da 100644 --- a/lib/Adafruit_ILI9341/Adafruit_ILI9341.cpp +++ b/lib/Adafruit_ILI9341/Adafruit_ILI9341.cpp @@ -448,82 +448,6 @@ static const uint8_t PROGMEM initcmd_9481_CMI8[] = { // ILI9481 CMI8 (TFT_eSPI I // clang-format on -#ifdef ILI9341_ENABLE_ILI948X - -// clang-format off -static const uint8_t PROGMEM initcmd_9486[] = { // ILI9486 - ILI9341_SLPOUT, 0x80, // Exit Sleep - ILI9341_PIXFMT, 1, 0x55, // Pixel format 0x55=16bit, 0x66=18bit - ILI9341_PWCTR3, 1, 0x44, // Power control3 - ILI9341_VMCTR1, 4, 0x00, 0x00, 0x00, 0x00, // VCM control - ILI9341_GMCTRP1, 15, 0x0F, 0x1F, 0x1c, 0x0C, 0x0F, 0x08, 0x48, 0x98, 0x37, 0x0A, 0x13, 0x04, 0x11, 0x0D, 0x00, - ILI9341_GMCTRN1, 15, 0x0F, 0x32, 0x2E, 0x0B, 0x0D, 0x05, 0x47, 0x75, 0x37, 0x06, 0x10, 0x03, 0x24, 0x20, 0x00, - ILI9341_INVOFF, 0, - ILI9341_MADCTL, 1, 0x48, // Memory Access Control - ILI9341_DISPON, 0x80, // Display on - 0x00 // End of list -}; - -// clang-format on - -// clang-format off -static const uint8_t PROGMEM initcmd_9488[] = { // ILI9488 - // Set gamma - ILI9341_GMCTRP1, 15, 0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, - 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, - 0x0F, - - // Set gamma - ILI9341_GMCTRN1, 15, 0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, - 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, - 0x0F, - - // Power control VRH[5:0] - ILI9341_PWCTR1, 2, 0x17, 0x15, - - // Power control SAP[2:0];BT[3:0] - ILI9341_PWCTR2, 1, 0x41, - - // VCM control - ILI9341_VMCTR1, 3, 0x00, 0x12, 0x80, - - // Memory access Control - ILI9341_MADCTL, 1, 0x48, - - // Pixel format 0x55=16bit, 0x66=18bit - ILI9341_PIXFMT, 1, 0x55, - - // Interface control Mode - 0xB0, 1, 0x80, - - // Frame rate - ILI9341_FRMCTR1, 1, 0xA0, - - // Display onversion control - ILI9341_INVCTR, 1, 0x02, - - // Display function control - ILI9341_DFUNCTR, 2, 0x02, 0x02, - - // Disable 24 bit data - 0xE9, 1, 0x00, - - // Adjust control - 0xF7, 4, 0xA9, 0x51, 0x2C, 0x82, - - // Exit sleep - ILI9341_SLPOUT, 0x80, - - // Display on - ILI9341_DISPON, 0x80, - - // End of list - 0x00 -}; -#endif // ifdef ILI9341_ENABLE_ILI948X - -// clang-format on - /**************************************************************************/ /*! @@ -578,14 +502,6 @@ void Adafruit_ILI9341::begin(uint32_t freq) { case ILI_TYPE_9481_CMI8: // ILI9481 CMI8 addr = initcmd_9481_CMI8; break; - #ifdef ILI9341_ENABLE_ILI948X - case ILI_TYPE_9486: // ILI9486 - addr = initcmd_9486; - break; - case ILI_TYPE_9488: // ILI9488 - addr = initcmd_9488; - break; - #endif // ifdef ILI9341_ENABLE_ILI948X default: addr = initcmd; break; @@ -637,14 +553,6 @@ void Adafruit_ILI9341::setRotation(uint8_t m) { case ILI_TYPE_9341: // ILI9341 // m = (MADCTL_MX | MADCTL_BGR); // break; - #ifdef ILI9341_ENABLE_ILI948X - case ILI_TYPE_9486: // ILI9486 - // m = (MADCTL_MX | MADCTL_BGR); - // break; - case ILI_TYPE_9488: // ILI9488 - // m = (MADCTL_MX | MADCTL_BGR); - // break; - #endif // ifdef ILI9341_ENABLE_ILI948X default: m = (MADCTL_MX | MADCTL_BGR); break; @@ -671,14 +579,6 @@ void Adafruit_ILI9341::setRotation(uint8_t m) { case ILI_TYPE_9341: // ILI9341 // m = (MADCTL_MV | MADCTL_BGR); // break; - #ifdef ILI9341_ENABLE_ILI948X - case ILI_TYPE_9486: // ILI9486 - // m = (MADCTL_MV | MADCTL_BGR); - // break; - case ILI_TYPE_9488: // ILI9488 - // m = (MADCTL_MV | MADCTL_BGR); - // break; - #endif // ifdef ILI9341_ENABLE_ILI948X default: m = (MADCTL_MV | MADCTL_BGR); break; @@ -705,14 +605,6 @@ void Adafruit_ILI9341::setRotation(uint8_t m) { case ILI_TYPE_9341: // ILI9341 // m = (MADCTL_MY | MADCTL_BGR); // break; - #ifdef ILI9341_ENABLE_ILI948X - case ILI_TYPE_9486: // ILI9486 - // m = (MADCTL_MY | MADCTL_BGR); - // break; - case ILI_TYPE_9488: // ILI9488 - // m = (MADCTL_MY | MADCTL_BGR); - // break; - #endif // ifdef ILI9341_ENABLE_ILI948X default: m = (MADCTL_MY | MADCTL_BGR); break; @@ -739,14 +631,6 @@ void Adafruit_ILI9341::setRotation(uint8_t m) { case ILI_TYPE_9341: // ILI9341 // m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); // break; - #ifdef ILI9341_ENABLE_ILI948X - case ILI_TYPE_9486: // ILI9486 - // m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); - // break; - case ILI_TYPE_9488: // ILI9488 - // m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); - // break; - #endif // ifdef ILI9341_ENABLE_ILI948X default: m = (MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); break; diff --git a/lib/Adafruit_ILI9341/Adafruit_ILI9341.h b/lib/Adafruit_ILI9341/Adafruit_ILI9341.h index 81fc609b3..346877acc 100644 --- a/lib/Adafruit_ILI9341/Adafruit_ILI9341.h +++ b/lib/Adafruit_ILI9341/Adafruit_ILI9341.h @@ -43,8 +43,6 @@ #include #include -// #define ILI9341_ENABLE_ILI948X ///< Enable ILI9486 and ILI9488 support, MUST reflect a similar #define in P095_data_struct.h ! - #define ILI9341_TFTWIDTH 240 ///< ILI9341 max TFT width #define ILI9341_TFTHEIGHT 320 ///< ILI9341 max TFT height @@ -138,10 +136,6 @@ #define ILI_TYPE_9481_RGB 7 #define ILI_TYPE_9481_CMI7 8 #define ILI_TYPE_9481_CMI8 9 -#ifdef ILI9341_ENABLE_ILI948X -# define ILI_TYPE_9486 10 -# define ILI_TYPE_9488 11 -#endif // ifndef ILI9341_ENABLE_ILI948X /**************************************************************************/ diff --git a/lib/Adafruit_NeoMatrix/examples/MatrixGFXDemo/MatrixGFXDemo.ino b/lib/Adafruit_NeoMatrix/examples/MatrixGFXDemo/MatrixGFXDemo.ino index 1960215b6..30b19b219 100644 --- a/lib/Adafruit_NeoMatrix/examples/MatrixGFXDemo/MatrixGFXDemo.ino +++ b/lib/Adafruit_NeoMatrix/examples/MatrixGFXDemo/MatrixGFXDemo.ino @@ -150,56 +150,56 @@ static const uint8_t PROGMEM mono_bmp[][8] = { { // 0: checkered 1 - B10101010, - B01010101, - B10101010, - B01010101, - B10101010, - B01010101, - B10101010, - B01010101, + 0b10101010, + 0b01010101, + 0b10101010, + 0b01010101, + 0b10101010, + 0b01010101, + 0b10101010, + 0b01010101, }, { // 1: checkered 2 - B01010101, - B10101010, - B01010101, - B10101010, - B01010101, - B10101010, - B01010101, - B10101010, + 0b01010101, + 0b10101010, + 0b01010101, + 0b10101010, + 0b01010101, + 0b10101010, + 0b01010101, + 0b10101010, }, { // 2: smiley - B00111100, - B01000010, - B10100101, - B10000001, - B10100101, - B10011001, - B01000010, - B00111100 }, + 0b00111100, + 0b01000010, + 0b10100101, + 0b10000001, + 0b10100101, + 0b10011001, + 0b01000010, + 0b00111100 }, { // 3: neutral - B00111100, - B01000010, - B10100101, - B10000001, - B10111101, - B10000001, - B01000010, - B00111100 }, + 0b00111100, + 0b01000010, + 0b10100101, + 0b10000001, + 0b10111101, + 0b10000001, + 0b01000010, + 0b00111100 }, { // 4; frowny - B00111100, - B01000010, - B10100101, - B10000001, - B10011001, - B10100101, - B01000010, - B00111100 }, + 0b00111100, + 0b01000010, + 0b10100101, + 0b10000001, + 0b10011001, + 0b10100101, + 0b01000010, + 0b00111100 }, }; static const uint16_t PROGMEM diff --git a/lib/Adafruit_NeoPixel/.gitignore b/lib/Adafruit_NeoPixel/.gitignore new file mode 100644 index 000000000..c2a26c038 --- /dev/null +++ b/lib/Adafruit_NeoPixel/.gitignore @@ -0,0 +1,4 @@ +# Our handy .gitignore for automation ease +Doxyfile* +doxygen_sqlite3.db +html diff --git a/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp b/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp new file mode 100644 index 000000000..c550b2361 --- /dev/null +++ b/lib/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp @@ -0,0 +1,3474 @@ +/*! + * @file Adafruit_NeoPixel.cpp + * + * @mainpage Arduino Library for driving Adafruit NeoPixel addressable LEDs, + * FLORA RGB Smart Pixels and compatible devicess -- WS2811, WS2812, WS2812B, + * SK6812, etc. + * + * @section intro_sec Introduction + * + * This is the documentation for Adafruit's NeoPixel library for the + * Arduino platform, allowing a broad range of microcontroller boards + * (most AVR boards, many ARM devices, ESP8266 and ESP32, among others) + * to control Adafruit NeoPixels, FLORA RGB Smart Pixels and compatible + * devices -- WS2811, WS2812, WS2812B, SK6812, etc. + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products + * from Adafruit! + * + * @section author Author + * + * Written by Phil "Paint Your Dragon" Burgess for Adafruit Industries, + * with contributions by PJRC, Michael Miller and other members of the + * open source community. + * + * @section license License + * + * This file is part of the Adafruit_NeoPixel library. + * + * Adafruit_NeoPixel is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * Adafruit_NeoPixel is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with NeoPixel. If not, see + * . + * + */ + +#include "Adafruit_NeoPixel.h" + +#if defined(TARGET_LPC1768) +#include +#endif + +#if defined(NRF52) || defined(NRF52_SERIES) +#include "nrf.h" + +// Interrupt is only disabled if there is no PWM device available +// Note: Adafruit Bluefruit nrf52 does not use this option +//#define NRF52_DISABLE_INT +#endif + +#if defined(ARDUINO_ARCH_NRF52840) +#if defined __has_include +#if __has_include() +#include +#endif +#endif +#endif + +/*! + @brief NeoPixel constructor when length, pin and pixel type are known + at compile-time. + @param n Number of NeoPixels in strand. + @param p Arduino pin number which will drive the NeoPixel data in. + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @return Adafruit_NeoPixel object. Call the begin() function before use. +*/ +Adafruit_NeoPixel::Adafruit_NeoPixel(uint16_t n, int16_t p, neoPixelType t) + : begun(false), brightness(0), pixels(NULL), endTime(0) { + updateType(t); + updateLength(n); + setPin(p); +#if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + init = true; +#endif +} + +/*! + @brief "Empty" NeoPixel constructor when length, pin and/or pixel type + are not known at compile-time, and must be initialized later with + updateType(), updateLength() and setPin(). + @return Adafruit_NeoPixel object. Call the begin() function before use. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +Adafruit_NeoPixel::Adafruit_NeoPixel() + : +#if defined(NEO_KHZ400) + is800KHz(true), +#endif + begun(false), numLEDs(0), numBytes(0), pin(-1), brightness(0), + pixels(NULL), rOffset(1), gOffset(0), bOffset(2), wOffset(1), endTime(0) { +} + +/*! + @brief Deallocate Adafruit_NeoPixel object, set data pin back to INPUT. +*/ +Adafruit_NeoPixel::~Adafruit_NeoPixel() { + free(pixels); + if (pin >= 0) + pinMode(pin, INPUT); +} + +/*! + @brief Configure NeoPixel pin for output. +*/ +void Adafruit_NeoPixel::begin(void) { + if (pin >= 0) { + pinMode(pin, OUTPUT); + digitalWrite(pin, LOW); + } + begun = true; +} + +/*! + @brief Change the length of a previously-declared Adafruit_NeoPixel + strip object. Old data is deallocated and new data is cleared. + Pin number and pixel format are unchanged. + @param n New length of strip, in pixels. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax (length, pin, + type). +*/ +void Adafruit_NeoPixel::updateLength(uint16_t n) { + free(pixels); // Free existing data (if any) + + // Allocate new data -- note: ALL PIXELS ARE CLEARED + numBytes = n * ((wOffset == rOffset) ? 3 : 4); + if ((pixels = (uint8_t *)malloc(numBytes))) { + memset(pixels, 0, numBytes); + numLEDs = n; + } else { + numLEDs = numBytes = 0; + } +} + +/*! + @brief Change the pixel format of a previously-declared + Adafruit_NeoPixel strip object. If format changes from one of + the RGB variants to an RGBW variant (or RGBW to RGB), the old + data will be deallocated and new data is cleared. Otherwise, + the old data will remain in RAM and is not reordered to the + new format, so it's advisable to follow up with clear(). + @param t Pixel type -- add together NEO_* constants defined in + Adafruit_NeoPixel.h, for example NEO_GRB+NEO_KHZ800 for + NeoPixels expecting an 800 KHz (vs 400 KHz) data stream + with color bytes expressed in green, red, blue order per + pixel. + @note This function is deprecated, here only for old projects that + may still be calling it. New projects should instead use the + 'new' keyword with the first constructor syntax + (length, pin, type). +*/ +void Adafruit_NeoPixel::updateType(neoPixelType t) { + bool oldThreeBytesPerPixel = (wOffset == rOffset); // false if RGBW + + wOffset = (t >> 6) & 0b11; // See notes in header file + rOffset = (t >> 4) & 0b11; // regarding R/G/B/W offsets + gOffset = (t >> 2) & 0b11; + bOffset = t & 0b11; +#if defined(NEO_KHZ400) + is800KHz = (t < 256); // 400 KHz flag is 1<<8 +#endif + + // If bytes-per-pixel has changed (and pixel data was previously + // allocated), re-allocate to new size. Will clear any data. + if (pixels) { + bool newThreeBytesPerPixel = (wOffset == rOffset); + if (newThreeBytesPerPixel != oldThreeBytesPerPixel) + updateLength(numLEDs); + } +} + +// RP2040 specific driver +#if defined(ARDUINO_ARCH_RP2040) +void Adafruit_NeoPixel::rp2040Init(uint8_t pin, bool is800KHz) +{ + uint offset = pio_add_program(pio, &ws2812_program); + + if (is800KHz) + { + // 800kHz, 8 bit transfers + ws2812_program_init(pio, sm, offset, pin, 800000, 8); + } + else + { + // 400kHz, 8 bit transfers + ws2812_program_init(pio, sm, offset, pin, 400000, 8); + } +} +// Not a user API +void Adafruit_NeoPixel::rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz) +{ + if (this->init) + { + // On first pass through initialise the PIO + rp2040Init(pin, is800KHz); + this->init = false; + } + + while(numBytes--) + // Bits for transmission must be shifted to top 8 bits + pio_sm_put_blocking(pio, sm, ((uint32_t)*pixels++)<< 24); +} + +#endif + +#if defined(ESP8266) +// ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution +extern "C" IRAM_ATTR void espShow(int16_t pin, uint8_t *pixels, + uint32_t numBytes, uint8_t type); +#elif defined(ESP32) +extern "C" void espShow(int16_t pin, uint8_t *pixels, uint32_t numBytes, + uint8_t type); +#endif // ESP8266 + +#if defined(K210) +#define KENDRYTE_K210 1 +#endif + +#if defined(KENDRYTE_K210) +extern "C" void k210Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, + boolean is800KHz); +#endif // KENDRYTE_K210 +/*! + @brief Transmit pixel data in RAM to NeoPixels. + @note On most architectures, interrupts are temporarily disabled in + order to achieve the correct NeoPixel signal timing. This means + that the Arduino millis() and micros() functions, which require + interrupts, will lose small intervals of time whenever this + function is called (about 30 microseconds per RGB pixel, 40 for + RGBW pixels). There's no easy fix for this, but a few + specialized alternative or companion libraries exist that use + very device-specific peripherals to work around it. +*/ +void Adafruit_NeoPixel::show(void) { + + if (!pixels) + return; + + // Data latch = 300+ microsecond pause in the output stream. Rather than + // put a delay at the end of the function, the ending time is noted and + // the function will simply hold off (if needed) on issuing the + // subsequent round of data until the latch time has elapsed. This + // allows the mainline code to start generating the next frame of data + // rather than stalling for the latch. + while (!canShow()) + ; + // endTime is a private member (rather than global var) so that multiple + // instances on different pins can be quickly issued in succession (each + // instance doesn't delay the next). + + // In order to make this code runtime-configurable to work with any pin, + // SBI/CBI instructions are eschewed in favor of full PORT writes via the + // OUT or ST instructions. It relies on two facts: that peripheral + // functions (such as PWM) take precedence on output pins, so our PORT- + // wide writes won't interfere, and that interrupts are globally disabled + // while data is being issued to the LEDs, so no other code will be + // accessing the PORT. The code takes an initial 'snapshot' of the PORT + // state, computes 'pin high' and 'pin low' values, and writes these back + // to the PORT register as needed. + + // NRF52 may use PWM + DMA (if available), may not need to disable interrupt + // ESP32 may not disable interrupts because espShow() uses RMT which tries to acquire locks +#if !(defined(NRF52) || defined(NRF52_SERIES) || defined(ESP32)) + noInterrupts(); // Need 100% focus on instruction timing +#endif + +#if defined(__AVR__) + // AVR MCUs -- ATmega & ATtiny (no XMEGA) --------------------------------- + + volatile uint16_t i = numBytes; // Loop counter + volatile uint8_t *ptr = pixels, // Pointer to next byte + b = *ptr++, // Current byte value + hi, // PORT w/output bit set high + lo; // PORT w/output bit set low + + // Hand-tuned assembly code issues data to the LED drivers at a specific + // rate. There's separate code for different CPU speeds (8, 12, 16 MHz) + // for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The + // datastream timing for the LED drivers allows a little wiggle room each + // way (listed in the datasheets), so the conditions for compiling each + // case are set up for a range of frequencies rather than just the exact + // 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on + // devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based + // on the datasheet figures and have not been extensively tested outside + // the canonical 8/12/16 MHz speeds; there's no guarantee these will work + // close to the extremes (or possibly they could be pushed further). + // Keep in mind only one CPU speed case actually gets compiled; the + // resulting program isn't as massive as it might look from source here. + +// 8 MHz(ish) AVR --------------------------------------------------------- +#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + volatile uint8_t n1, n2 = 0; // First, next bits out + + // Squeezing an 800 KHz stream out of an 8 MHz chip requires code + // specific to each PORT register. + + // 10 instruction clocks per bit: HHxxxxxLLL + // OUT instructions: ^ ^ ^ (T=0,2,7) + + // PORTD OUTPUT ---------------------------------------------------- + +#if defined(PORTD) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + n1 = lo; + if (b & 0x80) + n1 = hi; + + // Dirty trick: RJMPs proceeding to the next instruction are used + // to delay two clock cycles in one instruction word (rather than + // using two NOPs). This was necessary in order to squeeze the + // loop down to exactly 64 words -- the maximum possible for a + // relative branch. + + asm volatile( + "headD:" + "\n\t" // Clk Pseudocode + // Bit 7: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 6" + "\n\t" // 1-2 if(b & 0x40) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 6: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 5" + "\n\t" // 1-2 if(b & 0x20) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 5: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 4" + "\n\t" // 1-2 if(b & 0x10) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 4: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 3" + "\n\t" // 1-2 if(b & 0x08) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 3: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 2" + "\n\t" // 1-2 if(b & 0x04) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 2: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 1" + "\n\t" // 1-2 if(b & 0x02) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "rjmp .+0" + "\n\t" // 2 nop nop + // Bit 1: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" + "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" + "\n\t" // 1 PORT = n1 + "rjmp .+0" + "\n\t" // 2 nop nop + "sbrc %[byte] , 0" + "\n\t" // 1-2 if(b & 0x01) + "mov %[n2] , %[hi]" + "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "sbiw %[count], 1" + "\n\t" // 2 i-- (don't act on Z flag yet) + // Bit 0: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" + "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" + "\n\t" // 1 PORT = n2 + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 0x80) + "mov %[n1] , %[hi]" + "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo + "brne headD" + "\n" // 2 while(i) (Z flag set above) + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + } else // other PORT(s) +#endif // defined(PORTB/C/F) +#endif // defined(PORTD) + + // PORTB OUTPUT ---------------------------------------------------- + +#if defined(PORTB) +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + if (port == &PORTB) { +#endif // defined(PORTD/C/F) + + // Same as above, just switched to PORTB and stripped of comments. + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + n1 = lo; + if (b & 0x80) + n1 = hi; + + asm volatile( + "headB:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headB" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + } +#endif +#if defined(PORTC) || defined(PORTF) + else +#endif // defined(PORTC/F) +#endif // defined(PORTB) + + // PORTC OUTPUT ---------------------------------------------------- + +#if defined(PORTC) +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + if (port == &PORTC) { +#endif // defined(PORTD/B/F) + + // Same as above, just switched to PORTC and stripped of comments. + hi = PORTC | pinMask; + lo = PORTC & ~pinMask; + n1 = lo; + if (b & 0x80) + n1 = hi; + + asm volatile( + "headC:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headC" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + } +#endif // defined(PORTD/B/F) +#if defined(PORTF) + else +#endif +#endif // defined(PORTC) + + // PORTF OUTPUT ---------------------------------------------------- + +#if defined(PORTF) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) + + hi = PORTF | pinMask; + lo = PORTF & ~pinMask; + n1 = lo; + if (b & 0x80) + n1 = hi; + + asm volatile( + "headF:" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 6" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 5" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 4" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 3" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 2" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 1" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "rjmp .+0" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n2] , %[lo]" + "\n\t" + "out %[port] , %[n1]" + "\n\t" + "rjmp .+0" + "\n\t" + "sbrc %[byte] , 0" + "\n\t" + "mov %[n2] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "mov %[n1] , %[lo]" + "\n\t" + "out %[port] , %[n2]" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[n1] , %[hi]" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "brne headF" + "\n" + : [byte] "+r"(b), [n1] "+r"(n1), [n2] "+r"(n2), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr), [hi] "r"(hi), + [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + } +#endif // defined(PORTD/B/C) +#endif // defined(PORTF) + +#if defined(NEO_KHZ400) + } else { // end 800 KHz, do 400 KHz + + // Timing is more relaxed; unrolling the inner loop for each bit is + // not necessary. Still using the peculiar RJMPs as 2X NOPs, not out + // of need but just to trim the code size down a little. + // This 400-KHz-datastream-on-8-MHz-CPU code is not quite identical + // to the 800-on-16 code later -- the hi/lo timing between WS2811 and + // WS2812 is not simply a 2:1 scale! + + // 20 inst. clocks per bit: HHHHxxxxxxLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile("head20:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 6) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 7) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 8) + "breq nextbyte20" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 10) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp head20" + "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" + "\n\t" // (T = 10) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 12) + "nop" + "\n\t" // 1 nop (T = 13) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 14) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 16) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 18) + "brne head20" + "\n" // 2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr)); + } +#endif // NEO_KHZ400 + +// 12 MHz(ish) AVR -------------------------------------------------------- +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + // In the 12 MHz case, an optimized 800 KHz datastream (no dead time + // between bytes) requires a PORT-specific loop similar to the 8 MHz + // code (but a little more relaxed in this case). + + // 15 instruction clocks per bit: HHHHxxxxxxLLLLL + // OUT instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next; + + // PORTD OUTPUT ---------------------------------------------------- + +#if defined(PORTD) +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + if (port == &PORTD) { +#endif // defined(PORTB/C/F) + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + next = lo; + if (b & 0x80) + next = hi; + + // Don't "optimize" the OUT calls into the bitTime subroutine; + // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs! + asm volatile("headD:" + "\n\t" // (T = 0) + "out %[port], %[hi]" + "\n\t" // (T = 1) + "rcall bitTimeD" + "\n\t" // Bit 7 (T = 15) + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 6 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 5 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 4 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 3 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 2 + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeD" + "\n\t" // Bit 1 + // Bit 0: + "out %[port] , %[hi]" + "\n\t" // 1 PORT = hi (T = 1) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 3) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 5) + "out %[port] , %[next]" + "\n\t" // 1 PORT = next (T = 6) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 7) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next] , %[hi]" + "\n\t" // 0-1 next = hi (T = 9) + "nop" + "\n\t" // 1 (T = 10) + "out %[port] , %[lo]" + "\n\t" // 1 PORT = lo (T = 11) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 13) + "brne headD" + "\n\t" // 2 if(i != 0) -> (next byte) + "rjmp doneD" + "\n\t" + "bitTimeD:" + "\n\t" // nop nop nop (T = 4) + "out %[port], %[next]" + "\n\t" // 1 PORT = next (T = 5) + "mov %[next], %[lo]" + "\n\t" // 1 next = lo (T = 6) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 7) + "sbrc %[byte], 7" + "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 9) + "nop" + "\n\t" // 1 (T = 10) + "out %[port], %[lo]" + "\n\t" // 1 PORT = lo (T = 11) + "ret" + "\n\t" // 4 nop nop nop nop (T = 15) + "doneD:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTD)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTB) || defined(PORTC) || defined(PORTF) + } else // other PORT(s) +#endif // defined(PORTB/C/F) +#endif // defined(PORTD) + + // PORTB OUTPUT ---------------------------------------------------- + +#if defined(PORTB) +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + if (port == &PORTB) { +#endif // defined(PORTD/C/F) + + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + next = lo; + if (b & 0x80) + next = hi; + + // Same as above, just set for PORTB & stripped of comments + asm volatile("headB:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeB" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headB" + "\n\t" + "rjmp doneB" + "\n\t" + "bitTimeB:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneB:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTB)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTC) || defined(PORTF) + } +#endif +#if defined(PORTC) || defined(PORTF) + else +#endif // defined(PORTC/F) +#endif // defined(PORTB) + + // PORTC OUTPUT ---------------------------------------------------- + +#if defined(PORTC) +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + if (port == &PORTC) { +#endif // defined(PORTD/B/F) + + hi = PORTC | pinMask; + lo = PORTC & ~pinMask; + next = lo; + if (b & 0x80) + next = hi; + + // Same as above, just set for PORTC & stripped of comments + asm volatile("headC:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headC" + "\n\t" + "rjmp doneC" + "\n\t" + "bitTimeC:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneC:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTC)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTF) + } +#endif // defined(PORTD/B/F) +#if defined(PORTF) + else +#endif +#endif // defined(PORTC) + + // PORTF OUTPUT ---------------------------------------------------- + +#if defined(PORTF) +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + if (port == &PORTF) { +#endif // defined(PORTD/B/C) + + hi = PORTF | pinMask; + lo = PORTF & ~pinMask; + next = lo; + if (b & 0x80) + next = hi; + + // Same as above, just set for PORTF & stripped of comments + asm volatile("headF:" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port], %[hi]" + "\n\t" + "rcall bitTimeC" + "\n\t" + "out %[port] , %[hi]" + "\n\t" + "rjmp .+0" + "\n\t" + "ld %[byte] , %a[ptr]+" + "\n\t" + "out %[port] , %[next]" + "\n\t" + "mov %[next] , %[lo]" + "\n\t" + "sbrc %[byte] , 7" + "\n\t" + "mov %[next] , %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port] , %[lo]" + "\n\t" + "sbiw %[count], 1" + "\n\t" + "brne headF" + "\n\t" + "rjmp doneC" + "\n\t" + "bitTimeC:" + "\n\t" + "out %[port], %[next]" + "\n\t" + "mov %[next], %[lo]" + "\n\t" + "rol %[byte]" + "\n\t" + "sbrc %[byte], 7" + "\n\t" + "mov %[next], %[hi]" + "\n\t" + "nop" + "\n\t" + "out %[port], %[lo]" + "\n\t" + "ret" + "\n\t" + "doneC:" + "\n" + : [byte] "+r"(b), [next] "+r"(next), [count] "+w"(i) + : [port] "I"(_SFR_IO_ADDR(PORTF)), [ptr] "e"(ptr), + [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(PORTD) || defined(PORTB) || defined(PORTC) + } +#endif // defined(PORTD/B/C) +#endif // defined(PORTF) + +#if defined(NEO_KHZ400) + } else { // 400 KHz + + // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,6,15) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile("head30:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 6) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 8) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "nop" + "\n\t" // 1 nop (T = 15) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 17) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 19) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 20) + "breq nextbyte30" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 22) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 24) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 26) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 28) + "rjmp head30" + "\n\t" // 2 -> head30 (next bit out) + "nextbyte30:" + "\n\t" // (T = 22) + "nop" + "\n\t" // 1 nop (T = 23) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 24) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 26) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 28) + "brne head30" + "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [hi] "r"(hi), [lo] "r"(lo), [ptr] "e"(ptr)); + } +#endif // NEO_KHZ400 + +// 16 MHz(ish) AVR -------------------------------------------------------- +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + + // WS2811 and WS2812 have different hi/lo duty cycles; this is + // similar but NOT an exact copy of the prior 400-on-8 code. + + // 20 inst. clocks per bit: HHHHHxxxxxxxxLLLLLLL + // ST instructions: ^ ^ ^ (T=0,5,13) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile("head20:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte], 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next], %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 5) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 7) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 8) + "breq nextbyte20" + "\n\t" // 1-2 if(bit == 0) (from dec above) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "nop" + "\n\t" // 1 nop (T = 13) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 15) + "nop" + "\n\t" // 1 nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp head20" + "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" + "\n\t" // (T = 10) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 11) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 13) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 15) + "nop" + "\n\t" // 1 nop (T = 16) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 18) + "brne head20" + "\n" // 2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo)); + +#if defined(NEO_KHZ400) + } else { // 400 KHz + + // The 400 KHz clock on 16 MHz MCU is the most 'relaxed' version. + + // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,8,20) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile("head40:" + "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" + "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" + "\n\t" // 1-2 if(b & 128) + "mov %[next] , %[hi]" + "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 6) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 8) + "st %a[port], %[next]" + "\n\t" // 2 PORT = next (T = 10) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 18) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 20) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 22) + "nop" + "\n\t" // 1 nop (T = 23) + "mov %[next] , %[lo]" + "\n\t" // 1 next = lo (T = 24) + "dec %[bit]" + "\n\t" // 1 bit-- (T = 25) + "breq nextbyte40" + "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" + "\n\t" // 1 b <<= 1 (T = 27) + "nop" + "\n\t" // 1 nop (T = 28) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 30) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 32) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 34) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 36) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 38) + "rjmp head40" + "\n\t" // 2 -> head40 (next bit out) + "nextbyte40:" + "\n\t" // (T = 27) + "ldi %[bit] , 8" + "\n\t" // 1 bit = 8 (T = 28) + "ld %[byte] , %a[ptr]+" + "\n\t" // 2 b = *ptr++ (T = 30) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 32) + "st %a[port], %[lo]" + "\n\t" // 2 PORT = lo (T = 34) + "rjmp .+0" + "\n\t" // 2 nop nop (T = 36) + "sbiw %[count], 1" + "\n\t" // 2 i-- (T = 38) + "brne head40" + "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e"(port), [byte] "+r"(b), [bit] "+r"(bit), + [next] "+r"(next), [count] "+w"(i) + : [ptr] "e"(ptr), [hi] "r"(hi), [lo] "r"(lo)); + } +#endif // NEO_KHZ400 + +#else +#error "CPU SPEED NOT SUPPORTED" +#endif // end F_CPU ifdefs on __AVR__ + + // END AVR ---------------------------------------------------------------- + +#elif defined(__arm__) + + // ARM MCUs -- Teensy 3.0, 3.1, LC, Arduino Due, RP2040 ------------------- + +#if defined(ARDUINO_ARCH_RP2040) + // Use PIO + rp2040Show(pin, pixels, numBytes, is800KHz); + +#elif defined(TEENSYDUINO) && \ + defined(KINETISK) // Teensy 3.0, 3.1, 3.2, 3.5, 3.6 +#define CYCLES_800_T0H (F_CPU / 4000000) +#define CYCLES_800_T1H (F_CPU / 1250000) +#define CYCLES_800 (F_CPU / 800000) +#define CYCLES_400_T0H (F_CPU / 2000000) +#define CYCLES_400_T1H (F_CPU / 833333) +#define CYCLES_400 (F_CPU / 400000) + + uint8_t *p = pixels, *end = p + numBytes, pix, mask; + volatile uint8_t *set = portSetRegister(pin), *clr = portClearRegister(pin); + uint32_t cyc; + + ARM_DEMCR |= ARM_DEMCR_TRCENA; + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + cyc = ARM_DWT_CYCCNT + CYCLES_800; + while (p < end) { + pix = *p++; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; + cyc = ARM_DWT_CYCCNT; + *set = 1; + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; + } + *clr = 1; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; +#if defined(NEO_KHZ400) + } else { // 400 kHz bitstream + cyc = ARM_DWT_CYCCNT + CYCLES_400; + while (p < end) { + pix = *p++; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + cyc = ARM_DWT_CYCCNT; + *set = 1; + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; + } + *clr = 1; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + } +#endif // NEO_KHZ400 + +#elif defined(TEENSYDUINO) && (defined(__IMXRT1052__) || defined(__IMXRT1062__)) +#define CYCLES_800_T0H (F_CPU_ACTUAL / 4000000) +#define CYCLES_800_T1H (F_CPU_ACTUAL / 1250000) +#define CYCLES_800 (F_CPU_ACTUAL / 800000) +#define CYCLES_400_T0H (F_CPU_ACTUAL / 2000000) +#define CYCLES_400_T1H (F_CPU_ACTUAL / 833333) +#define CYCLES_400 (F_CPU_ACTUAL / 400000) + + uint8_t *p = pixels, *end = p + numBytes, pix, mask; + volatile uint32_t *set = portSetRegister(pin), *clr = portClearRegister(pin); + uint32_t cyc, msk = digitalPinToBitMask(pin); + + ARM_DEMCR |= ARM_DEMCR_TRCENA; + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + cyc = ARM_DWT_CYCCNT + CYCLES_800; + while (p < end) { + pix = *p++; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; + cyc = ARM_DWT_CYCCNT; + *set = msk; + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H) + ; + } + *clr = msk; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_800) + ; +#if defined(NEO_KHZ400) + } else { // 400 kHz bitstream + cyc = ARM_DWT_CYCCNT + CYCLES_400; + while (p < end) { + pix = *p++; + for (mask = 0x80; mask; mask >>= 1) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + cyc = ARM_DWT_CYCCNT; + *set = msk; + if (pix & mask) { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H) + ; + } else { + while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H) + ; + } + *clr = msk; + } + } + while (ARM_DWT_CYCCNT - cyc < CYCLES_400) + ; + } +#endif // NEO_KHZ400 + +#elif defined(TEENSYDUINO) && defined(__MKL26Z64__) // Teensy-LC + +#if F_CPU == 48000000 + uint8_t *p = pixels, pix, count, dly, bitmask = digitalPinToBitMask(pin); + volatile uint8_t *reg = portSetRegister(pin); + uint32_t num = numBytes; + asm volatile("L%=_begin:" + "\n\t" + "ldrb %[pix], [%[p], #0]" + "\n\t" + "lsl %[pix], #24" + "\n\t" + "movs %[count], #7" + "\n\t" + "L%=_loop:" + "\n\t" + "lsl %[pix], #1" + "\n\t" + "bcs L%=_loop_one" + "\n\t" + "L%=_loop_zero:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_loop_delay_T0H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T0H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_loop_delay_T0L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T0L" + "\n\t" + "b L%=_next" + "\n\t" + "L%=_loop_one:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_loop_delay_T1H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T1H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_loop_delay_T1L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_loop_delay_T1L" + "\n\t" + "nop" + "\n\t" + "L%=_next:" + "\n\t" + "sub %[count], #1" + "\n\t" + "bne L%=_loop" + "\n\t" + "lsl %[pix], #1" + "\n\t" + "bcs L%=_last_one" + "\n\t" + "L%=_last_zero:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #4" + "\n\t" + "L%=_last_delay_T0H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T0H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #10" + "\n\t" + "L%=_last_delay_T0L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T0L" + "\n\t" + "b L%=_repeat" + "\n\t" + "L%=_last_one:" + "\n\t" + "strb %[bitmask], [%[reg], #0]" + "\n\t" + "movs %[dly], #13" + "\n\t" + "L%=_last_delay_T1H:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T1H" + "\n\t" + "strb %[bitmask], [%[reg], #4]" + "\n\t" + "movs %[dly], #1" + "\n\t" + "L%=_last_delay_T1L:" + "\n\t" + "sub %[dly], #1" + "\n\t" + "bne L%=_last_delay_T1L" + "\n\t" + "nop" + "\n\t" + "L%=_repeat:" + "\n\t" + "add %[p], #1" + "\n\t" + "sub %[num], #1" + "\n\t" + "bne L%=_begin" + "\n\t" + "L%=_done:" + "\n\t" + : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count), + [dly] "=&r"(dly), [num] "+r"(num) + : [bitmask] "r"(bitmask), [reg] "r"(reg)); +#else +#error "Sorry, only 48 MHz is supported, please set Tools > CPU Speed to 48 MHz" +#endif // F_CPU == 48000000 + + // Begin of support for nRF52 based boards ------------------------- + +#elif defined(NRF52) || defined(NRF52_SERIES) +// [[[Begin of the Neopixel NRF52 EasyDMA implementation +// by the Hackerspace San Salvador]]] +// This technique uses the PWM peripheral on the NRF52. The PWM uses the +// EasyDMA feature included on the chip. This technique loads the duty +// cycle configuration for each cycle when the PWM is enabled. For this +// to work we need to store a 16 bit configuration for each bit of the +// RGB(W) values in the pixel buffer. +// Comparator values for the PWM were hand picked and are guaranteed to +// be 100% organic to preserve freshness and high accuracy. Current +// parameters are: +// * PWM Clock: 16Mhz +// * Minimum step time: 62.5ns +// * Time for zero in high (T0H): 0.31ms +// * Time for one in high (T1H): 0.75ms +// * Cycle time: 1.25us +// * Frequency: 800Khz +// For 400Khz we just double the calculated times. +// ---------- BEGIN Constants for the EasyDMA implementation ----------- +// The PWM starts the duty cycle in LOW. To start with HIGH we +// need to set the 15th bit on each register. + +// WS2812 (rev A) timing is 0.35 and 0.7us +//#define MAGIC_T0H 5UL | (0x8000) // 0.3125us +//#define MAGIC_T1H 12UL | (0x8000) // 0.75us + +// WS2812B (rev B) timing is 0.4 and 0.8 us +#define MAGIC_T0H 6UL | (0x8000) // 0.375us +#define MAGIC_T1H 13UL | (0x8000) // 0.8125us + +// WS2811 (400 khz) timing is 0.5 and 1.2 +#define MAGIC_T0H_400KHz 8UL | (0x8000) // 0.5us +#define MAGIC_T1H_400KHz 19UL | (0x8000) // 1.1875us + +// For 400Khz, we double value of CTOPVAL +#define CTOPVAL 20UL // 1.25us +#define CTOPVAL_400KHz 40UL // 2.5us + +// ---------- END Constants for the EasyDMA implementation ------------- +// +// If there is no device available an alternative cycle-counter +// implementation is tried. +// The nRF52 runs with a fixed clock of 64Mhz. The alternative +// implementation is the same as the one used for the Teensy 3.0/1/2 but +// with the Nordic SDK HAL & registers syntax. +// The number of cycles was hand picked and is guaranteed to be 100% +// organic to preserve freshness and high accuracy. +// ---------- BEGIN Constants for cycle counter implementation --------- +#define CYCLES_800_T0H 18 // ~0.36 uS +#define CYCLES_800_T1H 41 // ~0.76 uS +#define CYCLES_800 71 // ~1.25 uS + +#define CYCLES_400_T0H 26 // ~0.50 uS +#define CYCLES_400_T1H 70 // ~1.26 uS +#define CYCLES_400 156 // ~2.50 uS + // ---------- END of Constants for cycle counter implementation -------- + + // To support both the SoftDevice + Neopixels we use the EasyDMA + // feature from the NRF25. However this technique implies to + // generate a pattern and store it on the memory. The actual + // memory used in bytes corresponds to the following formula: + // totalMem = numBytes*8*2+(2*2) + // The two additional bytes at the end are needed to reset the + // sequence. + // + // If there is not enough memory, we will fall back to cycle counter + // using DWT + uint32_t pattern_size = + numBytes * 8 * sizeof(uint16_t) + 2 * sizeof(uint16_t); + uint16_t *pixels_pattern = NULL; + + NRF_PWM_Type *pwm = NULL; + + // Try to find a free PWM device, which is not enabled + // and has no connected pins + NRF_PWM_Type *PWM[] = { + NRF_PWM0, + NRF_PWM1, + NRF_PWM2 +#if defined(NRF_PWM3) + , + NRF_PWM3 +#endif + }; + + for (unsigned int device = 0; device < (sizeof(PWM) / sizeof(PWM[0])); + device++) { + if ((PWM[device]->ENABLE == 0) && + (PWM[device]->PSEL.OUT[0] & PWM_PSEL_OUT_CONNECT_Msk) && + (PWM[device]->PSEL.OUT[1] & PWM_PSEL_OUT_CONNECT_Msk) && + (PWM[device]->PSEL.OUT[2] & PWM_PSEL_OUT_CONNECT_Msk) && + (PWM[device]->PSEL.OUT[3] & PWM_PSEL_OUT_CONNECT_Msk)) { + pwm = PWM[device]; + break; + } + } + + // only malloc if there is PWM device available + if (pwm != NULL) { +#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe malloc + pixels_pattern = (uint16_t *)rtos_malloc(pattern_size); +#else + pixels_pattern = (uint16_t *)malloc(pattern_size); +#endif + } + + // Use the identified device to choose the implementation + // If a PWM device is available use DMA + if ((pixels_pattern != NULL) && (pwm != NULL)) { + uint16_t pos = 0; // bit position + + for (uint16_t n = 0; n < numBytes; n++) { + uint8_t pix = pixels[n]; + + for (uint8_t mask = 0x80; mask > 0; mask >>= 1) { +#if defined(NEO_KHZ400) + if (!is800KHz) { + pixels_pattern[pos] = + (pix & mask) ? MAGIC_T1H_400KHz : MAGIC_T0H_400KHz; + } else +#endif + { + pixels_pattern[pos] = (pix & mask) ? MAGIC_T1H : MAGIC_T0H; + } + + pos++; + } + } + + // Zero padding to indicate the end of que sequence + pixels_pattern[pos++] = 0 | (0x8000); // Seq end + pixels_pattern[pos++] = 0 | (0x8000); // Seq end + + // Set the wave mode to count UP + pwm->MODE = (PWM_MODE_UPDOWN_Up << PWM_MODE_UPDOWN_Pos); + + // Set the PWM to use the 16MHz clock + pwm->PRESCALER = + (PWM_PRESCALER_PRESCALER_DIV_1 << PWM_PRESCALER_PRESCALER_Pos); + + // Setting of the maximum count + // but keeping it on 16Mhz allows for more granularity just + // in case someone wants to do more fine-tuning of the timing. +#if defined(NEO_KHZ400) + if (!is800KHz) { + pwm->COUNTERTOP = (CTOPVAL_400KHz << PWM_COUNTERTOP_COUNTERTOP_Pos); + } else +#endif + { + pwm->COUNTERTOP = (CTOPVAL << PWM_COUNTERTOP_COUNTERTOP_Pos); + } + + // Disable loops, we want the sequence to repeat only once + pwm->LOOP = (PWM_LOOP_CNT_Disabled << PWM_LOOP_CNT_Pos); + + // On the "Common" setting the PWM uses the same pattern for the + // for supported sequences. The pattern is stored on half-word + // of 16bits + pwm->DECODER = (PWM_DECODER_LOAD_Common << PWM_DECODER_LOAD_Pos) | + (PWM_DECODER_MODE_RefreshCount << PWM_DECODER_MODE_Pos); + + // Pointer to the memory storing the patter + pwm->SEQ[0].PTR = (uint32_t)(pixels_pattern) << PWM_SEQ_PTR_PTR_Pos; + + // Calculation of the number of steps loaded from memory. + pwm->SEQ[0].CNT = (pattern_size / sizeof(uint16_t)) << PWM_SEQ_CNT_CNT_Pos; + + // The following settings are ignored with the current config. + pwm->SEQ[0].REFRESH = 0; + pwm->SEQ[0].ENDDELAY = 0; + + // The Neopixel implementation is a blocking algorithm. DMA + // allows for non-blocking operation. To "simulate" a blocking + // operation we enable the interruption for the end of sequence + // and block the execution thread until the event flag is set by + // the peripheral. + // pwm->INTEN |= (PWM_INTEN_SEQEND0_Enabled<PSEL.OUT[0] = g_APinDescription[pin].name; +#else + pwm->PSEL.OUT[0] = g_ADigitalPinMap[pin]; +#endif + + // Enable the PWM + pwm->ENABLE = 1; + + // After all of this and many hours of reading the documentation + // we are ready to start the sequence... + pwm->EVENTS_SEQEND[0] = 0; + pwm->TASKS_SEQSTART[0] = 1; + + // But we have to wait for the flag to be set. + while (!pwm->EVENTS_SEQEND[0]) { +#if defined(ARDUINO_NRF52_ADAFRUIT) || defined(ARDUINO_ARCH_NRF52840) + yield(); +#endif + } + + // Before leave we clear the flag for the event. + pwm->EVENTS_SEQEND[0] = 0; + + // We need to disable the device and disconnect + // all the outputs before leave or the device will not + // be selected on the next call. + // TODO: Check if disabling the device causes performance issues. + pwm->ENABLE = 0; + + pwm->PSEL.OUT[0] = 0xFFFFFFFFUL; + +#if defined(ARDUINO_NRF52_ADAFRUIT) // use thread-safe free + rtos_free(pixels_pattern); +#else + free(pixels_pattern); +#endif + } // End of DMA implementation + // --------------------------------------------------------------------- + else { +#ifndef ARDUINO_ARCH_NRF52840 +// Fall back to DWT +#if defined(ARDUINO_NRF52_ADAFRUIT) + // Bluefruit Feather 52 uses freeRTOS + // Critical Section is used since it does not block SoftDevice execution + taskENTER_CRITICAL(); +#elif defined(NRF52_DISABLE_INT) + // If you are using the Bluetooth SoftDevice we advise you to not disable + // the interrupts. Disabling the interrupts even for short periods of time + // causes the SoftDevice to stop working. + // Disable the interrupts only in cases where you need high performance for + // the LEDs and if you are not using the EasyDMA feature. + __disable_irq(); +#endif + + NRF_GPIO_Type *nrf_port = (NRF_GPIO_Type *)digitalPinToPort(pin); + uint32_t pinMask = digitalPinToBitMask(pin); + + uint32_t CYCLES_X00 = CYCLES_800; + uint32_t CYCLES_X00_T1H = CYCLES_800_T1H; + uint32_t CYCLES_X00_T0H = CYCLES_800_T0H; + +#if defined(NEO_KHZ400) + if (!is800KHz) { + CYCLES_X00 = CYCLES_400; + CYCLES_X00_T1H = CYCLES_400_T1H; + CYCLES_X00_T0H = CYCLES_400_T0H; + } +#endif + + // Enable DWT in debug core + CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; + DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; + + // Tries to re-send the frame if is interrupted by the SoftDevice. + while (1) { + uint8_t *p = pixels; + + uint32_t cycStart = DWT->CYCCNT; + uint32_t cyc = 0; + + for (uint16_t n = 0; n < numBytes; n++) { + uint8_t pix = *p++; + + for (uint8_t mask = 0x80; mask; mask >>= 1) { + while (DWT->CYCCNT - cyc < CYCLES_X00) + ; + cyc = DWT->CYCCNT; + + nrf_port->OUTSET |= pinMask; + + if (pix & mask) { + while (DWT->CYCCNT - cyc < CYCLES_X00_T1H) + ; + } else { + while (DWT->CYCCNT - cyc < CYCLES_X00_T0H) + ; + } + + nrf_port->OUTCLR |= pinMask; + } + } + while (DWT->CYCCNT - cyc < CYCLES_X00) + ; + + // If total time longer than 25%, resend the whole data. + // Since we are likely to be interrupted by SoftDevice + if ((DWT->CYCCNT - cycStart) < (8 * numBytes * ((CYCLES_X00 * 5) / 4))) { + break; + } + + // re-send need 300us delay + delayMicroseconds(300); + } + +// Enable interrupts again +#if defined(ARDUINO_NRF52_ADAFRUIT) + taskEXIT_CRITICAL(); +#elif defined(NRF52_DISABLE_INT) + __enable_irq(); +#endif +#endif + } + // END of NRF52 implementation + +#elif defined(__SAMD21E17A__) || defined(__SAMD21G18A__) || \ + defined(__SAMD21E18A__) || defined(__SAMD21J18A__) || \ + defined (__SAMD11C14A__) + // Arduino Zero, Gemma/Trinket M0, SODAQ Autonomo + // and others + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... + + uint8_t *ptr, *end, p, bitMask, portNum; + uint32_t pinMask; + + portNum = g_APinDescription[pin].ulPort; + pinMask = 1ul << g_APinDescription[pin].ulPin; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + + volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg), + *clr = &(PORT->Group[portNum].OUTCLR.reg); + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + for (;;) { + *set = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;"); + if (p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + *clr = pinMask; + } else { + *clr = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + } + if (bitMask >>= 1) { + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + for (;;) { + *set = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); + if (p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop;"); + *clr = pinMask; + } else { + *clr = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop;"); + } + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + if (bitMask >>= 1) { + asm("nop; nop; nop; nop; nop; nop; nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } + } +#endif + +//---- +#elif defined(XMC1100_XMC2GO) || defined(XMC1100_H_BRIDGE2GO) || defined(XMC1100_Boot_Kit) || defined(XMC1300_Boot_Kit) + + // XMC1100/1200/1300 with ARM Cortex M0 are running with 32MHz, XMC1400 runs with 48MHz so may not work + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... + + uint8_t *ptr, *end, p, bitMask, portNum; + uint32_t pinMask; + + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + + XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port; + uint8_t XMC_pin = mapping_port_pin[ pin ].pin; + + uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin; + uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin; + +#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled + if(is800KHz) { +#endif + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop;"); + } + if(bitMask >>= 1) { + asm("nop; nop; nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } +#ifdef NEO_KHZ400 // untested code + } else { // 400 KHz bitstream + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop;"); + } + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + if(bitMask >>= 1) { + asm("nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } + } + +#endif +//---- + +//---- +#elif defined(XMC4700_Relax_Kit) || defined(XMC4800_Relax_Kit) + +// XMC4700 and XMC4800 with ARM Cortex M4 are running with 144MHz +// Tried this with a timer/counter, couldn't quite get adequate +// resolution. So yay, you get a load of goofball NOPs... + +uint8_t *ptr, *end, p, bitMask, portNum; +uint32_t pinMask; + +ptr = pixels; +end = ptr + numBytes; +p = *ptr++; +bitMask = 0x80; + +XMC_GPIO_PORT_t* XMC_port = mapping_port_pin[ pin ].port; +uint8_t XMC_pin = mapping_port_pin[ pin ].pin; + +uint32_t omrhigh = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_HIGH << XMC_pin; +uint32_t omrlow = (uint32_t)XMC_GPIO_OUTPUT_LEVEL_LOW << XMC_pin; + +#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled +if(is800KHz) { +#endif + + for(;;) { + XMC_port->OMR = omrhigh; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + if(p & bitMask) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + XMC_port->OMR = omrlow; + } else { + XMC_port->OMR = omrlow; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + } + if(bitMask >>= 1) { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;"); + } else { + if(ptr >= end) break; + p = *ptr++; + bitMask = 0x80; + } + } + + +#ifdef NEO_KHZ400 + } else { // 400 KHz bitstream + // ToDo! + } +#endif +//---- + +#elif defined(__SAMD51__) // M4 + + uint8_t *ptr, *end, p, bitMask, portNum, bit; + uint32_t pinMask; + + portNum = g_APinDescription[pin].ulPort; + pinMask = 1ul << g_APinDescription[pin].ulPin; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + + volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg), + *clr = &(PORT->Group[portNum].OUTCLR.reg); + + // SAMD51 overclock-compatible timing is only a mild abomination. + // It uses SysTick for a consistent clock reference regardless of + // optimization / cache settings. That's the good news. The bad news, + // since SysTick->VAL is a volatile type it's slow to access...and then, + // with the SysTick interval that Arduino sets up (1 ms), this would + // require a subtract and MOD operation for gauging elapsed time, and + // all taken in combination that lacks adequate temporal resolution + // for NeoPixel timing. So a kind of horrible thing is done here... + // since interrupts are turned off anyway and it's generally accepted + // by now that we're gonna lose track of time in the NeoPixel lib, + // the SysTick timer is reconfigured for a period matching the NeoPixel + // bit timing (either 800 or 400 KHz) and we watch SysTick->VAL very + // closely (just a threshold, no subtract or MOD or anything) and that + // seems to work just well enough. When finished, the SysTick + // peripheral is set back to its original state. + + uint32_t t0, t1, top, ticks, saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + top = (uint32_t)(F_CPU * 0.00000125); // Bit hi + lo = 1.25 uS + t0 = top - (uint32_t)(F_CPU * 0.00000040); // 0 = 0.4 uS hi + t1 = top - (uint32_t)(F_CPU * 0.00000080); // 1 = 0.8 uS hi +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + top = (uint32_t)(F_CPU * 0.00000250); // Bit hi + lo = 2.5 uS + t0 = top - (uint32_t)(F_CPU * 0.00000050); // 0 = 0.5 uS hi + t1 = top - (uint32_t)(F_CPU * 0.00000120); // 1 = 1.2 uS hi + } +#endif + + SysTick->LOAD = top; // Config SysTick for NeoPixel bit freq + SysTick->VAL = top; // Set to start value (counts down) + (void)SysTick->VAL; // Dummy read helps sync up 1st bit + + for (;;) { + *set = pinMask; // Set output high + ticks = (p & bitMask) ? t1 : t0; // SysTick threshold, + while (SysTick->VAL > ticks) + ; // wait for it + *clr = pinMask; // Set output low + if (!(bitMask >>= 1)) { // Next bit for this byte...done? + if (ptr >= end) + break; // If last byte sent, exit loop + p = *ptr++; // Fetch next byte + bitMask = 0x80; // Reset bitmask + } + while (SysTick->VAL <= ticks) + ; // Wait for rollover to 'top' + } + + SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms + SysTick->VAL = saveVal; // Restore SysTick value + +#elif defined(ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz) + + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... + + uint8_t *ptr, *end, p, bitMask; + uint32_t pinMask; + + pinMask = BIT(PIN_MAP[pin].gpio_bit); + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + + volatile uint16_t *set = &(PIN_MAP[pin].gpio_device->regs->BSRRL); + volatile uint16_t *clr = &(PIN_MAP[pin].gpio_device->regs->BSRRH); + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + for (;;) { + if (p & bitMask) { // ONE + // High 800ns + *set = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop;"); + // Low 450ns + *clr = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop;"); + } else { // ZERO + // High 400ns + *set = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop;"); + // Low 850ns + *clr = pinMask; + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + } + if (bitMask >>= 1) { + // Move on to the next pixel + asm("nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + // ToDo! + } +#endif + +#elif defined(TARGET_LPC1768) + uint8_t *ptr, *end, p, bitMask; + ptr = pixels; + end = ptr + numBytes; + p = *ptr++; + bitMask = 0x80; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + for (;;) { + if (p & bitMask) { + // data ONE high + // min: 550 typ: 700 max: 5,500 + gpio_set(pin); + time::delay_ns(550); + // min: 450 typ: 600 max: 5,000 + gpio_clear(pin); + time::delay_ns(450); + } else { + // data ZERO high + // min: 200 typ: 350 max: 500 + gpio_set(pin); + time::delay_ns(200); + // data low + // min: 450 typ: 600 max: 5,000 + gpio_clear(pin); + time::delay_ns(450); + } + if (bitMask >>= 1) { + // Move on to the next pixel + asm("nop;"); + } else { + if (ptr >= end) + break; + p = *ptr++; + bitMask = 0x80; + } + } +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + // ToDo! + } +#endif +#elif defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + uint8_t *p = pixels, *end = p + numBytes, pix = *p++, mask = 0x80; + uint32_t cyc; + uint32_t saveLoad = SysTick->LOAD, saveVal = SysTick->VAL; +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + uint32_t top = (F_CPU / 800000); // 1.25µs + uint32_t t0 = top - (F_CPU / 2500000); // 0.4µs + uint32_t t1 = top - (F_CPU / 1250000); // 0.8µs + SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq + SysTick->VAL = 0; // Set to start value + for (;;) { + LL_GPIO_SetOutputPin(gpioPort, gpioPin); + cyc = (pix & mask) ? t1 : t0; + while (SysTick->VAL > cyc) + ; + LL_GPIO_ResetOutputPin(gpioPort, gpioPin); + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + while (SysTick->VAL <= cyc) + ; + } +#if defined(NEO_KHZ400) + } else { // 400 kHz bitstream + uint32_t top = (F_CPU / 400000); // 2.5µs + uint32_t t0 = top - (F_CPU / 2000000); // 0.5µs + uint32_t t1 = top - (F_CPU / 833333); // 1.2µs + SysTick->LOAD = top - 1; // Config SysTick for NeoPixel bit freq + SysTick->VAL = 0; // Set to start value + for (;;) { + LL_GPIO_SetOutputPin(gpioPort, gpioPin); + cyc = (pix & mask) ? t1 : t0; + while (SysTick->VAL > cyc) + ; + LL_GPIO_ResetOutputPin(gpioPort, gpioPin); + if (!(mask >>= 1)) { + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + while (SysTick->VAL <= cyc) + ; + } + } +#endif // NEO_KHZ400 + SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms + SysTick->VAL = saveVal; // Restore SysTick value +#elif defined(NRF51) + uint8_t *p = pixels, pix, count, mask; + int32_t num = numBytes; + unsigned int bitmask = (1 << g_ADigitalPinMap[pin]); + // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/variants/BBCmicrobit/variant.cpp + + volatile unsigned int *reg = (unsigned int *)(0x50000000UL + 0x508); + + // https://github.com/sandeepmistry/arduino-nRF5/blob/dc53980c8bac27898fca90d8ecb268e11111edc1/cores/nRF5/SDK/components/device/nrf51.h + // http://www.iot-programmer.com/index.php/books/27-micro-bit-iot-in-c/chapters-micro-bit-iot-in-c/47-micro-bit-iot-in-c-fast-memory-mapped-gpio?showall=1 + // https://github.com/Microsoft/pxt-neopixel/blob/master/sendbuffer.asm + + asm volatile( + // "cpsid i" ; disable irq + + // b .start + "b L%=_start" + "\n\t" + // .nextbit: ; C0 + "L%=_nextbit:" + "\n\t" //; C0 + // str r1, [r3, #0] ; pin := hi C2 + "strb %[bitmask], [%[reg], #0]" + "\n\t" //; pin := hi C2 + // tst r6, r0 ; C3 + "tst %[mask], %[pix]" + "\n\t" // ; C3 + // bne .islate ; C4 + "bne L%=_islate" + "\n\t" //; C4 + // str r1, [r2, #0] ; pin := lo C6 + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo C6 + // .islate: + "L%=_islate:" + "\n\t" + // lsrs r6, r6, #1 ; r6 >>= 1 C7 + "lsr %[mask], %[mask], #1" + "\n\t" //; r6 >>= 1 C7 + // bne .justbit ; C8 + "bne L%=_justbit" + "\n\t" //; C8 + + // ; not just a bit - need new byte + // adds r4, #1 ; r4++ C9 + "add %[p], #1" + "\n\t" //; r4++ C9 + // subs r5, #1 ; r5-- C10 + "sub %[num], #1" + "\n\t" //; r5-- C10 + // bcc .stop ; if (r5<0) goto .stop C11 + "bcc L%=_stop" + "\n\t" //; if (r5<0) goto .stop C11 + // .start: + "L%=_start:" + // movs r6, #0x80 ; reset mask C12 + "movs %[mask], #0x80" + "\n\t" //; reset mask C12 + // nop ; C13 + "nop" + "\n\t" //; C13 + + // .common: ; C13 + "L%=_common:" + "\n\t" //; C13 + // str r1, [r2, #0] ; pin := lo C15 + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo C15 + // ; always re-load byte - it just fits with the cycles better this way + // ldrb r0, [r4, #0] ; r0 := *r4 C17 + "ldrb %[pix], [%[p], #0]" + "\n\t" //; r0 := *r4 C17 + // b .nextbit ; C20 + "b L%=_nextbit" + "\n\t" //; C20 + + // .justbit: ; C10 + "L%=_justbit:" + "\n\t" //; C10 + // ; no nops, branch taken is already 3 cycles + // b .common ; C13 + "b L%=_common" + "\n\t" //; C13 + + // .stop: + "L%=_stop:" + "\n\t" + // str r1, [r2, #0] ; pin := lo + "strb %[bitmask], [%[reg], #4]" + "\n\t" //; pin := lo + // cpsie i ; enable irq + + : [p] "+r"(p), [pix] "=&r"(pix), [count] "=&r"(count), [mask] "=&r"(mask), + [num] "+r"(num) + : [bitmask] "r"(bitmask), [reg] "r"(reg)); + +#elif defined(__SAM3X8E__) // Arduino Due + +#define SCALE VARIANT_MCK / 2UL / 1000000UL +#define INST (2UL * F_CPU / VARIANT_MCK) +#define TIME_800_0 ((int)(0.40 * SCALE + 0.5) - (5 * INST)) +#define TIME_800_1 ((int)(0.80 * SCALE + 0.5) - (5 * INST)) +#define PERIOD_800 ((int)(1.25 * SCALE + 0.5) - (5 * INST)) +#define TIME_400_0 ((int)(0.50 * SCALE + 0.5) - (5 * INST)) +#define TIME_400_1 ((int)(1.20 * SCALE + 0.5) - (5 * INST)) +#define PERIOD_400 ((int)(2.50 * SCALE + 0.5) - (5 * INST)) + + int pinMask, time0, time1, period, t; + Pio *port; + volatile WoReg *portSet, *portClear, *timeValue, *timeReset; + uint8_t *p, *end, pix, mask; + + pmc_set_writeprotect(false); + pmc_enable_periph_clk((uint32_t)TC3_IRQn); + TC_Configure(TC1, 0, + TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1); + TC_Start(TC1, 0); + + pinMask = g_APinDescription[pin].ulPin; // Don't 'optimize' these into + port = g_APinDescription[pin].pPort; // declarations above. Want to + portSet = &(port->PIO_SODR); // burn a few cycles after + portClear = &(port->PIO_CODR); // starting timer to minimize + timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'. + timeReset = &(TC1->TC_CHANNEL[0].TC_CCR); + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; + +#if defined(NEO_KHZ400) // 800 KHz check needed only if 400 KHz support enabled + if (is800KHz) { +#endif + time0 = TIME_800_0; + time1 = TIME_800_1; + period = PERIOD_800; +#if defined(NEO_KHZ400) + } else { // 400 KHz bitstream + time0 = TIME_400_0; + time1 = TIME_400_1; + period = PERIOD_400; + } +#endif + + for (t = time0;; t = time0) { + if (pix & mask) + t = time1; + while (*timeValue < (unsigned)period) + ; + *portSet = pinMask; + *timeReset = TC_CCR_CLKEN | TC_CCR_SWTRG; + while (*timeValue < (unsigned)t) + ; + *portClear = pinMask; + if (!(mask >>= 1)) { // This 'inside-out' loop logic utilizes + if (p >= end) + break; // idle time to minimize inter-byte delays. + pix = *p++; + mask = 0x80; + } + } + while (*timeValue < (unsigned)period) + ; // Wait for last bit + TC_Stop(TC1, 0); + +#endif // end Due + + // END ARM ---------------------------------------------------------------- + +#elif defined(ESP8266) || defined(ESP32) + + // ESP8266 ---------------------------------------------------------------- + + // ESP8266 show() is external to enforce ICACHE_RAM_ATTR execution + espShow(pin, pixels, numBytes, is800KHz); + +#elif defined(KENDRYTE_K210) + + k210Show(pin, pixels, numBytes, is800KHz); + +#elif defined(__ARDUINO_ARC__) + + // Arduino 101 ----------------------------------------------------------- + +#define NOPx7 \ + { \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + __builtin_arc_nop(); \ + } + + PinDescription *pindesc = &g_APinDescription[pin]; + register uint32_t loop = + 8 * numBytes; // one loop to handle all bytes and all bits + register uint8_t *p = pixels; + register uint32_t currByte = (uint32_t)(*p); + register uint32_t currBit = 0x80 & currByte; + register uint32_t bitCounter = 0; + register uint32_t first = 1; + + // The loop is unusual. Very first iteration puts all the way LOW to the wire + // - constant LOW does not affect NEOPIXEL, so there is no visible effect + // displayed. During that very first iteration CPU caches instructions in the + // loop. Because of the caching process, "CPU slows down". NEOPIXEL pulse is + // very time sensitive that's why we let the CPU cache first and we start + // regular pulse from 2nd iteration + if (pindesc->ulGPIOType == SS_GPIO) { + register uint32_t reg = pindesc->ulGPIOBase + SS_GPIO_SWPORTA_DR; + uint32_t reg_val = __builtin_arc_lr((volatile uint32_t)reg); + register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId); + register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); + + loop += 1; // include first, special iteration + while (loop--) { + if (!first) { + currByte <<= 1; + bitCounter++; + } + + // 1 is >550ns high and >450ns low; 0 is 200..500ns high and >450ns low + __builtin_arc_sr(first ? reg_bit_low : reg_bit_high, + (volatile uint32_t)reg); + if (currBit) { // ~400ns HIGH (740ns overall) + NOPx7 NOPx7 + } + // ~340ns HIGH + NOPx7 __builtin_arc_nop(); + + // 820ns LOW; per spec, max allowed low here is 5000ns */ + __builtin_arc_sr(reg_bit_low, (volatile uint32_t)reg); + NOPx7 NOPx7 + + if (bitCounter >= 8) { + bitCounter = 0; + currByte = (uint32_t)(*++p); + } + + currBit = 0x80 & currByte; + first = 0; + } + } else if (pindesc->ulGPIOType == SOC_GPIO) { + register uint32_t reg = pindesc->ulGPIOBase + SOC_GPIO_SWPORTA_DR; + uint32_t reg_val = MMIO_REG_VAL(reg); + register uint32_t reg_bit_high = reg_val | (1 << pindesc->ulGPIOId); + register uint32_t reg_bit_low = reg_val & ~(1 << pindesc->ulGPIOId); + + loop += 1; // include first, special iteration + while (loop--) { + if (!first) { + currByte <<= 1; + bitCounter++; + } + MMIO_REG_VAL(reg) = first ? reg_bit_low : reg_bit_high; + if (currBit) { // ~430ns HIGH (740ns overall) + NOPx7 NOPx7 __builtin_arc_nop(); + } + // ~310ns HIGH + NOPx7 + + // 850ns LOW; per spec, max allowed low here is 5000ns */ + MMIO_REG_VAL(reg) = reg_bit_low; + NOPx7 NOPx7 + + if (bitCounter >= 8) { + bitCounter = 0; + currByte = (uint32_t)(*++p); + } + + currBit = 0x80 & currByte; + first = 0; + } + } + +#else +#error Architecture not supported +#endif + + // END ARCHITECTURE SELECT ------------------------------------------------ + +#if !(defined(NRF52) || defined(NRF52_SERIES) || defined(ESP32)) + interrupts(); +#endif + + endTime = micros(); // Save EOD time for latch on next call +} + +/*! + @brief Set/change the NeoPixel output pin number. Previous pin, + if any, is set to INPUT and the new pin is set to OUTPUT. + @param p Arduino pin number (-1 = no pin). +*/ +void Adafruit_NeoPixel::setPin(int16_t p) { + if (begun && (pin >= 0)) + pinMode(pin, INPUT); // Disable existing out pin + pin = p; + if (begun) { + pinMode(p, OUTPUT); + digitalWrite(p, LOW); + } +#if defined(__AVR__) + port = portOutputRegister(digitalPinToPort(p)); + pinMask = digitalPinToBitMask(p); +#endif +#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + gpioPort = digitalPinToPort(p); + gpioPin = STM_LL_GPIO_PIN(digitalPinToPinName(p)); +#endif +} + +/*! + @brief Set a pixel's color using separate red, green and blue + components. If using RGBW pixels, white will be set to 0. + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @param g Green brightness, 0 = minimum (off), 255 = maximum. + @param b Blue brightness, 0 = minimum (off), 255 = maximum. +*/ +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g, + uint8_t b) { + + if (n < numLEDs) { + if (brightness) { // See notes in setBrightness() + r = (r * brightness) >> 8; + g = (g * brightness) >> 8; + b = (b * brightness) >> 8; + } + uint8_t *p; + if (wOffset == rOffset) { // Is an RGB-type strip + p = &pixels[n * 3]; // 3 bytes per pixel + } else { // Is a WRGB-type strip + p = &pixels[n * 4]; // 4 bytes per pixel + p[wOffset] = 0; // But only R,G,B passed -- set W to 0 + } + p[rOffset] = r; // R,G,B always stored + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Set a pixel's color using separate red, green, blue and white + components (for RGBW NeoPixels only). + @param n Pixel index, starting from 0. + @param r Red brightness, 0 = minimum (off), 255 = maximum. + @param g Green brightness, 0 = minimum (off), 255 = maximum. + @param b Blue brightness, 0 = minimum (off), 255 = maximum. + @param w White brightness, 0 = minimum (off), 255 = maximum, ignored + if using RGB pixels. +*/ +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint8_t r, uint8_t g, + uint8_t b, uint8_t w) { + + if (n < numLEDs) { + if (brightness) { // See notes in setBrightness() + r = (r * brightness) >> 8; + g = (g * brightness) >> 8; + b = (b * brightness) >> 8; + w = (w * brightness) >> 8; + } + uint8_t *p; + if (wOffset == rOffset) { // Is an RGB-type strip + p = &pixels[n * 3]; // 3 bytes per pixel (ignore W) + } else { // Is a WRGB-type strip + p = &pixels[n * 4]; // 4 bytes per pixel + p[wOffset] = w; // Store W + } + p[rOffset] = r; // Store R,G,B + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Set a pixel's color using a 32-bit 'packed' RGB or RGBW value. + @param n Pixel index, starting from 0. + @param c 32-bit color value. Most significant byte is white (for RGBW + pixels) or ignored (for RGB pixels), next is red, then green, + and least significant byte is blue. +*/ +void Adafruit_NeoPixel::setPixelColor(uint16_t n, uint32_t c) { + if (n < numLEDs) { + uint8_t *p, r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8), b = (uint8_t)c; + if (brightness) { // See notes in setBrightness() + r = (r * brightness) >> 8; + g = (g * brightness) >> 8; + b = (b * brightness) >> 8; + } + if (wOffset == rOffset) { + p = &pixels[n * 3]; + } else { + p = &pixels[n * 4]; + uint8_t w = (uint8_t)(c >> 24); + p[wOffset] = brightness ? ((w * brightness) >> 8) : w; + } + p[rOffset] = r; + p[gOffset] = g; + p[bOffset] = b; + } +} + +/*! + @brief Fill all or part of the NeoPixel strip with a color. + @param c 32-bit color value. Most significant byte is white (for + RGBW pixels) or ignored (for RGB pixels), next is red, + then green, and least significant byte is blue. If all + arguments are unspecified, this will be 0 (off). + @param first Index of first pixel to fill, starting from 0. Must be + in-bounds, no clipping is performed. 0 if unspecified. + @param count Number of pixels to fill, as a positive value. Passing + 0 or leaving unspecified will fill to end of strip. +*/ +void Adafruit_NeoPixel::fill(uint32_t c, uint16_t first, uint16_t count) { + uint16_t i, end; + + if (first >= numLEDs) { + return; // If first LED is past end of strip, nothing to do + } + + // Calculate the index ONE AFTER the last pixel to fill + if (count == 0) { + // Fill to end of strip + end = numLEDs; + } else { + // Ensure that the loop won't go past the last pixel + end = first + count; + if (end > numLEDs) + end = numLEDs; + } + + for (i = first; i < end; i++) { + this->setPixelColor(i, c); + } +} + +/*! + @brief Convert hue, saturation and value into a packed 32-bit RGB color + that can be passed to setPixelColor() or other RGB-compatible + functions. + @param hue An unsigned 16-bit value, 0 to 65535, representing one full + loop of the color wheel, which allows 16-bit hues to "roll + over" while still doing the expected thing (and allowing + more precision than the wheel() function that was common to + prior NeoPixel examples). + @param sat Saturation, 8-bit value, 0 (min or pure grayscale) to 255 + (max or pure hue). Default of 255 if unspecified. + @param val Value (brightness), 8-bit value, 0 (min / black / off) to + 255 (max or full brightness). Default of 255 if unspecified. + @return Packed 32-bit RGB with the most significant byte set to 0 -- the + white element of WRGB pixels is NOT utilized. Result is linearly + but not perceptually correct, so you may want to pass the result + through the gamma32() function (or your own gamma-correction + operation) else colors may appear washed out. This is not done + automatically by this function because coders may desire a more + refined gamma-correction function than the simplified + one-size-fits-all operation of gamma32(). Diffusing the LEDs also + really seems to help when using low-saturation colors. +*/ +uint32_t Adafruit_NeoPixel::ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { + + uint8_t r, g, b; + + // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover; + // 0 is not the start of pure red, but the midpoint...a few values above + // zero and a few below 65536 all yield pure red (similarly, 32768 is the + // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values + // each for red, green, blue) really only allows for 1530 distinct hues + // (not 1536, more on that below), but the full unsigned 16-bit type was + // chosen for hue so that one's code can easily handle a contiguous color + // wheel by allowing hue to roll over in either direction. + hue = (hue * 1530L + 32768) / 65536; + // Because red is centered on the rollover point (the +32768 above, + // essentially a fixed-point +0.5), the above actually yields 0 to 1530, + // where 0 and 1530 would yield the same thing. Rather than apply a + // costly modulo operator, 1530 is handled as a special case below. + + // So you'd think that the color "hexcone" (the thing that ramps from + // pure red, to pure yellow, to pure green and so forth back to red, + // yielding six slices), and with each color component having 256 + // possible values (0-255), might have 1536 possible items (6*256), + // but in reality there's 1530. This is because the last element in + // each 256-element slice is equal to the first element of the next + // slice, and keeping those in there this would create small + // discontinuities in the color wheel. So the last element of each + // slice is dropped...we regard only elements 0-254, with item 255 + // being picked up as element 0 of the next slice. Like this: + // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0 + // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0 + // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254 + // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why + // the constants below are not the multiples of 256 you might expect. + + // Convert hue to R,G,B (nested ifs faster than divide+mod+switch): + if (hue < 510) { // Red to Green-1 + b = 0; + if (hue < 255) { // Red to Yellow-1 + r = 255; + g = hue; // g = 0 to 254 + } else { // Yellow to Green-1 + r = 510 - hue; // r = 255 to 1 + g = 255; + } + } else if (hue < 1020) { // Green to Blue-1 + r = 0; + if (hue < 765) { // Green to Cyan-1 + g = 255; + b = hue - 510; // b = 0 to 254 + } else { // Cyan to Blue-1 + g = 1020 - hue; // g = 255 to 1 + b = 255; + } + } else if (hue < 1530) { // Blue to Red-1 + g = 0; + if (hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 + b = 255; + } else { // Magenta to Red-1 + r = 255; + b = 1530 - hue; // b = 255 to 1 + } + } else { // Last 0.5 Red (quicker than % operator) + r = 255; + g = b = 0; + } + + // Apply saturation and value to R,G,B, pack into 32-bit result: + uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 + uint16_t s1 = 1 + sat; // 1 to 256; same reason + uint8_t s2 = 255 - sat; // 255 to 0 + return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | + (((((b * s1) >> 8) + s2) * v1) >> 8); +} + +/*! + @brief Query the color of a previously-set pixel. + @param n Index of pixel to read (0 = first). + @return 'Packed' 32-bit RGB or WRGB value. Most significant byte is white + (for RGBW pixels) or 0 (for RGB pixels), next is red, then green, + and least significant byte is blue. + @note If the strip brightness has been changed from the default value + of 255, the color read from a pixel may not exactly match what + was previously written with one of the setPixelColor() functions. + This gets more pronounced at lower brightness levels. +*/ +uint32_t Adafruit_NeoPixel::getPixelColor(uint16_t n) const { + if (n >= numLEDs) + return 0; // Out of bounds, return no color. + + uint8_t *p; + + if (wOffset == rOffset) { // Is RGB-type device + p = &pixels[n * 3]; + if (brightness) { + // Stored color was decimated by setBrightness(). Returned value + // attempts to scale back to an approximation of the original 24-bit + // value used when setting the pixel color, but there will always be + // some error -- those bits are simply gone. Issue is most + // pronounced at low brightness levels. + return (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ((uint32_t)(p[bOffset] << 8) / brightness); + } else { + // No brightness adjustment has been made -- return 'raw' color + return ((uint32_t)p[rOffset] << 16) | ((uint32_t)p[gOffset] << 8) | + (uint32_t)p[bOffset]; + } + } else { // Is RGBW-type device + p = &pixels[n * 4]; + if (brightness) { // Return scaled color + return (((uint32_t)(p[wOffset] << 8) / brightness) << 24) | + (((uint32_t)(p[rOffset] << 8) / brightness) << 16) | + (((uint32_t)(p[gOffset] << 8) / brightness) << 8) | + ((uint32_t)(p[bOffset] << 8) / brightness); + } else { // Return raw color + return ((uint32_t)p[wOffset] << 24) | ((uint32_t)p[rOffset] << 16) | + ((uint32_t)p[gOffset] << 8) | (uint32_t)p[bOffset]; + } + } +} + +/*! + @brief Adjust output brightness. Does not immediately affect what's + currently displayed on the LEDs. The next call to show() will + refresh the LEDs at this level. + @param b Brightness setting, 0=minimum (off), 255=brightest. + @note This was intended for one-time use in one's setup() function, + not as an animation effect in itself. Because of the way this + library "pre-multiplies" LED colors in RAM, changing the + brightness is often a "lossy" operation -- what you write to + pixels isn't necessary the same as what you'll read back. + Repeated brightness changes using this function exacerbate the + problem. Smart programs therefore treat the strip as a + write-only resource, maintaining their own state to render each + frame of an animation, not relying on read-modify-write. +*/ +void Adafruit_NeoPixel::setBrightness(uint8_t b) { + // Stored brightness value is different than what's passed. + // This simplifies the actual scaling math later, allowing a fast + // 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t, + // adding 1 here may (intentionally) roll over...so 0 = max brightness + // (color values are interpreted literally; no scaling), 1 = min + // brightness (off), 255 = just below max brightness. + uint8_t newBrightness = b + 1; + if (newBrightness != brightness) { // Compare against prior value + // Brightness has changed -- re-scale existing data in RAM, + // This process is potentially "lossy," especially when increasing + // brightness. The tight timing in the WS2811/WS2812 code means there + // aren't enough free cycles to perform this scaling on the fly as data + // is issued. So we make a pass through the existing color data in RAM + // and scale it (subsequent graphics commands also work at this + // brightness level). If there's a significant step up in brightness, + // the limited number of steps (quantization) in the old data will be + // quite visible in the re-scaled version. For a non-destructive + // change, you'll need to re-render the full strip data. C'est la vie. + uint8_t c, *ptr = pixels, + oldBrightness = brightness - 1; // De-wrap old brightness value + uint16_t scale; + if (oldBrightness == 0) + scale = 0; // Avoid /0 + else if (b == 255) + scale = 65535 / oldBrightness; + else + scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness; + for (uint16_t i = 0; i < numBytes; i++) { + c = *ptr; + *ptr++ = (c * scale) >> 8; + } + brightness = newBrightness; + } +} + +/*! + @brief Retrieve the last-set brightness value for the strip. + @return Brightness value: 0 = minimum (off), 255 = maximum. +*/ +uint8_t Adafruit_NeoPixel::getBrightness(void) const { return brightness - 1; } + +/*! + @brief Fill the whole NeoPixel strip with 0 / black / off. +*/ +void Adafruit_NeoPixel::clear(void) { memset(pixels, 0, numBytes); } + +// A 32-bit variant of gamma8() that applies the same function +// to all components of a packed RGB or WRGB value. +uint32_t Adafruit_NeoPixel::gamma32(uint32_t x) { + uint8_t *y = (uint8_t *)&x; + // All four bytes of a 32-bit value are filtered even if RGB (not WRGB), + // to avoid a bunch of shifting and masking that would be necessary for + // properly handling different endianisms (and each byte is a fairly + // trivial operation, so it might not even be wasting cycles vs a check + // and branch for the RGB case). In theory this might cause trouble *if* + // someone's storing information in the unused most significant byte + // of an RGB value, but this seems exceedingly rare and if it's + // encountered in reality they can mask values going in or coming out. + for (uint8_t i = 0; i < 4; i++) + y[i] = gamma8(y[i]); + return x; // Packed 32-bit return +} + +/*! + @brief Fill NeoPixel strip with one or more cycles of hues. + Everyone loves the rainbow swirl so much, now it's canon! + @param first_hue Hue of first pixel, 0-65535, representing one full + cycle of the color wheel. Each subsequent pixel will + be offset to complete one or more cycles over the + length of the strip. + @param reps Number of cycles of the color wheel over the length + of the strip. Default is 1. Negative values can be + used to reverse the hue order. + @param saturation Saturation (optional), 0-255 = gray to pure hue, + default = 255. + @param brightness Brightness/value (optional), 0-255 = off to max, + default = 255. This is distinct and in combination + with any configured global strip brightness. + @param gammify If true (default), apply gamma correction to colors + for better appearance. +*/ +void Adafruit_NeoPixel::rainbow(uint16_t first_hue, int8_t reps, + uint8_t saturation, uint8_t brightness, bool gammify) { + for (uint16_t i=0; i. + * + */ + +#ifndef ADAFRUIT_NEOPIXEL_H +#define ADAFRUIT_NEOPIXEL_H + +#ifdef ARDUINO +#if (ARDUINO >= 100) +#include +#else +#include +#include +#endif + +#ifdef USE_TINYUSB // For Serial when selecting TinyUSB +#include +#endif + +#endif + +#ifdef TARGET_LPC1768 +#include +#endif + +#if defined(ARDUINO_ARCH_RP2040) +#include +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "rp2040_pio.h" +#endif + +// The order of primary colors in the NeoPixel data stream can vary among +// device types, manufacturers and even different revisions of the same +// item. The third parameter to the Adafruit_NeoPixel constructor encodes +// the per-pixel byte offsets of the red, green and blue primaries (plus +// white, if present) in the data stream -- the following #defines provide +// an easier-to-use named version for each permutation. e.g. NEO_GRB +// indicates a NeoPixel-compatible device expecting three bytes per pixel, +// with the first byte transmitted containing the green value, second +// containing red and third containing blue. The in-memory representation +// of a chain of NeoPixels is the same as the data-stream order; no +// re-ordering of bytes is required when issuing data to the chain. +// Most of these values won't exist in real-world devices, but it's done +// this way so we're ready for it (also, if using the WS2811 driver IC, +// one might have their pixels set up in any weird permutation). + +// Bits 5,4 of this value are the offset (0-3) from the first byte of a +// pixel to the location of the red color byte. Bits 3,2 are the green +// offset and 1,0 are the blue offset. If it is an RGBW-type device +// (supporting a white primary in addition to R,G,B), bits 7,6 are the +// offset to the white byte...otherwise, bits 7,6 are set to the same value +// as 5,4 (red) to indicate an RGB (not RGBW) device. +// i.e. binary representation: +// 0bWWRRGGBB for RGBW devices +// 0bRRRRGGBB for RGB + +// RGB NeoPixel permutations; white and red offsets are always same +// Offset: W R G B +#define NEO_RGB ((0 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B +#define NEO_RBG ((0 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G +#define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B +#define NEO_GBR ((2 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R +#define NEO_BRG ((1 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G +#define NEO_BGR ((2 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R + +// RGBW NeoPixel permutations; all 4 offsets are distinct +// Offset: W R G B +#define NEO_WRGB ((0 << 6) | (1 << 4) | (2 << 2) | (3)) ///< Transmit as W,R,G,B +#define NEO_WRBG ((0 << 6) | (1 << 4) | (3 << 2) | (2)) ///< Transmit as W,R,B,G +#define NEO_WGRB ((0 << 6) | (2 << 4) | (1 << 2) | (3)) ///< Transmit as W,G,R,B +#define NEO_WGBR ((0 << 6) | (3 << 4) | (1 << 2) | (2)) ///< Transmit as W,G,B,R +#define NEO_WBRG ((0 << 6) | (2 << 4) | (3 << 2) | (1)) ///< Transmit as W,B,R,G +#define NEO_WBGR ((0 << 6) | (3 << 4) | (2 << 2) | (1)) ///< Transmit as W,B,G,R + +#define NEO_RWGB ((1 << 6) | (0 << 4) | (2 << 2) | (3)) ///< Transmit as R,W,G,B +#define NEO_RWBG ((1 << 6) | (0 << 4) | (3 << 2) | (2)) ///< Transmit as R,W,B,G +#define NEO_RGWB ((2 << 6) | (0 << 4) | (1 << 2) | (3)) ///< Transmit as R,G,W,B +#define NEO_RGBW ((3 << 6) | (0 << 4) | (1 << 2) | (2)) ///< Transmit as R,G,B,W +#define NEO_RBWG ((2 << 6) | (0 << 4) | (3 << 2) | (1)) ///< Transmit as R,B,W,G +#define NEO_RBGW ((3 << 6) | (0 << 4) | (2 << 2) | (1)) ///< Transmit as R,B,G,W + +#define NEO_GWRB ((1 << 6) | (2 << 4) | (0 << 2) | (3)) ///< Transmit as G,W,R,B +#define NEO_GWBR ((1 << 6) | (3 << 4) | (0 << 2) | (2)) ///< Transmit as G,W,B,R +#define NEO_GRWB ((2 << 6) | (1 << 4) | (0 << 2) | (3)) ///< Transmit as G,R,W,B +#define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W +#define NEO_GBWR ((2 << 6) | (3 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,W,R +#define NEO_GBRW ((3 << 6) | (2 << 4) | (0 << 2) | (1)) ///< Transmit as G,B,R,W + +#define NEO_BWRG ((1 << 6) | (2 << 4) | (3 << 2) | (0)) ///< Transmit as B,W,R,G +#define NEO_BWGR ((1 << 6) | (3 << 4) | (2 << 2) | (0)) ///< Transmit as B,W,G,R +#define NEO_BRWG ((2 << 6) | (1 << 4) | (3 << 2) | (0)) ///< Transmit as B,R,W,G +#define NEO_BRGW ((3 << 6) | (1 << 4) | (2 << 2) | (0)) ///< Transmit as B,R,G,W +#define NEO_BGWR ((2 << 6) | (3 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,W,R +#define NEO_BGRW ((3 << 6) | (2 << 4) | (1 << 2) | (0)) ///< Transmit as B,G,R,W + +// Add NEO_KHZ400 to the color order value to indicate a 400 KHz device. +// All but the earliest v1 NeoPixels expect an 800 KHz data stream, this is +// the default if unspecified. Because flash space is very limited on ATtiny +// devices (e.g. Trinket, Gemma), v1 NeoPixels aren't handled by default on +// those chips, though it can be enabled by removing the ifndef/endif below, +// but code will be bigger. Conversely, can disable the NEO_KHZ400 line on +// other MCUs to remove v1 support and save a little space. + +#define NEO_KHZ800 0x0000 ///< 800 KHz data transmission +#ifndef __AVR_ATtiny85__ +#define NEO_KHZ400 0x0100 ///< 400 KHz data transmission +#endif + +// If 400 KHz support is enabled, the third parameter to the constructor +// requires a 16-bit value (in order to select 400 vs 800 KHz speed). +// If only 800 KHz is enabled (as is default on ATtiny), an 8-bit value +// is sufficient to encode pixel color order, saving some space. + +#ifdef NEO_KHZ400 +typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +#else +typedef uint8_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +#endif + +// These two tables are declared outside the Adafruit_NeoPixel class +// because some boards may require oldschool compilers that don't +// handle the C++11 constexpr keyword. + +/* A PROGMEM (flash mem) table containing 8-bit unsigned sine wave (0-255). + Copy & paste this snippet into a Python REPL to regenerate: +import math +for x in range(256): + print("{:3},".format(int((math.sin(x/128.0*math.pi)+1.0)*127.5+0.5))), + if x&15 == 15: print +*/ +static const uint8_t PROGMEM _NeoPixelSineTable[256] = { + 128, 131, 134, 137, 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170, + 173, 176, 179, 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211, + 213, 215, 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240, + 241, 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254, + 254, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251, + 250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234, 232, + 230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203, 201, 198, + 196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165, 162, 158, 155, + 152, 149, 146, 143, 140, 137, 134, 131, 128, 124, 121, 118, 115, 112, 109, + 106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73, 70, 67, 65, + 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29, + 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6, + 5, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10, 11, + 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, + 40, 42, 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, + 79, 82, 85, 88, 90, 93, 97, 100, 103, 106, 109, 112, 115, 118, 121, + 124}; + +/* Similar to above, but for an 8-bit gamma-correction table. + Copy & paste this snippet into a Python REPL to regenerate: +import math +gamma=2.6 +for x in range(256): + print("{:3},".format(int(math.pow((x)/255.0,gamma)*255.0+0.5))), + if x&15 == 15: print +*/ +static const uint8_t PROGMEM _NeoPixelGammaTable[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, + 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, + 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10, + 11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, + 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, 23, 24, 24, 25, + 25, 26, 27, 27, 28, 29, 29, 30, 31, 31, 32, 33, 34, 34, 35, + 36, 37, 38, 38, 39, 40, 41, 42, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 68, 69, 70, 71, 72, 73, 75, 76, 77, 78, 80, 81, + 82, 84, 85, 86, 88, 89, 90, 92, 93, 94, 96, 97, 99, 100, 102, + 103, 105, 106, 108, 109, 111, 112, 114, 115, 117, 119, 120, 122, 124, 125, + 127, 129, 130, 132, 134, 136, 137, 139, 141, 143, 145, 146, 148, 150, 152, + 154, 156, 158, 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, + 184, 186, 188, 191, 193, 195, 197, 199, 202, 204, 206, 209, 211, 213, 215, + 218, 220, 223, 225, 227, 230, 232, 235, 237, 240, 242, 245, 247, 250, 252, + 255}; + +/*! + @brief Class that stores state and functions for interacting with + Adafruit NeoPixels and compatible devices. +*/ +class Adafruit_NeoPixel { + +public: + // Constructor: number of LEDs, pin number, LED type + Adafruit_NeoPixel(uint16_t n, int16_t pin = 6, + neoPixelType type = NEO_GRB + NEO_KHZ800); + Adafruit_NeoPixel(void); + ~Adafruit_NeoPixel(); + + void begin(void); + void show(void); + void setPin(int16_t p); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); + void setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w); + void setPixelColor(uint16_t n, uint32_t c); + void fill(uint32_t c = 0, uint16_t first = 0, uint16_t count = 0); + void setBrightness(uint8_t); + void clear(void); + void updateLength(uint16_t n); + void updateType(neoPixelType t); + /*! + @brief Check whether a call to show() will start sending data + immediately or will 'block' for a required interval. NeoPixels + require a short quiet time (about 300 microseconds) after the + last bit is received before the data 'latches' and new data can + start being received. Usually one's sketch is implicitly using + this time to generate a new frame of animation...but if it + finishes very quickly, this function could be used to see if + there's some idle time available for some low-priority + concurrent task. + @return 1 or true if show() will start sending immediately, 0 or false + if show() would block (meaning some idle time is available). + */ + bool canShow(void) { + // It's normal and possible for endTime to exceed micros() if the + // 32-bit clock counter has rolled over (about every 70 minutes). + // Since both are uint32_t, a negative delta correctly maps back to + // positive space, and it would seem like the subtraction below would + // suffice. But a problem arises if code invokes show() very + // infrequently...the micros() counter may roll over MULTIPLE times in + // that interval, the delta calculation is no longer correct and the + // next update may stall for a very long time. The check below resets + // the latch counter if a rollover has occurred. This can cause an + // extra delay of up to 300 microseconds in the rare case where a + // show() call happens precisely around the rollover, but that's + // neither likely nor especially harmful, vs. other code that might + // stall for 30+ minutes, or having to document and frequently remind + // and/or provide tech support explaining an unintuitive need for + // show() calls at least once an hour. + uint32_t now = micros(); + if (endTime > now) { + endTime = now; + } + return (now - endTime) >= 300L; + } + /*! + @brief Get a pointer directly to the NeoPixel data buffer in RAM. + Pixel data is stored in a device-native format (a la the NEO_* + constants) and is not translated here. Applications that access + this buffer will need to be aware of the specific data format + and handle colors appropriately. + @return Pointer to NeoPixel buffer (uint8_t* array). + @note This is for high-performance applications where calling + setPixelColor() on every single pixel would be too slow (e.g. + POV or light-painting projects). There is no bounds checking + on the array, creating tremendous potential for mayhem if one + writes past the ends of the buffer. Great power, great + responsibility and all that. + */ + uint8_t *getPixels(void) const { return pixels; }; + uint8_t getBrightness(void) const; + /*! + @brief Retrieve the pin number used for NeoPixel data output. + @return Arduino pin number (-1 if not set). + */ + int16_t getPin(void) const { return pin; }; + /*! + @brief Return the number of pixels in an Adafruit_NeoPixel strip object. + @return Pixel count (0 if not set). + */ + uint16_t numPixels(void) const { return numLEDs; } + uint32_t getPixelColor(uint16_t n) const; + /*! + @brief An 8-bit integer sine wave function, not directly compatible + with standard trigonometric units like radians or degrees. + @param x Input angle, 0-255; 256 would loop back to zero, completing + the circle (equivalent to 360 degrees or 2 pi radians). + One can therefore use an unsigned 8-bit variable and simply + add or subtract, allowing it to overflow/underflow and it + still does the expected contiguous thing. + @return Sine result, 0 to 255, or -128 to +127 if type-converted to + a signed int8_t, but you'll most likely want unsigned as this + output is often used for pixel brightness in animation effects. + */ + static uint8_t sine8(uint8_t x) { + return pgm_read_byte(&_NeoPixelSineTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief An 8-bit gamma-correction function for basic pixel brightness + adjustment. Makes color transitions appear more perceptially + correct. + @param x Input brightness, 0 (minimum or off/black) to 255 (maximum). + @return Gamma-adjusted brightness, can then be passed to one of the + setPixelColor() functions. This uses a fixed gamma correction + exponent of 2.6, which seems reasonably okay for average + NeoPixels in average tasks. If you need finer control you'll + need to provide your own gamma-correction function instead. + */ + static uint8_t gamma8(uint8_t x) { + return pgm_read_byte(&_NeoPixelGammaTable[x]); // 0-255 in, 0-255 out + } + /*! + @brief Convert separate red, green and blue values into a single + "packed" 32-bit RGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @return 32-bit packed RGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed RGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + /*! + @brief Convert separate red, green, blue and white values into a + single "packed" 32-bit WRGB color. + @param r Red brightness, 0 to 255. + @param g Green brightness, 0 to 255. + @param b Blue brightness, 0 to 255. + @param w White brightness, 0 to 255. + @return 32-bit packed WRGB value, which can then be assigned to a + variable for later use or passed to the setPixelColor() + function. Packed WRGB format is predictable, regardless of + LED strand color order. + */ + static uint32_t Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; + } + static uint32_t ColorHSV(uint16_t hue, uint8_t sat = 255, uint8_t val = 255); + /*! + @brief A gamma-correction function for 32-bit packed RGB or WRGB + colors. Makes color transitions appear more perceptially + correct. + @param x 32-bit packed RGB or WRGB color. + @return Gamma-adjusted packed color, can then be passed in one of the + setPixelColor() functions. Like gamma8(), this uses a fixed + gamma correction exponent of 2.6, which seems reasonably okay + for average NeoPixels in average tasks. If you need finer + control you'll need to provide your own gamma-correction + function instead. + */ + static uint32_t gamma32(uint32_t x); + + void rainbow(uint16_t first_hue = 0, int8_t reps = 1, + uint8_t saturation = 255, uint8_t brightness = 255, + bool gammify = true); + + static neoPixelType str2order(const char *v); + +private: +#if defined(ARDUINO_ARCH_RP2040) + void rp2040Init(uint8_t pin, bool is800KHz); + void rp2040Show(uint8_t pin, uint8_t *pixels, uint32_t numBytes, bool is800KHz); +#endif + +protected: +#ifdef NEO_KHZ400 // If 400 KHz NeoPixel support enabled... + bool is800KHz; ///< true if 800 KHz pixels +#endif + bool begun; ///< true if begin() previously called + uint16_t numLEDs; ///< Number of RGB LEDs in strip + uint16_t numBytes; ///< Size of 'pixels' buffer below + int16_t pin; ///< Output pin number (-1 if not yet set) + uint8_t brightness; ///< Strip brightness 0-255 (stored as +1) + uint8_t *pixels; ///< Holds LED color values (3 or 4 bytes each) + uint8_t rOffset; ///< Red index within each 3- or 4-byte pixel + uint8_t gOffset; ///< Index of green byte + uint8_t bOffset; ///< Index of blue byte + uint8_t wOffset; ///< Index of white (==rOffset if no white) + uint32_t endTime; ///< Latch timing reference +#ifdef __AVR__ + volatile uint8_t *port; ///< Output PORT register + uint8_t pinMask; ///< Output PORT bitmask +#endif +#if defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_ARDUINO_CORE_STM32) + GPIO_TypeDef *gpioPort; ///< Output GPIO PORT + uint32_t gpioPin; ///< Output GPIO PIN +#endif +#if defined(ARDUINO_ARCH_RP2040) + PIO pio = pio0; + int sm = 0; + bool init = true; +#endif +}; + +#endif // ADAFRUIT_NEOPIXEL_H diff --git a/lib/Adafruit_NeoPixel/CONTRIBUTING.md b/lib/Adafruit_NeoPixel/CONTRIBUTING.md new file mode 100644 index 000000000..aa753894e --- /dev/null +++ b/lib/Adafruit_NeoPixel/CONTRIBUTING.md @@ -0,0 +1,13 @@ +# Contribution Guidelines + +This library is the culmination of the expertise of many members of the open source community who have dedicated their time and hard work. The best way to ask for help or propose a new idea is to [create a new issue](https://github.com/adafruit/Adafruit_NeoPixel/issues/new) while creating a Pull Request with your code changes allows you to share your own innovations with the rest of the community. + +The following are some guidelines to observe when creating issues or PRs: + +- Be friendly; it is important that we can all enjoy a safe space as we are all working on the same project and it is okay for people to have different ideas + +- [Use code blocks](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code); it helps us help you when we can read your code! On that note also refrain from pasting more than 30 lines of code in a post, instead [create a gist](https://gist.github.com/) if you need to share large snippets + +- Use reasonable titles; refrain from using overly long or capitalized titles as they are usually annoying and do little to encourage others to help :smile: + +- Be detailed; refrain from mentioning code problems without sharing your source code and always give information regarding your board and version of the library diff --git a/lib/Adafruit_NeoPixel/COPYING b/lib/Adafruit_NeoPixel/COPYING new file mode 100644 index 000000000..65c5ca88a --- /dev/null +++ b/lib/Adafruit_NeoPixel/COPYING @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/Adafruit_NeoPixel/README.md b/lib/Adafruit_NeoPixel/README.md new file mode 100644 index 000000000..62fef219f --- /dev/null +++ b/lib/Adafruit_NeoPixel/README.md @@ -0,0 +1,158 @@ +# Adafruit NeoPixel Library [![Build Status](https://github.com/adafruit/Adafruit_NeoPixel/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_NeoPixel/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_NeoPixel/html/index.html) + +Arduino library for controlling single-wire-based LED pixels and strip such as the [Adafruit 60 LED/meter Digital LED strip][strip], the [Adafruit FLORA RGB Smart Pixel][flora], the [Adafruit Breadboard-friendly RGB Smart Pixel][pixel], the [Adafruit NeoPixel Stick][stick], and the [Adafruit NeoPixel Shield][shield]. + +After downloading, rename folder to 'Adafruit_NeoPixel' and install in Arduino Libraries folder. Restart Arduino IDE, then open File->Sketchbook->Library->Adafruit_NeoPixel->strandtest sketch. + +Compatibility notes: Port A is not supported on any AVR processors at this time + +[flora]: http://adafruit.com/products/1060 +[strip]: http://adafruit.com/products/1138 +[pixel]: http://adafruit.com/products/1312 +[stick]: http://adafruit.com/products/1426 +[shield]: http://adafruit.com/products/1430 + +--- + +## Installation + +### First Method + +![image](https://user-images.githubusercontent.com/36513474/68967967-3e37f480-0803-11ea-91d9-601848c306ee.png) + +1. In the Arduino IDE, navigate to Sketch > Include Library > Manage Libraries +1. Then the Library Manager will open and you will find a list of libraries that are already installed or ready for installation. +1. Then search for Neopixel strip using the search bar. +1. Click on the text area and then select the specific version and install it. + +### Second Method + +1. Navigate to the [Releases page](https://github.com/adafruit/Adafruit_NeoPixel/releases). +1. Download the latest release. +1. Extract the zip file +1. In the Arduino IDE, navigate to Sketch > Include Library > Add .ZIP Library + +## Features + +- ### Simple to use + + Controlling NeoPixels “from scratch†is quite a challenge, so we provide a library letting you focus on the fun and interesting bits. + +- ### Give back + + The library is free; you don’t have to pay for anything. Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +- ### Supported Chipsets + + We have included code for the following chips - sometimes these break for exciting reasons that we can't control in which case please open an issue! + + - AVR ATmega and ATtiny (any 8-bit) - 8 MHz, 12 MHz and 16 MHz + - Teensy 3.x and LC + - Arduino Due + - Arduino 101 + - ATSAMD21 (Arduino Zero/M0 and other SAMD21 boards) @ 48 MHz + - ATSAMD51 @ 120 MHz + - Adafruit STM32 Feather @ 120 MHz + - ESP8266 any speed + - ESP32 any speed + - Nordic nRF52 (Adafruit Feather nRF52), nRF51 (micro:bit) + - Infineon XMC1100 BootKit @ 32 MHz + - Infineon XMC1100 2Go @ 32 MHz + - Infineon XMC1300 BootKit @ 32 MHz + - Infineon XMC4700 RelaxKit, XMC4800 RelaxKit, XMC4800 IoT Amazon FreeRTOS Kit @ 144 MHz + + Check forks for other architectures not listed here! + +- ### GNU Lesser General Public License + + Adafruit_NeoPixel is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +## Functions + +- begin() +- updateLength() +- updateType() +- show() +- delay_ns() +- setPin() +- setPixelColor() +- fill() +- ColorHSV() +- getPixelColor() +- setBrightness() +- getBrightness() +- clear() +- gamma32() + +## Examples + +There are many examples implemented in this library. One of the examples is below. You can find other examples [here](https://github.com/adafruit/Adafruit_NeoPixel/tree/master/examples) + +### Simple + +```Cpp +#include +#ifdef __AVR__ + #include +#endif +#define PIN 6 +#define NUMPIXELS 16 + +Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); +#define DELAYVAL 500 + +void setup() { +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + + pixels.begin(); +} + +void loop() { + pixels.clear(); + + for(int i=0; i + +#if defined(ESP_IDF_VERSION) +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) +#define HAS_ESP_IDF_4 +#endif +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) +#define HAS_ESP_IDF_5 +#endif +#endif + + + +#ifdef HAS_ESP_IDF_5 + +void espShow(int16_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { + rmt_data_t led_data[numBytes * 8]; + + if (!rmtInit(pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) { + log_e("Failed to init RMT TX mode on pin %d", pin); + return; + } + + int i=0; + for (int b=0; b < numBytes; b++) { + for (int bit=0; bit<8; bit++){ + if ( pixels[b] & (1<<(7-bit)) ) { + led_data[i].level0 = 1; + led_data[i].duration0 = 8; + led_data[i].level1 = 0; + led_data[i].duration1 = 4; + } else { + led_data[i].level0 = 1; + led_data[i].duration0 = 4; + led_data[i].level1 = 0; + led_data[i].duration1 = 8; + } + i++; + } + } + + //pinMode(pin, OUTPUT); // don't do this, will cause the rmt to disable! + rmtWrite(pin, led_data, numBytes * 8, RMT_WAIT_FOR_EVER); +} + + + +#else + +#include "driver/rmt.h" + + +// This code is adapted from the ESP-IDF v3.4 RMT "led_strip" example, altered +// to work with the Arduino version of the ESP-IDF (3.2) + +#define WS2812_T0H_NS (400) +#define WS2812_T0L_NS (850) +#define WS2812_T1H_NS (800) +#define WS2812_T1L_NS (450) + +#define WS2811_T0H_NS (500) +#define WS2811_T0L_NS (2000) +#define WS2811_T1H_NS (1200) +#define WS2811_T1L_NS (1300) + +static uint32_t t0h_ticks = 0; +static uint32_t t1h_ticks = 0; +static uint32_t t0l_ticks = 0; +static uint32_t t1l_ticks = 0; + +// Limit the number of RMT channels available for the Neopixels. Defaults to all +// channels (8 on ESP32, 4 on ESP32-S2 and S3). Redefining this value will free +// any channels with a higher number for other uses, such as IR send-and-recieve +// libraries. Redefine as 1 to restrict Neopixels to only a single channel. +#define ADAFRUIT_RMT_CHANNEL_MAX RMT_CHANNEL_MAX + +#define RMT_LL_HW_BASE (&RMT) + +bool rmt_reserved_channels[ADAFRUIT_RMT_CHANNEL_MAX]; + +static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, + size_t wanted_num, size_t *translated_size, size_t *item_num) +{ + if (src == NULL || dest == NULL) { + *translated_size = 0; + *item_num = 0; + return; + } + const rmt_item32_t bit0 = {{{ t0h_ticks, 1, t0l_ticks, 0 }}}; //Logical 0 + const rmt_item32_t bit1 = {{{ t1h_ticks, 1, t1l_ticks, 0 }}}; //Logical 1 + size_t size = 0; + size_t num = 0; + uint8_t *psrc = (uint8_t *)src; + rmt_item32_t *pdest = dest; + while (size < src_size && num < wanted_num) { + for (int i = 0; i < 8; i++) { + // MSB first + if (*psrc & (1 << (7 - i))) { + pdest->val = bit1.val; + } else { + pdest->val = bit0.val; + } + num++; + pdest++; + } + size++; + psrc++; + } + *translated_size = size; + *item_num = num; +} + +void espShow(int16_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { + // Reserve channel + rmt_channel_t channel = ADAFRUIT_RMT_CHANNEL_MAX; + for (size_t i = 0; i < ADAFRUIT_RMT_CHANNEL_MAX; i++) { + if (!rmt_reserved_channels[i]) { + rmt_reserved_channels[i] = true; + channel = i; + break; + } + } + if (channel == ADAFRUIT_RMT_CHANNEL_MAX) { + // Ran out of channels! + return; + } + +#if defined(HAS_ESP_IDF_4) + rmt_config_t config = RMT_DEFAULT_CONFIG_TX(pin, channel); + config.clk_div = 2; +#else + // Match default TX config from ESP-IDF version 3.4 + rmt_config_t config = { + .rmt_mode = RMT_MODE_TX, + .channel = channel, + .gpio_num = pin, + .clk_div = 2, + .mem_block_num = 1, + .tx_config = { + .carrier_freq_hz = 38000, + .carrier_level = RMT_CARRIER_LEVEL_HIGH, + .idle_level = RMT_IDLE_LEVEL_LOW, + .carrier_duty_percent = 33, + .carrier_en = false, + .loop_en = false, + .idle_output_en = true, + } + }; +#endif + rmt_config(&config); + rmt_driver_install(config.channel, 0, 0); + + // Convert NS timings to ticks + uint32_t counter_clk_hz = 0; + +#if defined(HAS_ESP_IDF_4) + rmt_get_counter_clock(channel, &counter_clk_hz); +#else + // this emulates the rmt_get_counter_clock() function from ESP-IDF 3.4 + if (RMT_LL_HW_BASE->conf_ch[config.channel].conf1.ref_always_on == RMT_BASECLK_REF) { + uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; + uint32_t div = div_cnt == 0 ? 256 : div_cnt; + counter_clk_hz = REF_CLK_FREQ / (div); + } else { + uint32_t div_cnt = RMT_LL_HW_BASE->conf_ch[config.channel].conf0.div_cnt; + uint32_t div = div_cnt == 0 ? 256 : div_cnt; + counter_clk_hz = APB_CLK_FREQ / (div); + } +#endif + + // NS to tick converter + float ratio = (float)counter_clk_hz / 1e9; + + if (is800KHz) { + t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); + t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); + t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); + t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); + } else { + t0h_ticks = (uint32_t)(ratio * WS2811_T0H_NS); + t0l_ticks = (uint32_t)(ratio * WS2811_T0L_NS); + t1h_ticks = (uint32_t)(ratio * WS2811_T1H_NS); + t1l_ticks = (uint32_t)(ratio * WS2811_T1L_NS); + } + + // Initialize automatic timing translator + rmt_translator_init(config.channel, ws2812_rmt_adapter); + + // Write and wait to finish + rmt_write_sample(config.channel, pixels, (size_t)numBytes, true); + rmt_wait_tx_done(config.channel, pdMS_TO_TICKS(100)); + + // Free channel again + rmt_driver_uninstall(config.channel); + rmt_reserved_channels[channel] = false; + + gpio_set_direction(pin, GPIO_MODE_OUTPUT); +} + +#endif // ifndef IDF5 + + +#endif // ifdef(ESP32) diff --git a/lib/Adafruit_NeoPixel/esp8266.c b/lib/Adafruit_NeoPixel/esp8266.c new file mode 100644 index 000000000..89c345e74 --- /dev/null +++ b/lib/Adafruit_NeoPixel/esp8266.c @@ -0,0 +1,86 @@ +// This is a mash-up of the Due show() code + insights from Michael Miller's +// ESP8266 work for the NeoPixelBus library: github.com/Makuna/NeoPixelBus +// Needs to be a separate .c file to enforce ICACHE_RAM_ATTR execution. + +#if defined(ESP8266) + +#include +#ifdef ESP8266 +#include +#endif + +static uint32_t _getCycleCount(void) __attribute__((always_inline)); +static inline uint32_t _getCycleCount(void) { + uint32_t ccount; + __asm__ __volatile__("rsr %0,ccount":"=a" (ccount)); + return ccount; +} + +#ifdef ESP8266 +IRAM_ATTR void espShow( + int16_t pin, uint8_t *pixels, uint32_t numBytes, __attribute__((unused)) boolean is800KHz) { +#else +void espShow( + int16_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) { +#endif + +#define CYCLES_800_T0H (F_CPU / 2500001) // 0.4us +#define CYCLES_800_T1H (F_CPU / 1250001) // 0.8us +#define CYCLES_800 (F_CPU / 800001) // 1.25us per bit +#define CYCLES_400_T0H (F_CPU / 2000000) // 0.5uS +#define CYCLES_400_T1H (F_CPU / 833333) // 1.2us +#define CYCLES_400 (F_CPU / 400000) // 2.5us per bit + + uint8_t *p, *end, pix, mask; + uint32_t t, time0, time1, period, c, startTime; + +#ifdef ESP8266 + uint32_t pinMask; + pinMask = _BV(pin); +#endif + + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; + startTime = 0; + +#ifdef NEO_KHZ400 + if(is800KHz) { +#endif + time0 = CYCLES_800_T0H; + time1 = CYCLES_800_T1H; + period = CYCLES_800; +#ifdef NEO_KHZ400 + } else { // 400 KHz bitstream + time0 = CYCLES_400_T0H; + time1 = CYCLES_400_T1H; + period = CYCLES_400; + } +#endif + + for(t = time0;; t = time0) { + if(pix & mask) t = time1; // Bit high duration + while(((c = _getCycleCount()) - startTime) < period); // Wait for bit start +#ifdef ESP8266 + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, pinMask); // Set high +#else + gpio_set_level(pin, HIGH); +#endif + startTime = c; // Save start time + while(((c = _getCycleCount()) - startTime) < t); // Wait high duration +#ifdef ESP8266 + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinMask); // Set low +#else + gpio_set_level(pin, LOW); +#endif + if(!(mask >>= 1)) { // Next bit/byte + if(p >= end) break; + pix = *p++; + mask = 0x80; + } + } + while((_getCycleCount() - startTime) < period); // Wait for last bit +} + +#endif // ESP8266 diff --git a/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/.esp8266.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/.trinket.test.skip b/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/.trinket.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/RGBWstrandtest.ino b/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/RGBWstrandtest.ino new file mode 100644 index 000000000..95335cdfc --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/RGBWstrandtest/RGBWstrandtest.ino @@ -0,0 +1,177 @@ +// NeoPixel test program showing use of the WHITE channel for RGBW +// pixels only (won't look correct on regular RGB NeoPixel strips). + +#include +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Which pin on the Arduino is connected to the NeoPixels? +// On a Trinket or Gemma we suggest changing this to 1: +#define LED_PIN 6 + +// How many NeoPixels are attached to the Arduino? +#define LED_COUNT 60 + +// NeoPixel brightness, 0 (min) to 255 (max) +#define BRIGHTNESS 50 // Set BRIGHTNESS to about 1/5 (max = 255) + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +void setup() { + // These lines are specifically to support the Adafruit Trinket 5V 16 MHz. + // Any other board, you can remove this part (but no harm leaving it): +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + // END of Trinket-specific code. + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + strip.setBrightness(BRIGHTNESS); +} + +void loop() { + // Fill along the length of the strip in various colors... + colorWipe(strip.Color(255, 0, 0) , 50); // Red + colorWipe(strip.Color( 0, 255, 0) , 50); // Green + colorWipe(strip.Color( 0, 0, 255) , 50); // Blue + colorWipe(strip.Color( 0, 0, 0, 255), 50); // True white (not RGB white) + + whiteOverRainbow(75, 5); + + pulseWhite(5); + + rainbowFade2White(3, 3, 1); +} + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + for(int i=0; i= strip.numPixels()) whiteLength = strip.numPixels() - 1; + + int head = whiteLength - 1; + int tail = 0; + int loops = 3; + int loopNum = 0; + uint32_t lastTime = millis(); + uint32_t firstPixelHue = 0; + + for(;;) { // Repeat forever (or until a 'break' or 'return') + for(int i=0; i= tail) && (i <= head)) || // If between head & tail... + ((tail > head) && ((i >= tail) || (i <= head)))) { + strip.setPixelColor(i, strip.Color(0, 0, 0, 255)); // Set white + } else { // else set rainbow + int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels()); + strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue))); + } + } + + strip.show(); // Update strip with new contents + // There's no delay here, it just runs full-tilt until the timer and + // counter combination below runs out. + + firstPixelHue += 40; // Advance just a little along the color wheel + + if((millis() - lastTime) > whiteSpeed) { // Time to update head/tail? + if(++head >= strip.numPixels()) { // Advance head, wrap around + head = 0; + if(++loopNum >= loops) return; + } + if(++tail >= strip.numPixels()) { // Advance tail, wrap around + tail = 0; + } + lastTime = millis(); // Save time of last movement + } + } +} + +void pulseWhite(uint8_t wait) { + for(int j=0; j<256; j++) { // Ramp up from 0 to 255 + // Fill entire strip with white at gamma-corrected brightness level 'j': + strip.fill(strip.Color(0, 0, 0, strip.gamma8(j))); + strip.show(); + delay(wait); + } + + for(int j=255; j>=0; j--) { // Ramp down from 255 to 0 + strip.fill(strip.Color(0, 0, 0, strip.gamma8(j))); + strip.show(); + delay(wait); + } +} + +void rainbowFade2White(int wait, int rainbowLoops, int whiteLoops) { + int fadeVal=0, fadeMax=100; + + // Hue of first pixel runs 'rainbowLoops' complete loops through the color + // wheel. Color wheel has a range of 65536 but it's OK if we roll over, so + // just count from 0 to rainbowLoops*65536, using steps of 256 so we + // advance around the wheel at a decent clip. + for(uint32_t firstPixelHue = 0; firstPixelHue < rainbowLoops*65536; + firstPixelHue += 256) { + + for(int i=0; i= ((rainbowLoops-1) * 65536)) { // Last loop, + if(fadeVal > 0) fadeVal--; // fade out + } else { + fadeVal = fadeMax; // Interim loop, make sure fade is at max + } + } + + for(int k=0; k=0; j--) { // Ramp down 255 to 0 + strip.fill(strip.Color(0, 0, 0, strip.gamma8(j))); + strip.show(); + } + } + + delay(500); // Pause 1/2 second +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLE/.none.test.only b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLE/.none.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLE/StrandtestArduinoBLE.ino b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLE/StrandtestArduinoBLE.ino new file mode 100644 index 000000000..80e02d21b --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLE/StrandtestArduinoBLE.ino @@ -0,0 +1,231 @@ +/**************************************************************************** + * This example is based on StrandtestBLE example and adapts it to use + * the new ArduinoBLE library. + * + * https://github.com/arduino-libraries/ArduinoBLE + * + * Supported boards: + * Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + * + * You can use a generic BLE central app, like LightBlue (iOS and Android) or + * nRF Connect (Android), to interact with the services and characteristics + * created in this sketch. + * + * This example code is in the public domain. + * + */ +#include + +#define PIN 15 // Pin where NeoPixels are connected + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +uint8_t rgb_values[3]; + +#include + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service + +// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + +void setup() +{ + Serial.begin(115200); + Serial.println("Hello World!"); + + // custom services and characteristics can be added as well + // begin initialization + if (!BLE.begin()) + { + Serial.println("starting BLE failed!"); + + while (1) + ; + } + + Serial.print("Peripheral address: "); + Serial.println(BLE.address()); + + // set advertised local name and service UUID: + BLE.setLocalName("LED"); + BLE.setAdvertisedService(ledService); + + // add the characteristic to the service + ledService.addCharacteristic(switchCharacteristic); + + // add service + BLE.addService(ledService); + + // set the initial value for the characeristic: + switchCharacteristic.writeValue(0); + + // start advertising + BLE.advertise(); + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + + pinMode(PIN, OUTPUT); + digitalWrite(PIN, LOW); + +} + +void loop() +{ + BLEDevice central = BLE.central(); + + // if a central is connected to peripheral: + if (central) + { + Serial.print("Connected to central: "); + // print the central's MAC address: + Serial.println(central.address()); + + // while the central is still connected to peripheral: + while (central.connected()) + { + // if the remote device wrote to the characteristic, + // use the value to control the LED: + if (switchCharacteristic.written()) + { + switch (switchCharacteristic.value()) + { + case 'a': + colorWipe(strip.Color(255, 0, 0), 20); // Red + break; + case 'b': + colorWipe(strip.Color(0, 255, 0), 20); // Green + break; + case 'c': + colorWipe(strip.Color(0, 0, 255), 20); // Blue + break; + case 'd': + theaterChase(strip.Color(255, 0, 0), 20); // Red + break; + case 'e': + theaterChase(strip.Color(0, 255, 0), 20); // Green + break; + case 'f': + theaterChase(strip.Color(255, 0, 255), 20); // Cyan + break; + case 'g': + rainbow(10); + break; + case 'h': + theaterChaseRainbow(20); + break; + } + } + } + } +} + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) +{ + for (int i = 0; i < strip.numPixels(); i++) + { // For each pixel in strip... + strip.setPixelColor(i, color); // Set pixel's color (in RAM) + strip.show(); // Update strip to match + delay(wait); // Pause for a moment + } +} + +// Theater-marquee-style chasing lights. Pass in a color (32-bit value, +// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms) +// between frames. +void theaterChase(uint32_t color, int wait) +{ + for (int a = 0; a < 10; a++) + { // Repeat 10 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + strip.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in steps of 3... + for (int c = b; c < strip.numPixels(); c += 3) + { + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + } + } +} + +// Rainbow cycle along whole strip. Pass delay time (in ms) between frames. +void rainbow(int wait) +{ + // Hue of first pixel runs 5 complete loops through the color wheel. + // Color wheel has a range of 65536 but it's OK if we roll over, so + // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time + // means we'll make 5*65536/256 = 1280 passes through this outer loop: + for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) + { + for (int i = 0; i < strip.numPixels(); i++) + { // For each pixel in strip... + // Offset pixel hue by an amount to make one full revolution of the + // color wheel (range of 65536) along the length of the strip + // (strip.numPixels() steps): + int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels()); + // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or + // optionally add saturation and value (brightness) (each 0 to 255). + // Here we're using just the single-argument hue variant. The result + // is passed through strip.gamma32() to provide 'truer' colors + // before assigning to each pixel: + strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue))); + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + } +} + +// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames. +void theaterChaseRainbow(int wait) +{ + int firstPixelHue = 0; // First pixel starts at red (hue 0) + for (int a = 0; a < 30; a++) + { // Repeat 30 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + strip.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in increments of 3... + for (int c = b; c < strip.numPixels(); c += 3) + { + // hue of pixel 'c' is offset by an amount to make one full + // revolution of the color wheel (range 65536) along the length + // of the strip (strip.numPixels() steps): + int hue = firstPixelHue + c * 65536L / strip.numPixels(); + uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLECallback/.none.test.only b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLECallback/.none.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLECallback/StrandtestArduinoBLECallback.ino b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLECallback/StrandtestArduinoBLECallback.ino new file mode 100644 index 000000000..b986943ae --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestArduinoBLECallback/StrandtestArduinoBLECallback.ino @@ -0,0 +1,239 @@ +/**************************************************************************** + * This example is based on StrandtestArduinoBLE example to make use of + * callbacks features of the ArduinoBLE library. + * + * https://github.com/arduino-libraries/ArduinoBLE + * + * Supported boards: + * Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT, + Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board. + * + * You can use a generic BLE central app, like LightBlue (iOS and Android) or + * nRF Connect (Android), to interact with the services and characteristics + * created in this sketch. + * + * This example code is in the public domain. + * + */ +#include + +#define PIN 15 // Pin where NeoPixels are connected + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +uint8_t rgb_values[3]; + +#include + +BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service + +// BLE LED Switch Characteristic - custom 128-bit UUID, read and writable by central +BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLEWrite); + +void setup() +{ + Serial.begin(115200); + Serial.println("Hello World!"); + + // custom services and characteristics can be added as well + // begin initialization + if (!BLE.begin()) + { + Serial.println("starting BLE failed!"); + + while (1) + ; + } + + Serial.print("Peripheral address: "); + Serial.println(BLE.address()); + + // set advertised local name and service UUID: + BLE.setLocalName("LEDCallback"); + BLE.setAdvertisedService(ledService); + + // add the characteristic to the service + ledService.addCharacteristic(switchCharacteristic); + + // add service + BLE.addService(ledService); + // assign event handlers for connected, disconnected to peripheral + BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler); + BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler); + + // assign event handlers for characteristic + switchCharacteristic.setEventHandler(BLEWritten, switchCharacteristicWritten); + // set the initial value for the characeristic: + switchCharacteristic.writeValue(0); + + // start advertising + BLE.advertise(); + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + + pinMode(PIN, OUTPUT); + digitalWrite(PIN, LOW); +} + +void loop() +{ + // poll for BLE events + BLE.poll(); +} + +void blePeripheralConnectHandler(BLEDevice central) +{ + // central connected event handler + Serial.print("Connected event, central: "); + Serial.println(central.address()); +} + +void blePeripheralDisconnectHandler(BLEDevice central) +{ + // central disconnected event handler + Serial.print("Disconnected event, central: "); + Serial.println(central.address()); +} + +void switchCharacteristicWritten(BLEDevice central, BLECharacteristic characteristic) +{ + // central wrote new value to characteristic, update LED + Serial.print("Characteristic event, written: "); + + switch (switchCharacteristic.value()) + { + case 'a': + colorWipe(strip.Color(255, 0, 0), 20); // Red + break; + case 'b': + colorWipe(strip.Color(0, 255, 0), 20); // Green + break; + case 'c': + colorWipe(strip.Color(0, 0, 255), 20); // Blue + break; + case 'd': + theaterChase(strip.Color(255, 0, 0), 20); // Red + break; + case 'e': + theaterChase(strip.Color(0, 255, 0), 20); // Green + break; + case 'f': + theaterChase(strip.Color(255, 0, 255), 20); // Cyan + break; + case 'g': + rainbow(10); + break; + case 'h': + theaterChaseRainbow(20); + break; + } +} + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) +{ + for (int i = 0; i < strip.numPixels(); i++) + { // For each pixel in strip... + strip.setPixelColor(i, color); // Set pixel's color (in RAM) + strip.show(); // Update strip to match + delay(wait); // Pause for a moment + } +} + +// Theater-marquee-style chasing lights. Pass in a color (32-bit value, +// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms) +// between frames. +void theaterChase(uint32_t color, int wait) +{ + for (int a = 0; a < 10; a++) + { // Repeat 10 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + strip.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in steps of 3... + for (int c = b; c < strip.numPixels(); c += 3) + { + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + } + } +} + +// Rainbow cycle along whole strip. Pass delay time (in ms) between frames. +void rainbow(int wait) +{ + // Hue of first pixel runs 5 complete loops through the color wheel. + // Color wheel has a range of 65536 but it's OK if we roll over, so + // just count from 0 to 5*65536. Adding 256 to firstPixelHue each time + // means we'll make 5*65536/256 = 1280 passes through this outer loop: + for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) + { + for (int i = 0; i < strip.numPixels(); i++) + { // For each pixel in strip... + // Offset pixel hue by an amount to make one full revolution of the + // color wheel (range of 65536) along the length of the strip + // (strip.numPixels() steps): + int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels()); + // strip.ColorHSV() can take 1 or 3 arguments: a hue (0 to 65535) or + // optionally add saturation and value (brightness) (each 0 to 255). + // Here we're using just the single-argument hue variant. The result + // is passed through strip.gamma32() to provide 'truer' colors + // before assigning to each pixel: + strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue))); + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + } +} + +// Rainbow-enhanced theater marquee. Pass delay time (in ms) between frames. +void theaterChaseRainbow(int wait) +{ + int firstPixelHue = 0; // First pixel starts at red (hue 0) + for (int a = 0; a < 30; a++) + { // Repeat 30 times... + for (int b = 0; b < 3; b++) + { // 'b' counts from 0 to 2... + strip.clear(); // Set all pixels in RAM to 0 (off) + // 'c' counts up from 'b' to end of strip in increments of 3... + for (int c = b; c < strip.numPixels(); c += 3) + { + // hue of pixel 'c' is offset by an amount to make one full + // revolution of the color wheel (range 65536) along the length + // of the strip (strip.numPixels() steps): + int hue = firstPixelHue + c * 65536L / strip.numPixels(); + uint32_t color = strip.gamma32(strip.ColorHSV(hue)); // hue -> RGB + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE/.none.test.only b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/.none.test.only new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.cpp b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.cpp new file mode 100644 index 000000000..d1693dec8 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.cpp @@ -0,0 +1,133 @@ +#include "BLESerial.h" + +// #define BLE_SERIAL_DEBUG + +BLESerial* BLESerial::_instance = NULL; + +BLESerial::BLESerial(unsigned char req, unsigned char rdy, unsigned char rst) : + BLEPeripheral(req, rdy, rst) +{ + this->_txCount = 0; + this->_rxHead = this->_rxTail = 0; + this->_flushed = 0; + BLESerial::_instance = this; + + addAttribute(this->_uartService); + addAttribute(this->_uartNameDescriptor); + setAdvertisedServiceUuid(this->_uartService.uuid()); + addAttribute(this->_rxCharacteristic); + addAttribute(this->_rxNameDescriptor); + this->_rxCharacteristic.setEventHandler(BLEWritten, BLESerial::_received); + addAttribute(this->_txCharacteristic); + addAttribute(this->_txNameDescriptor); +} + +void BLESerial::begin(...) { + BLEPeripheral::begin(); + #ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLESerial::begin()")); + #endif +} + +void BLESerial::poll() { + if (millis() < this->_flushed + 100) { + BLEPeripheral::poll(); + } else { + flush(); + } +} + +void BLESerial::end() { + this->_rxCharacteristic.setEventHandler(BLEWritten, NULL); + this->_rxHead = this->_rxTail = 0; + flush(); + BLEPeripheral::disconnect(); +} + +int BLESerial::available(void) { + BLEPeripheral::poll(); + int retval = (this->_rxHead - this->_rxTail + sizeof(this->_rxBuffer)) % sizeof(this->_rxBuffer); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::available() = ")); + Serial.println(retval); + #endif + return retval; +} + +int BLESerial::peek(void) { + BLEPeripheral::poll(); + if (this->_rxTail == this->_rxHead) return -1; + uint8_t byte = this->_rxBuffer[this->_rxTail]; + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::peek() = ")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.println(byte, HEX); + #endif + return byte; +} + +int BLESerial::read(void) { + BLEPeripheral::poll(); + if (this->_rxTail == this->_rxHead) return -1; + this->_rxTail = (this->_rxTail + 1) % sizeof(this->_rxBuffer); + uint8_t byte = this->_rxBuffer[this->_rxTail]; + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::read() = ")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.println(byte, HEX); + #endif + return byte; +} + +void BLESerial::flush(void) { + if (this->_txCount == 0) return; + this->_txCharacteristic.setValue(this->_txBuffer, this->_txCount); + this->_flushed = millis(); + this->_txCount = 0; + BLEPeripheral::poll(); + #ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLESerial::flush()")); + #endif +} + +size_t BLESerial::write(uint8_t byte) { + BLEPeripheral::poll(); + if (this->_txCharacteristic.subscribed() == false) return 0; + this->_txBuffer[this->_txCount++] = byte; + if (this->_txCount == sizeof(this->_txBuffer)) flush(); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::write(")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.print(byte, HEX); + Serial.println(F(") = 1")); + #endif + return 1; +} + +BLESerial::operator bool() { + bool retval = BLEPeripheral::connected(); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::operator bool() = ")); + Serial.println(retval); + #endif + return retval; +} + +void BLESerial::_received(const uint8_t* data, size_t size) { + for (int i = 0; i < size; i++) { + this->_rxHead = (this->_rxHead + 1) % sizeof(this->_rxBuffer); + this->_rxBuffer[this->_rxHead] = data[i]; + } + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::received(")); + for (int i = 0; i < size; i++) Serial.print((char) data[i]); + Serial.println(F(")")); + #endif +} + +void BLESerial::_received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic) { + BLESerial::_instance->_received(rxCharacteristic.value(), rxCharacteristic.valueLength()); +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.h b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.h new file mode 100644 index 000000000..01904c788 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/BLESerial.h @@ -0,0 +1,46 @@ +#ifndef _BLE_SERIAL_H_ +#define _BLE_SERIAL_H_ + +#include +#include + +class BLESerial : public BLEPeripheral, public Stream +{ + public: + BLESerial(unsigned char req, unsigned char rdy, unsigned char rst); + + void begin(...); + void poll(); + void end(); + + virtual int available(void); + virtual int peek(void); + virtual int read(void); + virtual void flush(void); + virtual size_t write(uint8_t byte); + using Print::write; + virtual operator bool(); + + private: + unsigned long _flushed; + static BLESerial* _instance; + + size_t _rxHead; + size_t _rxTail; + size_t _rxCount() const; + uint8_t _rxBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH]; + size_t _txCount; + uint8_t _txBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH]; + + BLEService _uartService = BLEService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); + BLEDescriptor _uartNameDescriptor = BLEDescriptor("2901", "UART"); + BLECharacteristic _rxCharacteristic = BLECharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse, BLE_ATTRIBUTE_MAX_VALUE_LENGTH); + BLEDescriptor _rxNameDescriptor = BLEDescriptor("2901", "RX - Receive Data (Write)"); + BLECharacteristic _txCharacteristic = BLECharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLENotify, BLE_ATTRIBUTE_MAX_VALUE_LENGTH); + BLEDescriptor _txNameDescriptor = BLEDescriptor("2901", "TX - Transfer Data (Notify)"); + + void _received(const uint8_t* data, size_t size); + static void _received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic); +}; + +#endif diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE/StrandtestBLE.ino b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/StrandtestBLE.ino new file mode 100644 index 000000000..593b35b6d --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE/StrandtestBLE.ino @@ -0,0 +1,192 @@ +/**************************************************************************** + * This example was developed by the Hackerspace San Salvador to demonstrate + * the simultaneous use of the NeoPixel library and the Bluetooth SoftDevice. + * To compile this example you'll need to add support for the NRF52 based + * following the instructions at: + * https://github.com/sandeepmistry/arduino-nRF5 + * Or adding the following URL to the board manager URLs on Arduino preferences: + * https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json + * Then you can install the BLEPeripheral library avaiable at: + * https://github.com/sandeepmistry/arduino-BLEPeripheral + * To test it, compile this example and use the UART module from the nRF + * Toolbox App for Android. Edit the interface and send the characters + * 'a' to 'i' to switch the animation. + * There is a delay because this example blocks the thread of execution but + * the change will be shown after the current animation ends. (This might + * take a couple of seconds) + * For more info write us at: info _at- teubi.co + */ +#include +#include +#include "BLESerial.h" +#include + +#define PIN 15 // Pin where NeoPixels are connected + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +// define pins (varies per shield/board) +#define BLE_REQ 10 +#define BLE_RDY 2 +#define BLE_RST 9 + +// create ble serial instance, see pinouts above +BLESerial BLESerial(BLE_REQ, BLE_RDY, BLE_RST); + +uint8_t current_state = 0; +uint8_t rgb_values[3]; + +void setup() { + Serial.begin(115200); + Serial.println("Hello World!"); + // custom services and characteristics can be added as well + BLESerial.setLocalName("UART_HS"); + BLESerial.begin(); + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + + //pinMode(PIN, OUTPUT); + //digitalWrite(PIN, LOW); + + current_state = 'a'; +} + +void loop() { + while(BLESerial.available()) { + uint8_t character = BLESerial.read(); + switch(character) { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + current_state = character; + break; + }; + } + switch(current_state) { + case 'a': + colorWipe(strip.Color(255, 0, 0), 20); // Red + break; + case 'b': + colorWipe(strip.Color( 0, 255, 0), 20); // Green + break; + case 'c': + colorWipe(strip.Color( 0, 0, 255), 20); // Blue + break; + case 'd': + theaterChase(strip.Color(255, 0, 0), 20); // Red + break; + case 'e': + theaterChase(strip.Color( 0, 255, 0), 20); // Green + break; + case 'f': + theaterChase(strip.Color(255, 0, 255), 20); // Cyan + break; + case 'g': + rainbow(10); + break; + case 'h': + theaterChaseRainbow(20); + break; + } +} + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + for(int i=0; i RGB + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/.none.test.only b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/.none.test.only new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/.none.test.only @@ -0,0 +1 @@ + diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.cpp b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.cpp new file mode 100644 index 000000000..d1693dec8 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.cpp @@ -0,0 +1,133 @@ +#include "BLESerial.h" + +// #define BLE_SERIAL_DEBUG + +BLESerial* BLESerial::_instance = NULL; + +BLESerial::BLESerial(unsigned char req, unsigned char rdy, unsigned char rst) : + BLEPeripheral(req, rdy, rst) +{ + this->_txCount = 0; + this->_rxHead = this->_rxTail = 0; + this->_flushed = 0; + BLESerial::_instance = this; + + addAttribute(this->_uartService); + addAttribute(this->_uartNameDescriptor); + setAdvertisedServiceUuid(this->_uartService.uuid()); + addAttribute(this->_rxCharacteristic); + addAttribute(this->_rxNameDescriptor); + this->_rxCharacteristic.setEventHandler(BLEWritten, BLESerial::_received); + addAttribute(this->_txCharacteristic); + addAttribute(this->_txNameDescriptor); +} + +void BLESerial::begin(...) { + BLEPeripheral::begin(); + #ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLESerial::begin()")); + #endif +} + +void BLESerial::poll() { + if (millis() < this->_flushed + 100) { + BLEPeripheral::poll(); + } else { + flush(); + } +} + +void BLESerial::end() { + this->_rxCharacteristic.setEventHandler(BLEWritten, NULL); + this->_rxHead = this->_rxTail = 0; + flush(); + BLEPeripheral::disconnect(); +} + +int BLESerial::available(void) { + BLEPeripheral::poll(); + int retval = (this->_rxHead - this->_rxTail + sizeof(this->_rxBuffer)) % sizeof(this->_rxBuffer); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::available() = ")); + Serial.println(retval); + #endif + return retval; +} + +int BLESerial::peek(void) { + BLEPeripheral::poll(); + if (this->_rxTail == this->_rxHead) return -1; + uint8_t byte = this->_rxBuffer[this->_rxTail]; + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::peek() = ")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.println(byte, HEX); + #endif + return byte; +} + +int BLESerial::read(void) { + BLEPeripheral::poll(); + if (this->_rxTail == this->_rxHead) return -1; + this->_rxTail = (this->_rxTail + 1) % sizeof(this->_rxBuffer); + uint8_t byte = this->_rxBuffer[this->_rxTail]; + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::read() = ")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.println(byte, HEX); + #endif + return byte; +} + +void BLESerial::flush(void) { + if (this->_txCount == 0) return; + this->_txCharacteristic.setValue(this->_txBuffer, this->_txCount); + this->_flushed = millis(); + this->_txCount = 0; + BLEPeripheral::poll(); + #ifdef BLE_SERIAL_DEBUG + Serial.println(F("BLESerial::flush()")); + #endif +} + +size_t BLESerial::write(uint8_t byte) { + BLEPeripheral::poll(); + if (this->_txCharacteristic.subscribed() == false) return 0; + this->_txBuffer[this->_txCount++] = byte; + if (this->_txCount == sizeof(this->_txBuffer)) flush(); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::write(")); + Serial.print((char) byte); + Serial.print(F(" 0x")); + Serial.print(byte, HEX); + Serial.println(F(") = 1")); + #endif + return 1; +} + +BLESerial::operator bool() { + bool retval = BLEPeripheral::connected(); + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::operator bool() = ")); + Serial.println(retval); + #endif + return retval; +} + +void BLESerial::_received(const uint8_t* data, size_t size) { + for (int i = 0; i < size; i++) { + this->_rxHead = (this->_rxHead + 1) % sizeof(this->_rxBuffer); + this->_rxBuffer[this->_rxHead] = data[i]; + } + #ifdef BLE_SERIAL_DEBUG + Serial.print(F("BLESerial::received(")); + for (int i = 0; i < size; i++) Serial.print((char) data[i]); + Serial.println(F(")")); + #endif +} + +void BLESerial::_received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic) { + BLESerial::_instance->_received(rxCharacteristic.value(), rxCharacteristic.valueLength()); +} diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.h b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.h new file mode 100644 index 000000000..01904c788 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/BLESerial.h @@ -0,0 +1,46 @@ +#ifndef _BLE_SERIAL_H_ +#define _BLE_SERIAL_H_ + +#include +#include + +class BLESerial : public BLEPeripheral, public Stream +{ + public: + BLESerial(unsigned char req, unsigned char rdy, unsigned char rst); + + void begin(...); + void poll(); + void end(); + + virtual int available(void); + virtual int peek(void); + virtual int read(void); + virtual void flush(void); + virtual size_t write(uint8_t byte); + using Print::write; + virtual operator bool(); + + private: + unsigned long _flushed; + static BLESerial* _instance; + + size_t _rxHead; + size_t _rxTail; + size_t _rxCount() const; + uint8_t _rxBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH]; + size_t _txCount; + uint8_t _txBuffer[BLE_ATTRIBUTE_MAX_VALUE_LENGTH]; + + BLEService _uartService = BLEService("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); + BLEDescriptor _uartNameDescriptor = BLEDescriptor("2901", "UART"); + BLECharacteristic _rxCharacteristic = BLECharacteristic("6E400002-B5A3-F393-E0A9-E50E24DCCA9E", BLEWriteWithoutResponse, BLE_ATTRIBUTE_MAX_VALUE_LENGTH); + BLEDescriptor _rxNameDescriptor = BLEDescriptor("2901", "RX - Receive Data (Write)"); + BLECharacteristic _txCharacteristic = BLECharacteristic("6E400003-B5A3-F393-E0A9-E50E24DCCA9E", BLENotify, BLE_ATTRIBUTE_MAX_VALUE_LENGTH); + BLEDescriptor _txNameDescriptor = BLEDescriptor("2901", "TX - Transfer Data (Notify)"); + + void _received(const uint8_t* data, size_t size); + static void _received(BLECentral& /*central*/, BLECharacteristic& rxCharacteristic); +}; + +#endif diff --git a/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/StrandtestBLE_nodelay.ino b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/StrandtestBLE_nodelay.ino new file mode 100644 index 000000000..20c924d01 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/StrandtestBLE_nodelay/StrandtestBLE_nodelay.ino @@ -0,0 +1,198 @@ +/**************************************************************************** + * This example was developed by the Hackerspace San Salvador to demonstrate + * the simultaneous use of the NeoPixel library and the Bluetooth SoftDevice. + * To compile this example you'll need to add support for the NRF52 based + * following the instructions at: + * https://github.com/sandeepmistry/arduino-nRF5 + * Or adding the following URL to the board manager URLs on Arduino preferences: + * https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json + * Then you can install the BLEPeripheral library avaiable at: + * https://github.com/sandeepmistry/arduino-BLEPeripheral + * To test it, compile this example and use the UART module from the nRF + * Toolbox App for Android. Edit the interface and send the characters + * 'a' to 'i' to switch the animation. + * There is a no delay because this example does not block the threads execution + * so the change will be shown immediately and will not need to wait for the current + * animation to end. + * For more info write us at: info _at- teubi.co + */ +#include +#include +#include "BLESerial.h" +#include + +#define PIN 15 // Pin where NeoPixels are connected + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(64, PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +// define pins (varies per shield/board) +#define BLE_REQ 10 +#define BLE_RDY 2 +#define BLE_RST 9 + +// create ble serial instance, see pinouts above +BLESerial BLESerial(BLE_REQ, BLE_RDY, BLE_RST); + +uint8_t current_state = 0; +uint8_t rgb_values[3]; + +void setup() { + Serial.begin(115200); + Serial.println("Hello World!"); + // custom services and characteristics can be added as well + BLESerial.setLocalName("UART_HS"); + BLESerial.begin(); + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + + //pinMode(PIN, OUTPUT); + //digitalWrite(PIN, LOW); + + current_state = 'a'; +} + +void loop() { + while(BLESerial.available()) { + uint8_t character = BLESerial.read(); + switch(character) { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + current_state = character; + break; + }; + } + switch(current_state) { + case 'a': + colorWipe(strip.Color(255, 0, 0), 20); // Red + break; + case 'b': + colorWipe(strip.Color( 0, 255, 0), 20); // Green + break; + case 'c': + colorWipe(strip.Color( 0, 0, 255), 20); // Blue + break; + case 'd': + theaterChase(strip.Color(255, 0, 0), 20); // Red + break; + case 'e': + theaterChase(strip.Color( 0, 255, 0), 20); // Green + break; + case 'f': + theaterChase(strip.Color(255, 0, 255), 20); // Cyan + break; + case 'g': + rainbow(10); + break; + case 'h': + theaterChaseRainbow(20); + break; + } +} + +// Some functions of our own for creating animated effects ----------------- + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + strip.setPixelColor(pixelCurrent, color); // Set pixel's color (in RAM) + strip.show(); // Update strip to match + pixelCurrent++; // Advance current pixel + if(pixelCurrent >= pixelNumber) // Loop the pattern from the first LED + pixelCurrent = 0; +} + +// Theater-marquee-style chasing lights. Pass in a color (32-bit value, +// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms) +// between frames. +void theaterChase(uint32_t color, int wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + for(int i = 0; i < pixelNumber; i++) { + strip.setPixelColor(i + pixelQueue, color); // Set pixel's color (in RAM) + } + strip.show(); // Update strip to match + for(int i=0; i < pixelNumber; i+3) { + strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Set pixel's color (in RAM) + } + pixelQueue++; // Advance current pixel + if(pixelQueue >= 3) + pixelQueue = 0; // Loop the pattern from the first LED +} + +// Rainbow cycle along whole strip. Pass delay time (in ms) between frames. +void rainbow(uint8_t wait) { + if(pixelInterval != wait) + pixelInterval = wait; + for(uint16_t i=0; i < pixelNumber; i++) { + strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time + } + strip.show(); // Update strip to match + pixelCycle++; // Advance current cycle + if(pixelCycle >= 256) + pixelCycle = 0; // Loop the cycle back to the begining +} + +//Theatre-style crawling lights with rainbow effect +void theaterChaseRainbow(uint8_t wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + for(int i=0; i < pixelNumber; i+3) { + strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time + } + strip.show(); + for(int i=0; i < pixelNumber; i+3) { + strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time + } + pixelQueue++; // Advance current queue + pixelCycle++; // Advance current cycle + if(pixelQueue >= 3) + pixelQueue = 0; // Loop + if(pixelCycle >= 256) + pixelCycle = 0; // Loop +} + +// Input a value 0 to 255 to get a color value. +// The colours are a transition r - g - b - back to r. +uint32_t Wheel(byte WheelPos) { + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + if(WheelPos < 170) { + WheelPos -= 85; + return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + WheelPos -= 170; + return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); +} diff --git a/lib/Adafruit_NeoPixel/examples/buttoncycler/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/buttoncycler/.esp8266.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/buttoncycler/buttoncycler.ino b/lib/Adafruit_NeoPixel/examples/buttoncycler/buttoncycler.ino new file mode 100644 index 000000000..f6d87edcb --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/buttoncycler/buttoncycler.ino @@ -0,0 +1,164 @@ +// Simple demonstration on using an input device to trigger changes on your +// NeoPixels. Wire a momentary push button to connect from ground to a +// digital IO pin. When the button is pressed it will change to a new pixel +// animation. Initial state has all pixels off -- press the button once to +// start the first animation. As written, the button does not interrupt an +// animation in-progress, it works only when idle. + +#include +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Digital IO pin connected to the button. This will be driven with a +// pull-up resistor so the switch pulls the pin to ground momentarily. +// On a high -> low transition the button press logic will execute. +#define BUTTON_PIN 2 + +#define PIXEL_PIN 6 // Digital IO pin connected to the NeoPixels. + +#define PIXEL_COUNT 16 // Number of NeoPixels + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +boolean oldState = HIGH; +int mode = 0; // Currently-active animation mode, 0-9 + +void setup() { + pinMode(BUTTON_PIN, INPUT_PULLUP); + strip.begin(); // Initialize NeoPixel strip object (REQUIRED) + strip.show(); // Initialize all pixels to 'off' +} + +void loop() { + // Get current button state. + boolean newState = digitalRead(BUTTON_PIN); + + // Check if state changed from high to low (button press). + if((newState == LOW) && (oldState == HIGH)) { + // Short delay to debounce button. + delay(20); + // Check if button is still low after debounce. + newState = digitalRead(BUTTON_PIN); + if(newState == LOW) { // Yes, still low + if(++mode > 8) mode = 0; // Advance to next mode, wrap around after #8 + switch(mode) { // Start the new animation... + case 0: + colorWipe(strip.Color( 0, 0, 0), 50); // Black/off + break; + case 1: + colorWipe(strip.Color(255, 0, 0), 50); // Red + break; + case 2: + colorWipe(strip.Color( 0, 255, 0), 50); // Green + break; + case 3: + colorWipe(strip.Color( 0, 0, 255), 50); // Blue + break; + case 4: + theaterChase(strip.Color(127, 127, 127), 50); // White + break; + case 5: + theaterChase(strip.Color(127, 0, 0), 50); // Red + break; + case 6: + theaterChase(strip.Color( 0, 0, 127), 50); // Blue + break; + case 7: + rainbow(10); + break; + case 8: + theaterChaseRainbow(50); + break; + } + } + } + + // Set the last-read button state to the old state. + oldState = newState; +} + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + for(int i=0; i RGB + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} diff --git a/lib/Adafruit_NeoPixel/examples/simple/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/simple/.esp8266.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/simple/simple.ino b/lib/Adafruit_NeoPixel/examples/simple/simple.ino new file mode 100644 index 000000000..09f458ecb --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/simple/simple.ino @@ -0,0 +1,50 @@ +// NeoPixel Ring simple sketch (c) 2013 Shae Erisson +// Released under the GPLv3 license to match the rest of the +// Adafruit NeoPixel library + +#include +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Which pin on the Arduino is connected to the NeoPixels? +#define PIN 6 // On Trinket or Gemma, suggest changing this to 1 + +// How many NeoPixels are attached to the Arduino? +#define NUMPIXELS 16 // Popular NeoPixel ring size + +// When setting up the NeoPixel library, we tell it how many pixels, +// and which pin to use to send signals. Note that for older NeoPixel +// strips you might need to change the third parameter -- see the +// strandtest example for more information on possible values. +Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800); + +#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels + +void setup() { + // These lines are specifically to support the Adafruit Trinket 5V 16 MHz. + // Any other board, you can remove this part (but no harm leaving it): +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + // END of Trinket-specific code. + + pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) +} + +void loop() { + pixels.clear(); // Set all pixel colors to 'off' + + // The first NeoPixel in a strand is #0, second is 1, all the way up + // to the count of pixels minus one. + for(int i=0; i +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Which pin on the Arduino is connected to the NeoPixels? +int pin = 6; // On Trinket or Gemma, suggest changing this to 1 + +// How many NeoPixels are attached to the Arduino? +int numPixels = 16; // Popular NeoPixel ring size + +// NeoPixel color format & data rate. See the strandtest example for +// information on possible values. +int pixelFormat = NEO_GRB + NEO_KHZ800; + +// Rather than declaring the whole NeoPixel object here, we just create +// a pointer for one, which we'll then allocate later... +Adafruit_NeoPixel *pixels; + +#define DELAYVAL 500 // Time (in milliseconds) to pause between pixels + +void setup() { + // These lines are specifically to support the Adafruit Trinket 5V 16 MHz. + // Any other board, you can remove this part (but no harm leaving it): +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + // END of Trinket-specific code. + + // Right about here is where we could read 'pin', 'numPixels' and/or + // 'pixelFormat' from EEPROM or a file on SD or whatever. This is a simple + // example and doesn't do that -- those variables are just set to fixed + // values at the top of this code -- but this is where it would happen. + + // Then create a new NeoPixel object dynamically with these values: + pixels = new Adafruit_NeoPixel(numPixels, pin, pixelFormat); + + // Going forward from here, code works almost identically to any other + // NeoPixel example, but instead of the dot operator on function calls + // (e.g. pixels.begin()), we instead use pointer indirection (->) like so: + pixels->begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + // You'll see more of this in the loop() function below. +} + +void loop() { + pixels->clear(); // Set all pixel colors to 'off' + + // The first NeoPixel in a strand is #0, second is 1, all the way up + // to the count of pixels minus one. + for(int i=0; iColor() takes RGB values, from 0,0,0 up to 255,255,255 + // Here we're using a moderately bright green color: + pixels->setPixelColor(i, pixels->Color(0, 150, 0)); + + pixels->show(); // Send the updated pixel colors to the hardware. + + delay(DELAYVAL); // Pause before next pass through loop + } +} diff --git a/lib/Adafruit_NeoPixel/examples/strandtest/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/strandtest/.esp8266.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/strandtest/strandtest.ino b/lib/Adafruit_NeoPixel/examples/strandtest/strandtest.ino new file mode 100644 index 000000000..f4554cc82 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/strandtest/strandtest.ino @@ -0,0 +1,143 @@ +// A basic everyday NeoPixel strip test program. + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +#include +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Which pin on the Arduino is connected to the NeoPixels? +// On a Trinket or Gemma we suggest changing this to 1: +#define LED_PIN 6 + +// How many NeoPixels are attached to the Arduino? +#define LED_COUNT 60 + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + + +// setup() function -- runs once at startup -------------------------------- + +void setup() { + // These lines are specifically to support the Adafruit Trinket 5V 16 MHz. + // Any other board, you can remove this part (but no harm leaving it): +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + // END of Trinket-specific code. + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255) +} + + +// loop() function -- runs repeatedly as long as board is on --------------- + +void loop() { + // Fill along the length of the strip in various colors... + colorWipe(strip.Color(255, 0, 0), 50); // Red + colorWipe(strip.Color( 0, 255, 0), 50); // Green + colorWipe(strip.Color( 0, 0, 255), 50); // Blue + + // Do a theater marquee effect in various colors... + theaterChase(strip.Color(127, 127, 127), 50); // White, half brightness + theaterChase(strip.Color(127, 0, 0), 50); // Red, half brightness + theaterChase(strip.Color( 0, 0, 127), 50); // Blue, half brightness + + rainbow(10); // Flowing rainbow cycle along the whole strip + theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant +} + + +// Some functions of our own for creating animated effects ----------------- + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + for(int i=0; i RGB + strip.setPixelColor(c, color); // Set pixel 'c' to value 'color' + } + strip.show(); // Update strip with new contents + delay(wait); // Pause for a moment + firstPixelHue += 65536 / 90; // One cycle of color wheel over 90 frames + } + } +} diff --git a/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/.esp8266.test.skip new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/.esp8266.test.skip @@ -0,0 +1 @@ + diff --git a/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/strandtest_nodelay.ino b/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/strandtest_nodelay.ino new file mode 100644 index 000000000..9c392e2be --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/strandtest_nodelay/strandtest_nodelay.ino @@ -0,0 +1,186 @@ +// A non-blocking everyday NeoPixel strip test program. + +// NEOPIXEL BEST PRACTICES for most reliable operation: +// - Add 1000 uF CAPACITOR between NeoPixel strip's + and - connections. +// - MINIMIZE WIRING LENGTH between microcontroller board and first pixel. +// - NeoPixel strip's DATA-IN should pass through a 300-500 OHM RESISTOR. +// - AVOID connecting NeoPixels on a LIVE CIRCUIT. If you must, ALWAYS +// connect GROUND (-) first, then +, then data. +// - When using a 3.3V microcontroller with a 5V-powered NeoPixel strip, +// a LOGIC-LEVEL CONVERTER on the data line is STRONGLY RECOMMENDED. +// (Skipping these may work OK on your workbench but can fail in the field) + +#include +#ifdef __AVR__ + #include // Required for 16 MHz Adafruit Trinket +#endif + +// Which pin on the Arduino is connected to the NeoPixels? +// On a Trinket or Gemma we suggest changing this to 1: +#ifdef ESP32 +// Cannot use 6 as output for ESP. Pins 6-11 are connected to SPI flash. Use 16 instead. +#define LED_PIN 16 +#else +#define LED_PIN 6 +#endif + +// How many NeoPixels are attached to the Arduino? +#define LED_COUNT 60 + +// Declare our NeoPixel strip object: +Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); +// Argument 1 = Number of pixels in NeoPixel strip +// Argument 2 = Arduino pin number (most are valid) +// Argument 3 = Pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) + +unsigned long pixelPrevious = 0; // Previous Pixel Millis +unsigned long patternPrevious = 0; // Previous Pattern Millis +int patternCurrent = 0; // Current Pattern Number +int patternInterval = 5000; // Pattern Interval (ms) +int pixelInterval = 50; // Pixel Interval (ms) +int pixelQueue = 0; // Pattern Pixel Queue +int pixelCycle = 0; // Pattern Pixel Cycle +uint16_t pixelCurrent = 0; // Pattern Current Pixel Number +uint16_t pixelNumber = LED_COUNT; // Total Number of Pixels + +// setup() function -- runs once at startup -------------------------------- +void setup() { + // These lines are specifically to support the Adafruit Trinket 5V 16 MHz. + // Any other board, you can remove this part (but no harm leaving it): +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000) + clock_prescale_set(clock_div_1); +#endif + // END of Trinket-specific code. + + strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) + strip.show(); // Turn OFF all pixels ASAP + strip.setBrightness(50); // Set BRIGHTNESS to about 1/5 (max = 255) +} + +// loop() function -- runs repeatedly as long as board is on --------------- +void loop() { + unsigned long currentMillis = millis(); // Update current time + if((currentMillis - patternPrevious) >= patternInterval) { // Check for expired time + patternPrevious = currentMillis; + patternCurrent++; // Advance to next pattern + if(patternCurrent >= 7) + patternCurrent = 0; + } + + if(currentMillis - pixelPrevious >= pixelInterval) { // Check for expired time + pixelPrevious = currentMillis; // Run current frame + switch (patternCurrent) { + case 7: + theaterChaseRainbow(50); // Rainbow-enhanced theaterChase variant + break; + case 6: + rainbow(10); // Flowing rainbow cycle along the whole strip + break; + case 5: + theaterChase(strip.Color(0, 0, 127), 50); // Blue + break; + case 4: + theaterChase(strip.Color(127, 0, 0), 50); // Red + break; + case 3: + theaterChase(strip.Color(127, 127, 127), 50); // White + break; + case 2: + colorWipe(strip.Color(0, 0, 255), 50); // Blue + break; + case 1: + colorWipe(strip.Color(0, 255, 0), 50); // Green + break; + default: + colorWipe(strip.Color(255, 0, 0), 50); // Red + break; + } + } +} + +// Some functions of our own for creating animated effects ----------------- + +// Fill strip pixels one after another with a color. Strip is NOT cleared +// first; anything there will be covered pixel by pixel. Pass in color +// (as a single 'packed' 32-bit value, which you can get by calling +// strip.Color(red, green, blue) as shown in the loop() function above), +// and a delay time (in milliseconds) between pixels. +void colorWipe(uint32_t color, int wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + strip.setPixelColor(pixelCurrent, color); // Set pixel's color (in RAM) + strip.show(); // Update strip to match + pixelCurrent++; // Advance current pixel + if(pixelCurrent >= pixelNumber) // Loop the pattern from the first LED + pixelCurrent = 0; +} + +// Theater-marquee-style chasing lights. Pass in a color (32-bit value, +// a la strip.Color(r,g,b) as mentioned above), and a delay time (in ms) +// between frames. +void theaterChase(uint32_t color, int wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + for(int i = 0; i < pixelNumber; i++) { + strip.setPixelColor(i + pixelQueue, color); // Set pixel's color (in RAM) + } + strip.show(); // Update strip to match + for(int i=0; i < pixelNumber; i+=3) { + strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Set pixel's color (in RAM) + } + pixelQueue++; // Advance current pixel + if(pixelQueue >= 3) + pixelQueue = 0; // Loop the pattern from the first LED +} + +// Rainbow cycle along whole strip. Pass delay time (in ms) between frames. +void rainbow(uint8_t wait) { + if(pixelInterval != wait) + pixelInterval = wait; + for(uint16_t i=0; i < pixelNumber; i++) { + strip.setPixelColor(i, Wheel((i + pixelCycle) & 255)); // Update delay time + } + strip.show(); // Update strip to match + pixelCycle++; // Advance current cycle + if(pixelCycle >= 256) + pixelCycle = 0; // Loop the cycle back to the begining +} + +//Theatre-style crawling lights with rainbow effect +void theaterChaseRainbow(uint8_t wait) { + if(pixelInterval != wait) + pixelInterval = wait; // Update delay time + for(int i=0; i < pixelNumber; i+=3) { + strip.setPixelColor(i + pixelQueue, Wheel((i + pixelCycle) % 255)); // Update delay time + } + strip.show(); + for(int i=0; i < pixelNumber; i+=3) { + strip.setPixelColor(i + pixelQueue, strip.Color(0, 0, 0)); // Update delay time + } + pixelQueue++; // Advance current queue + pixelCycle++; // Advance current cycle + if(pixelQueue >= 3) + pixelQueue = 0; // Loop + if(pixelCycle >= 256) + pixelCycle = 0; // Loop +} + +// Input a value 0 to 255 to get a color value. +// The colours are a transition r - g - b - back to r. +uint32_t Wheel(byte WheelPos) { + WheelPos = 255 - WheelPos; + if(WheelPos < 85) { + return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3); + } + if(WheelPos < 170) { + WheelPos -= 85; + return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3); + } + WheelPos -= 170; + return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0); +} diff --git a/lib/Adafruit_NeoPixel/examples/strandtest_wheel/.esp8266.test.skip b/lib/Adafruit_NeoPixel/examples/strandtest_wheel/.esp8266.test.skip new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Adafruit_NeoPixel/examples/strandtest_wheel/strandtest_wheel.ino b/lib/Adafruit_NeoPixel/examples/strandtest_wheel/strandtest_wheel.ino new file mode 100644 index 000000000..c0ca41edc --- /dev/null +++ b/lib/Adafruit_NeoPixel/examples/strandtest_wheel/strandtest_wheel.ino @@ -0,0 +1,134 @@ +#include +#ifdef __AVR__ + #include +#endif + +#define PIN 6 + +// Parameter 1 = number of pixels in strip +// Parameter 2 = Arduino pin number (most are valid) +// Parameter 3 = pixel type flags, add together as needed: +// NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) +// NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) +// NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) +// NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) +// NEO_RGBW Pixels are wired for RGBW bitstream (NeoPixel RGBW products) +Adafruit_NeoPixel strip = Adafruit_NeoPixel(60, PIN, NEO_GRB + NEO_KHZ800); + +// IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across +// pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input +// and minimize distance between Arduino and first pixel. Avoid connecting +// on a live circuit...if you must, connect GND first. + +void setup() { + // This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket + #if defined (__AVR_ATtiny85__) + if (F_CPU == 16000000) clock_prescale_set(clock_div_1); + #endif + // End of trinket special code + + strip.begin(); + strip.setBrightness(50); + strip.show(); // Initialize all pixels to 'off' +} + +void loop() { + // Some example procedures showing how to display to the pixels: + colorWipe(strip.Color(255, 0, 0), 50); // Red + colorWipe(strip.Color(0, 255, 0), 50); // Green + colorWipe(strip.Color(0, 0, 255), 50); // Blue +//colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW + // Send a theater pixel chase in... + theaterChase(strip.Color(127, 127, 127), 50); // White + theaterChase(strip.Color(127, 0, 0), 50); // Red + theaterChase(strip.Color(0, 0, 127), 50); // Blue + + rainbow(20); + rainbowCycle(20); + theaterChaseRainbow(50); +} + +// Fill the dots one after the other with a color +void colorWipe(uint32_t c, uint8_t wait) { + for(uint16_t i=0; i +#include "sysctl.h" + +void k210Show( + int16_t pin, uint8_t *pixels, uint32_t numBytes, boolean is800KHz) +{ + +#define CYCLES_800_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2500000) // 0.4us +#define CYCLES_800_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 1250000) // 0.8us +#define CYCLES_800 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 800000) // 1.25us per bit +#define CYCLES_400_T0H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 2000000) // 0.5uS +#define CYCLES_400_T1H (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 833333) // 1.2us +#define CYCLES_400 (sysctl_clock_get_freq(SYSCTL_CLOCK_CPU) / 400000) // 2.5us per bit + + uint8_t *p, *end, pix, mask; + uint32_t t, time0, time1, period, c, startTime; + + p = pixels; + end = p + numBytes; + pix = *p++; + mask = 0x80; + startTime = 0; + +#ifdef NEO_KHZ400 + if (is800KHz) + { +#endif + time0 = CYCLES_800_T0H; + time1 = CYCLES_800_T1H; + period = CYCLES_800; +#ifdef NEO_KHZ400 + } + else + { // 400 KHz bitstream + time0 = CYCLES_400_T0H; + time1 = CYCLES_400_T1H; + period = CYCLES_400; + } +#endif + + for (t = time0;; t = time0) + { + if (pix & mask) + t = time1; // Bit high duration + while (((c = read_cycle()) - startTime) < period) + ; // Wait for bit start + digitalWrite(pin, HIGH); + startTime = c; // Save start time + while (((c = read_cycle()) - startTime) < t) + ; // Wait high duration + digitalWrite(pin, LOW); + + if (!(mask >>= 1)) + { // Next bit/byte + if (p >= end) + break; + pix = *p++; + mask = 0x80; + } + } + while ((read_cycle() - startTime) < period) + ; // Wait for last bit +} + +#endif // KENDRYTE_K210 diff --git a/lib/Adafruit_NeoPixel/keywords.txt b/lib/Adafruit_NeoPixel/keywords.txt new file mode 100644 index 000000000..4003ede9d --- /dev/null +++ b/lib/Adafruit_NeoPixel/keywords.txt @@ -0,0 +1,72 @@ +####################################### +# Syntax Coloring Map For Adafruit_NeoPixel +####################################### +# Class +####################################### + +Adafruit_NeoPixel KEYWORD1 + +####################################### +# Methods and Functions +####################################### + +begin KEYWORD2 +show KEYWORD2 +setPin KEYWORD2 +setPixelColor KEYWORD2 +fill KEYWORD2 +setBrightness KEYWORD2 +clear KEYWORD2 +updateLength KEYWORD2 +updateType KEYWORD2 +canShow KEYWORD2 +getPixels KEYWORD2 +getBrightness KEYWORD2 +getPin KEYWORD2 +numPixels KEYWORD2 +getPixelColor KEYWORD2 +sine8 KEYWORD2 +gamma8 KEYWORD2 +Color KEYWORD2 +ColorHSV KEYWORD2 +gamma32 KEYWORD2 + +####################################### +# Constants +####################################### + +NEO_COLMASK LITERAL1 +NEO_SPDMASK LITERAL1 +NEO_KHZ800 LITERAL1 +NEO_KHZ400 LITERAL1 +NEO_RGB LITERAL1 +NEO_RBG LITERAL1 +NEO_GRB LITERAL1 +NEO_GBR LITERAL1 +NEO_BRG LITERAL1 +NEO_BGR LITERAL1 +NEO_WRGB LITERAL1 +NEO_WRBG LITERAL1 +NEO_WGRB LITERAL1 +NEO_WGBR LITERAL1 +NEO_WBRG LITERAL1 +NEO_WBGR LITERAL1 +NEO_RWGB LITERAL1 +NEO_RWBG LITERAL1 +NEO_RGWB LITERAL1 +NEO_RGBW LITERAL1 +NEO_RBWG LITERAL1 +NEO_RBGW LITERAL1 +NEO_GWRB LITERAL1 +NEO_GWBR LITERAL1 +NEO_GRWB LITERAL1 +NEO_GRBW LITERAL1 +NEO_GBWR LITERAL1 +NEO_GBRW LITERAL1 +NEO_BWRG LITERAL1 +NEO_BWGR LITERAL1 +NEO_BRWG LITERAL1 +NEO_BRGW LITERAL1 +NEO_BGWR LITERAL1 +NEO_BGRW LITERAL1 + diff --git a/lib/Adafruit_NeoPixel/library.properties b/lib/Adafruit_NeoPixel/library.properties new file mode 100644 index 000000000..e25f333dd --- /dev/null +++ b/lib/Adafruit_NeoPixel/library.properties @@ -0,0 +1,10 @@ +name=Adafruit NeoPixel +version=1.12.0 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for controlling single-wire-based LED pixels and strip. +paragraph=Arduino library for controlling single-wire-based LED pixels and strip. +category=Display +url=https://github.com/adafruit/Adafruit_NeoPixel +architectures=* +includes=Adafruit_NeoPixel.h diff --git a/lib/Adafruit_NeoPixel/rp2040_pio.h b/lib/Adafruit_NeoPixel/rp2040_pio.h new file mode 100644 index 000000000..f7ccd46de --- /dev/null +++ b/lib/Adafruit_NeoPixel/rp2040_pio.h @@ -0,0 +1,63 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +// Unless you know what you are doing... +// Lines 47 and 52 have been edited to set transmit bit count + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// ------ // +// ws2812 // +// ------ // + +#define ws2812_wrap_target 0 +#define ws2812_wrap 3 + +#define ws2812_T1 2 +#define ws2812_T2 5 +#define ws2812_T3 3 + +static const uint16_t ws2812_program_instructions[] = { + // .wrap_target + 0x6221, // 0: out x, 1 side 0 [2] + 0x1123, // 1: jmp !x, 3 side 1 [1] + 0x1400, // 2: jmp 0 side 1 [4] + 0xa442, // 3: nop side 0 [4] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program ws2812_program = { + .instructions = ws2812_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config ws2812_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} + +#include "hardware/clocks.h" +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, + float freq, uint bits) { + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, + bits); // <----<<< Length changed to "bits" + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +#endif diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp index 2ddd4e242..4f688a90f 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp +++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.cpp @@ -181,6 +181,17 @@ static const uint8_t PROGMEM 0x00, 0x00, // XSTART = 0 0x00, 0x9F }, // XEND = 159 + #if ST7735_EXTRA_INIT + Rcmd2black135x240[] = { // 7735R init, part 2 (mini 160x80) + 2, // 2 commands in list: + ST77XX_CASET, 4, // 1: Column addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 0x00, 135, // XEND = 135 + ST77XX_RASET, 4, // 2: Row addr set, 4 args, no delay: + 0x00, 0x00, // XSTART = 0 + 240 >> 8, 240 & 0xFF }, // XEND = 240 + #endif // if ST7735_EXTRA_INIT + Rcmd3[] = { // 7735R init, part 3 (red or green tab) 4, // 4 commands in list: ST7735_GMCTRP1, 16 , // 1: Gamma Adjustments (pos. polarity), 16 args + delay: @@ -243,6 +254,15 @@ void Adafruit_ST7735::initR(uint8_t options) { sendCommand(ST77XX_INVON, &data, 0); // Write twice... _colstart = 26; _rowstart = 1; + #if ST7735_EXTRA_INIT + } else if (options == INITR_BLACKTAB135x240) { + _height = ST7735_TFTHEIGHT_240; + _width = ST7735_TFTWIDTH_135; + displayInit(Rcmd2black135x240); + 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 displayInit(Rcmd2red); @@ -288,6 +308,10 @@ void Adafruit_ST7735::setRotation(uint8_t m) { case 0: 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) { + madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + #endif // if ST7735_EXTRA_INIT } else { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST7735_MADCTL_BGR; } @@ -298,6 +322,13 @@ void Adafruit_ST7735::setRotation(uint8_t m) { } else if (tabcolor == INITR_MINI160x80) { _height = ST7735_TFTHEIGHT_160; _width = ST7735_TFTWIDTH_80; + #if ST7735_EXTRA_INIT + } else if (tabcolor == INITR_BLACKTAB135x240) { + _height = ST7735_TFTHEIGHT_240; + _width = ST7735_TFTWIDTH_135; + _colstart = 53; + _rowstart = 40; + #endif // if ST7735_EXTRA_INIT } else { _height = ST7735_TFTHEIGHT_160; _width = ST7735_TFTWIDTH_128; @@ -308,6 +339,10 @@ void Adafruit_ST7735::setRotation(uint8_t m) { case 1: 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) { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; + #endif // if ST7735_EXTRA_INIT } else { madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR; } @@ -318,6 +353,13 @@ void Adafruit_ST7735::setRotation(uint8_t m) { } else if (tabcolor == INITR_MINI160x80) { _width = ST7735_TFTHEIGHT_160; _height = ST7735_TFTWIDTH_80; + #if ST7735_EXTRA_INIT + } else if (tabcolor == INITR_BLACKTAB135x240) { + _width = ST7735_TFTHEIGHT_240; + _height = ST7735_TFTWIDTH_135; + _colstart = 52; + _rowstart = 40; + #endif // if ST7735_EXTRA_INIT } else { _width = ST7735_TFTHEIGHT_160; _height = ST7735_TFTWIDTH_128; @@ -328,6 +370,10 @@ void Adafruit_ST7735::setRotation(uint8_t m) { case 2: if ((tabcolor == INITR_BLACKTAB) || (tabcolor == INITR_MINI160x80)) { madctl = ST77XX_MADCTL_RGB; + #if ST7735_EXTRA_INIT + } else if (tabcolor == INITR_BLACKTAB135x240) { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + #endif // if ST7735_EXTRA_INIT } else { madctl = ST7735_MADCTL_BGR; } @@ -338,6 +384,13 @@ void Adafruit_ST7735::setRotation(uint8_t m) { } else if (tabcolor == INITR_MINI160x80) { _height = ST7735_TFTHEIGHT_160; _width = ST7735_TFTWIDTH_80; + #if ST7735_EXTRA_INIT + } else if (tabcolor == INITR_BLACKTAB135x240) { + _height = ST7735_TFTHEIGHT_240; + _width = ST7735_TFTWIDTH_135; + _colstart = 52; + _rowstart = 40; + #endif // if ST7735_EXTRA_INIT } else { _height = ST7735_TFTHEIGHT_160; _width = ST7735_TFTWIDTH_128; @@ -348,6 +401,10 @@ void Adafruit_ST7735::setRotation(uint8_t m) { case 3: 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) { + madctl = ST77XX_MADCTL_RGB; + #endif // if ST7735_EXTRA_INIT } else { madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR; } @@ -358,6 +415,13 @@ void Adafruit_ST7735::setRotation(uint8_t m) { } else if (tabcolor == INITR_MINI160x80) { _width = ST7735_TFTHEIGHT_160; _height = ST7735_TFTWIDTH_80; + #if ST7735_EXTRA_INIT + } else if (tabcolor == INITR_BLACKTAB135x240) { + _width = ST7735_TFTHEIGHT_240; + _height = ST7735_TFTWIDTH_135; + _colstart = 53; + _rowstart = 40; + #endif // if ST7735_EXTRA_INIT } else { _width = ST7735_TFTHEIGHT_160; _height = ST7735_TFTWIDTH_128; diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7735.h b/lib/Adafruit_ST77xx/Adafruit_ST7735.h index 6d138bce3..935dda4e3 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7735.h +++ b/lib/Adafruit_ST77xx/Adafruit_ST7735.h @@ -3,6 +3,22 @@ #include "Adafruit_ST77xx.h" +/** + * 2024-03-17 tonhuisman: Add additional initialization sequences for ST7735 displays, with the intention to get 'm working + * on some devices that seem to use peculiarly configured hardware like LiliGO TTGO T-Display (16MB flash), + * and possibly the T-Display S3 + * By default only enabled on ESP32, unless -D ST7735_EXTRA_INIT=1 is defined, f.e. via the build script + */ + +#ifndef ST7735_EXTRA_INIT // Enable setting from 'outside', like Platformio.ini +# ifdef ESP8266 +# define ST7735_EXTRA_INIT 0 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define ST7735_EXTRA_INIT 1 +# endif // ifdef ESP32 +#endif + // some flags for initR() :( #define INITR_GREENTAB 0x00 #define INITR_REDTAB 0x01 @@ -14,6 +30,7 @@ #define INITR_MINI160x80 0x04 #define INITR_HALLOWING 0x05 #define INITR_GREENTAB160x80 0x06 +#define INITR_BLACKTAB135x240 0x07 // Some register settings #define ST7735_MADCTL_BGR 0x08 diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7789.cpp b/lib/Adafruit_ST77xx/Adafruit_ST7789.cpp index 9fd2f00e4..6467aeb53 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7789.cpp +++ b/lib/Adafruit_ST77xx/Adafruit_ST7789.cpp @@ -76,6 +76,167 @@ static const uint8_t PROGMEM ST77XX_DISPON , ST_CMD_DELAY, // 9: Main screen turn on, no args, delay 10 }; // 10 ms delay +#if ST7789_EXTRA_INIT +static const uint8_t PROGMEM // Source: https://github.com/Xinyuan-LilyGO/TTGO-T-Display + alt1_st7789[] = { // Init commands for 7789 screens Alternative 1 + 21, // 21 commands in list: + ST77XX_SLPOUT, ST_CMD_DELAY, // 1: Out of sleep mode, no args, w/delay + 120, // 120 ms delay + ST77XX_NORON, ST_CMD_DELAY, // 2: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_MADCTL, 1, // 3: Mem access ctrl (directions), 1 arg: + 0x08, // Row/col addr, bottom-top refresh + 0xB6, 2, // 4: ?JXL240 datasheet? + 0x0A, 0x82, + ST77XX_COLMOD, 1+ ST_CMD_DELAY, // 5: Set color mode, 1 arg + delay: + 0x55, // 16-bit color + 10, // 10 ms delay + ST77XX_PORCTRL, 5, // 6: Porch control, Framerate setting + 0x0c, 0x0c, 0x00, 0x33, 0x33, + ST77XX_GCTRL, 1, // 7: Gate control, Voltages VGH/VGL + 0x35, + ST77XX_VCOMS, 1, // 8: Power settings + 0x28, + ST77XX_LCMCTRL, 1, // 9: LCM Control + 0x0C, + ST77XX_VDVVRHEN, 2, // 10: VDV & VRH command enable + 0x01, 0xFF, + ST77XX_VRHS, 1, // 11: VRH set + 0x10, + ST77XX_VDVSET, 1, // 12: VDV set + 0x20, + ST77XX_FRCTR2, 1, // 13: FR Control 2 + 0x0F, + ST77XX_PWCTRL1, 2, // 14: Power Control 1 + 0xA4, 0xA1, + ST77XX_PVGAMCTRL, 14, // 15: Positive Voltage Gamma control + 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32, 0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17, + ST77XX_NVGAMCTRL, 14, // 16: Negative Voltage Gamma control + 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31, 0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E, + ST77XX_INVON, ST_CMD_DELAY, // 17: hack + 10, + ST77XX_CASET , 4, // 18: Column addr set, 4 args, no delay: + 0x00, + 0, // XSTART = 0 + 0, + 240, // XEND = 240 + ST77XX_RASET , 4, // 19: Row addr set, 4 args, no delay: + 0x00, + 0, // YSTART = 0 + 320>>8, + 320&0xFF, // YEND = 320 + ST77XX_INVON, ST_CMD_DELAY, // 20: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_DISPON, ST_CMD_DELAY, // 21: Main screen turn on, no args, delay + 255 // 120 ms delay + }; + +static const uint8_t PROGMEM // Source: https://github.com/Bodmer/TFT_eSPI (ST7789_init.h, _NOT_ INIT_SEQUENCE_3) + alt2_st7789[] = { // Init commands for 7789 screens Alternative 2 + 21, // 21 commands in list: + ST77XX_SLPOUT, ST_CMD_DELAY, // 1: Out of sleep mode, no args, w/delay + 120, // 120 ms delay + ST77XX_NORON, ST_CMD_DELAY, // 2: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_MADCTL, 1, // 3: Mem access ctrl (directions), 1 arg: + 0x08, // Row/col addr, bottom-top refresh + 0xB6, 2, // 4: ?JXL240 datasheet? + 0x0A, 0x82, + ST77XX_RAMCTRL, 2, // 5: RAM control + 0x00, 0xE0, // 5 to 6-bit conversion: r0 = r5, b0 = b5 + ST77XX_COLMOD, 1+ ST_CMD_DELAY, // 6: Set color mode, 1 arg + delay: + 0x55, // 16-bit color + 10, // 10 ms delay + ST77XX_PORCTRL, 5, // 7: Porch control, Framerate setting + 0x0c, 0x0c, 0x00, 0x33, 0x33, + ST77XX_GCTRL, 1, // 8: Gate control, Voltages VGH/VGL + 0x35, + ST77XX_VCOMS, 1, // 9: Power settings + 0x28, + ST77XX_LCMCTRL, 1, // 10: LCM Control + 0x0C, + ST77XX_VDVVRHEN, 2, // 11: VDV & VRH command enable + 0x01, 0xFF, + ST77XX_VRHS, 1, // 12: VRH set + 0x10, + ST77XX_VDVSET, 1, // 13: VDV set + 0x20, + ST77XX_FRCTR2, 1, // 14: FR Control 2 + 0x0F, + ST77XX_PWCTRL1, 2, // 15: Power Control 1 + 0xA4, 0xA1, + ST77XX_PVGAMCTRL, 14, // 16: Positive Voltage Gamma control + 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x32, 0x44, 0x42, 0x06, 0x0E, 0x12, 0x14, 0x17, + ST77XX_NVGAMCTRL, 14, // 17: Negative Voltage Gamma control + 0xD0, 0x00, 0x02, 0x07, 0x0A, 0x28, 0x31, 0x54, 0x47, 0x0E, 0x1C, 0x17, 0x1B, 0x1E, + ST77XX_INVON, ST_CMD_DELAY, // 18: hack + 10, + ST77XX_CASET , 4, // 19: Column addr set, 4 args, no delay: + 0x00, + 0, // XSTART = 0 + 0, + 239, // XEND = 239 + ST77XX_RASET , 4, // 20: Row addr set, 4 args, no delay: + 0x00, + 0, // YSTART = 0 + 319>>8, + 319&0xFF, // YEND = 319 + ST77XX_DISPON, ST_CMD_DELAY, // 21: Main screen turn on, no args, delay + 120 // 120 ms delay + }; + +static const uint8_t PROGMEM // Source: https://github.com/Bodmer/TFT_eSPI (ST7789_init.h, _WITH_ INIT_SEQUENCE_3) + alt3_st7789[] = { // Init commands for 7789 screens Alternative 2 + 18, // 18 commands in list: + ST77XX_SLPOUT, ST_CMD_DELAY, // 1: Out of sleep mode, no args, w/delay + 120, // 120 ms delay + ST77XX_NORON, ST_CMD_DELAY, // 2: Normal display on, no args, w/delay + 10, // 10 ms delay + ST77XX_MADCTL, 1, // 3: Mem access ctrl (directions), 1 arg: + 0x08, // Row/col addr, bottom-top refresh + 0xB6, 2, // 4: ?JXL240 datasheet? + 0x0A, 0x82, + ST77XX_COLMOD, 1+ ST_CMD_DELAY, // 5: Set color mode, 1 arg + delay: + 0x55, // 16-bit color + 10, // 10 ms delay + ST77XX_PORCTRL, 5, // 6: Porch control, Framerate setting + 0x0c, 0x0c, 0x00, 0x33, 0x33, + ST77XX_GCTRL, 1, // 7: Gate control, Voltages VGH/VGL + 0x75, + ST77XX_VCOMS, 1, // 8: Power settings + 0x28, + ST77XX_LCMCTRL, 1, // 9: LCM Control + 0x2C, + ST77XX_VDVVRHEN, 1, // 10: VDV & VRH command enable + 0x01, + ST77XX_VRHS, 1, // 11: VRH set + 0x1F, + ST77XX_FRCTR2, 1, // 12: FR Control 2 + 0x13, + ST77XX_PWCTRL1, 1, // 13: Power Control 1 + 0xA7, + ST77XX_PWCTRL1, 2, // 14: Power Control 1 + 0xA4, 0xA1, + 0xD6, 1, // 15: ? + 0xA1, + ST77XX_PVGAMCTRL, 14, // 16: Positive Voltage Gamma control + 0xF0, 0x05, 0x0A, 0x06, 0x06, 0x03, 0x2B, 0x32, 0x43, 0x36, 0x11, 0x10, 0x2B, 0x32, + ST77XX_NVGAMCTRL, 14, // 17: Negative Voltage Gamma control + 0xF0, 0x08, 0x0C, 0x0B, 0x09, 0x24, 0x2B, 0x22, 0x43, 0x38, 0x15, 0x16, 0x2F, 0x37, + // ST77XX_CASET , 4, // 18: Column addr set, 4 args, no delay: + // 0x00, + // 0, // XSTART = 0 + // 0, + // 239, // XEND = 239 + // ST77XX_RASET , 4, // 19: Row addr set, 4 args, no delay: + // 0x00, + // 0, // YSTART = 0 + // 319>>8, + // 319&0xFF, // YEND = 319 + ST77XX_DISPON, ST_CMD_DELAY, // 18: Main screen turn on, no args, delay + 120 // 120 ms delay + }; +#endif // if ST7789_EXTRA_INIT // clang-format on /**************************************************************************/ @@ -88,7 +249,7 @@ static const uint8_t PROGMEM the defines only, the values are NOT the same!) */ /**************************************************************************/ -void Adafruit_ST7789::init(uint16_t width, uint16_t height, uint8_t mode) { +void Adafruit_ST7789::init(uint16_t width, uint16_t height, uint8_t mode, uint8_t init_seq) { // Save SPI data mode. commonInit() calls begin() (in Adafruit_ST77xx.cpp), // which in turn calls initSPI() (in Adafruit_SPITFT.cpp), passing it the // value of spiMode. It's done this way because begin() really should not @@ -101,6 +262,8 @@ void Adafruit_ST7789::init(uint16_t width, uint16_t height, uint8_t mode) { // (Might get added similarly to other display types as needed on a // case-by-case basis.) + _init_seq = init_seq; + commonInit(NULL); if (width < 240) { @@ -123,7 +286,18 @@ void Adafruit_ST7789::init(uint16_t width, uint16_t height, uint8_t mode) { windowWidth = width; windowHeight = height; - displayInit(generic_st7789); + const uint8_t *init_ = generic_st7789; + + #if ST7789_EXTRA_INIT + if (1 == _init_seq) { + init_ = alt1_st7789; + } else if (2 == _init_seq) { + init_ = alt2_st7789; + } else if (3 == _init_seq) { + init_ = alt3_st7789; + } + #endif // if ST7789_EXTRA_INIT + displayInit(init_); setRotation(0); } @@ -140,28 +314,64 @@ void Adafruit_ST7789::setRotation(uint8_t m) { switch (rotation) { case 0: - madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; + #if ST7789_EXTRA_INIT + if (_init_seq > 0) { + madctl = ST77XX_MADCTL_BGR; + _colstart = 52; + _rowstart = 40; + } else + #endif // if ST7789_EXTRA_INIT + { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; + } _xstart = _colstart; _ystart = _rowstart; _width = windowWidth; _height = windowHeight; break; case 1: - madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + #if ST7789_EXTRA_INIT + if (_init_seq > 0) { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_BGR; + _colstart = 40; + _rowstart = 53; + } else + #endif // if ST7789_EXTRA_INIT + { + madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + } _xstart = _rowstart; _ystart = _colstart; _height = windowWidth; _width = windowHeight; break; case 2: - madctl = ST77XX_MADCTL_RGB; + #if ST7789_EXTRA_INIT + if (_init_seq > 0) { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST77XX_MADCTL_RGB; + _colstart2 = 53; + _rowstart2 = 40; + } else + #endif // if ST7789_EXTRA_INIT + { + madctl = ST77XX_MADCTL_RGB; + } _xstart = _colstart2; _ystart = _rowstart2; _width = windowWidth; _height = windowHeight; break; case 3: - madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + #if ST7789_EXTRA_INIT + if (_init_seq > 0) { + madctl = ST77XX_MADCTL_MV | ST77XX_MADCTL_MY | ST77XX_MADCTL_BGR; + _colstart2 = 40; + _rowstart2 = 52; + } else + #endif // if ST7789_EXTRA_INIT + { + madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST77XX_MADCTL_RGB; + } _xstart = _rowstart2; _ystart = _colstart2; _height = windowWidth; diff --git a/lib/Adafruit_ST77xx/Adafruit_ST7789.h b/lib/Adafruit_ST77xx/Adafruit_ST7789.h index 7fe09d6f0..4714a9968 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST7789.h +++ b/lib/Adafruit_ST77xx/Adafruit_ST7789.h @@ -3,6 +3,22 @@ #include "Adafruit_ST77xx.h" +/** + * 2024-03-09 tonhuisman: Add additional initialization sequences for ST7789 displays, with the intention to get 'm working + * on some devices that seem to use peculiarly configured hardware like LiliGO TTGO T-Display (16MB flash), + * and possibly the T-Display S3 + * By default only enabled on ESP32, unless -D ST7789_EXTRA_INIT=1 is defined, f.e. via the build script + */ + +#ifndef ST7789_EXTRA_INIT // Enable setting from 'outside', like Platformio.ini +# ifdef ESP8266 +# define ST7789_EXTRA_INIT 0 +# endif // ifdef ESP8266 +# ifdef ESP32 +# define ST7789_EXTRA_INIT 1 +# endif // ifdef ESP32 +#endif + /// Subclass of ST77XX type display for ST7789 TFT Driver class Adafruit_ST7789 : public Adafruit_ST77xx { public: @@ -14,7 +30,7 @@ public: #endif // end !ESP8266 void setRotation(uint8_t m); - void init(uint16_t width, uint16_t height, uint8_t spiMode = SPI_MODE0); + void init(uint16_t width, uint16_t height, uint8_t spiMode = SPI_MODE0, uint8_t init_seq = 0u); protected: uint8_t _colstart2 = 0, ///< Offset from the right @@ -23,6 +39,7 @@ protected: private: uint16_t windowWidth; uint16_t windowHeight; + uint8_t _init_seq = 0u; }; #endif // _ADAFRUIT_ST7789H_ diff --git a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h index fcf0c6f6e..9a299b51f 100644 --- a/lib/Adafruit_ST77xx/Adafruit_ST77xx.h +++ b/lib/Adafruit_ST77xx/Adafruit_ST77xx.h @@ -33,8 +33,10 @@ #define ST7735_TFTWIDTH_128 128 // for 1.44 and mini #define ST7735_TFTWIDTH_80 80 // for mini +#define ST7735_TFTWIDTH_135 135 #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 ST_CMD_DELAY 0x80 // special signifier for command lists @@ -63,11 +65,48 @@ #define ST77XX_MADCTL 0x36 #define ST77XX_COLMOD 0x3A +#define ST77XX_RAMCTRL 0xB0 // RAM control +#define ST77XX_RGBCTRL 0xB1 // RGB control +#define ST77XX_PORCTRL 0xB2 // Porch control +#define ST77XX_FRCTRL1 0xB3 // Frame rate control +#define ST77XX_PARCTRL 0xB5 // Partial mode control +#define ST77XX_GCTRL 0xB7 // Gate control +#define ST77XX_GTADJ 0xB8 // Gate on timing adjustment +#define ST77XX_DGMEN 0xBA // Digital gamma enable +#define ST77XX_VCOMS 0xBB // VCOMS setting +#define ST77XX_LCMCTRL 0xC0 // LCM control +#define ST77XX_IDSET 0xC1 // ID setting +#define ST77XX_VDVVRHEN 0xC2 // VDV and VRH command enable +#define ST77XX_VRHS 0xC3 // VRH set +#define ST77XX_VDVSET 0xC4 // VDV setting +#define ST77XX_VCMOFSET 0xC5 // VCOMS offset set +#define ST77XX_FRCTR2 0xC6 // FR Control 2 +#define ST77XX_CABCCTRL 0xC7 // CABC control +#define ST77XX_REGSEL1 0xC8 // Register value section 1 +#define ST77XX_REGSEL2 0xCA // Register value section 2 +#define ST77XX_PWMFRSEL 0xCC // PWM frequency selection +#define ST77XX_PWCTRL1 0xD0 // Power control 1 +#define ST77XX_VAPVANEN 0xD2 // Enable VAP/VAN signal output +#define ST77XX_CMD2EN 0xDF // Command 2 enable +#define ST77XX_PVGAMCTRL 0xE0 // Positive voltage gamma control +#define ST77XX_NVGAMCTRL 0xE1 // Negative voltage gamma control +#define ST77XX_DGMLUTR 0xE2 // Digital gamma look-up table for red +#define ST77XX_DGMLUTB 0xE3 // Digital gamma look-up table for blue +#define ST77XX_GATECTRL 0xE4 // Gate control +#define ST77XX_SPI2EN 0xE7 // SPI2 enable +#define ST77XX_PWCTRL2 0xE8 // Power control 2 +#define ST77XX_EQCTRL 0xE9 // Equalize time control +#define ST77XX_PROMCTRL 0xEC // Program control +#define ST77XX_PROMEN 0xFA // Program mode enable +#define ST77XX_NVMSET 0xFC // NVM setting +#define ST77XX_PROMACT 0xFE // Program action + #define ST77XX_MADCTL_MY 0x80 #define ST77XX_MADCTL_MX 0x40 #define ST77XX_MADCTL_MV 0x20 #define ST77XX_MADCTL_ML 0x10 #define ST77XX_MADCTL_RGB 0x00 +#define ST77XX_MADCTL_BGR 0x08 #define ST77XX_RDID1 0xDA #define ST77XX_RDID2 0xDB diff --git a/lib/Adafruit_VEML7700/Adafruit_VEML7700.cpp b/lib/Adafruit_VEML7700/Adafruit_VEML7700.cpp new file mode 100644 index 000000000..71a90fcdc --- /dev/null +++ b/lib/Adafruit_VEML7700/Adafruit_VEML7700.cpp @@ -0,0 +1,462 @@ +/*! + * @file Adafruit_VEML7700.cpp + * + * @mainpage Adafruit VEML7700 I2C Lux Sensor + * + * @section intro_sec Introduction + * + * I2C Driver for the VEML7700 I2C Lux sensor + * + * This is a library for the Adafruit VEML7700 breakout: + * http://www.adafruit.com/ + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products from + * Adafruit! + * + * @section author Author + * + * Limor Fried (Adafruit Industries) + * + * @section license License + * + * BSD (see license.txt) + * + * @section HISTORY + * + * v1.0 - First release + */ + +#include "Adafruit_VEML7700.h" + +/*! + * @brief Instantiates a new VEML7700 class + */ +Adafruit_VEML7700::Adafruit_VEML7700(void) {} + +/*! + * @brief Sets up the hardware for talking to the VEML7700 + * @param theWire An optional pointer to an I2C interface + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_VEML7700::begin(int8_t i2cAddr, TwoWire *theWire) { + i2c_dev = new Adafruit_I2CDevice(i2cAddr, theWire); + + if (!i2c_dev->begin()) { + return false; + } + + ALS_Config = + new Adafruit_I2CRegister(i2c_dev, VEML7700_ALS_CONFIG, 2, LSBFIRST); + ALS_HighThreshold = new Adafruit_I2CRegister( + i2c_dev, VEML7700_ALS_THREHOLD_HIGH, 2, LSBFIRST); + ALS_LowThreshold = + new Adafruit_I2CRegister(i2c_dev, VEML7700_ALS_THREHOLD_LOW, 2, LSBFIRST); + Power_Saving = + new Adafruit_I2CRegister(i2c_dev, VEML7700_ALS_POWER_SAVE, 2, LSBFIRST); + ALS_Data = new Adafruit_I2CRegister(i2c_dev, VEML7700_ALS_DATA, 2, LSBFIRST); + White_Data = + new Adafruit_I2CRegister(i2c_dev, VEML7700_WHITE_DATA, 2, LSBFIRST); + Interrupt_Status = + new Adafruit_I2CRegister(i2c_dev, VEML7700_INTERRUPTSTATUS, 2, LSBFIRST); + + ALS_Shutdown = + new Adafruit_I2CRegisterBits(ALS_Config, 1, 0); // # bits, bit_shift + ALS_Interrupt_Enable = new Adafruit_I2CRegisterBits(ALS_Config, 1, 1); + ALS_Persistence = new Adafruit_I2CRegisterBits(ALS_Config, 2, 4); + ALS_Integration_Time = new Adafruit_I2CRegisterBits(ALS_Config, 4, 6); + ALS_Gain = new Adafruit_I2CRegisterBits(ALS_Config, 2, 11); + PowerSave_Enable = new Adafruit_I2CRegisterBits(Power_Saving, 1, 0); + PowerSave_Mode = new Adafruit_I2CRegisterBits(Power_Saving, 2, 1); + + enable(false); + interruptEnable(false); + setPersistence(VEML7700_PERS_1); + setGain(VEML7700_GAIN_1_8); + setIntegrationTime(VEML7700_IT_100MS); + powerSaveEnable(false); + enable(true); + + lastRead = millis(); + + return true; +} + +/*! + * @brief Read the calibrated lux value. See app note lux table on page 5 + * @param method Lux comptation method to use. One of + * @returns Floating point Lux data + */ +float Adafruit_VEML7700::readLux(luxMethod method) { + bool wait = true; + switch (method) { + case VEML_LUX_NORMAL_NOWAIT: + wait = false; + VEML7700_FALLTHROUGH + case VEML_LUX_NORMAL: + return computeLux(readALS(wait)); + case VEML_LUX_CORRECTED_NOWAIT: + wait = false; + VEML7700_FALLTHROUGH + case VEML_LUX_CORRECTED: + return computeLux(readALS(wait), true); + case VEML_LUX_AUTO: + return autoLux(); + default: + return -1; + } +} + +/*! + * @brief Read the raw ALS data + * @param wait If false (default), read out measurement with no delay. If + * true, wait as need based on integration time before reading out measurement + * results. + * @returns 16-bit data value from the ALS register + */ +uint16_t Adafruit_VEML7700::readALS(bool wait) { + if (wait) + readWait(); + lastRead = millis(); + return ALS_Data->read(); +} + +/*! + * @brief Read the raw white light data + * @param wait If false (default), read out measurement with no delay. If + * true, wait as need based on integration time before reading out measurement + * results. + * @returns 16-bit data value from the WHITE register + */ +uint16_t Adafruit_VEML7700::readWhite(bool wait) { + if (wait) + readWait(); + lastRead = millis(); + return White_Data->read(); +} + +/*! + * @brief Enable or disable the sensor + * @param enable The flag to enable/disable + */ +void Adafruit_VEML7700::enable(bool enable) { + ALS_Shutdown->write(!enable); + // From app note: + // ''' + // When activating the sensor, set bit 0 of the command register + // to “0†with a wait time of 2.5 ms before the first measurement + // is needed, allowing for the correct start of the signal + // processor and oscillator. + // ''' + if (enable) + delay(5); // doubling 2.5ms spec to be sure +} + +/*! + * @brief Ask if the interrupt is enabled + * @returns True if enabled, false otherwise + */ +bool Adafruit_VEML7700::enabled(void) { return !ALS_Shutdown->read(); } + +/*! + * @brief Enable or disable the interrupt + * @param enable The flag to enable/disable + */ +void Adafruit_VEML7700::interruptEnable(bool enable) { + ALS_Interrupt_Enable->write(enable); +} + +/*! + * @brief Ask if the interrupt is enabled + * @returns True if enabled, false otherwise + */ +bool Adafruit_VEML7700::interruptEnabled(void) { + return ALS_Interrupt_Enable->read(); +} + +/*! + * @brief Set the ALS IRQ persistence setting + * @param pers Persistence constant, can be VEML7700_PERS_1, VEML7700_PERS_2, + * VEML7700_PERS_4 or VEML7700_PERS_8 + */ +void Adafruit_VEML7700::setPersistence(uint8_t pers) { + ALS_Persistence->write(pers); +} + +/*! + * @brief Get the ALS IRQ persistence setting + * @returns Persistence constant, can be VEML7700_PERS_1, VEML7700_PERS_2, + * VEML7700_PERS_4 or VEML7700_PERS_8 + */ +uint8_t Adafruit_VEML7700::getPersistence(void) { + return ALS_Persistence->read(); +} + +/*! + * @brief Set ALS integration time + * @param it Can be VEML7700_IT_100MS, VEML7700_IT_200MS, VEML7700_IT_400MS, + * VEML7700_IT_800MS, VEML7700_IT_50MS or VEML7700_IT_25MS + * @param wait Waits to insure old integration time cycle has completed. This + * is a blocking delay. If disabled by passing false, user code must insure a + * new reading is not done before old integration cycle completes. + */ +void Adafruit_VEML7700::setIntegrationTime(uint8_t it, bool wait) { + // save current integration time + int flushDelay = wait ? getIntegrationTimeValue() : 0; + // set new integration time + ALS_Integration_Time->write(it); + // pause old integration time to insure sensor cycle has completed + delay(flushDelay); + // reset counter + lastRead = millis(); +} + +/*! + * @brief Get ALS integration time setting + * @returns IT index, can be VEML7700_IT_100MS, VEML7700_IT_200MS, + * VEML7700_IT_400MS, VEML7700_IT_800MS, VEML7700_IT_50MS or VEML7700_IT_25MS + */ +uint8_t Adafruit_VEML7700::getIntegrationTime(void) { + return ALS_Integration_Time->read(); +} + +/*! + * @brief Get ALS integration time value + * @returns ALS integration time in milliseconds + */ +int Adafruit_VEML7700::getIntegrationTimeValue(void) { + switch (getIntegrationTime()) { + case VEML7700_IT_25MS: + return 25; + case VEML7700_IT_50MS: + return 50; + case VEML7700_IT_100MS: + return 100; + case VEML7700_IT_200MS: + return 200; + case VEML7700_IT_400MS: + return 400; + case VEML7700_IT_800MS: + return 800; + default: + return -1; + } +} + +/*! + * @brief Set ALS gain + * @param gain Can be VEML7700_GAIN_1, VEML7700_GAIN_2, VEML7700_GAIN_1_8 or + * VEML7700_GAIN_1_4 + */ +void Adafruit_VEML7700::setGain(uint8_t gain) { + ALS_Gain->write(gain); + lastRead = millis(); // reset +} + +/*! + * @brief Get ALS gain setting + * @returns Gain index, can be VEML7700_GAIN_1, VEML7700_GAIN_2, + * VEML7700_GAIN_1_8 or VEML7700_GAIN_1_4 + */ +uint8_t Adafruit_VEML7700::getGain(void) { return ALS_Gain->read(); } + +/*! + * @brief Get ALS gain value + * @returns Actual gain value as float + */ +float Adafruit_VEML7700::getGainValue(void) { + switch (getGain()) { + case VEML7700_GAIN_1_8: + return 0.125; + case VEML7700_GAIN_1_4: + return 0.25; + case VEML7700_GAIN_1: + return 1; + case VEML7700_GAIN_2: + return 2; + default: + return -1; + } +} + +/*! + * @brief Enable power save mode + * @param enable True if power save should be enabled + */ +void Adafruit_VEML7700::powerSaveEnable(bool enable) { + PowerSave_Enable->write(enable); +} + +/*! + * @brief Check if power save mode is enabled + * @returns True if power save is enabled + */ +bool Adafruit_VEML7700::powerSaveEnabled(void) { + return PowerSave_Enable->read(); +} + +/*! + * @brief Assign the power save register data + * @param mode The 16-bit data to write to VEML7700_ALS_POWER_SAVE + */ +void Adafruit_VEML7700::setPowerSaveMode(uint8_t mode) { + PowerSave_Mode->write(mode); +} + +/*! + * @brief Retrieve the power save register data + * @return 16-bit data from VEML7700_ALS_POWER_SAVE + */ +uint8_t Adafruit_VEML7700::getPowerSaveMode(void) { + return PowerSave_Mode->read(); +} + +/*! + * @brief Assign the low threshold register data + * @param value The 16-bit data to write to VEML7700_ALS_THREHOLD_LOW + */ +void Adafruit_VEML7700::setLowThreshold(uint16_t value) { + ALS_LowThreshold->write(value); +} + +/*! + * @brief Retrieve the low threshold register data + * @return 16-bit data from VEML7700_ALS_THREHOLD_LOW + */ +uint16_t Adafruit_VEML7700::getLowThreshold(void) { + return ALS_LowThreshold->read(); +} + +/*! + * @brief Assign the high threshold register data + * @param value The 16-bit data to write to VEML7700_ALS_THREHOLD_HIGH + */ +void Adafruit_VEML7700::setHighThreshold(uint16_t value) { + ALS_HighThreshold->write(value); +} + +/*! + * @brief Retrieve the high threshold register data + * @return 16-bit data from VEML7700_ALS_THREHOLD_HIGH + */ +uint16_t Adafruit_VEML7700::getHighThreshold(void) { + return ALS_HighThreshold->read(); +} + +/*! + * @brief Retrieve the interrupt status register data + * @return 16-bit data from VEML7700_INTERRUPTSTATUS + */ +uint16_t Adafruit_VEML7700::interruptStatus(void) { + return Interrupt_Status->read(); +} + +/*! + * @brief Determines resolution for current gain and integration time + * settings. + */ +float Adafruit_VEML7700::getResolution(void) { + return MAX_RES * (IT_MAX / getIntegrationTimeValue()) * + (GAIN_MAX / getGainValue()); +} + +/*! + * @brief Copmute lux from ALS reading. + * @param rawALS raw ALS register value + * @param corrected if true, apply non-linear correction + * @return lux value + */ +float Adafruit_VEML7700::computeLux(uint16_t rawALS, bool corrected) { + float lux = getResolution() * rawALS; + if (corrected) + lux = (((6.0135e-13 * lux - 9.3924e-9) * lux + 8.1488e-5) * lux + 1.0023) * + lux; + return lux; +} + +void Adafruit_VEML7700::readWait(void) { + // From app note: + // ''' + // Without using the power-saving feature (PSM_EN = 0), the + // controller has to wait before reading out measurement results, + // at least for the programmed integration time. For example, + // for ALS_IT = 100 ms a wait time of ≥ 100 ms is needed. + // ''' + // Based on testing, it needs more. So doubling to be sure. + + unsigned long timeToWait = 2 * getIntegrationTimeValue(); // see above + unsigned long timeWaited = millis() - lastRead; + + if (timeWaited < timeToWait) + delay(timeToWait - timeWaited); +} + +bool Adafruit_VEML7700::readReady(void) { + // From app note: + // ''' + // Without using the power-saving feature (PSM_EN = 0), the + // controller has to wait before reading out measurement results, + // at least for the programmed integration time. For example, + // for ALS_IT = 100 ms a wait time of ≥ 100 ms is needed. + // ''' + // Based on testing, it needs more. So doubling to be sure. + + unsigned long timeToWait = 2 * getIntegrationTimeValue(); // see above + unsigned long timeWaited = millis() - lastRead; + + return (timeWaited >= timeToWait); +} + +/*! + * @brief Implemenation of App Note "Designing the VEML7700 Into an + * Application", Vishay Document Number: 84323, Fig. 24 Flow Chart. This will + * automatically adjust gain and integration time as needed to obtain a good raw + * count value. Additionally, a non-linear correction is applied if needed. + */ +float Adafruit_VEML7700::autoLux(void) { + const uint8_t gains[] = {VEML7700_GAIN_1_8, VEML7700_GAIN_1_4, + VEML7700_GAIN_1, VEML7700_GAIN_2}; + const uint8_t intTimes[] = {VEML7700_IT_25MS, VEML7700_IT_50MS, + VEML7700_IT_100MS, VEML7700_IT_200MS, + VEML7700_IT_400MS, VEML7700_IT_800MS}; + + uint8_t gainIndex = 0; // start with ALS gain = 1/8 + uint8_t itIndex = 2; // start with ALS integration time = 100ms + bool useCorrection = false; // flag for non-linear correction + + setGain(gains[gainIndex]); + setIntegrationTime(intTimes[itIndex]); + + uint16_t ALS = readALS(true); + // Serial.println("** AUTO LUX DEBUG **"); + // Serial.print("ALS initial = "); Serial.println(ALS); + + if (ALS <= 100) { + + // increase first gain and then integration time as needed + // compute lux using simple linear formula + while ((ALS <= 100) && !((gainIndex == 3) && (itIndex == 5))) { + if (gainIndex < 3) { + setGain(gains[++gainIndex]); + } else if (itIndex < 5) { + setIntegrationTime(intTimes[++itIndex]); + } + ALS = readALS(true); + // Serial.print("ALS low lux = "); Serial.println(ALS); + } + + } else { + + // decrease integration time as needed + // compute lux using non-linear correction + useCorrection = true; + while ((ALS > 10000) && (itIndex > 0)) { + setIntegrationTime(intTimes[--itIndex]); + ALS = readALS(true); + // Serial.print("ALS hi lux = "); Serial.println(ALS); + } + } + // Serial.println("** AUTO LUX DEBUG **"); + + return computeLux(ALS, useCorrection); +} \ No newline at end of file diff --git a/lib/Adafruit_VEML7700/Adafruit_VEML7700.h b/lib/Adafruit_VEML7700/Adafruit_VEML7700.h new file mode 100644 index 000000000..9350d4cf0 --- /dev/null +++ b/lib/Adafruit_VEML7700/Adafruit_VEML7700.h @@ -0,0 +1,136 @@ +/*! + * @file Adafruit_VEML7700.h + * + * I2C Driver for VEML7700 Lux sensor + * + * This is a library for the Adafruit VEML7700 breakout: + * http://www.adafruit.com/ + * + * Adafruit invests time and resources providing this open source code, + *please support Adafruit and open-source hardware by purchasing products from + * Adafruit! + * + * + * BSD license (see license.txt) + */ + +#ifndef _ADAFRUIT_VEML7700_H +#define _ADAFRUIT_VEML7700_H + +#include "Arduino.h" +#include +#include +#include + +#define VEML7700_I2CADDR_DEFAULT 0x10 ///< I2C address + +#define VEML7700_ALS_CONFIG 0x00 ///< Light configuration register +#define VEML7700_ALS_THREHOLD_HIGH 0x01 ///< Light high threshold for irq +#define VEML7700_ALS_THREHOLD_LOW 0x02 ///< Light low threshold for irq +#define VEML7700_ALS_POWER_SAVE 0x03 ///< Power save regiester +#define VEML7700_ALS_DATA 0x04 ///< The light data output +#define VEML7700_WHITE_DATA 0x05 ///< The white light data output +#define VEML7700_INTERRUPTSTATUS 0x06 ///< What IRQ (if any) + +#define VEML7700_INTERRUPT_HIGH 0x4000 ///< Interrupt status for high threshold +#define VEML7700_INTERRUPT_LOW 0x8000 ///< Interrupt status for low threshold + +#define VEML7700_GAIN_1 0x00 ///< ALS gain 1x +#define VEML7700_GAIN_2 0x01 ///< ALS gain 2x +#define VEML7700_GAIN_1_8 0x02 ///< ALS gain 1/8x +#define VEML7700_GAIN_1_4 0x03 ///< ALS gain 1/4x + +#define VEML7700_IT_100MS 0x00 ///< ALS intetgration time 100ms +#define VEML7700_IT_200MS 0x01 ///< ALS intetgration time 200ms +#define VEML7700_IT_400MS 0x02 ///< ALS intetgration time 400ms +#define VEML7700_IT_800MS 0x03 ///< ALS intetgration time 800ms +#define VEML7700_IT_50MS 0x08 ///< ALS intetgration time 50ms +#define VEML7700_IT_25MS 0x0C ///< ALS intetgration time 25ms + +#define VEML7700_PERS_1 0x00 ///< ALS irq persistence 1 sample +#define VEML7700_PERS_2 0x01 ///< ALS irq persistence 2 samples +#define VEML7700_PERS_4 0x02 ///< ALS irq persistence 4 samples +#define VEML7700_PERS_8 0x03 ///< ALS irq persistence 8 samples + +#define VEML7700_POWERSAVE_MODE1 0x00 ///< Power saving mode 1 +#define VEML7700_POWERSAVE_MODE2 0x01 ///< Power saving mode 2 +#define VEML7700_POWERSAVE_MODE3 0x02 ///< Power saving mode 3 +#define VEML7700_POWERSAVE_MODE4 0x03 ///< Power saving mode 4 + +/*! + * @brief Used to explicitly annotate switch case fall throughs. + * Newer compilers will throw a warning otherwise. + */ +#if defined(__GNUC__) && __GNUC__ >= 7 +#define VEML7700_FALLTHROUGH __attribute__((fallthrough)); +#else +#define VEML7700_FALLTHROUGH +#endif + +/** Options for lux reading method */ +typedef enum { + VEML_LUX_NORMAL, + VEML_LUX_CORRECTED, + VEML_LUX_AUTO, + VEML_LUX_NORMAL_NOWAIT, + VEML_LUX_CORRECTED_NOWAIT +} luxMethod; + +/*! + * @brief Class that stores state and functions for interacting with + * VEML7700 Light Sensor + */ +class Adafruit_VEML7700 { +public: + Adafruit_VEML7700(); + bool begin(int8_t i2cAddr = VEML7700_I2CADDR_DEFAULT, TwoWire *theWire = &Wire); + + void enable(bool enable); + bool enabled(void); + + void interruptEnable(bool enable); + bool interruptEnabled(void); + void setPersistence(uint8_t pers); + uint8_t getPersistence(void); + void setIntegrationTime(uint8_t it, bool wait = true); + uint8_t getIntegrationTime(void); + int getIntegrationTimeValue(void); + void setGain(uint8_t gain); + uint8_t getGain(void); + float getGainValue(void); + void powerSaveEnable(bool enable); + bool powerSaveEnabled(void); + void setPowerSaveMode(uint8_t mode); + uint8_t getPowerSaveMode(void); + + void setLowThreshold(uint16_t value); + uint16_t getLowThreshold(void); + void setHighThreshold(uint16_t value); + uint16_t getHighThreshold(void); + uint16_t interruptStatus(void); + + uint16_t readALS(bool wait = false); + uint16_t readWhite(bool wait = false); + float readLux(luxMethod method = VEML_LUX_NORMAL); + + bool readReady(void); // 2024-05-18 tonhuisman: Added for ESPEasy + +private: + const float MAX_RES = 0.0036; + const float GAIN_MAX = 2; + const float IT_MAX = 800; + float getResolution(void); + float computeLux(uint16_t rawALS, bool corrected = false); + float autoLux(void); + void readWait(void); + unsigned long lastRead; + + Adafruit_I2CRegister *ALS_Config, *ALS_Data, *White_Data, *ALS_HighThreshold, + *ALS_LowThreshold, *Power_Saving, *Interrupt_Status; + Adafruit_I2CRegisterBits *ALS_Shutdown, *ALS_Interrupt_Enable, + *ALS_Persistence, *ALS_Integration_Time, *ALS_Gain, *PowerSave_Enable, + *PowerSave_Mode; + Adafruit_I2CDevice *i2c_dev; +}; + +#endif diff --git a/lib/Adafruit_VEML7700/README.md b/lib/Adafruit_VEML7700/README.md new file mode 100644 index 000000000..51cc70a41 --- /dev/null +++ b/lib/Adafruit_VEML7700/README.md @@ -0,0 +1,16 @@ +Adafruit_VEML7700 [![Build Status](https://github.com/adafruit/Adafruit_VEML7700/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_VEML7700/actions) +================ + +This is the Adafruit VEML7700 Lux sensor library + +Tested and works great with the [Adafruit VEML7700 Breakout Board](http://www.adafruit.com/) + +This chip uses I2C to communicate, 2 pins are required to interface + +Adafruit invests time and resources providing this open source code, +please support Adafruit and open-source hardware by purchasing +products from Adafruit! + +Written by Kevin Townsend/Limor Fried for Adafruit Industries. +BSD license, check license.txt for more information +All text above must be included in any redistribution diff --git a/lib/Adafruit_VEML7700/examples/VEML7700_oled/VEML7700_oled.ino b/lib/Adafruit_VEML7700/examples/VEML7700_oled/VEML7700_oled.ino new file mode 100644 index 000000000..4c959f1b6 --- /dev/null +++ b/lib/Adafruit_VEML7700/examples/VEML7700_oled/VEML7700_oled.ino @@ -0,0 +1,40 @@ +#include +#include "Adafruit_VEML7700.h" + +Adafruit_VEML7700 veml = Adafruit_VEML7700(); +Adafruit_SSD1306 display = Adafruit_SSD1306(128, 32, &Wire); + +void setup() { + Serial.begin(115200); + //while (!Serial); + Serial.println("VEML7700 demo"); + + if (veml.begin()) { + Serial.println("Found a VEML7700 sensor"); + } else { + Serial.println("No sensor found ... check your wiring?"); + while (1); + } + + // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally + if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32 + Serial.println(F("SSD1306 allocation failed")); + for(;;); // Don't proceed, loop forever + } + display.display(); + delay(500); // Pause for 2 seconds + display.setTextSize(2); + display.setTextColor(WHITE); + + veml.setGain(VEML7700_GAIN_1); + veml.setIntegrationTime(VEML7700_IT_100MS); +} + + +void loop() { + display.clearDisplay(); + display.setCursor(0,8); + display.print("Lux "); display.println(veml.readLux()); + display.display(); + delay(50); +} diff --git a/lib/Adafruit_VEML7700/examples/veml7700_autolux/veml7700_autolux.ino b/lib/Adafruit_VEML7700/examples/veml7700_autolux/veml7700_autolux.ino new file mode 100644 index 000000000..6f4b311ec --- /dev/null +++ b/lib/Adafruit_VEML7700/examples/veml7700_autolux/veml7700_autolux.ino @@ -0,0 +1,52 @@ +/* VEML7700 Auto Lux Example + * + * This example sketch demonstrates reading lux using the automatic + * method which adjusts gain and integration time as needed to obtain + * a good reading. A non-linear correction is also applied if needed. + * + * See Vishy App Note "Designing the VEML7700 Into an Application" + * Vishay Document Number: 84323, Fig. 24 Flow Chart + */ + +#include "Adafruit_VEML7700.h" + +Adafruit_VEML7700 veml = Adafruit_VEML7700(); + +void setup() { + Serial.begin(115200); + while (!Serial) { delay(10); } + Serial.println("Adafruit VEML7700 Auto Lux Test"); + + if (!veml.begin()) { + Serial.println("Sensor not found"); + while (1); + } + Serial.println("Sensor found"); +} + +void loop() { + // to read lux using automatic method, specify VEML_LUX_AUTO + float lux = veml.readLux(VEML_LUX_AUTO); + + Serial.println("------------------------------------"); + Serial.print("Lux = "); Serial.println(lux); + Serial.println("Settings used for reading:"); + Serial.print(F("Gain: ")); + switch (veml.getGain()) { + case VEML7700_GAIN_1: Serial.println("1"); break; + case VEML7700_GAIN_2: Serial.println("2"); break; + case VEML7700_GAIN_1_4: Serial.println("1/4"); break; + case VEML7700_GAIN_1_8: Serial.println("1/8"); break; + } + Serial.print(F("Integration Time (ms): ")); + switch (veml.getIntegrationTime()) { + case VEML7700_IT_25MS: Serial.println("25"); break; + case VEML7700_IT_50MS: Serial.println("50"); break; + case VEML7700_IT_100MS: Serial.println("100"); break; + case VEML7700_IT_200MS: Serial.println("200"); break; + case VEML7700_IT_400MS: Serial.println("400"); break; + case VEML7700_IT_800MS: Serial.println("800"); break; + } + + delay(1000); +} \ No newline at end of file diff --git a/lib/Adafruit_VEML7700/examples/veml7700_test/veml7700_test.ino b/lib/Adafruit_VEML7700/examples/veml7700_test/veml7700_test.ino new file mode 100644 index 000000000..f5362aa04 --- /dev/null +++ b/lib/Adafruit_VEML7700/examples/veml7700_test/veml7700_test.ino @@ -0,0 +1,59 @@ +#include "Adafruit_VEML7700.h" + +Adafruit_VEML7700 veml = Adafruit_VEML7700(); + +void setup() { + Serial.begin(115200); + while (!Serial) { delay(10); } + Serial.println("Adafruit VEML7700 Test"); + + if (!veml.begin()) { + Serial.println("Sensor not found"); + while (1); + } + Serial.println("Sensor found"); + + // == OPTIONAL ===== + // Can set non-default gain and integration time to + // adjust for different lighting conditions. + // ================= + // veml.setGain(VEML7700_GAIN_1_8); + // veml.setIntegrationTime(VEML7700_IT_100MS); + + Serial.print(F("Gain: ")); + switch (veml.getGain()) { + case VEML7700_GAIN_1: Serial.println("1"); break; + case VEML7700_GAIN_2: Serial.println("2"); break; + case VEML7700_GAIN_1_4: Serial.println("1/4"); break; + case VEML7700_GAIN_1_8: Serial.println("1/8"); break; + } + + Serial.print(F("Integration Time (ms): ")); + switch (veml.getIntegrationTime()) { + case VEML7700_IT_25MS: Serial.println("25"); break; + case VEML7700_IT_50MS: Serial.println("50"); break; + case VEML7700_IT_100MS: Serial.println("100"); break; + case VEML7700_IT_200MS: Serial.println("200"); break; + case VEML7700_IT_400MS: Serial.println("400"); break; + case VEML7700_IT_800MS: Serial.println("800"); break; + } + + veml.setLowThreshold(10000); + veml.setHighThreshold(20000); + veml.interruptEnable(true); +} + +void loop() { + Serial.print("raw ALS: "); Serial.println(veml.readALS()); + Serial.print("raw white: "); Serial.println(veml.readWhite()); + Serial.print("lux: "); Serial.println(veml.readLux()); + + uint16_t irq = veml.interruptStatus(); + if (irq & VEML7700_INTERRUPT_LOW) { + Serial.println("** Low threshold"); + } + if (irq & VEML7700_INTERRUPT_HIGH) { + Serial.println("** High threshold"); + } + delay(500); +} \ No newline at end of file diff --git a/lib/Adafruit_VEML7700/library.properties b/lib/Adafruit_VEML7700/library.properties new file mode 100644 index 000000000..6d8d60f9e --- /dev/null +++ b/lib/Adafruit_VEML7700/library.properties @@ -0,0 +1,10 @@ +name=Adafruit VEML7700 Library +version=2.1.6 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for the VEML7700 sensors in the Adafruit shop +paragraph=Arduino library for the VEML7700 sensors in the Adafruit shop +category=Sensors +url=https://github.com/adafruit/Adafruit_VEML7700 +architectures=* +depends=Adafruit BusIO, Adafruit SSD1306 diff --git a/lib/Adafruit_VEML7700/license.txt b/lib/Adafruit_VEML7700/license.txt new file mode 100644 index 000000000..f6a0f22b8 --- /dev/null +++ b/lib/Adafruit_VEML7700/license.txt @@ -0,0 +1,26 @@ +Software License Agreement (BSD License) + +Copyright (c) 2012, Adafruit Industries +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/CircularBuffer/CircularBuffer.h b/lib/CircularBuffer/CircularBuffer.h index ed339b44a..088c675c3 100644 --- a/lib/CircularBuffer/CircularBuffer.h +++ b/lib/CircularBuffer/CircularBuffer.h @@ -1,148 +1,150 @@ -/* - CircularBuffer.h - Circular buffer library for Arduino. - Copyright (c) 2017 Roberto Lo Giacco. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ -#ifndef CIRCULAR_BUFFER_H_ -#define CIRCULAR_BUFFER_H_ -#include -#include - -#ifdef CIRCULAR_BUFFER_DEBUG -#include -#endif - -namespace Helper { - template struct Index { - using Type = uint32_t; - }; - - template<> struct Index { - using Type = uint16_t; - }; - - template<> struct Index { - using Type = uint8_t; - }; -} - -template::Type> class CircularBuffer { -public: - /** - * The buffer capacity: read only as it cannot ever change. - */ - static constexpr IT capacity = static_cast(S); - - /** - * Aliases the index type, can be used to obtain the right index type with `decltype(buffer)::index_t`. - */ - using index_t = IT; - - constexpr CircularBuffer(); - - /** - * Disables copy constructor - */ - CircularBuffer(const CircularBuffer&) = delete; - CircularBuffer(CircularBuffer&&) = delete; - - /** - * Disables assignment operator - */ - CircularBuffer& operator=(const CircularBuffer&) = delete; - CircularBuffer& operator=(CircularBuffer&&) = delete; - - /** - * Adds an element to the beginning of buffer: the operation returns `false` if the addition caused overwriting an existing element. - */ - bool unshift(T value); - - /** - * Adds an element to the end of buffer: the operation returns `false` if the addition caused overwriting an existing element. - */ - bool push(T value); - - /** - * Removes an element from the beginning of the buffer. - * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. - */ - T shift(); - - /** - * Removes an element from the end of the buffer. - * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. - */ - T pop(); - - /** - * Returns the element at the beginning of the buffer. - */ - T inline first() const; - - /** - * Returns the element at the end of the buffer. - */ - T inline last() const; - - /** - * Array-like access to buffer. - * Calling this operation using and index value greater than `size - 1` returns the tail element. - * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. - */ - T operator [] (IT index) const; - - /** - * Returns how many elements are actually stored in the buffer. - */ - IT inline size() const; - - /** - * Returns how many elements can be safely pushed into the buffer. - */ - IT inline available() const; - - /** - * Returns `true` if no elements can be removed from the buffer. - */ - bool inline isEmpty() const; - - /** - * Returns `true` if no elements can be added to the buffer without overwriting existing elements. - */ - bool inline isFull() const; - - /** - * Resets the buffer to a clean status, making all buffer positions available. - */ - void inline clear(); - - #ifdef CIRCULAR_BUFFER_DEBUG - void inline debug(Print* out); - void inline debugFn(Print* out, void (*printFunction)(Print*, T)); - #endif - -private: - T buffer[S] = {0}; - T *head; - T *tail; -#ifndef CIRCULAR_BUFFER_INT_SAFE - IT count; -#else - volatile IT count; -#endif -}; - -#include -#endif +/* + CircularBuffer.h - Circular buffer library for Arduino. + Copyright (c) 2017 Roberto Lo Giacco. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +#ifndef CIRCULAR_BUFFER_H_ +#define CIRCULAR_BUFFER_H_ +#include +#include + +#ifdef CIRCULAR_BUFFER_DEBUG +#include +#endif + +namespace Helper { + template struct Index { + using Type = uint32_t; + }; + + template<> struct Index { + using Type = uint16_t; + }; + + template<> struct Index { + using Type = uint8_t; + }; +} + +template::Type> class CircularBuffer { +public: + /** + * The buffer capacity: read only as it cannot ever change. + */ + static constexpr IT capacity = static_cast(S); + + /** + * Aliases the index type, can be used to obtain the right index type with `decltype(buffer)::index_t`. + */ + using index_t = IT; + + constexpr CircularBuffer(); + + /** + * Disables copy constructor + */ + CircularBuffer(const CircularBuffer&) = delete; + CircularBuffer(CircularBuffer&&) = delete; + + /** + * Disables assignment operator + */ + CircularBuffer& operator=(const CircularBuffer&) = delete; + CircularBuffer& operator=(CircularBuffer&&) = delete; + + /** + * Adds an element to the beginning of buffer: the operation returns `false` if the addition caused overwriting an existing element. + */ + bool unshift(T value); + + /** + * Adds an element to the end of buffer: the operation returns `false` if the addition caused overwriting an existing element. + */ + bool push(T value); + + bool set(IT index, T value); + + /** + * Removes an element from the beginning of the buffer. + * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. + */ + T shift(); + + /** + * Removes an element from the end of the buffer. + * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. + */ + T pop(); + + /** + * Returns the element at the beginning of the buffer. + */ + T inline first() const; + + /** + * Returns the element at the end of the buffer. + */ + T inline last() const; + + /** + * Array-like access to buffer. + * Calling this operation using and index value greater than `size - 1` returns the tail element. + * *WARNING* Calling this operation on an empty buffer has an unpredictable behaviour. + */ + T operator [] (IT index) const; + + /** + * Returns how many elements are actually stored in the buffer. + */ + IT inline size() const; + + /** + * Returns how many elements can be safely pushed into the buffer. + */ + IT inline available() const; + + /** + * Returns `true` if no elements can be removed from the buffer. + */ + bool inline isEmpty() const; + + /** + * Returns `true` if no elements can be added to the buffer without overwriting existing elements. + */ + bool inline isFull() const; + + /** + * Resets the buffer to a clean status, making all buffer positions available. + */ + void inline clear(); + + #ifdef CIRCULAR_BUFFER_DEBUG + void inline debug(Print* out); + void inline debugFn(Print* out, void (*printFunction)(Print*, T)); + #endif + +private: + T buffer[S] = {0}; + T *head; + T *tail; +#ifndef CIRCULAR_BUFFER_INT_SAFE + IT count; +#else + volatile IT count; +#endif +}; + +#include +#endif diff --git a/lib/CircularBuffer/CircularBuffer.tpp b/lib/CircularBuffer/CircularBuffer.tpp index 0d214a48d..db832f137 100644 --- a/lib/CircularBuffer/CircularBuffer.tpp +++ b/lib/CircularBuffer/CircularBuffer.tpp @@ -1,163 +1,172 @@ -/* - CircularBuffer.tpp - Circular buffer library for Arduino. - Copyright (c) 2017 Roberto Lo Giacco. - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - */ - -template -constexpr CircularBuffer::CircularBuffer() : - head(buffer), tail(buffer), count(0) { -} - -template -bool CircularBuffer::unshift(T value) { - if (head == buffer) { - head = buffer + capacity; - } - *--head = value; - if (count == capacity) { - if (tail-- == buffer) { - tail = buffer + capacity - 1; - } - return false; - } else { - if (count++ == 0) { - tail = head; - } - return true; - } -} - -template -bool CircularBuffer::push(T value) { - if (++tail == buffer + capacity) { - tail = buffer; - } - *tail = value; - if (count == capacity) { - if (++head == buffer + capacity) { - head = buffer; - } - return false; - } else { - if (count++ == 0) { - head = tail; - } - return true; - } -} - -template -T CircularBuffer::shift() { - if (count == 0) return *head; - T result = *head++; - if (head >= buffer + capacity) { - head = buffer; - } - count--; - return result; -} - -template -T CircularBuffer::pop() { - if (count == 0) return *tail; - T result = *tail--; - if (tail < buffer) { - tail = buffer + capacity - 1; - } - count--; - return result; -} - -template -T inline CircularBuffer::first() const { - return *head; -} - -template -T inline CircularBuffer::last() const { - return *tail; -} - -template -T CircularBuffer::operator [](IT index) const { - if (index >= count) return *tail; - return *(buffer + ((head - buffer + index) % capacity)); -} - -template -IT inline CircularBuffer::size() const { - return count; -} - -template -IT inline CircularBuffer::available() const { - return capacity - count; -} - -template -bool inline CircularBuffer::isEmpty() const { - return count == 0; -} - -template -bool inline CircularBuffer::isFull() const { - return count == capacity; -} - -template -void inline CircularBuffer::clear() { - head = tail = buffer; - count = 0; -} - -#ifdef CIRCULAR_BUFFER_DEBUG -#include -template -void inline CircularBuffer::debug(Print* out) { - for (IT i = 0; i < capacity; i++) { - int hex = (int)buffer + i; - out->print("["); - out->print(hex, HEX); - out->print("] "); - out->print(*(buffer + i)); - if (head == buffer + i) { - out->print("<-head"); - } - if (tail == buffer + i) { - out->print("<-tail"); - } - out->println(); - } -} - -template -void inline CircularBuffer::debugFn(Print* out, void (*printFunction)(Print*, T)) { - for (IT i = 0; i < capacity; i++) { - int hex = (int)buffer + i; - out->print("["); - out->print(hex, HEX); - out->print("] "); - printFunction(out, *(buffer + i)); - if (head == buffer + i) { - out->print("<-head"); - } - if (tail == buffer + i) { - out->print("<-tail"); - } - out->println(); - } -} -#endif +/* + CircularBuffer.tpp - Circular buffer library for Arduino. + Copyright (c) 2017 Roberto Lo Giacco. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation, either version 3 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +template +constexpr CircularBuffer::CircularBuffer() : + head(buffer), tail(buffer), count(0) { +} + +template +bool CircularBuffer::unshift(T value) { + if (head == buffer) { + head = buffer + capacity; + } + *--head = value; + if (count == capacity) { + if (tail-- == buffer) { + tail = buffer + capacity - 1; + } + return false; + } else { + if (count++ == 0) { + tail = head; + } + return true; + } +} + +template +bool CircularBuffer::push(T value) { + if (++tail == buffer + capacity) { + tail = buffer; + } + *tail = value; + if (count == capacity) { + if (++head == buffer + capacity) { + head = buffer; + } + return false; + } else { + if (count++ == 0) { + head = tail; + } + return true; + } +} + +template +bool CircularBuffer::set(IT index, T value) { + if (index >= count) return false; + + *(buffer + ((head - buffer + index) % capacity)) = value; + return true; +} + + +template +T CircularBuffer::shift() { + if (count == 0) return *head; + T result = *head++; + if (head >= buffer + capacity) { + head = buffer; + } + count--; + return result; +} + +template +T CircularBuffer::pop() { + if (count == 0) return *tail; + T result = *tail--; + if (tail < buffer) { + tail = buffer + capacity - 1; + } + count--; + return result; +} + +template +T inline CircularBuffer::first() const { + return *head; +} + +template +T inline CircularBuffer::last() const { + return *tail; +} + +template +T CircularBuffer::operator [](IT index) const { + if (index >= count) return *tail; + return *(buffer + ((head - buffer + index) % capacity)); +} + +template +IT inline CircularBuffer::size() const { + return count; +} + +template +IT inline CircularBuffer::available() const { + return capacity - count; +} + +template +bool inline CircularBuffer::isEmpty() const { + return count == 0; +} + +template +bool inline CircularBuffer::isFull() const { + return count == capacity; +} + +template +void inline CircularBuffer::clear() { + head = tail = buffer; + count = 0; +} + +#ifdef CIRCULAR_BUFFER_DEBUG +#include +template +void inline CircularBuffer::debug(Print* out) { + for (IT i = 0; i < capacity; i++) { + int hex = (int)buffer + i; + out->print("["); + out->print(hex, HEX); + out->print("] "); + out->print(*(buffer + i)); + if (head == buffer + i) { + out->print("<-head"); + } + if (tail == buffer + i) { + out->print("<-tail"); + } + out->println(); + } +} + +template +void inline CircularBuffer::debugFn(Print* out, void (*printFunction)(Print*, T)) { + for (IT i = 0; i < capacity; i++) { + int hex = (int)buffer + i; + out->print("["); + out->print(hex, HEX); + out->print("] "); + printFunction(out, *(buffer + i)); + if (head == buffer + i) { + out->print("<-head"); + } + if (tail == buffer + i) { + out->print("<-tail"); + } + out->println(); + } +} +#endif diff --git a/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.cpp b/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.cpp new file mode 100644 index 000000000..b11dd6d24 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.cpp @@ -0,0 +1,576 @@ +/*! + * @file DFRobot_GP8403.cpp + * @brief This is a method implementation file for the DAC module. + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-2-18 + * @url https://github.com/DFRobot/DFRobot_GP8403 + */ +#include "DFRobot_GP8403.h" +#ifdef GP8403_SINE_WAVE_ENABLED +const PROGMEM uint16_t DACLookup_FullSine_5Bit[32] = +{ + 2048, 2447, 2831, 3185, 3495, 3750, 3939, 4056, + 4095, 4056, 3939, 3750, 3495, 3185, 2831, 2447, + 2048, 1648, 1264, 910, 600, 345, 156, 39, + 0, 39, 156, 345, 600, 910, 1264, 1648 +}; + +const PROGMEM uint16_t DACLookup_FullSine_6Bit[64] = +{ + 2048, 2248, 2447, 2642, 2831, 3013, 3185, 3346, + 3495, 3630, 3750, 3853, 3939, 4007, 4056, 4085, + 4095, 4085, 4056, 4007, 3939, 3853, 3750, 3630, + 3495, 3346, 3185, 3013, 2831, 2642, 2447, 2248, + 2048, 1847, 1648, 1453, 1264, 1082, 910, 749, + 600, 465, 345, 242, 156, 88, 39, 10, + 0, 10, 39, 88, 156, 242, 345, 465, + 600, 749, 910, 1082, 1264, 1453, 1648, 1847 +}; + +const PROGMEM uint16_t DACLookup_FullSine_7Bit[128] = +{ + 2048, 2148, 2248, 2348, 2447, 2545, 2642, 2737, + 2831, 2923, 3013, 3100, 3185, 3267, 3346, 3423, + 3495, 3565, 3630, 3692, 3750, 3804, 3853, 3898, + 3939, 3975, 4007, 4034, 4056, 4073, 4085, 4093, + 4095, 4093, 4085, 4073, 4056, 4034, 4007, 3975, + 3939, 3898, 3853, 3804, 3750, 3692, 3630, 3565, + 3495, 3423, 3346, 3267, 3185, 3100, 3013, 2923, + 2831, 2737, 2642, 2545, 2447, 2348, 2248, 2148, + 2048, 1947, 1847, 1747, 1648, 1550, 1453, 1358, + 1264, 1172, 1082, 995, 910, 828, 749, 672, + 600, 530, 465, 403, 345, 291, 242, 197, + 156, 120, 88, 61, 39, 22, 10, 2, + 0, 2, 10, 22, 39, 61, 88, 120, + 156, 197, 242, 291, 345, 403, 465, 530, + 600, 672, 749, 828, 910, 995, 1082, 1172, + 1264, 1358, 1453, 1550, 1648, 1747, 1847, 1947 +}; + +const PROGMEM uint16_t DACLookup_FullSine_8Bit[256] = +{ + 2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, + 2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784, + 2831, 2877, 2923, 2968, 3013, 3057, 3100, 3143, + 3185, 3226, 3267, 3307, 3346, 3385, 3423, 3459, + 3495, 3530, 3565, 3598, 3630, 3662, 3692, 3722, + 3750, 3777, 3804, 3829, 3853, 3876, 3898, 3919, + 3939, 3958, 3975, 3992, 4007, 4021, 4034, 4045, + 4056, 4065, 4073, 4080, 4085, 4089, 4093, 4094, + 4095, 4094, 4093, 4089, 4085, 4080, 4073, 4065, + 4056, 4045, 4034, 4021, 4007, 3992, 3975, 3958, + 3939, 3919, 3898, 3876, 3853, 3829, 3804, 3777, + 3750, 3722, 3692, 3662, 3630, 3598, 3565, 3530, + 3495, 3459, 3423, 3385, 3346, 3307, 3267, 3226, + 3185, 3143, 3100, 3057, 3013, 2968, 2923, 2877, + 2831, 2784, 2737, 2690, 2642, 2594, 2545, 2496, + 2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, + 2048, 1997, 1947, 1897, 1847, 1797, 1747, 1697, + 1648, 1599, 1550, 1501, 1453, 1405, 1358, 1311, + 1264, 1218, 1172, 1127, 1082, 1038, 995, 952, + 910, 869, 828, 788, 749, 710, 672, 636, + 600, 565, 530, 497, 465, 433, 403, 373, + 345, 318, 291, 266, 242, 219, 197, 176, + 156, 137, 120, 103, 88, 74, 61, 50, + 39, 30, 22, 15, 10, 6, 2, 1, + 0, 1, 2, 6, 10, 15, 22, 30, + 39, 50, 61, 74, 88, 103, 120, 137, + 156, 176, 197, 219, 242, 266, 291, 318, + 345, 373, 403, 433, 465, 497, 530, 565, + 600, 636, 672, 710, 749, 788, 828, 869, + 910, 952, 995, 1038, 1082, 1127, 1172, 1218, + 1264, 1311, 1358, 1405, 1453, 1501, 1550, 1599, + 1648, 1697, 1747, 1797, 1847, 1897, 1947, 1997 +}; + +const PROGMEM uint16_t DACLookup_FullSine_9Bit[512] = +{ + 2048, 2073, 2098, 2123, 2148, 2174, 2199, 2224, + 2249, 2274, 2299, 2324, 2349, 2373, 2398, 2423, + 2448, 2472, 2497, 2521, 2546, 2570, 2594, 2618, + 2643, 2667, 2690, 2714, 2738, 2762, 2785, 2808, + 2832, 2855, 2878, 2901, 2924, 2946, 2969, 2991, + 3013, 3036, 3057, 3079, 3101, 3122, 3144, 3165, + 3186, 3207, 3227, 3248, 3268, 3288, 3308, 3328, + 3347, 3367, 3386, 3405, 3423, 3442, 3460, 3478, + 3496, 3514, 3531, 3548, 3565, 3582, 3599, 3615, + 3631, 3647, 3663, 3678, 3693, 3708, 3722, 3737, + 3751, 3765, 3778, 3792, 3805, 3817, 3830, 3842, + 3854, 3866, 3877, 3888, 3899, 3910, 3920, 3930, + 3940, 3950, 3959, 3968, 3976, 3985, 3993, 4000, + 4008, 4015, 4022, 4028, 4035, 4041, 4046, 4052, + 4057, 4061, 4066, 4070, 4074, 4077, 4081, 4084, + 4086, 4088, 4090, 4092, 4094, 4095, 4095, 4095, + 4095, 4095, 4095, 4095, 4094, 4092, 4090, 4088, + 4086, 4084, 4081, 4077, 4074, 4070, 4066, 4061, + 4057, 4052, 4046, 4041, 4035, 4028, 4022, 4015, + 4008, 4000, 3993, 3985, 3976, 3968, 3959, 3950, + 3940, 3930, 3920, 3910, 3899, 3888, 3877, 3866, + 3854, 3842, 3830, 3817, 3805, 3792, 3778, 3765, + 3751, 3737, 3722, 3708, 3693, 3678, 3663, 3647, + 3631, 3615, 3599, 3582, 3565, 3548, 3531, 3514, + 3496, 3478, 3460, 3442, 3423, 3405, 3386, 3367, + 3347, 3328, 3308, 3288, 3268, 3248, 3227, 3207, + 3186, 3165, 3144, 3122, 3101, 3079, 3057, 3036, + 3013, 2991, 2969, 2946, 2924, 2901, 2878, 2855, + 2832, 2808, 2785, 2762, 2738, 2714, 2690, 2667, + 2643, 2618, 2594, 2570, 2546, 2521, 2497, 2472, + 2448, 2423, 2398, 2373, 2349, 2324, 2299, 2274, + 2249, 2224, 2199, 2174, 2148, 2123, 2098, 2073, + 2048, 2023, 1998, 1973, 1948, 1922, 1897, 1872, + 1847, 1822, 1797, 1772, 1747, 1723, 1698, 1673, + 1648, 1624, 1599, 1575, 1550, 1526, 1502, 1478, + 1453, 1429, 1406, 1382, 1358, 1334, 1311, 1288, + 1264, 1241, 1218, 1195, 1172, 1150, 1127, 1105, + 1083, 1060, 1039, 1017, 995, 974, 952, 931, + 910, 889, 869, 848, 828, 808, 788, 768, + 749, 729, 710, 691, 673, 654, 636, 618, + 600, 582, 565, 548, 531, 514, 497, 481, + 465, 449, 433, 418, 403, 388, 374, 359, + 345, 331, 318, 304, 291, 279, 266, 254, + 242, 230, 219, 208, 197, 186, 176, 166, + 156, 146, 137, 128, 120, 111, 103, 96, + 88, 81, 74, 68, 61, 55, 50, 44, + 39, 35, 30, 26, 22, 19, 15, 12, + 10, 8, 6, 4, 2, 1, 1, 0, + 0, 0, 1, 1, 2, 4, 6, 8, + 10, 12, 15, 19, 22, 26, 30, 35, + 39, 44, 50, 55, 61, 68, 74, 81, + 88, 96, 103, 111, 120, 128, 137, 146, + 156, 166, 176, 186, 197, 208, 219, 230, + 242, 254, 266, 279, 291, 304, 318, 331, + 345, 359, 374, 388, 403, 418, 433, 449, + 465, 481, 497, 514, 531, 548, 565, 582, + 600, 618, 636, 654, 673, 691, 710, 729, + 749, 768, 788, 808, 828, 848, 869, 889, + 910, 931, 952, 974, 995, 1017, 1039, 1060, + 1083, 1105, 1127, 1150, 1172, 1195, 1218, 1241, + 1264, 1288, 1311, 1334, 1358, 1382, 1406, 1429, + 1453, 1478, 1502, 1526, 1550, 1575, 1599, 1624, + 1648, 1673, 1698, 1723, 1747, 1772, 1797, 1822, + 1847, 1872, 1897, 1922, 1948, 1973, 1998, 2023 +}; +#endif // ifdef GP8403_SINE_WAVE_ENABLED + +#ifdef GP8403_STORE_ENABLED +#define GP8302_STORE_TIMING_HEAD 0x02 ///< Store function timing start head +#define GP8302_STORE_TIMING_ADDR 0x10 ///< The first address for entering store timing +#define GP8302_STORE_TIMING_CMD1 0x03 ///< The command 1 to enter store timing +#define GP8302_STORE_TIMING_CMD2 0x00 ///< The command 2 to enter store timing +#define GP8302_STORE_TIMING_DELAY 10 ///< Store procedure interval delay time: 10ms, more than 7ms +#define GP8302_STORE_TIMING_DELAY 10 ///< Store procedure interval delay time: 10ms, more than 7ms +#define I2C_CYCLE_TOTAL 5 ///< Total I2C communication cycle +#define I2C_CYCLE_BEFORE 1 ///< The first half cycle 2 of the total I2C communication cycle +#define I2C_CYCLE_AFTER 2 ///< The second half cycle 3 of the total I2C communication cycle +#endif // ifdef GP8403_STORE_ENABLED + +DFRobot_GP8403::DFRobot_GP8403(TwoWire *pWire,uint8_t addr) +{ + _pWire = pWire; + _addr = addr; +} + +uint8_t DFRobot_GP8403::begin(void) +{ + // _pWire->begin(); // I2C bus is already initialized correctly by ESPEasy core + // _pWire->setClock(400000); + _pWire->beginTransmission(_addr); + _pWire->write(OUTPUT_RANGE); + if(_pWire->endTransmission() != 0) + return 1; + return 0; +} + +void DFRobot_GP8403::setDACOutRange(eOutPutRange_t range) +{ + if(range == eOutPutRange_t::eOutputRange5V) + { + voltage = 5000; + }else{ + voltage = 10000; + } + writeReg(OUTPUT_RANGE,&range,1); +} + +void DFRobot_GP8403::setDACOutVoltage(uint16_t data, uint8_t channel) +{ + uint16_t dataTransmission = (uint16_t)(((float)data / voltage) * 4095); + DBG(dataTransmission); + dataTransmission = dataTransmission << 4; + sendData(dataTransmission,channel); +} + +#ifdef GP8403_SINE_WAVE_ENABLED +void DFRobot_GP8403::outputSin(uint16_t amp, uint16_t freq, uint16_t offset,uint8_t channel) +{ + uint64_t starttime; + uint64_t stoptime; + uint64_t looptime; + uint64_t frame; + uint16_t num=512; + int16_t data = 0; + #ifdef TWBR + uint8_t twbrback = TWBR; + TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz + #endif + if(freq < 8){ + num = 512; + }else if( 8 <= freq && freq <= 16){ + num = 256; + }else if(16 < freq && freq < 33){ + num = 128; + }else if(33 <= freq && freq <= 68 ){ + num = 64; + }else{ + num = 32; + } + if(freq > 100){ + freq = 100; + } + frame = 1000000/(freq*num); + for(uint16_t i=0;i= 4095){ + data=4095; + } + + data = data << 4; + sendData(data,channel); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } + #ifdef TWBR + TWBR = twbrback; + #endif +} +#endif // ifdef GP8403_SINE_WAVE_ENABLED + +#ifdef GP8403_TRIANGLE_WAVE_ENABLED +void DFRobot_GP8403::outputTriangle(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle,uint8_t channel) +{ + uint64_t starttime; + uint64_t stoptime; + uint64_t looptime; + uint64_t frame; + uint16_t num = 64; + uint16_t up_num; + uint16_t down_num; + uint16_t maxV; + maxV=amp*(4096/(float)voltage); + if(freq > 100){ + num = 16; + }else if(50 <= freq && freq <= 100){ + num = 32; + }else{ + num = 64; + } + frame = 1000000/(freq*num*2); + if(dutyCycle>100){ + dutyCycle = 100; + } + if(dutyCycle<0){ + dutyCycle=0; + } + up_num = (2*num)*((float)dutyCycle/100); + down_num = ((2*num) - up_num); +#ifdef TWBR + uint8_t twbrback = TWBR; + TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz +#endif + uint16_t counter; + int16_t enterV; + + for (counter = 0; counter < (maxV-(maxV/up_num)-1); counter+=(maxV/up_num)){ + starttime = micros(); + enterV=counter+(offset*(4096/(float)voltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + enterV = enterV << 4; + sendData(enterV,channel); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } + for (counter = maxV-1; counter > (maxV/down_num); counter-=(maxV/down_num)){ + starttime = micros(); + enterV=counter+(offset*(4096/(float)voltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + enterV = enterV << 4; + sendData(enterV,channel); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } +#ifdef TWBR + TWBR = twbrback; +#endif +} +#endif // ifdef GP8403_TRIANGLE_WAVE_ENABLED + +#ifdef GP8403_SQUARE_WAVE_ENABLED +void DFRobot_GP8403::outputSquare(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle, uint8_t channel) +{ + uint64_t starttime; + uint64_t stoptime; + uint64_t looptime; + uint64_t frame; + uint16_t num = 64; + uint16_t up_num; + uint16_t down_num; + uint16_t data; + data=amp*(4096/(float)voltage); + if(freq > 100){ + num = 16; + }else if(50 <= freq && freq <= 100){ + num = 32; + }else{ + num = 64; + } + frame = 1000000/(freq*num*2); + if(dutyCycle>100){ + dutyCycle = 100; + } + if(dutyCycle<0){ + dutyCycle=0; + } + up_num = (2*num)*((float)dutyCycle/100);//64 + down_num = ((2*num) - up_num);//64 +#ifdef TWBR + uint8_t twbrback = TWBR; + TWBR = ((F_CPU / 400000L) - 16) / 2; // Set I2C frequency to 400kHz +#endif + uint16_t counter; + int16_t enterV; + + for (counter = 0; counter < up_num; counter++){ + starttime = micros(); + enterV=data+(offset*(4096/(float)voltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + enterV = enterV << 4; + sendData(enterV,channel); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } + for (counter=0;counter < down_num; counter++){ + starttime = micros(); + enterV=data-(offset*(4096/(float)voltage)); + if(enterV > 4095){ + enterV = 4095; + }else if(enterV < 0){ + enterV = 0; + } + enterV = enterV << 4; + sendData(enterV,channel); + stoptime = micros(); + looptime = stoptime-starttime; + while(looptime <= frame){ + stoptime = micros(); + looptime = stoptime-starttime; + } + } +#ifdef TWBR + TWBR = twbrback; +#endif + +} +#endif // ifdef GP8403_SQUARE_WAVE_ENABLED + +void DFRobot_GP8403::sendData(uint16_t data, uint8_t channel) +{ + if(channel == 0){ + _pWire->beginTransmission(_addr); + _pWire->write(GP8302_CONFIG_CURRENT_REG); + _pWire->write(data & 0xff); + _pWire->write((data >>8) & 0xff); + _pWire->endTransmission(); + DBG(channel); + }else if(channel == 1){ + _pWire->beginTransmission(_addr); + _pWire->write(GP8302_CONFIG_CURRENT_REG<<1); + _pWire->write(data & 0xff); + _pWire->write((data >>8) & 0xff); + _pWire->endTransmission(); + DBG(channel); + }else{ + _pWire->beginTransmission(_addr); + _pWire->write(GP8302_CONFIG_CURRENT_REG); + _pWire->write(data & 0xff); + _pWire->write((data >>8) & 0xff); + _pWire->write(data & 0xff); + _pWire->write((data >>8) & 0xff); + _pWire->endTransmission(); + DBG(channel); + } +} + +#ifdef GP8403_STORE_ENABLED +void DFRobot_GP8403::store(){ + #if defined(ESP32) + _pWire->~TwoWire(); + #elif !defined(ESP8266) + _pWire->end(); + #endif + pinMode(_scl, OUTPUT); + pinMode(_sda, OUTPUT); + digitalWrite(_scl, HIGH); + digitalWrite(_sda, HIGH); + startSignal(); + sendByte(GP8302_STORE_TIMING_HEAD, 0, 3, false); + stopSignal(); + startSignal(); + sendByte(GP8302_STORE_TIMING_ADDR); + sendByte(GP8302_STORE_TIMING_CMD1); + stopSignal(); + + startSignal(); + sendByte(_addr<<1, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + sendByte(GP8302_STORE_TIMING_CMD2, 1); + stopSignal(); + + delay(GP8302_STORE_TIMING_DELAY); + + startSignal(); + sendByte(GP8302_STORE_TIMING_HEAD, 0, 3, false); + stopSignal(); + startSignal(); + sendByte(GP8302_STORE_TIMING_ADDR); + sendByte(GP8302_STORE_TIMING_CMD2); + stopSignal(); + _pWire->begin(); +} + +void DFRobot_GP8403::startSignal(void){ + digitalWrite(_scl,HIGH); + digitalWrite(_sda,HIGH); + delayMicroseconds(I2C_CYCLE_BEFORE); + digitalWrite(_sda,LOW); + delayMicroseconds(I2C_CYCLE_AFTER); + digitalWrite(_scl,LOW); + delayMicroseconds(I2C_CYCLE_TOTAL); +} + +void DFRobot_GP8403::stopSignal(void){ + digitalWrite(_sda,LOW); + delayMicroseconds(I2C_CYCLE_BEFORE); + digitalWrite(_scl,HIGH); + delayMicroseconds(I2C_CYCLE_TOTAL); + digitalWrite(_sda,HIGH); + delayMicroseconds(I2C_CYCLE_TOTAL); +} + +uint8_t DFRobot_GP8403::sendByte(uint8_t data, uint8_t ack, uint8_t bits, bool flag){ + for(int i=bits-1; i>=0;i--){ + if(data & (1< 250) break; + } + ack_=digitalRead(_sda); + delayMicroseconds(I2C_CYCLE_BEFORE); + digitalWrite(_scl,LOW); + delayMicroseconds(I2C_CYCLE_AFTER); + pinMode(_sda,OUTPUT); + return ack_; +} +#endif // ifdef GP8403_STORE_ENABLED + +void DFRobot_GP8403::writeReg(uint8_t reg, void *pBuf, size_t size) +{ + uint8_t *_pBuf = (uint8_t*)pBuf; + _pWire->beginTransmission(_addr); + _pWire->write(reg); + + for(size_t i = 0; i < size; i++){ + _pWire->write(_pBuf[i]); + } + _pWire->endTransmission(); +} diff --git a/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.h b/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.h new file mode 100644 index 000000000..bdbb92e5a --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/DFRobot_GP8403.h @@ -0,0 +1,146 @@ +/*! + * @file DFRobot_GP8403.h + * @brief This is a method description file for the DAC module + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-03-07 + * @url https://github.com/DFRobot/DFRobot_Microphone + * + * 2024-01-21 tonhuisman: Make Sine wave, Triangle wave and Square wave functions optional, for size and timing issues + * Make store() feature optional, as that is absolutely NOT I2C friendly or compatible + */ +#ifndef _DFROBOT_GP8403_H_ +#define _DFROBOT_GP8403_H + +#include "Arduino.h" +#include "Wire.h" + +// #define GP8403_SINE_WAVE_ENABLED // Optionally enable Sine wave support +// #define GP8403_TRIANGLE_WAVE_ENABLED // Optionally enable Triangle wave support +// #define GP8403_SQUARE_WAVE_ENABLED // Optionally enable Square wave support +// #define GP8403_STORE_ENABLED // Optionally enable storing the current value in the device (!!! *** _NOT_ I2C Compatible *** !!!) + +// #define ENABLE_DBG //!< Open the macro and you can see the detailed procedure of the program +#ifdef ENABLE_DBG +#define DBG(...) {Serial.print("[");Serial.print(__FUNCTION__); Serial.print("(): "); Serial.print(__LINE__); Serial.print(" ] "); Serial.println(__VA_ARGS__);} +#else +#define DBG(...) +#endif + +#define GP8302_CONFIG_CURRENT_REG 0x02 +#define OUTPUT_RANGE 0x01 + +class DFRobot_GP8403 +{ +public: + /** + * @enum eOutPutRange_t + * @brief Analog voltage output range select + */ + enum class eOutPutRange_t : uint8_t{ + eOutputRange5V = 0x00, + eOutputRange10V = 0x11, + } ; + /** + * @brief DFRobot_GP8403 constructor + * @param pWire I2C object + * @param addr I2C address + */ + DFRobot_GP8403(TwoWire *pWire = &Wire,uint8_t addr = 0x58); + /** + * @fn begin + * @brief Initialize the module + */ + uint8_t begin(void); + + /** + * @fn setDACOutRange + * @brief Set DAC output range + * @param range DAC output range + * @return NONE + */ + void setDACOutRange(eOutPutRange_t range); + + /** + * @fn setDACOutVoltage + * @brief Set output DAC voltage of different channels + * @param data The voltage value to be output + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + * @return NONE + */ + void setDACOutVoltage(uint16_t data,uint8_t channel); + + #ifdef GP8403_STORE_ENABLED + /** + * @brief Save the set voltage in the chip + */ + void store(void); + #endif // ifdef GP8403_STORE_ENABLED + + #ifdef GP8403_SINE_WAVE_ENABLED + /** + * @brief Call the function to output sine wave + * @param amp Set sine wave amplitude Vp + * @param freq Set sine wave frequency f + * @param offset Set sine wave DC offset Voffset + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputSin(uint16_t amp, uint16_t freq, uint16_t offset,uint8_t channel); + #endif // ifdef GP8403_SINE_WAVE_ENABLED + + #ifdef GP8403_TRIANGLE_WAVE_ENABLED + /** + * @brief Call the function to output triangle wave + * @param amp Set triangle wave amplitude Vp + * @param freq Set triangle wave frequency f + * @param offset Set triangle wave DC offset Voffset + * @param dutyCycle Set triangle (sawtooth) wave duty cycle + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputTriangle(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle, uint8_t channel); + #endif // ifdef GP8403_TRIANGLE_WAVE_ENABLED + + #ifdef GP8403_SQUARE_WAVE_ENABLED + /** + * @brief Call the function to output square wave + * @param amp Set square wave amplitude Vp + * @param freq Set square wave frequency f + * @param offset Set square wave DC offset Voffset + * @param dutyCycle Set square wave duty cycle + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputSquare(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle, uint8_t channel); + #endif // ifdef GP8403_SQUARE_WAVE_ENABLED + +#ifdef GP8403_STORE_ENABLED +protected: + void startSignal(void); + void stopSignal(void); + uint8_t recvAck(uint8_t ack); + uint8_t sendByte(uint8_t data, uint8_t ack = 0, uint8_t bits = 8, bool flag = true); +#endif // ifdef GP8403_STORE_ENABLED + +private: + /** + * @fn writeReg + * @brief Write register value through IIC bus + * @param reg Register address 8bits + * @param pBuf Storage cache to write data in + * @param size The length of data to be written + */ + void writeReg(uint8_t reg, void *pBuf, size_t size); + TwoWire *_pWire; + uint8_t _addr; + uint16_t voltage = 0; + #ifdef GP8403_STORE_ENABLED + int _scl= SCL; + int _sda = SDA; + #endif // ifdef GP8403_STORE_ENABLED + void sendData(uint16_t data, uint8_t channel); +}; + + + +#endif diff --git a/lib/DFRobot_GP8403_ESPEasy/LICENSE b/lib/DFRobot_GP8403_ESPEasy/LICENSE new file mode 100644 index 000000000..79f310082 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/LICENSE @@ -0,0 +1,7 @@ +Copyright 2010 DFRobot Co.Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/DFRobot_GP8403_ESPEasy/README.md b/lib/DFRobot_GP8403_ESPEasy/README.md new file mode 100644 index 000000000..a22709f62 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/README.md @@ -0,0 +1,109 @@ +# DFRobot_GP8403 + +Update: port for Raspberry Pi Pico in Python in RP2 folder. + +This I2C to 0-5V/0-10V DAC module can be used to output voltage of 0-5V or 0-10V. It has the following features: +1. Output voltage of 0-5V or 0-10V. +2. It can control the output voltage with an I2C interface, the I2C address is default to be 0x58. +3. The output voltage config will be lost after the module is powered down. Save the config if you want to use it for the next power-up. + + +## Product Link([www.dfrobot.com](www.dfrobot.com)) + SKU: DFR0971 + +## Table of Contents + - [Summary](#summary) + - [Installation](#installation) + - [Methods](#methods) + - [Compatibility](#compatibility) + - [History](#history) + - [Credits](#credits) + +## Summary +The Arduino library is provided for the I2C 0-5V/0-10V DAC module to set and save the output voltage config of the module. And the library has the following functions: +1. Set the voltage of 0-5V or 0-10V directly; +2. Output the corresponding voltage by setting the DAC range of 0-0xFFF; +3. Save the voltage config(Will not be lost when powered down). + +## Installation + +There two methods: +1. To use this library, first download the library file, paste it into the \Arduino\libraries directory, then open the examples folder and run the demo in the folder. +2. Search the DFRobot_GP8302 library from the Arduino Software Library Manager and download it. + +## Methods + +```C++ + /** + * @fn begin + * @brief Initialize the module + */ + uint8_t begin(void); + + /** + * @fn setDACOutRange + * @brief Set DAC output range + * @param range DAC output range + * @return NONE + */ + void setDACOutRange(eOutPutRange range); + + /** + * @fn setDACOutVoltage + * @brief Set output DAC voltage of different channels + * @param data The voltage value to be output + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + * @return NONE + */ + void setDACOutVoltage(uint16_t data,uint8_t channel); + /** + * @brief Save the set voltage inside the chip + */ + void store(void); + /** + * @brief Call the function to output sine wave + * @param amp Set sine wave amplitude Vp + * @param freq Set sine wave frequency f + * @param offset Set sine wave DC offset Voffset + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputSin(uint16_t amp, uint16_t freq, uint16_t offset,uint8_t channel); + /** + * @brief Call the function to output triangle wave + * @param amp Set triangle wave amplitude Vp + * @param freq Set triangle wave frequency f + * @param offset Set triangle wave DC offset Voffset + * @param dutyCycle Set triangle (sawtooth) wave duty cycle + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputTriangle(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle, uint8_t channel); + /** + * @brief Call the function to output square wave + * @param amp Set square wave amplitude Vp + * @param freq Set square wave frequency f + * @param offset Set square wave DC offset Voffset + * @param dutyCycle Set square wave duty cycle + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + void outputSquare(uint16_t amp, uint16_t freq, uint16_t offset, int8_t dutyCycle, uint8_t channel); +``` +## Compatibility + +MCU | Work Well | Work Wrong | Untested | Remarks +------------------ | :----------: | :----------: | :---------: | ----- +Arduino Uno | √ | | | +Mega2560 | √ | | | +Leonardo | √ | | | +ESP32 | √ | | | +ESP8266 | √ | | | +micro:bit | √ | | | +FireBeetle M0 | √ | | | + +## History + +- 2022/03/10 - Version 1.0.0 released. + +## Credits + +Written by tangjie(jie.tang@dfrobot.com), 2022. (Welcome to our [website](https://www.dfrobot.com/)) + diff --git a/lib/DFRobot_GP8403_ESPEasy/docs/GP8403-Datasheet.pdf b/lib/DFRobot_GP8403_ESPEasy/docs/GP8403-Datasheet.pdf new file mode 100644 index 000000000..2a8da986c Binary files /dev/null and b/lib/DFRobot_GP8403_ESPEasy/docs/GP8403-Datasheet.pdf differ diff --git a/lib/DFRobot_GP8403_ESPEasy/examples/outputData/outputData.ino b/lib/DFRobot_GP8403_ESPEasy/examples/outputData/outputData.ino new file mode 100644 index 000000000..432c810dc --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/examples/outputData/outputData.ino @@ -0,0 +1,32 @@ +/*! + * @file outputData.ino + * @brief A use example for DAC, execute it to output different values from different channels. + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-03-07 + * @url https://github.com/DFRobot/DFRobot_GP8403 + */ + +#include "DFRobot_GP8403.h" +DFRobot_GP8403 dac(&Wire,0x58); + +void setup() { + Serial.begin(115200); + while(dac.begin()!=0){ + Serial.println("init error"); + delay(1000); + } + Serial.println("init succeed"); + //Set DAC output range + dac.setDACOutRange(dac.eOutputRange5V); + //Set the output value for DAC channel 0, range 0-5000 + dac.setDACOutVoltage(2500, 0); + delay(1000); + //Store data in the chip + dac.store(); +} + +void loop(){ +} diff --git a/lib/DFRobot_GP8403_ESPEasy/examples/outputSin/outputSin.ino b/lib/DFRobot_GP8403_ESPEasy/examples/outputSin/outputSin.ino new file mode 100644 index 000000000..82c53c37f --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/examples/outputSin/outputSin.ino @@ -0,0 +1,35 @@ +/*! + * @file outputSin.ino + * @brief Use DAC to output sine wave. + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-03-07 + * @url https://github.com/DFRobot/DFRobot_GP8403 + */ + +#include "DFRobot_GP8403.h" +DFRobot_GP8403 dac(&Wire,0x58); + +void setup() { + Serial.begin(115200); + while(dac.begin()!=0){ + Serial.println("init error"); + delay(1000); + } + Serial.println("init succeed"); + //Set DAC output range + dac.setDACOutRange(dac.eOutputRange5V); +} + +void loop(){ + /** + * @brief Output sine wave from channel 0 + * @param amp Set sine wave amplitude Vp, 10V range 0-5000, 5V range 0-2500 + * @param freq Set sine wave frequency f, range 0-100 + * @param offset Set sine wave DC offset Voffset, 10V range 0-5000, 5V range 0-2500 + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + dac.outputSin(2500, 10, 2500, 0); +} diff --git a/lib/DFRobot_GP8403_ESPEasy/examples/outputSquare/outputSquare.ino b/lib/DFRobot_GP8403_ESPEasy/examples/outputSquare/outputSquare.ino new file mode 100644 index 000000000..416f50bcc --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/examples/outputSquare/outputSquare.ino @@ -0,0 +1,36 @@ +/*! + * @file outputSquare.ino + * @brief Use DAC to output square wave. + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-03-07 + * @url https://github.com/DFRobot/DFRobot_GP8403 + */ + +#include "DFRobot_GP8403.h" +DFRobot_GP8403 dac(&Wire,0x58); + +void setup() { + Serial.begin(115200); + while(dac.begin()!=0){ + Serial.println("init error"); + delay(1000); + } + Serial.println("init succeed"); + //Set DAC output range + dac.setDACOutRange(dac.eOutputRange5V); +} + +void loop(){ + /** + * @brief Output square wave from channel 0 + * @param amp Set square wave amplitude Vp, 10V range 0-5000, 5V range 0-2500 + * @param freq Set square wave frequency f, range 0-100 + * @param offset Set square wave DC offset Voffset, 10V range 0-5000, 5V range 0-2500 + * @param dutyCycle Set square wave duty cycle, range 0-100 + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + dac.outputSquare(2500, 10, 2500, 50, 0); +} diff --git a/lib/DFRobot_GP8403_ESPEasy/examples/outputTriangle/outPutTriangle.ino b/lib/DFRobot_GP8403_ESPEasy/examples/outputTriangle/outPutTriangle.ino new file mode 100644 index 000000000..e292bfea4 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/examples/outputTriangle/outPutTriangle.ino @@ -0,0 +1,37 @@ +/*! + * @file outPutTriangle.ino + * @brief Use DAC to output triangular wave. + * @copyright Copyright (c) 2021 DFRobot Co.Ltd (http://www.dfrobot.com) + * @license The MIT License (MIT) + * @author [TangJie](jie.tang@dfrobot.com) + * @version V1.0 + * @date 2022-03-07 + * @url https://github.com/DFRobot/DFRobot_GP8403 + */ + +#include "DFRobot_GP8403.h" + +DFRobot_GP8403 dac(&Wire,0x58); + +void setup() { + Serial.begin(115200); + while(dac.begin()!=0){ + Serial.println("init error"); + delay(1000); + } + Serial.println("init succeed"); + //Set DAC output range + dac.setDACOutRange(dac.eOutputRange5V); +} + +void loop(){ + /** + * @brief Call the function to output triangle wave + * @param amp Set triangle wave amplitude Vp, 10V range 0-5000, 5V range 0-2500 + * @param freq Set triangle wave frequency f, range 0-100 + * @param offset Set triangle wave DC offset Voffset, 10V range 0-5000, 5V range 0-2500 + * @param dutyCycle Set triangle(sawtooth) wave duty cycle, range 0-100 + * @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + */ + dac.outputTriangle(5000, 10, 0, 50, 0); +} diff --git a/lib/DFRobot_GP8403_ESPEasy/keywords.txt b/lib/DFRobot_GP8403_ESPEasy/keywords.txt new file mode 100644 index 000000000..e7f76a999 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/keywords.txt @@ -0,0 +1,30 @@ +####################################### +# Syntax Coloring Map For DFRobot_GP8403 +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +DFRobot_GP8403 KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +begin KEYWORD2 +setDACOutRange KEYWORD2 +setDACOutVoltage KEYWORD2 +store KEYWORD2 +outputSin KEYWORD2 +outputTriangle KEYWORD2 +outputSquare KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### +eChannel0 LITERAL1 +eChannel1 LITERAL1 +eChannelAll LITERAL1 +eOutputRange5V LITERAL1 +eOutputRange10V LITERAL1 diff --git a/lib/DFRobot_GP8403_ESPEasy/library.properties b/lib/DFRobot_GP8403_ESPEasy/library.properties new file mode 100644 index 000000000..803ddb0bd --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/library.properties @@ -0,0 +1,9 @@ +name=DFRobot_GP8403_ESPEasy +version=1.0.0 +author=DFRobot +maintainer=tangjie and ESPEasy team +sentence=0-10V DAC module(SKU:DFR0971) with ESPEasy mods. +paragraph=I2C control output 0-10VDAC modules +category=Device Control +url=https://github.com/DFRobot/DFRobot_GP8403 +architectures=* diff --git a/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/DfrobotGP8403.py b/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/DfrobotGP8403.py new file mode 100644 index 000000000..097578993 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/DfrobotGP8403.py @@ -0,0 +1,254 @@ +# -*- coding: utf-8 -* +""" +@file DFRobot_GP8403.py +@brief This is a function library of the DAC module. +@copyright Copyright (c) 2023 Couillonnade +@license The MIT License (MIT) +@author [Rémi] +@version V1.0 +@date 2023-03-29 +@url https://github.com/couillonnade/DFRobot_GP8403 +""" + +import machine +import utime +import ustruct +import sys + +# Select DAC output voltage of 0-5V +OUTPUT_RANGE_5V = 0 +# Select DAC output voltage of 0-10V +OUTPUT_RANGE_10V = 17 +# Select to output from channel 0 +CHANNEL0 = 1 +# Select to output from channel 1 +CHANNEL1 = 2 +# Select to output from all the channels +CHANNELALL = 3 + + +class DfrobotGP8403(): + # Configure current sensor register + GP8403_CONFIG_CURRENT_REG = 0x02 + # Store function timing start head + GP8302_STORE_TIMING_HEAD = 0x02 + # The first address for entering store timing + GP8302_STORE_TIMING_ADDR = 0x10 + # The command 1 to enter store timing + GP8302_STORE_TIMING_CMD1 = 0x03 + # The command 2 to enter store timing + GP8302_STORE_TIMING_CMD2 = 0x00 + # Total I2C communication cycle 5us + I2C_CYCLE_TOTAL = 5 + # The first half cycle of the total I2C communication cycle 2us + I2C_CYCLE_BEFORE = 2 + # The second half cycle of the total I2C communication cycle 3us + I2C_CYCLE_AFTER = 3 + # Store procedure interval delay time: 10ms (1000us) + # (should be more than 7ms according to spec) + GP8302_STORE_TIMING_DELAY = 1000 + + + def __init__(self, addr, sclpin, sdapin, i2cfreq, hard = False): + """ + Initilize the I2C bus. + On Pico, Software I2C (using bit-banging) works on all output-capable pins + :param addr: I2C address + :param sclpin: SCL pin + :param sdapin: SDA pin + :param i2cfreq: I2C frequency + :param hard: I2C or SoftI2C + """ + self._addr = addr + self.outPutSetRange = 0x01 + self.voltage = 5000 + self._sclpin = sclpin + self._sdapin = sdapin + self._scl = machine.Pin(sclpin) + self._sda = machine.Pin(sdapin) + self._i2cfreq = i2cfreq + self.dataTransmission = 0 + self._hard = hard + + # Need it because "store" bit bangs and uninitialize the I2C bus + self._initializeI2C() + + def _initializeI2C(self): + if self._hard: + self.i2c = machine.I2C(0, + scl=self._scl, + sda=self._sda, + freq=self._i2cfreq) + else: + # Pylance is not happy with this because stubs are wrong. + # see: https://github.com/paulober/Pico-W-Go/issues/55 + self.i2c = machine.SoftI2C(scl=self._scl, + sda=self._sda, + freq=self._i2cfreq) + + + def begin(self): + # List devices + print("Found i2c addresses: ", self.i2c.scan()) + + # Initialize the sensor + try: + if self.i2c.readfrom(self._addr, 1) != 0: + return 0 + return 1 + except OSError as e: + print("Error: {0} on address {1}".format(e, hex(self._addr))) + return 1 + except Exception as e: # exception if read_byte fails + print("Error unk: {0} on address {1}".format(e, hex(self._addr))) + return 1 + + + def set_dac_out_range(self, mode): + """ + Set DAC output range + :param mode: 5V or 10V OUTPUT_RANGE mode + """ + if mode == OUTPUT_RANGE_5V: + self.voltage = 5000 + elif mode == OUTPUT_RANGE_10V: + self.voltage = 10000 + + b = bytearray(1) + b[0] = mode + self.i2c.writeto_mem(self._addr, self.outPutSetRange, b, addrsize=8) + + def get_dac_out_range(self): + return self.voltage + + def set_dac_out_voltage(self, data, channel): + """ + Select DAC output channel & range + :param data: Set voltage in mV between 0-5000 or 0-10000 depending on range + :param channel: Set output channel + """ + self.dataTransmission = int((float(data) / self.voltage) * 4095) + self.dataTransmission = int(self.dataTransmission) << 4 + self._send_data(self.dataTransmission, channel) + + + def _send_data(self, data, channel): + if channel == 0 or channel == 3: + b = bytearray(3) + b[0] = self.GP8403_CONFIG_CURRENT_REG + b[1] = data & 0xFF + b[2] = (data >> 8) & 0xFF + self.i2c.writeto(self._addr, b) + + if channel == 1 or channel == 3: + b = bytearray(3) + b[0] = self.GP8403_CONFIG_CURRENT_REG << 1 + b[1] = data & 0xFF + b[2] = (data >> 8) & 0xFF + self.i2c.writeto(self._addr, b) + + + def store(self): + """ + Save the present current config, after the config is saved successfully, + it will be enabled when the module is powered down and restarts + + This is done with bit-banging because the chip does custom I2C with less than 1 Byte data. + """ + # Re-initialise Pin because it was initialized + # with SoftI2C and we need to use it as GPIO + _scl = machine.Pin(self._sclpin, machine.Pin.OUT) + _sda = machine.Pin(self._sdapin, machine.Pin.OUT) + + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_HEAD, 0, 3, False) + self._stop_signal() + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_ADDR) + self._send_byte(self.GP8302_STORE_TIMING_CMD1) + self._stop_signal() + + self._start_signal() + self._send_byte(self._addr<<1, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._stop_signal() + + utime.sleep_us(self.GP8302_STORE_TIMING_DELAY) + + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_HEAD, 0, 3, False) + self._stop_signal() + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_ADDR) + self._send_byte(self.GP8302_STORE_TIMING_CMD2) + self._stop_signal() + + # re-initialize I2C + self._initializeI2C() + + + def _start_signal(self): + self._scl.high() + self._sda.high() + utime.sleep_us(self.I2C_CYCLE_BEFORE) + self._sda.low() + utime.sleep_us(self.I2C_CYCLE_AFTER) + self._scl.low() + utime.sleep_us(self.I2C_CYCLE_TOTAL) + + def _stop_signal(self): + self._sda.low() + utime.sleep_us(self.I2C_CYCLE_BEFORE) + self._scl.high() + utime.sleep_us(self.I2C_CYCLE_TOTAL) + self._sda.high() + utime.sleep_us(self.I2C_CYCLE_TOTAL) + + def _recv_ack(self, ack = 0): + ack_ = 0 + error_time = 0 + self._sda = machine.Pin(self._sdapin, machine.Pin.IN) + + utime.sleep_us(self.I2C_CYCLE_BEFORE) + self._scl.high() + utime.sleep_us(self.I2C_CYCLE_AFTER) + while self._sda.value() != ack: + utime.sleep_us(1) + error_time += 1 + if error_time > 250: + break + ack_ = self._sda.value() # suspicious to read the value here, should save it before the while loop? + utime.sleep_us(self.I2C_CYCLE_BEFORE) + self._scl.low() + utime.sleep_us(self.I2C_CYCLE_AFTER) + self._sda = machine.Pin(self._sdapin, machine.Pin.OUT) + return ack_ + + def _send_byte(self, data, ack = 0, bits = 8, flag = True): + i = bits + # Ensure 8 bits only + data = data & 0xFF + while i > 0: + i -= 1 + if data & (1 << i): + self._sda.high() + else: + self._sda.low() + utime.sleep_us(self.I2C_CYCLE_BEFORE) + self._scl.high() + utime.sleep_us(self.I2C_CYCLE_TOTAL) + self._scl.low() + utime.sleep_us(self.I2C_CYCLE_AFTER) + if flag: + return self._recv_ack(ack) + else: + self._sda.low() + self._scl.high() + return ack diff --git a/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/README.md b/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/README.md new file mode 100644 index 000000000..636889c47 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/micropython/RP2/README.md @@ -0,0 +1,125 @@ +# DFRobot_GP8403 + +Port of the Library for Raspberry Pi Pico. + +This I2C to 0-5V/0-10V DAC module can be used to output voltage of 0-5V or 0-10V. It has the following features: +1. Output voltage of 0-5V or 0-10V. +2. It can control the output voltage with an I2C interface, the I2C address is default to be 0x58. +3. The output voltage config will be lost after the module is powered down. Save the config if you want to use it for the next power-up. + + +## Product Link([www.dfrobot.com](www.dfrobot.com)) + SKU: DFR0971 + +## Table of Contents + - [Summary](#summary) + - [Methods](#methods) + - [Examples](#examples) + - [Compatibility](#compatibility) + - [History](#history) + - [Credits](#credits) + +## Summary +The Arduino library is provided for the I2C 0-5V/0-10V DAC module to set and save the output voltage config of the module. And the library has the following functions: +1. Set the voltage of 0-5V or 0-10V directlyï¼› +2. Output the corresponding voltage by setting the DAC range of 0-0xFFFï¼› +3. Save the voltage config(Will not be lost when powered down). + +## Methods + +```python + '''! + @param Initialize the sensor + ''' + def begin(self): + + '''! + @brief Set DAC output range + @param mode Select DAC output range + ''' + def set_DAC_outrange(self,mode): + + '''! + @brief Select DAC output channel & range + @param data Set the output data + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + def set_DAC_out_voltage(self,data,channel) + + '''! + @brief Save the present current config, after the config is saved successfully, it will be enabled when the module is powered down and restarts. + ''' + def store(self) + +``` + +## Examples + +```python + from DfrobotGP8403 import * + import utime + + def store(): + # Store data in the chip + DAC.store() + + def wave(): + # Triangle with 180 phase output 1 and 2 + vmax = DAC.get_dac_out_range() + for i in range(10): + for x in range(vmax+1): + DAC.set_dac_out_voltage(x*1000,0) + DAC.set_dac_out_voltage((vmax-x)*1000,1) + utime.sleep(0.25) + + for x in reversed(range(vmax+1)): + DAC.set_dac_out_voltage(x*1000,0) + DAC.set_dac_out_voltage((vmax-x)*1000,1) + utime.sleep(0.25) + + if __name__ == "__main__": + # Init DAC with desired address, pins, and hard/soft mode + DAC = DfrobotGP8403 (0x5F, 5, 4, 400000, True) + + while DAC.begin() != 0: + print("Init error") + utime.sleep(1) + print("Init succeed") + + # Set output range + DAC.set_dac_out_range(OUTPUT_RANGE_10V) + + # Output value from DAC channel 0 + # Value in mV = 0-5000 or 0-10000 depending on range + DAC.set_dac_out_voltage(1000,0) + DAC.set_dac_out_voltage(2000,1) +``` + +## Compatibility + +| MCU | Work Well | Work Wrong | Untested | Remarks | +| ------------ | :--: | :----: | :----: | :--: | +| RaspberryPi Pico | √ | | | | + + +* Python Version + +| Python | Work Well | Work Wrong | Untested | Remarks | +| ------- | :--: | :----: | :----: | ---- | +| Python3 | | | √ | | +| MicroPython 1.19 | √ | | | | + + +## History + +- 2023-04-03 - Version 1.0.0. +- 2023-04-04 - Version 1.0.1. + +## Credits + +Written by Rémi, 2023. + + + + + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/DFRobot_GP8403.py b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/DFRobot_GP8403.py new file mode 100644 index 000000000..c5bbe1097 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/DFRobot_GP8403.py @@ -0,0 +1,419 @@ +# -*- coding: utf-8 -* +'''! + @file DFRobot_GP8403.py + @brief This is a function library of the DAC module. + @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + @license The MIT License (MIT) + @author [tangjie](jie.tang@dfrobot.com) + @version V1.0 + @date 2022-03-03 + @url https://github.com/DFRobot/DFRobot_GP8403 +''' +from __future__ import print_function +import sys +import smbus +import time +import datetime +import RPi.GPIO as GPIO + + +FullSine5Bit = [ + 2048,2447,2831,3185,3495,3750,3939,4056, + 4095,4056,3939,3750,3495,3185,2831,2447, + 2048,1648,1264, 910, 600, 345, 156, 39, + 0, 39, 156, 345, 600, 910,1264,1648] + +FullSine6Bit = [ + 2048, 2248, 2447, 2642, 2831, 3013, 3185, 3346, + 3495, 3630, 3750, 3853, 3939, 4007, 4056, 4085, + 4095, 4085, 4056, 4007, 3939, 3853, 3750, 3630, + 3495, 3346, 3185, 3013, 2831, 2642, 2447, 2248, + 2048, 1847, 1648, 1453, 1264, 1082, 910, 749, + 600, 465, 345, 242, 156, 88, 39, 10, + 0, 10, 39, 88, 156, 242, 345, 465, + 600, 749, 910, 1082, 1264, 1453, 1648, 1847] + +FullSine7Bit = [ + 2048, 2148, 2248, 2348, 2447, 2545, 2642, 2737, + 2831, 2923, 3013, 3100, 3185, 3267, 3346, 3423, + 3495, 3565, 3630, 3692, 3750, 3804, 3853, 3898, + 3939, 3975, 4007, 4034, 4056, 4073, 4085, 4093, + 4095, 4093, 4085, 4073, 4056, 4034, 4007, 3975, + 3939, 3898, 3853, 3804, 3750, 3692, 3630, 3565, + 3495, 3423, 3346, 3267, 3185, 3100, 3013, 2923, + 2831, 2737, 2642, 2545, 2447, 2348, 2248, 2148, + 2048, 1947, 1847, 1747, 1648, 1550, 1453, 1358, + 1264, 1172, 1082, 995, 910, 828, 749, 672, + 600, 530, 465, 403, 345, 291, 242, 197, + 156, 120, 88, 61, 39, 22, 10, 2, + 0, 2, 10, 22, 39, 61, 88, 120, + 156, 197, 242, 291, 345, 403, 465, 530, + 600, 672, 749, 828, 910, 995, 1082, 1172, + 1264, 1358, 1453, 1550, 1648, 1747, 1847, 1947] + +FullSine8Bit = [ + 2048, 2098, 2148, 2198, 2248, 2298, 2348, 2398, + 2447, 2496, 2545, 2594, 2642, 2690, 2737, 2784, + 2831, 2877, 2923, 2968, 3013, 3057, 3100, 3143, + 3185, 3226, 3267, 3307, 3346, 3385, 3423, 3459, + 3495, 3530, 3565, 3598, 3630, 3662, 3692, 3722, + 3750, 3777, 3804, 3829, 3853, 3876, 3898, 3919, + 3939, 3958, 3975, 3992, 4007, 4021, 4034, 4045, + 4056, 4065, 4073, 4080, 4085, 4089, 4093, 4094, + 4095, 4094, 4093, 4089, 4085, 4080, 4073, 4065, + 4056, 4045, 4034, 4021, 4007, 3992, 3975, 3958, + 3939, 3919, 3898, 3876, 3853, 3829, 3804, 3777, + 3750, 3722, 3692, 3662, 3630, 3598, 3565, 3530, + 3495, 3459, 3423, 3385, 3346, 3307, 3267, 3226, + 3185, 3143, 3100, 3057, 3013, 2968, 2923, 2877, + 2831, 2784, 2737, 2690, 2642, 2594, 2545, 2496, + 2447, 2398, 2348, 2298, 2248, 2198, 2148, 2098, + 2048, 1997, 1947, 1897, 1847, 1797, 1747, 1697, + 1648, 1599, 1550, 1501, 1453, 1405, 1358, 1311, + 1264, 1218, 1172, 1127, 1082, 1038, 995, 952, + 910, 869, 828, 788, 749, 710, 672, 636, + 600, 565, 530, 497, 465, 433, 403, 373, + 345, 318, 291, 266, 242, 219, 197, 176, + 156, 137, 120, 103, 88, 74, 61, 50, + 39, 30, 22, 15, 10, 6, 2, 1, + 0, 1, 2, 6, 10, 15, 22, 30, + 39, 50, 61, 74, 88, 103, 120, 137, + 156, 176, 197, 219, 242, 266, 291, 318, + 345, 373, 403, 433, 465, 497, 530, 565, + 600, 636, 672, 710, 749, 788, 828, 869, + 910, 952, 995, 1038, 1082, 1127, 1172, 1218, + 1264, 1311, 1358, 1405, 1453, 1501, 1550, 1599, + 1648, 1697, 1747, 1797, 1847, 1897, 1947, 1997] + + +##Select DAC output voltage of 0-5V +OUTPUT_RANGE_5V = 0 +##Select DAC output voltage of 0-10V +OUTPUT_RANGE_10V = 17 +##Select to output from channel 0 +CHANNEL0 = 1 +##Select to output from channel 1 +CHANNEL1 = 2 +##Select to output from all the channels +CHANNELALL = 3 + +class DFRobot_GP8403(): + ## Configure current sensor register + GP8403_CONFIG_CURRENT_REG = 0x02 + ## Store function timing start head + GP8302_STORE_TIMING_HEAD = 0x02 + ## The first address for entering store timing + GP8302_STORE_TIMING_ADDR = 0x10 + ## The command 1 to enter store timing + GP8302_STORE_TIMING_CMD1 = 0x03 + ## The command 2 to enter store timing + GP8302_STORE_TIMING_CMD2 = 0x00 + ## Total I2C communication cycle 5us + I2C_CYCLE_TOTAL = 0.000005 + ## The first half cycle of the total I2C communication cycle 2us + I2C_CYCLE_BEFORE = 0.000002 + ## The second half cycle of the total I2C communication cycle 3us + I2C_CYCLE_AFTER = 0.000003 + + # Store procedure interval delay time: 10ms (1000us) + # (should be more than 7ms according to spec) + GP8302_STORE_TIMING_DELAY = 0.0000010 + + + def __init__(self,addr): + self._addr = addr + self.outPutSetRange = 0x01 + self.voltage = 5000 + self._scl = 3 + self._sda = 2 + self.dataTransmission = 0 + GPIO.setmode(GPIO.BCM) + GPIO.setwarnings(False) + self.i2c = smbus.SMBus(1) + + + def begin(self): + '''! + @param Initialize the sensor + ''' + if(self.i2c.read_byte(self._addr) != 0): + return 0 + return 1 + + def set_DAC_outrange(self,mode): + '''! + @brief Set DAC output range + @param mode Select DAC output range + ''' + if mode == OUTPUT_RANGE_5V: + self.voltage = 5000 + elif mode == OUTPUT_RANGE_10V : + self.voltage = 10000 + self.i2c.write_word_data(self._addr,self.outPutSetRange,mode) + + def set_DAC_out_voltage(self,data,channel): + '''! + @brief Select DAC output channel & range + @param data Set output data + @param channel Set output channel + ''' + self.dataTransmission = ((float(data) / self.voltage) * 4095) + self.dataTransmission = int(self.dataTransmission) << 4 + self._send_data(self.dataTransmission,channel) + + def store(self): + '''! + @brief Save the present current config, after the config is saved successfully, it will be enabled when the module is powered down and restarts + ''' + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_HEAD, 0, 3, False) + self._stop_signal() + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_ADDR) + self._send_byte(self.GP8302_STORE_TIMING_CMD1) + self._stop_signal() + + self._start_signal() + self._send_byte(self._addr<<1, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._send_byte(self.GP8302_STORE_TIMING_CMD2, 1) + self._stop_signal() + + time.sleep(self.GP8302_STORE_TIMING_DELAY) + + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_HEAD, 0, 3, False) + self._stop_signal() + self._start_signal() + self._send_byte(self.GP8302_STORE_TIMING_ADDR) + self._send_byte(self.GP8302_STORE_TIMING_CMD2) + self._stop_signal() + + + def output_sin(self,amp,freq,offset,channel): + '''! + @brief Set the sensor outputs sine wave + @param amp Set sine wave amplitude Vp + @param freq Set sine wave frequency f + @param offset Set sine wave DC offset Voffset + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + if(freq < 6): + num = 256 + elif( 6 <= freq and freq <= 10): + num = 128 + elif(10 < freq and freq <22): + num = 64 + elif(22 <= freq and freq <= 42): + num = 32 + else: + num = 32 + if(freq > 42): + freq = 42 + frame = int(1000000/(freq*(num+1))) + for i in range(0,num-1): + start = datetime.datetime.now() + if num == 256: + data = (FullSine8Bit[i] - 2047) * (amp/float(self.voltage)) *2 + elif num == 128: + data = (FullSine7Bit[i] - 2047) * (amp/float(self.voltage)) *2 + elif num == 64: + data = (FullSine6Bit[i] - 2047) * (amp/float(self.voltage)) *2 + elif num == 32: + data = (FullSine5Bit[i] - 2047) * (amp/float(self.voltage)) *2 + else: + data = (FullSine5Bit[i] - 2047) * (amp/float(self.voltage)) *2 + data = int(data + offset*(4096/float(self.voltage))) + if data <= 0: + data = 0 + if data >= 4095: + data = 4095 + data = data <<4 + self._send_data(data,channel) + endtime = datetime.datetime.now() + looptime = (endtime - start).microseconds + while looptime <= frame: + endtime = datetime.datetime.now() + looptime = (endtime - start).microseconds + + def output_triangle(self,amp,freq,offset,dutyCycle,channel): + '''! + @brief Call the function to output triangle wave + @param amp Set triangle wave amplitude Vp + @param freq Set triangle wave frequency f + @param offset Set triangle wave DC offset Voffset + @param dutyCycle Set triangle (sawtooth) wave duty cycle + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + maxV = int(amp*(4096/float(self.voltage))) + if freq > 20: + num = 16 + elif freq >= 11 and freq<=20: + num = 32 + else: + num = 64 + frame = 1000000/(freq*num*2) + if dutyCycle > 100: + dutyCycle = 100 + if dutyCycle < 0: + dutyCycle = 0 + up_num = (2*num)*(float(dutyCycle)/100) + down_num = ((2*num) - up_num) + if up_num == 0: + up_num = 1 + for i in range(0,(maxV-int(maxV/up_num)-1),int(maxV/up_num)): + starttime = datetime.datetime.now() + enterV = i + int(offset*(4096/float(self.voltage))) + if enterV > 4095: + enterV = 4095 + elif enterV < 0: + enterV = 0 + enterV = enterV <<4 + self._send_data(enterV,channel) + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + while looptime <= frame: + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + + for i in range(0,int(down_num)): + starttime = datetime.datetime.now() + enterV = maxV-1-(i*int(maxV/down_num))+int(offset*(4096/float(self.voltage))) + if enterV > 4095: + enterV = 4095 + elif enterV < 0: + enterV = 0 + enterV = enterV <<4 + self._send_data(enterV,channel) + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + while looptime <= frame: + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + + def output_square(self,amp,freq,offset,dutyCycle,channel): + '''! + @brief Call the function to output square wave + @param amp Set square wave amplitude Vp + @param freq Set square wave frequency f + @param offset Set square wave DC offset Voffset + @param dutyCycle Set square wave duty cycle + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + maxV = int(amp*(4096/float(self.voltage))) + if freq > 20: + num = 16 + elif freq >= 11 and freq<=20: + num = 32 + else: + num = 64 + frame = 1000000/(freq*num*2) + if dutyCycle > 100: + dutyCycle = 100 + if dutyCycle < 0: + dutyCycle = 0 + up_num = (2*num)*(float(dutyCycle)/100) + down_num = ((2*num) - up_num) + if up_num == 0: + up_num = 1 + for i in range(int(up_num)): + starttime = datetime.datetime.now() + enterV = int(maxV + offset*(4096/float(self.voltage))) + if enterV > 4095: + enterV = 4095 + elif enterV < 0: + enterV = 0 + enterV = enterV <<4 + self._send_data(enterV,channel) + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + while looptime <= frame: + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + for i in range(int(down_num)): + starttime = datetime.datetime.now() + enterV = int(maxV - offset*(4096/float(self.voltage))) + if enterV > 4095: + enterV = 4095 + elif enterV < 0: + enterV = 0 + self._send_data(enterV,channel) + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + while looptime <= frame: + endtime = datetime.datetime.now() + looptime = (endtime - starttime).microseconds + + def _send_data(self,data,channel): + if channel == 0: + self.i2c.write_word_data(self._addr,self.GP8403_CONFIG_CURRENT_REG,data) + + elif channel == 1: + self.i2c.write_word_data(self._addr,self.GP8403_CONFIG_CURRENT_REG<<1,data) + else: + self.i2c.write_word_data(self._addr,self.GP8403_CONFIG_CURRENT_REG,data) + self.i2c.write_word_data(self._addr,self.GP8403_CONFIG_CURRENT_REG<<1,data) + + def _start_signal(self): + GPIO.output(self._scl, GPIO.HIGH) + GPIO.output(self._sda, GPIO.HIGH) + time.sleep(self.I2C_CYCLE_BEFORE) + GPIO.output(self._sda, GPIO.LOW) + time.sleep(self.I2C_CYCLE_AFTER) + GPIO.output(self._scl, GPIO.LOW) + time.sleep(self.I2C_CYCLE_TOTAL) + + def _stop_signal(self): + GPIO.output(self._sda, GPIO.LOW) + time.sleep(self.I2C_CYCLE_BEFORE) + GPIO.output(self._scl, GPIO.HIGH) + time.sleep(self.I2C_CYCLE_TOTAL) + GPIO.output(self._sda, GPIO.HIGH) + time.sleep(self.I2C_CYCLE_TOTAL) + + def _recv_ack(self, ack = 0): + ack_ = 0 + error_time = 0 + GPIO.setup(self._sda, GPIO.IN) + time.sleep(self.I2C_CYCLE_BEFORE) + GPIO.output(self._scl, GPIO.HIGH) + time.sleep(self.I2C_CYCLE_AFTER) + while GPIO.input(self._sda) != ack: + time.sleep(0.000001) + error_time += 1 + if error_time > 250: + break + ack_ = GPIO.input(self._sda) + time.sleep(self.I2C_CYCLE_BEFORE) + GPIO.output(self._scl, GPIO.LOW) + time.sleep(self.I2C_CYCLE_AFTER) + GPIO.setup(self._sda, GPIO.OUT) + return ack_ + + def _send_byte(self, data, ack = 0, bits = 8, flag = True): + i = bits + data = data & 0xFF + while i > 0: + i -= 1 + if data & (1 << i): + GPIO.output(self._sda, GPIO.HIGH) + else: + GPIO.output(self._sda, GPIO.LOW) + time.sleep(self.I2C_CYCLE_BEFORE) + GPIO.output(self._scl, GPIO.HIGH) + time.sleep(self.I2C_CYCLE_TOTAL) + GPIO.output(self._scl, GPIO.LOW) + time.sleep(self.I2C_CYCLE_AFTER) + if flag: + return self._recv_ack(ack) + else: + GPIO.output(self._sda, GPIO.LOW) + GPIO.output(self._scl, GPIO.HIGH) + return ack + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/README.md b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/README.md new file mode 100644 index 000000000..b3a5384eb --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/README.md @@ -0,0 +1,126 @@ +# DFRobot_GP8403 + +This I2C to 0-5V/0-10V DAC module can be used to output voltage of 0-5V or 0-10V. It has the following features: +1. Output voltage of 0-5V or 0-10V. +2. It can control the output voltage with an I2C interface, the I2C address is default to be 0x58. +3. The output voltage config will be lost after the module is powered down. Save the config if you want to use it for the next power-up. + + +## Product Link([www.dfrobot.com](www.dfrobot.com)) + SKU: DFR0971 + +## Table of Contents + - [Summary](#summary) + - [Installation](#installation) + - [Methods](#methods) + - [Compatibility](#compatibility) + - [History](#history) + - [Credits](#credits) + +## Summary +The Arduino library is provided for the I2C 0-5V/0-10V DAC module to set and save the output voltage config of the module. And the library has the following functions: +1. Set the voltage of 0-5V or 0-10V directlyï¼› +2. Output the corresponding voltage by setting the DAC range of 0-0xFFFï¼› +3. Save the voltage config(Will not be lost when powered down). + +## Installation +1. To use this library, first download the library file
+```python +sudo git clone https://github.com/DFRobot/DFRobot_GP8302 +``` +2. Open and run the routine. To execute a routine demo_x.py, enter python demo_x.py in the command line. For example, to execute the demo_set_current.py routine, you need to enter :
+ +```python +python demo_set_current.py +or +python2 demo_set_current.py +or +python3 demo_set_current.py +``` + +## Methods + +```python + '''! + @param Initialize the sensor + ''' + def begin(self): + + '''! + @brief Set DAC output range + @param mode Select DAC output range + ''' + def set_DAC_outrange(self,mode): + + '''! + @brief Select DAC output channel & range + @param data Set the output data + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + def set_DAC_out_voltage(self,data,channel) + + '''! + @brief Save the present current config, after the config is saved successfully, it will be enabled when the module is powered down and restarts. + ''' + def store(self) + + '''! + @brief Set the sensor to output sine wave + @param amp Set sine wave amplitude Vp + @param freq Set sine wave frequency f + @param offset Set sine wave DC offset Voffset + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + def output_sin(self,amp,freq,offset,channel) + + + '''! + @brief Call the function to output triangle wave + @param amp Set triangle wave amplitude Vp + @param freq Set triangle wave frequency f + @param offset Set triangle wave DC offset Voffset + @param dutyCycle Set triangle (sawtooth) wave duty cycle + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + def output_triangle(self,amp,freq,offset,dutyCycle,channel): + + '''! + @brief Call the function to output square wave + @param amp Set square wave amplitude Vp + @param freq Set square wave frequency f + @param offset Set square wave DC offset Voffset + @param dutyCycle Set square wave duty cycle + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + def output_square(self,amp,freq,offset,dutyCycle,channel) + +``` + +## Compatibility + +| MCU | Work Well | Work Wrong | Untested | Remarks | +| ------------ | :--: | :----: | :----: | :--: | +| RaspberryPi2 | | | √ | | +| RaspberryPi3 | | | √ | | +| RaspberryPi4 | √ | | | | + +* Python Version + +| Python | Work Well | Work Wrong | Untested | Remarks | +| ------- | :--: | :----: | :----: | ---- | +| Python2 | √ | | | | +| Python3 | √ | | | | + + +## History + +- 2022/03/10 - Version 1.0.0 released. + +## Credits + +Written by tangjie(jie.tang@dfrobot.com), 2022. (Welcome to our [website](https://www.dfrobot.com/)) + + + + + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_data.py b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_data.py new file mode 100644 index 000000000..f7274eb39 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_data.py @@ -0,0 +1,31 @@ +# -*- coding:utf-8 -*- +'''! + @file output_data.py + @brief A use example for the DAC, execute it to output different values from different channels. + @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + @license The MIT License (MIT) + @author [tangjie](jie.tang@dfrobot.com) + @version V1.0 + @date 2022-03-07 + @url https://github.com/DFRobot/DFRobot_GP8403 +''' +from __future__ import print_function +import sys +import os +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +from DFRobot_GP8403 import * + +DAC = DFRobot_GP8403(0x58) +while DAC.begin() != 0: + print("init error") + time.sleep(1) +print("init succeed") + +#Set output range +DAC.set_DAC_outrange(OUTPUT_RANGE_5V) + +#Output value from DAC channel 0 +DAC.set_DAC_out_voltage(2500,0) + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_sin.py b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_sin.py new file mode 100644 index 000000000..548ba352f --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_sin.py @@ -0,0 +1,38 @@ +# -*- coding:utf-8 -*- +'''! + @file output_sin.py + @brief Use DAC to output sine wave. + @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + @license The MIT License (MIT) + @author [tangjie](jie.tang@dfrobot.com) + @version V1.0 + @date 2022-03-07 + @url https://github.com/DFRobot/DFRobot_GP8403 +''' +from __future__ import print_function +import sys +import os +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +from DFRobot_GP8403 import * + +DAC = DFRobot_GP8403(0x58) +while DAC.begin() != 0: + print("init error") + time.sleep(1) +print("init succeed") + +#Set output range +DAC.set_DAC_outrange(OUTPUT_RANGE_5V) +while True: + '''! + @brief output sine wave from channel 0 + @param amp Set sine wave amplitude Vp + @param freq Set sine wave frequency f + @param offset Set sine wave DC offset Voffset + @param channel Output channel. 0: channel 0; 1: channel 1; 2: all the channels + ''' + DAC.output_sin(2500, 10, 2500, 0) + + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_square.py b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_square.py new file mode 100644 index 000000000..88415051a --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_square.py @@ -0,0 +1,39 @@ +# -*- coding:utf-8 -*- +'''! + @file output_square.py + @brief Use DAC to output square wave. + @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + @license The MIT License (MIT) + @author [tangjie](jie.tang@dfrobot.com) + @version V1.0 + @date 2022-03-07 + @url https://github.com/DFRobot/DFRobot_GP8403 +''' +from __future__ import print_function +import sys +import os +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +from DFRobot_GP8403 import * + +DAC = DFRobot_GP8403(0x58) +while DAC.begin() != 0: + print("init error") + time.sleep(1) +print("init succeed") + +#Set output range +DAC.set_DAC_outrange(OUTPUT_RANGE_5V) +while True: + '''! + @brief Output square wave from channel 0 + @param amp Set square wave amplitude Vp + @param freq Set square wave frequency f + @param offset Set square wave DC offset Voffset + @param dutyCycle Set square wave duty cycle + @param channel Channel select + ''' + DAC.output_square(2500, 10, 2500, 50, 0) + + diff --git a/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_triangle.py b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_triangle.py new file mode 100644 index 000000000..b8ae0c786 --- /dev/null +++ b/lib/DFRobot_GP8403_ESPEasy/python/raspberryPi/examples/output_triangle.py @@ -0,0 +1,39 @@ +# -*- coding:utf-8 -*- +'''! + @file output_triangle.py + @brief Use DAC to output triangle wave using DAC. + @copyright Copyright (c) 2010 DFRobot Co.Ltd (http://www.dfrobot.com) + @license The MIT License (MIT) + @author [tangjie](jie.tang@dfrobot.com) + @version V1.0 + @date 2022-03-07 + @url https://github.com/DFRobot/DFRobot_GP8403 +''' +from __future__ import print_function +import sys +import os +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) +from DFRobot_GP8403 import * + +DAC = DFRobot_GP8403(0x58) +while DAC.begin() != 0: + print("init error") + time.sleep(1) +print("init succeed") + +#Set output range +DAC.set_DAC_outrange(OUTPUT_RANGE_5V) +while True: + '''! + @brief Output triangular wave from channel 0 + @param amp Set triangle wave amplitude Vp + @param freq Set triangle wave frequency f + @param offset Set triangle wave DC offset Voffset + @param dutyCycle Set triangle (sawtooth) wave duty cycle + @param channel Channel select + ''' + DAC.output_triangle(5000, 10, 0, 50, 0) + + diff --git a/lib/ESPEasySerial/ESPEasySerialPort.cpp b/lib/ESPEasySerial/ESPEasySerialPort.cpp index 92aef1b30..951c50de5 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.cpp +++ b/lib/ESPEasySerial/ESPEasySerialPort.cpp @@ -1,31 +1,32 @@ #include "ESPEasySerialPort.h" -const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port) +const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port, bool shortName) { switch (port) { case ESPEasySerialPort::not_set: break; #if USES_I2C_SC16IS752 - case ESPEasySerialPort::sc16is752: return F("I2C Serial"); + case ESPEasySerialPort::sc16is752: return shortName ? F("seriali2c") : F("I2C Serial"); #endif // if USES_I2C_SC16IS752 #ifdef ESP8266 - case ESPEasySerialPort::serial0_swap: return F("HW Serial0 swap"); + case ESPEasySerialPort::serial0_swap: return shortName ? F("serial0swap") : F("HW Serial0 swap"); #endif // ifdef ESP8266 - case ESPEasySerialPort::serial0: return F("HW Serial0"); + case ESPEasySerialPort::serial0: return shortName ? F("serial0") : F("HW Serial0"); #if SOC_UART_NUM > 1 - case ESPEasySerialPort::serial1: return F("HW Serial1"); + case ESPEasySerialPort::serial1: return shortName ? F("serial1") : F("HW Serial1"); #endif // if SOC_UART_NUM > 1 #if SOC_UART_NUM > 2 - case ESPEasySerialPort::serial2: return F("HW Serial2"); + case ESPEasySerialPort::serial2: return shortName ? F("serial2") : F("HW Serial2"); #endif // if SOC_UART_NUM > 2 #if USES_SW_SERIAL - case ESPEasySerialPort::software: return F("SW Serial"); + case ESPEasySerialPort::software: return shortName ? F("serialsw") : F("SW Serial"); #endif // if USES_SW_SERIAL #if USES_HWCDC - case ESPEasySerialPort::usb_hw_cdc: return F("USB HWCDC"); + case ESPEasySerialPort::usb_hw_cdc: return shortName ? F("serialhwcdc") : F("USB HWCDC"); #endif // if USES_HWCDC #if USES_USBCDC - case ESPEasySerialPort::usb_cdc_0: return F("USB CDC"); -// case ESPEasySerialPort::usb_cdc_1: return F("USB CDC1"); + case ESPEasySerialPort::usb_cdc_0: return shortName ? F("serialcdc") : F("USB CDC"); + + // case ESPEasySerialPort::usb_cdc_1: return F("USB CDC1"); #endif // if USES_USBCDC case ESPEasySerialPort::MAX_SERIAL_TYPE: break; @@ -103,7 +104,7 @@ bool validSerialPort(ESPEasySerialPort port) #endif // if USES_HWCDC #if USES_USBCDC case ESPEasySerialPort::usb_cdc_0: -// case ESPEasySerialPort::usb_cdc_1: + // case ESPEasySerialPort::usb_cdc_1: #endif // if USES_USBCDC return true; diff --git a/lib/ESPEasySerial/ESPEasySerialPort.h b/lib/ESPEasySerial/ESPEasySerialPort.h index 1d66dd3a8..9d6f76759 100644 --- a/lib/ESPEasySerial/ESPEasySerialPort.h +++ b/lib/ESPEasySerial/ESPEasySerialPort.h @@ -36,7 +36,7 @@ enum class ESPEasySerialPort : uint8_t { }; -const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port); +const __FlashStringHelper* ESPEasySerialPort_toString(ESPEasySerialPort port, bool shortName = false); bool isHWserial(ESPEasySerialPort port); diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp index 2fded8da4..5c6fa8bdb 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.cpp @@ -58,36 +58,46 @@ Port_ESPEasySerial_USB_HWCDC_t::Port_ESPEasySerial_USB_HWCDC_t(const ESPEasySeri // USB.begin(); if (_hwcdc_serial != nullptr) { - _config.rxBuffSize = _hwcdc_serial->setRxBufferSize(_config.rxBuffSize); - _config.txBuffSize = _hwcdc_serial->setRxBufferSize(_config.txBuffSize); - _hwcdc_serial->begin(); + // _hwcdc_serial->end(); + + // _config.rxBuffSize = _hwcdc_serial->setRxBufferSize(_config.rxBuffSize); + // _config.txBuffSize = _hwcdc_serial->setTxBufferSize(_config.txBuffSize); + + // See: https://github.com/espressif/arduino-esp32/issues/9043 + // _hwcdc_serial->setTxTimeoutMs(0); // sets no timeout when trying to write to USB HW CDC + + // _hwcdc_serial->begin(); // _hwcdc_serial->onEvent(hwcdcEventCallback); } } -Port_ESPEasySerial_USB_HWCDC_t::~Port_ESPEasySerial_USB_HWCDC_t() {} +Port_ESPEasySerial_USB_HWCDC_t::~Port_ESPEasySerial_USB_HWCDC_t() { + if (_hwcdc_serial != nullptr) { + // _hwcdc_serial->end(); + } +} void Port_ESPEasySerial_USB_HWCDC_t::begin(unsigned long baud) { _config.baud = baud; - /* - if (_hwcdc_serial != nullptr) { - _config.rxBuffSize = _hwcdc_serial->setRxBufferSize(_config.rxBuffSize); - _config.txBuffSize = _hwcdc_serial->setRxBufferSize(_config.txBuffSize); - _hwcdc_serial->begin(); - delay(10); - _hwcdc_serial->onEvent(hwcdcEventCallback); - delay(1); - } - */ + + if (_hwcdc_serial != nullptr) { + _config.rxBuffSize = _hwcdc_serial->setRxBufferSize(_config.rxBuffSize); + _config.txBuffSize = _hwcdc_serial->setTxBufferSize(_config.txBuffSize); + _hwcdc_serial->begin(); + delay(10); + + // _hwcdc_serial->onEvent(hwcdcEventCallback); + delay(1); + } } void Port_ESPEasySerial_USB_HWCDC_t::end() { // Disabled for now // See: https://github.com/espressif/arduino-esp32/issues/8224 if (_hwcdc_serial != nullptr) { - _hwcdc_serial->end(); + // _hwcdc_serial->end(); } } @@ -200,5 +210,4 @@ bool Port_ESPEasySerial_USB_HWCDC_t::setRS485Mode(int8_t rtsPin, bool enableColl return false; } - #endif // if USES_HWCDC diff --git a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h index b9801fe8c..a71bfd411 100644 --- a/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h +++ b/lib/ESPEasySerial/Port_ESPEasySerial_USB_HWCDC.h @@ -13,7 +13,7 @@ class Port_ESPEasySerial_USB_HWCDC_t : public Port_ESPEasySerial_base { public: - Port_ESPEasySerial_USB_HWCDC_t(const ESPEasySerialConfig& config); + explicit Port_ESPEasySerial_USB_HWCDC_t(const ESPEasySerialConfig& config); virtual ~Port_ESPEasySerial_USB_HWCDC_t(); @@ -44,11 +44,7 @@ public: private: -# if ARDUINO_USB_CDC_ON_BOOT // Serial used for USB CDC - HWCDC *_hwcdc_serial = &Serial; -# else // if ARDUINO_USB_CDC_ON_BOOT - HWCDC *_hwcdc_serial = &USBSerial; -# endif // if ARDUINO_USB_CDC_ON_BOOT + HWCDC *_hwcdc_serial= nullptr; }; diff --git a/lib/HeatpumpIR/ZHJG01HeatpumpIR.cpp b/lib/HeatpumpIR/ZHJG01HeatpumpIR.cpp index 1a37066d5..5d440caa3 100644 --- a/lib/HeatpumpIR/ZHJG01HeatpumpIR.cpp +++ b/lib/HeatpumpIR/ZHJG01HeatpumpIR.cpp @@ -108,16 +108,16 @@ void ZHJG01HeatpumpIR::sendZHJG01(IRSender& IR, /******************************************************************************** * Byte[0]: Turbo, Eco, Fan, Vertical Swing - * TURBO ON: B0x1xxxxx - * ECO ON: B0x0xxxxx - * TURBO/ECO OFF: B1xxxxxxx - * FAN1: Bx00xxxxx - * FAN2: Bx01xxxxx - * FAN3: Bx10xxxxx - * FAN AUTO: Bx11xxxxx - * VERTICAL FIXED: Bxxx01xxx - * VERTICAL SWING: Bxxx10xxx - * VERTICAL WIND: Bxxx11xxx + * TURBO ON: 0b0x1xxxxx + * ECO ON: 0b0x0xxxxx + * TURBO/ECO OFF: 0b1xxxxxxx + * FAN1: 0bx00xxxxx + * FAN2: 0bx01xxxxx + * FAN3: 0bx10xxxxx + * FAN AUTO: 0bx11xxxxx + * VERTICAL FIXED: 0bxxx01xxx + * VERTICAL SWING: 0bxxx10xxx + * VERTICAL WIND: 0bxxx11xxx *******************************************************************************/ ZHJG01Template[1] = fanSpeed | swingV; ZHJG01Template[0] = ~ ZHJG01Template[1]; @@ -125,13 +125,13 @@ void ZHJG01HeatpumpIR::sendZHJG01(IRSender& IR, /******************************************************************************** * Byte[2]: Temp, Power, Mode * TEMP: Bttttxxxx - * POWER ON: Bxxxx0xxx - * POWER OFF: Bxxxx1xxx - * MODE HEAT: Bxxxxx011 - * MODE VENT: Bxxxxx100 - * MODE DRY: Bxxxxx101 - * MODE COOL: Bxxxxx110 - * MODE AUTO: Bxxxxx111 + * POWER ON: 0bxxxx0xxx + * POWER OFF: 0bxxxx1xxx + * MODE HEAT: 0bxxxxx011 + * MODE VENT: 0bxxxxx100 + * MODE DRY: 0bxxxxx101 + * MODE COOL: 0bxxxxx110 + * MODE AUTO: 0bxxxxx111 *******************************************************************************/ uint8_t tempBits = ((temperature - 17) << 4) & 0b11110000; diff --git a/lib/HeatpumpIR/ZHJG01HeatpumpIR.h b/lib/HeatpumpIR/ZHJG01HeatpumpIR.h index c91f2d007..e88d66fdd 100644 --- a/lib/HeatpumpIR/ZHJG01HeatpumpIR.h +++ b/lib/HeatpumpIR/ZHJG01HeatpumpIR.h @@ -22,14 +22,14 @@ * Every UNeven Byte (01,03,05,07 and 09) hold a checksum of the corresponding * command by inverting the bits, for example: * - * The identifier byte[0] = 0xD5 = B1101 0101 - * The checksum byte[1] = 0x2A = B0010 1010 + * The identifier byte[0] = 0xD5 = 0b1101 0101 + * The checksum byte[1] = 0x2A = 0b0010 1010 * * So, you can check the message by: * - inverting the bits of the checksum byte with the corresponding command, they * should be the same, or * - Summing up the checksum byte and the corresponding command, - * they should always add up to 0xFF = B11111111 = 255 + * they should always add up to 0xFF = 0b11111111 = 255 * * ****************************************************************************** * Written by: Abílio Costa diff --git a/lib/HeatpumpIR/ZHLT01HeatpumpIR.cpp b/lib/HeatpumpIR/ZHLT01HeatpumpIR.cpp index 3030183e8..e1902d1be 100644 --- a/lib/HeatpumpIR/ZHLT01HeatpumpIR.cpp +++ b/lib/HeatpumpIR/ZHLT01HeatpumpIR.cpp @@ -149,29 +149,29 @@ void ZHLT01HeatpumpIR::sendZHLT01(IRSender& IR, uint8_t powerMode, /******************************************************************************** * Byte[07]: POWER, FAN, SLEEP, HORIZONTAL, VERTICAL - * POWER ON: B0xxxxx1x - * POWER OFF: B0xxxxx0x - * VERTICAL SWING: B0xxx01xx - * VERTICAL WIND: B0xxx00xx - * VERTICAL FIXED: B0xxx10xx - * HORIZONTAL SWING: B0xx0xxxx - * HORIZONTAL OFF: B0xx1xxxx - * FAN AUTO: B000xxxx0 - * FAN SILENT: B000xxxx1 - * FAN3: B001xxxx0 - * FAN2: B010xxxx0 - * FAN1: B011xxxx0 + * POWER ON: 0b0xxxxx1x + * POWER OFF: 0b0xxxxx0x + * VERTICAL SWING: 0b0xxx01xx + * VERTICAL WIND: 0b0xxx00xx + * VERTICAL FIXED: 0b0xxx10xx + * HORIZONTAL SWING: 0b0xx0xxxx + * HORIZONTAL OFF: 0b0xx1xxxx + * FAN AUTO: 0b000xxxx0 + * FAN SILENT: 0b000xxxx1 + * FAN3: 0b001xxxx0 + * FAN2: 0b010xxxx0 + * FAN1: 0b011xxxx0 *******************************************************************************/ ZHLT01Template[7] = fanSpeed | powerMode | swingV | swingH; ZHLT01Template[6] = ~ ZHLT01Template[7]; /******************************************************************************** * Byte[09]: Mode, Temperature - * MODE AUTO: B000xxxxx - * MODE COOL: B001xxxxx - * MODE VENT: B011xxxxx - * MODE DRY: B010xxxxx - * MODE HEAT: B100xxxxx + * MODE AUTO: 0b000xxxxx + * MODE COOL: 0b001xxxxx + * MODE VENT: 0b011xxxxx + * MODE DRY: 0b010xxxxx + * MODE HEAT: 0b100xxxxx * Temperature is determined by bit0-4: * 0x00 = 16C * 0x10 = 32C diff --git a/lib/HeatpumpIR/ZHLT01HeatpumpIR.h b/lib/HeatpumpIR/ZHLT01HeatpumpIR.h index 33e9623e4..d402afd87 100644 --- a/lib/HeatpumpIR/ZHLT01HeatpumpIR.h +++ b/lib/HeatpumpIR/ZHLT01HeatpumpIR.h @@ -41,14 +41,14 @@ * Every EVEN Byte (00,02,04,06,08 and 10) holds a checksum of the corresponding * command-, or identifier-byte by _inverting_ the bits, for example: * - * The identifier byte[11] = 0xD5 = B1101 0101 - * The checksum byte[10] = 0x2A = B0010 1010 + * The identifier byte[11] = 0xD5 = 0b1101 0101 + * The checksum byte[10] = 0x2A = 0b0010 1010 * * So, you can check the message by: * - inverting the bits of the checksum byte with the corresponding command-, or * identifier byte, they should me the same, or * - Summing up the checksum byte and the corresponding command-, or identifier byte, - * they should always add up to 0xFF = B11111111 = 255 + * they should always add up to 0xFF = 0b11111111 = 255 * * Control bytes: * [01] - Timer (1-24 hours, Off) diff --git a/lib/ILI9488-jaretburkett/ILI9488.cpp b/lib/ILI9488-jaretburkett/ILI9488.cpp new file mode 100644 index 000000000..ac33d377a --- /dev/null +++ b/lib/ILI9488-jaretburkett/ILI9488.cpp @@ -0,0 +1,898 @@ +/*************************************************** + STM32 Support added by Jaret Burkett at OSHlab.com + + This is our library for the Adafruit ILI9488 Breakout and Shield + ----> http://www.adafruit.com/products/1651 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ + +#include "ILI9488.h" +#ifdef __AVR + # include +#elif defined(ESP8266) || defined(ESP32) + # include +#endif // ifdef __AVR + +#ifndef ARDUINO_STM32_FEATHER + # include "pins_arduino.h" + # include "wiring_private.h" +#endif // ifndef ARDUINO_STM32_FEATHER + +#include +#include + + +// If the SPI library has transaction support, these functions +// establish settings and protect from interference from other +// libraries. Otherwise, they simply do nothing. +#ifdef SPI_HAS_TRANSACTION +static inline void spi_begin(void) __attribute__((always_inline)); +static inline void spi_begin(void) { +# if defined(ARDUINO_ARCH_ARC32) + + // max speed! + SPI.beginTransaction(SPISettings(16000000, MSBFIRST, SPI_MODE0)); +# else // if defined(ARDUINO_ARCH_ARC32) + + // max speed! + SPI.beginTransaction(SPISettings(24000000, MSBFIRST, SPI_MODE0)); +# endif // if defined(ARDUINO_ARCH_ARC32) +} + +static inline void spi_end(void) __attribute__((always_inline)); +static inline void spi_end(void) { + SPI.endTransaction(); +} + +#else // ifdef SPI_HAS_TRANSACTION +# define spi_begin() +# define spi_end() +#endif // ifdef SPI_HAS_TRANSACTION + +// Constructor when using software SPI. All output pins are configurable. +ILI9488::ILI9488(int8_t cs, int8_t dc, int8_t mosi, + int8_t sclk, int8_t rst, int8_t miso) : Adafruit_GFX(ILI9488_TFTWIDTH, ILI9488_TFTHEIGHT) { + _cs = cs; + _dc = dc; + _mosi = mosi; + _miso = miso; + _sclk = sclk; + _rst = rst; + hwSPI = false; +} + +// Constructor when using hardware SPI. Faster, but must use SPI pins +// specific to each board type (e.g. 11,13 for Uno, 51,52 for Mega, etc.) +ILI9488::ILI9488(int8_t cs, int8_t dc, int8_t rst) : Adafruit_GFX(ILI9488_TFTWIDTH, ILI9488_TFTHEIGHT) { + _cs = cs; + _dc = dc; + _rst = rst; + hwSPI = true; + _mosi = _sclk = 0; +} + +void ILI9488::spiwrite(uint8_t c) { + // Serial.print("0x"); Serial.print(c, HEX); Serial.print(", "); + + if (hwSPI) { +#if defined(__AVR__) + # ifndef SPI_HAS_TRANSACTION + uint8_t backupSPCR = SPCR; + SPCR = mySPCR; + # endif // ifndef SPI_HAS_TRANSACTION + SPDR = c; + + while (!(SPSR & _BV(SPIF))) {} + # ifndef SPI_HAS_TRANSACTION + SPCR = backupSPCR; + # endif // ifndef SPI_HAS_TRANSACTION +#else // if defined(__AVR__) + SPI.transfer(c); +#endif // if defined(__AVR__) + } else { +#if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_ARC32) + + for (uint8_t bit = 0x80; bit; bit >>= 1) { + if (c & bit) { + digitalWrite(_mosi, HIGH); + } else { + digitalWrite(_mosi, LOW); + } + digitalWrite(_sclk, HIGH); + digitalWrite(_sclk, LOW); + } +#else // if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_ARC32) + + // Fast SPI bitbang swiped from LPD8806 library + for (uint8_t bit = 0x80; bit; bit >>= 1) { + if (c & bit) { + // digitalWrite(_mosi, HIGH); + *mosiport |= mosipinmask; + } else { + // digitalWrite(_mosi, LOW); + *mosiport &= ~mosipinmask; + } + + // digitalWrite(_sclk, HIGH); + *clkport |= clkpinmask; + + // digitalWrite(_sclk, LOW); + *clkport &= ~clkpinmask; + } +#endif // if defined(ESP8266) || defined(ESP32) || defined(ARDUINO_ARCH_ARC32) + } +} + +void ILI9488::writecommand(uint8_t c) { +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport &= ~dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, LOW); + digitalWrite(_sclk, LOW); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + spiwrite(c); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) +} + +void ILI9488::writedata(uint8_t c) { +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + spiwrite(c); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) +} + +// Rather than a bazillion writecommand() and writedata() calls, screen +// initialization commands and arguments are organized in these tables +// stored in PROGMEM. The table may look bulky, but that's mostly the +// formatting -- storage-wise this is hundreds of bytes more compact +// than the equivalent code. Companion function follows. +#define DELAY 0x80 + + +// Companion code to the above tables. Reads and issues +// a series of LCD commands stored in PROGMEM byte array. +void ILI9488::commandList(uint8_t *addr) { + uint8_t numCommands, numArgs; + uint16_t ms; + + numCommands = pgm_read_byte(addr++); // Number of commands to follow + + while (numCommands--) { // For each command... + writecommand(pgm_read_byte(addr++)); // Read, issue command + numArgs = pgm_read_byte(addr++); // Number of args to follow + ms = numArgs & DELAY; // If hibit set, delay follows args + numArgs &= ~DELAY; // Mask out delay bit + + while (numArgs--) { // For each argument... + writedata(pgm_read_byte(addr++)); // Read, issue argument + } + + if (ms) { + ms = pgm_read_byte(addr++); // Read post-command delay time (ms) + + if (ms == 255) { ms = 500; // If 255, delay for 500 ms + } + delay(ms); + } + } +} + +void ILI9488::begin(void) { + if (_rst > 0) { + pinMode(_rst, OUTPUT); + digitalWrite(_rst, LOW); + } + + pinMode(_dc, OUTPUT); + pinMode(_cs, OUTPUT); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + csport = portOutputRegister(digitalPinToPort(_cs)); + cspinmask = digitalPinToBitMask(_cs); + dcport = portOutputRegister(digitalPinToPort(_dc)); + dcpinmask = digitalPinToBitMask(_dc); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { // Using hardware SPI + SPI.begin(); + +#ifndef SPI_HAS_TRANSACTION + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + # if defined(_AVR__) + SPI.setClockDivider(SPI_CLOCK_DIV2); // 8 MHz (full! speed!) + mySPCR = SPCR; + # elif defined(TEENSYDUINO) || defined(__STM32F1__) + SPI.setClockDivider(SPI_CLOCK_DIV2); // 8 MHz (full! speed!) + # elif defined(__arm__) + SPI.setClockDivider(11); // 8-ish MHz (full! speed!) + # endif // if defined(_AVR__) +#endif // ifndef SPI_HAS_TRANSACTION + } else { + pinMode(_sclk, OUTPUT); + pinMode(_mosi, OUTPUT); + pinMode(_miso, INPUT); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + clkport = portOutputRegister(digitalPinToPort(_sclk)); + clkpinmask = digitalPinToBitMask(_sclk); + mosiport = portOutputRegister(digitalPinToPort(_mosi)); + mosipinmask = digitalPinToBitMask(_mosi); + *clkport &= ~clkpinmask; + *mosiport &= ~mosipinmask; +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + } + + // toggle RST low to reset + if (_rst > 0) { + digitalWrite(_rst, HIGH); + delay(5); + digitalWrite(_rst, LOW); + delay(20); + digitalWrite(_rst, HIGH); + delay(150); + } + + /* + uint8_t x = readcommand8(ILI9488_RDMODE); + Serial.print("\nDisplay Power Mode: 0x"); Serial.println(x, HEX); + x = readcommand8(ILI9488_RDMADCTL); + Serial.print("\nMADCTL Mode: 0x"); Serial.println(x, HEX); + x = readcommand8(ILI9488_RDPIXFMT); + Serial.print("\nPixel Format: 0x"); Serial.println(x, HEX); + x = readcommand8(ILI9488_RDIMGFMT); + Serial.print("\nImage Format: 0x"); Serial.println(x, HEX); + x = readcommand8(ILI9488_RDSELFDIAG); + Serial.print("\nSelf Diagnostic: 0x"); Serial.println(x, HEX); + */ + + // if(cmdList) commandList(cmdList); + + if (hwSPI) { spi_begin(); } + + writecommand(ILI9488_GMCTRP1); + writedata(0x00); + writedata(0x03); + writedata(0x09); + writedata(0x08); + writedata(0x16); + writedata(0x0A); + writedata(0x3F); + writedata(0x78); + writedata(0x4C); + writedata(0x09); + writedata(0x0A); + writedata(0x08); + writedata(0x16); + writedata(0x1A); + writedata(0x0F); + + + writecommand(ILI9488_GMCTRN1); + writedata(0x00); + writedata(0x16); + writedata(0x19); + writedata(0x03); + writedata(0x0F); + writedata(0x05); + writedata(0x32); + writedata(0x45); + writedata(0x46); + writedata(0x04); + writedata(0x0E); + writedata(0x0D); + writedata(0x35); + writedata(0x37); + writedata(0x0F); + + + writecommand(ILI9488_PWCTR1); // Power Control 1 + writedata(0x17); // Vreg1out + writedata(0x15); // Verg2out + + writecommand(ILI9488_PWCTR2); // Power Control 2 + writedata(0x41); // VGH,VGL + + writecommand(ILI9488_VMCTR1); // Power Control 3 + writedata(0x00); + writedata(0x12); // Vcom + writedata(0x80); + + writecommand(ILI9488_MADCTL); // Memory Access + writedata(0x48); + + writecommand(ILI9488_PIXFMT); // Interface Pixel Format + writedata(0x66); // 18 bit + + writecommand(0XB0); // Interface Mode Control + writedata(0x80); // SDO NOT USE + + writecommand(ILI9488_FRMCTR1); // Frame rate + writedata(0xA0); // 60Hz + + writecommand(ILI9488_INVCTR); // Display Inversion Control + writedata(0x02); // 2-dot + + writecommand(ILI9488_DFUNCTR); // Display Function Control RGB/MCU Interface Control + writedata(0x02); // MCU + writedata(0x02); // Source,Gate scan dieection + + writecommand(0XE9); // Set Image Functio + writedata(0x00); // Disable 24 bit data + + writecommand(0xF7); // Adjust Control + writedata(0xA9); + writedata(0x51); + writedata(0x2C); + writedata(0x82); // D7 stream, loose + + + writecommand(ILI9488_SLPOUT); // Exit Sleep + + if (hwSPI) { spi_end(); } + delay(120); + + if (hwSPI) { spi_begin(); } + writecommand(ILI9488_DISPON); // Display on + + if (hwSPI) { spi_end(); } +} + +void ILI9488::setScrollArea(uint16_t topFixedArea, uint16_t bottomFixedArea) { + if (hwSPI) { spi_begin(); } + writecommand(0x33); // Vertical scroll definition + writedata(topFixedArea >> 8); + writedata(topFixedArea); + writedata((_height - topFixedArea - bottomFixedArea) >> 8); + writedata(_height - topFixedArea - bottomFixedArea); + writedata(bottomFixedArea >> 8); + writedata(bottomFixedArea); + + if (hwSPI) { spi_end(); } +} + +void ILI9488::scroll(uint16_t pixels) { + if (hwSPI) { spi_begin(); } + writecommand(0x37); // Vertical scrolling start address + writedata(pixels >> 8); + writedata(pixels); + + if (hwSPI) { spi_end(); } +} + +void ILI9488::setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, + uint16_t y1) { + writecommand(ILI9488_CASET); // Column addr set + writedata(x0 >> 8); + writedata(x0 & 0xFF); // XSTART + writedata(x1 >> 8); + writedata(x1 & 0xFF); // XEND + + writecommand(ILI9488_PASET); // Row addr set + writedata(y0 >> 8); + writedata(y0 & 0xff); // YSTART + writedata(y1 >> 8); + writedata(y1 & 0xff); // YEND + + writecommand(ILI9488_RAMWR); // write to RAM +} + +void ILI9488::drawImage(const uint8_t *img, uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + // rudimentary clipping (drawChar w/big text requires this) + if ((x >= _width) || (y >= _height)) { return; } + + if ((x + w - 1) >= _width) { w = _width - x; } + + if ((y + h - 1) >= _height) { h = _height - y; } + + if (hwSPI) { spi_begin(); } + setAddrWindow(x, y, x + w - 1, y + h - 1); + + // uint8_t hi = color >> 8, lo = color; + + #if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; + #else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); + #endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + uint8_t linebuff[w * 3 + 1]; + + uint32_t count = 0; + + for (uint16_t i = 0; i < h; i++) { + uint16_t pixcount = 0; + + for (uint16_t o = 0; o < w; o++) { + uint8_t b1 = img[count]; + count++; + uint8_t b2 = img[count]; + count++; + uint16_t color = b1 << 8 | b2; + linebuff[pixcount] = (((color & 0xF800) >> 11) * 255) / 31; + pixcount++; + linebuff[pixcount] = (((color & 0x07E0) >> 5) * 255) / 63; + pixcount++; + linebuff[pixcount] = ((color & 0x001F) * 255) / 31; + pixcount++; + } // for row + #if defined(__STM32F1__) + SPI.dmaSend(linebuff, w * 3); + #else // if defined(__STM32F1__) + + for (uint16_t b = 0; b < w * 3; b++) { + spiwrite(linebuff[b]); + } + #endif // if defined(__STM32F1__) + } // for col + #if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; + #else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); + #endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::pushColor(uint16_t color) { + if (hwSPI) { spi_begin(); } + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + // spiwrite(color >> 8); + // spiwrite(color); + // spiwrite(0); // added for 24 bit + write16BitColor(color); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::pushColors(uint16_t *data, uint8_t len, boolean first) { + uint16_t color; + uint8_t buff[len * 3 + 1]; + uint16_t count = 0; + uint8_t lencount = len; + + if (hwSPI) { spi_begin(); } + #if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport &= ~cspinmask; + #else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, LOW); + #endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (first == true) { // Issue GRAM write command only on first call + #if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + #else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + #endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + } + + while (lencount--) { + color = *data++; + buff[count] = (((color & 0xF800) >> 11) * 255) / 31; + count++; + buff[count] = (((color & 0x07E0) >> 5) * 255) / 63; + count++; + buff[count] = ((color & 0x001F) * 255) / 31; + count++; + } + #if defined(__STM32F1__) + SPI.dmaSend(buff, len * 3); + #else // if defined(__STM32F1__) + + for (uint16_t b = 0; b < len * 3; b++) { + spiwrite(buff[b]); + } + #endif // if defined(__STM32F1__) + #if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; + #else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); + #endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::write16BitColor(uint16_t color) { + // #if (__STM32F1__) + // uint8_t buff[4] = { + // (((color & 0xF800) >> 11)* 255) / 31, + // (((color & 0x07E0) >> 5) * 255) / 63, + // ((color & 0x001F)* 255) / 31 + // }; + // SPI.dmaSend(buff, 3); + // #else + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + + spiwrite(r); + spiwrite(g); + spiwrite(b); + + // #endif +} + +void ILI9488::drawPixel(int16_t x, int16_t y, uint16_t color) { + if ((x < 0) || (x >= _width) || (y < 0) || (y >= _height)) { return; } + + if (hwSPI) { spi_begin(); } + setAddrWindow(x, y, x + 1, y + 1); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + // spiwrite(color >> 8); + // spiwrite(color); + // spiwrite(0); // added for 24 bit + write16BitColor(color); + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::drawFastVLine(int16_t x, int16_t y, int16_t h, + uint16_t color) { + // Rudimentary clipping + if ((x >= _width) || (y >= _height)) { return; } + + if ((y + h - 1) >= _height) { + h = _height - y; + } + + if (hwSPI) { spi_begin(); } + setAddrWindow(x, y, x, y + h - 1); + + // uint8_t hi = color >> 8, lo = color; + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + while (h--) { + // spiwrite(hi); + // spiwrite(lo); + // spiwrite(0); // added for 24 bit + write16BitColor(color); + } + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::drawFastHLine(int16_t x, int16_t y, int16_t w, + uint16_t color) { + // Rudimentary clipping + if ((x >= _width) || (y >= _height)) { return; } + + if ((x + w - 1) >= _width) { w = _width - x; } + + if (hwSPI) { spi_begin(); } + setAddrWindow(x, y, x + w - 1, y); + + // uint8_t hi = color >> 8, lo = color; +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + while (w--) { + // spiwrite(hi); + // spiwrite(lo); + // spiwrite(0); // added for 24 bit + write16BitColor(color); + } +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +void ILI9488::fillScreen(uint16_t color) { + fillRect(0, 0, _width, _height, color); +} + +// fill a rectangle +void ILI9488::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) { + // rudimentary clipping (drawChar w/big text requires this) + if ((x >= _width) || (y >= _height)) { return; } + + if ((x + w - 1) >= _width) { w = _width - x; } + + if ((y + h - 1) >= _height) { h = _height - y; } + + if (hwSPI) { spi_begin(); } + setAddrWindow(x, y, x + w - 1, y + h - 1); + + // uint8_t hi = color >> 8, lo = color; + +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *dcport |= dcpinmask; + *csport &= ~cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) +#if (__STM32F1__) + + // use dma fast fills + uint8_t buff[4] = { + (((color & 0xF800) >> 11) * 255) / 31, + (((color & 0x07E0) >> 5) * 255) / 63, + ((color & 0x001F) * 255) / 31 + }; + uint8_t linebuff[w * 3 + 1]; + int cnt = 0; + + for (int i = 0; i < w; i++) { + linebuff[cnt] = buff[0]; + cnt++; + linebuff[cnt] = buff[1]; + cnt++; + linebuff[cnt] = buff[2]; + cnt++; + } + + for (y = h; y > 0; y--) { + SPI.dmaSend(linebuff, w * 3); + } +#else // if (__STM32F1__) + + for (y = h; y > 0; y--) { + for (x = w; x > 0; x--) { + // spiwrite(hi); + // spiwrite(lo); + // spiwrite(0); // added for 24 bit + write16BitColor(color); + } + } +#endif // if (__STM32F1__) +#if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + *csport |= cspinmask; +#else // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + digitalWrite(_cs, HIGH); +#endif // if defined(USE_FAST_PINIO) && !defined(_VARIANT_ARDUINO_STM32_) + + if (hwSPI) { spi_end(); } +} + +// Pass 8-bit (each) R,G,B, get back 16-bit packed color +uint16_t ILI9488::color565(uint8_t r, uint8_t g, uint8_t b) { + return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); +} + +#define MADCTL_MY 0x80 +#define MADCTL_MX 0x40 +#define MADCTL_MV 0x20 +#define MADCTL_ML 0x10 +#define MADCTL_RGB 0x00 +#define MADCTL_BGR 0x08 +#define MADCTL_MH 0x04 + +void ILI9488::setRotation(uint8_t m) { + if (hwSPI) { spi_begin(); } + writecommand(ILI9488_MADCTL); + rotation = m % 4; // can't be higher than 3 + + switch (rotation) { + case 0: + writedata(MADCTL_MX | MADCTL_BGR); + _width = ILI9488_TFTWIDTH; + _height = ILI9488_TFTHEIGHT; + break; + case 1: + writedata(MADCTL_MV | MADCTL_BGR); + _width = ILI9488_TFTHEIGHT; + _height = ILI9488_TFTWIDTH; + break; + case 2: + writedata(MADCTL_MY | MADCTL_BGR); + _width = ILI9488_TFTWIDTH; + _height = ILI9488_TFTHEIGHT; + break; + case 3: + writedata(MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR); + _width = ILI9488_TFTHEIGHT; + _height = ILI9488_TFTWIDTH; + break; + } + + if (hwSPI) { spi_end(); } +} + +void ILI9488::invertDisplay(boolean i) { + if (hwSPI) { spi_begin(); } + writecommand(i ? ILI9488_INVON : ILI9488_INVOFF); + + if (hwSPI) { spi_end(); } +} + +////////// stuff not actively being used, but kept for posterity + + +uint8_t ILI9488::spiread(void) { + uint8_t r = 0; + + if (hwSPI) { +#if defined(__AVR__) + # ifndef SPI_HAS_TRANSACTION + uint8_t backupSPCR = SPCR; + SPCR = mySPCR; + # endif // ifndef SPI_HAS_TRANSACTION + SPDR = 0x00; + + while (!(SPSR & _BV(SPIF))) {} + r = SPDR; + + # ifndef SPI_HAS_TRANSACTION + SPCR = backupSPCR; + # endif // ifndef SPI_HAS_TRANSACTION +#else // if defined(__AVR__) + r = SPI.transfer(0x00); +#endif // if defined(__AVR__) + } else { + for (uint8_t i = 0; i < 8; i++) { + digitalWrite(_sclk, LOW); + digitalWrite(_sclk, HIGH); + r <<= 1; + + if (digitalRead(_miso)) { + r |= 0x1; + } + } + } + + // Serial.print("read: 0x"); Serial.print(r, HEX); + + return r; +} + +uint8_t ILI9488::readdata(void) { + digitalWrite(_dc, HIGH); + digitalWrite(_cs, LOW); + uint8_t r = spiread(); + + digitalWrite(_cs, HIGH); + + return r; +} + +uint8_t ILI9488::readcommand8(uint8_t c, uint8_t index) { + if (hwSPI) { spi_begin(); } + digitalWrite(_dc, LOW); // command + digitalWrite(_cs, LOW); + spiwrite(0xD9); // woo sekret command? + digitalWrite(_dc, HIGH); // data + spiwrite(0x10 + index); + digitalWrite(_cs, HIGH); + + digitalWrite(_dc, LOW); + digitalWrite(_sclk, LOW); + digitalWrite(_cs, LOW); + spiwrite(c); + + digitalWrite(_dc, HIGH); + uint8_t r = spiread(); + + digitalWrite(_cs, HIGH); + + if (hwSPI) { spi_end(); } + return r; +} + +/* + + uint16_t ILI9488::readcommand16(uint8_t c) { + digitalWrite(_dc, LOW); + if (_cs) + digitalWrite(_cs, LOW); + + spiwrite(c); + pinMode(_sid, INPUT); // input! + uint16_t r = spiread(); + r <<= 8; + r |= spiread(); + if (_cs) + digitalWrite(_cs, HIGH); + + pinMode(_sid, OUTPUT); // back to output + return r; + } + + uint32_t ILI9488::readcommand32(uint8_t c) { + digitalWrite(_dc, LOW); + if (_cs) + digitalWrite(_cs, LOW); + spiwrite(c); + pinMode(_sid, INPUT); // input! + + dummyclock(); + dummyclock(); + + uint32_t r = spiread(); + r <<= 8; + r |= spiread(); + r <<= 8; + r |= spiread(); + r <<= 8; + r |= spiread(); + if (_cs) + digitalWrite(_cs, HIGH); + + pinMode(_sid, OUTPUT); // back to output + return r; + } + + */ diff --git a/lib/ILI9488-jaretburkett/ILI9488.h b/lib/ILI9488-jaretburkett/ILI9488.h new file mode 100644 index 000000000..613755c29 --- /dev/null +++ b/lib/ILI9488-jaretburkett/ILI9488.h @@ -0,0 +1,210 @@ +/*************************************************** + STM32 Support added by Jaret Burkett at OSHlab.com + 2024-06-23: ESP32 Support added by Ton Huisman for ESPEasy + + This is our library for the Adafruit ILI9488 Breakout and Shield + ----> http://www.adafruit.com/products/1651 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ + +#ifndef _ILI9488H_ +#define _ILI9488H_ + +#if ARDUINO >= 100 + # include "Arduino.h" + # include "Print.h" +#else // if ARDUINO >= 100 + # include "WProgram.h" +#endif // if ARDUINO >= 100 +#include +#ifdef __AVR + # include +#elif defined(ESP8266) || defined(ESP32) + # include +#endif // ifdef __AVR + +#ifdef ARDUINO_STM32_FEATHER +typedef volatile uint32 RwReg; +#endif // ifdef ARDUINO_STM32_FEATHER +#if defined(__AVR__) || defined(TEENSYDUINO) || defined(__arm__) || defined(__STM32F1__) +# define USE_FAST_PINIO +#endif // if defined(__AVR__) || defined(TEENSYDUINO) || defined(__arm__) || defined(__STM32F1__) + +#define ILI9488_TFTWIDTH 320 +#define ILI9488_TFTHEIGHT 480 + +#define ILI9488_NOP 0x00 +#define ILI9488_SWRESET 0x01 +#define ILI9488_RDDID 0x04 +#define ILI9488_RDDST 0x09 + +#define ILI9488_SLPIN 0x10 +#define ILI9488_SLPOUT 0x11 +#define ILI9488_PTLON 0x12 +#define ILI9488_NORON 0x13 + +#define ILI9488_RDMODE 0x0A +#define ILI9488_RDMADCTL 0x0B +#define ILI9488_RDPIXFMT 0x0C +#define ILI9488_RDIMGFMT 0x0D +#define ILI9488_RDSELFDIAG 0x0F + +#define ILI9488_INVOFF 0x20 +#define ILI9488_INVON 0x21 +#define ILI9488_GAMMASET 0x26 +#define ILI9488_DISPOFF 0x28 +#define ILI9488_DISPON 0x29 + +#define ILI9488_CASET 0x2A +#define ILI9488_PASET 0x2B +#define ILI9488_RAMWR 0x2C +#define ILI9488_RAMRD 0x2E + +#define ILI9488_PTLAR 0x30 +#define ILI9488_MADCTL 0x36 +#define ILI9488_PIXFMT 0x3A + +#define ILI9488_FRMCTR1 0xB1 +#define ILI9488_FRMCTR2 0xB2 +#define ILI9488_FRMCTR3 0xB3 +#define ILI9488_INVCTR 0xB4 +#define ILI9488_DFUNCTR 0xB6 + +#define ILI9488_PWCTR1 0xC0 +#define ILI9488_PWCTR2 0xC1 +#define ILI9488_PWCTR3 0xC2 +#define ILI9488_PWCTR4 0xC3 +#define ILI9488_PWCTR5 0xC4 +#define ILI9488_VMCTR1 0xC5 +#define ILI9488_VMCTR2 0xC7 + +#define ILI9488_RDID1 0xDA +#define ILI9488_RDID2 0xDB +#define ILI9488_RDID3 0xDC +#define ILI9488_RDID4 0xDD + +#define ILI9488_GMCTRP1 0xE0 +#define ILI9488_GMCTRN1 0xE1 + +/* + #define ILI9488_PWCTR6 0xFC + + */ + +// Color definitions +#define ILI9488_BLACK 0x0000 /* 0, 0, 0 */ +#define ILI9488_NAVY 0x000F /* 0, 0, 128 */ +#define ILI9488_DARKGREEN 0x03E0 /* 0, 128, 0 */ +#define ILI9488_DARKCYAN 0x03EF /* 0, 128, 128 */ +#define ILI9488_MAROON 0x7800 /* 128, 0, 0 */ +#define ILI9488_PURPLE 0x780F /* 128, 0, 128 */ +#define ILI9488_OLIVE 0x7BE0 /* 128, 128, 0 */ +#define ILI9488_LIGHTGREY 0xC618 /* 192, 192, 192 */ +#define ILI9488_DARKGREY 0x7BEF /* 128, 128, 128 */ +#define ILI9488_BLUE 0x001F /* 0, 0, 255 */ +#define ILI9488_GREEN 0x07E0 /* 0, 255, 0 */ +#define ILI9488_CYAN 0x07FF /* 0, 255, 255 */ +#define ILI9488_RED 0xF800 /* 255, 0, 0 */ +#define ILI9488_MAGENTA 0xF81F /* 255, 0, 255 */ +#define ILI9488_YELLOW 0xFFE0 /* 255, 255, 0 */ +#define ILI9488_WHITE 0xFFFF /* 255, 255, 255 */ +#define ILI9488_ORANGE 0xFD20 /* 255, 165, 0 */ +#define ILI9488_GREENYELLOW 0xAFE5 /* 173, 255, 47 */ +#define ILI9488_PINK 0xF81F + +class ILI9488 : public Adafruit_GFX { +public: + + ILI9488(int8_t _CS, + int8_t _DC, + int8_t _MOSI, + int8_t _SCLK, + int8_t _RST, + int8_t _MISO); + ILI9488(int8_t _CS, + int8_t _DC, + int8_t _RST = -1); + + virtual ~ILI9488() {} + + void begin(void), + setAddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1), + setScrollArea(uint16_t topFixedArea, uint16_t bottomFixedArea), + scroll(uint16_t pixels), + pushColor(uint16_t color), + pushColors(uint16_t * data, uint8_t len, boolean first), + drawImage(const uint8_t * img, uint16_t x, uint16_t y, uint16_t w, uint16_t h), + fillScreen(uint16_t color), + drawPixel(int16_t x, int16_t y, uint16_t color), + drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color), + drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color), + fillRect(int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color), + setRotation(uint8_t r), + invertDisplay(boolean i); + uint16_t color565(uint8_t r, + uint8_t g, + uint8_t b); + + /* These are not for current use, 8-bit protocol only! */ + uint8_t readdata(void), + readcommand8(uint8_t reg, uint8_t index = 0); + + /* + uint16_t readcommand16(uint8_t); + uint32_t readcommand32(uint8_t); + void dummyclock(void); + */ + + void spiwrite(uint8_t), + writecommand(uint8_t c), + write16BitColor(uint16_t color), + writedata(uint8_t d), + commandList(uint8_t * addr); + uint8_t spiread(void); + +private: + + uint8_t tabcolor; + + + boolean hwSPI; + +#if defined(__AVR__) || defined(TEENSYDUINO) + uint8_t mySPCR; + volatile uint8_t *mosiport, *clkport, *dcport, *rsport, *csport; + int8_t _cs, _dc, _rst, _mosi, _miso, _sclk; + uint8_t mosipinmask, clkpinmask, cspinmask, dcpinmask; + + ////This def is for the Arduino.ORG M0!!! + // #elif defined(ARDUINO_SAM_ZERO) + // volatile PORT_OUT_Type *mosiport, *clkport, *dcport, *rsport, *csport; + // int32_t _cs, _dc, _rst, _mosi, _miso, _sclk; + // PORT_OUT_Type mosipinmask, clkpinmask, cspinmask, dcpinmask; +#elif defined(__STM32F1__) || defined(_VARIANT_ARDUINO_STM32_) || defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || \ + defined(STM32F103xE) || defined(STM32F103xG) || defined(STM32F105xC) || defined(STM32F107xC) + uint8_t mySPCR; + volatile uint32_t *mosiport, *clkport, *dcport, *rsport, *csport; + int32_t _cs, _dc, _rst, _mosi, _miso, _sclk; + uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask; +#elif defined(__arm__) + volatile RwReg *mosiport, *clkport, *dcport, *rsport, *csport; + int32_t _cs, _dc, _rst, _mosi, _miso, _sclk; + uint32_t mosipinmask, clkpinmask, cspinmask, dcpinmask; +#elif defined(ESP8266) || defined(ESP32) + int32_t _cs, _dc, _rst, _mosi, _miso, _sclk; +#else // if defined(__AVR__) || defined(TEENSYDUINO) + int8_t _cs, _dc, _rst, _mosi, _miso, _sclk; +#endif // if defined(__AVR__) || defined(TEENSYDUINO) +}; + +#endif // ifndef _ILI9488H_ diff --git a/lib/ILI9488-jaretburkett/README.md b/lib/ILI9488-jaretburkett/README.md new file mode 100644 index 000000000..fd203455b --- /dev/null +++ b/lib/ILI9488-jaretburkett/README.md @@ -0,0 +1,8 @@ +### ILI9488 Arduino Library +This library is for support for the 320x480 tft controller over 4 wire SPI. It is based heavily on the [Adafruit_ILI9341](https://github.com/adafruit/Adafruit_ILI9341) library and is designed to work with the [Adafruit_GFX library](https://github.com/adafruit/Adafruit-GFX-Library). + +I have made some heavy modifications, as the typical Adafruit TFT libraries are designed to work with 16bit color (RGB565), and the ILI9488 can only do 24bit (RGB888) color in 4 wire SPI mode. You can still use the library EXACTLY like you would for 16bit mode color, the colors are converted before sending to the display. What this means is, things will be slower than normal. Not only do you have to write twice as many pixels as a normal 240x320 display, 153,600px (320x480) vs 76,800px (240x320), but you also have to do a lightweight conversion on each color, and write 3 bytes vs 2bytes per pixel. + +For this reason, I do not recommend an AVR based Arduino for this library, although it will still work. I highly recommend a faster microcontroller based on ARM such as the Teensy, [STM32duino](https://github.com/rogerclarkmelbourne/Arduino_STM32), Arduino Zero, or the Arduing Due. + +On the STM32duino, DMA is supported and is therefore much faster. \ No newline at end of file diff --git a/lib/ILI9488-jaretburkett/examples/graphicstest/graphicstest.ino b/lib/ILI9488-jaretburkett/examples/graphicstest/graphicstest.ino new file mode 100644 index 000000000..30b0cbd9a --- /dev/null +++ b/lib/ILI9488-jaretburkett/examples/graphicstest/graphicstest.ino @@ -0,0 +1,350 @@ +/*************************************************** + This is our GFX example for the Adafruit ILI9488 Breakout and Shield + ----> http://www.adafruit.com/products/1651 + + Check out the links above for our tutorials and wiring diagrams + These displays use SPI to communicate, 4 or 5 pins are required to + interface (RST is optional) + Adafruit invests time and resources providing this open source code, + please support Adafruit and open-source hardware by purchasing + products from Adafruit! + + Written by Limor Fried/Ladyada for Adafruit Industries. + MIT license, all text above must be included in any redistribution + ****************************************************/ + + +#include "SPI.h" +#include +#include + +#define TFT_CS PA1 +#define TFT_DC PB3 +#define TFT_LED PB0 +#define TFT_RST PB4 + +// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC +ILI9488 tft = ILI9488(TFT_CS, TFT_DC, TFT_RST); +// If using the breakout, change pins as desired +//Adafruit_ILI9488 tft = Adafruit_ILI9488(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO); + +void setup() { + Serial.begin(9600); + Serial.println("ILI9488 Test!"); + + tft.begin(); + + // read diagnostics (optional but can help debug problems) + uint8_t x = tft.readcommand8(ILI9488_RDMODE); + Serial.print("Display Power Mode: 0x"); Serial.println(x, HEX); + x = tft.readcommand8(ILI9488_RDMADCTL); + Serial.print("MADCTL Mode: 0x"); Serial.println(x, HEX); + x = tft.readcommand8(ILI9488_RDPIXFMT); + Serial.print("Pixel Format: 0x"); Serial.println(x, HEX); + x = tft.readcommand8(ILI9488_RDIMGFMT); + Serial.print("Image Format: 0x"); Serial.println(x, HEX); + x = tft.readcommand8(ILI9488_RDSELFDIAG); + Serial.print("Self Diagnostic: 0x"); Serial.println(x, HEX); + + Serial.println(F("Benchmark Time (microseconds)")); + + Serial.print(F("Screen fill ")); + Serial.println(testFillScreen()); + delay(500); + + Serial.print(F("Text ")); + Serial.println(testText()); + delay(3000); + + Serial.print(F("Lines ")); + Serial.println(testLines(ILI9488_CYAN)); + delay(500); + + Serial.print(F("Horiz/Vert Lines ")); + Serial.println(testFastLines(ILI9488_RED, ILI9488_BLUE)); + delay(500); + + Serial.print(F("Rectangles (outline) ")); + Serial.println(testRects(ILI9488_GREEN)); + delay(500); + + Serial.print(F("Rectangles (filled) ")); + Serial.println(testFilledRects(ILI9488_YELLOW, ILI9488_MAGENTA)); + delay(500); + + Serial.print(F("Circles (filled) ")); + Serial.println(testFilledCircles(10, ILI9488_MAGENTA)); + + Serial.print(F("Circles (outline) ")); + Serial.println(testCircles(10, ILI9488_WHITE)); + delay(500); + + Serial.print(F("Triangles (outline) ")); + Serial.println(testTriangles()); + delay(500); + + Serial.print(F("Triangles (filled) ")); + Serial.println(testFilledTriangles()); + delay(500); + + Serial.print(F("Rounded rects (outline) ")); + Serial.println(testRoundRects()); + delay(500); + + Serial.print(F("Rounded rects (filled) ")); + Serial.println(testFilledRoundRects()); + delay(500); + + Serial.println(F("Done!")); + +} + + +void loop(void) { + for(uint8_t rotation=0; rotation<4; rotation++) { + tft.setRotation(rotation); + testText(); + delay(1000); + } +} + +unsigned long testFillScreen() { + unsigned long start = micros(); + tft.fillScreen(ILI9488_BLACK); + tft.fillScreen(ILI9488_RED); + tft.fillScreen(ILI9488_GREEN); + tft.fillScreen(ILI9488_BLUE); + tft.fillScreen(ILI9488_BLACK); + return micros() - start; +} + +unsigned long testText() { + tft.fillScreen(ILI9488_BLACK); + unsigned long start = micros(); + tft.setCursor(0, 0); + tft.setTextColor(ILI9488_WHITE); tft.setTextSize(1); + tft.println("Hello World!"); + tft.setTextColor(ILI9488_YELLOW); tft.setTextSize(2); + tft.println(1234.56); + tft.setTextColor(ILI9488_RED); tft.setTextSize(3); + tft.println(0xDEADBEEF, HEX); + tft.println(); + tft.setTextColor(ILI9488_GREEN); + tft.setTextSize(5); + tft.println("Groop"); + tft.setTextSize(2); + tft.println("I implore thee,"); + tft.setTextSize(1); + tft.println("my foonting turlingdromes."); + tft.println("And hooptiously drangle me"); + tft.println("with crinkly bindlewurdles,"); + tft.println("Or I will rend thee"); + tft.println("in the gobberwarts"); + tft.println("with my blurglecruncheon,"); + tft.println("see if I don't!"); + return micros() - start; +} + +unsigned long testLines(uint16_t color) { + unsigned long start, t; + int x1, y1, x2, y2, + w = tft.width(), + h = tft.height(); + + tft.fillScreen(ILI9488_BLACK); + + x1 = y1 = 0; + y2 = h - 1; + start = micros(); + for(x2=0; x20; i-=6) { + i2 = i / 2; + start = micros(); + tft.fillRect(cx-i2, cy-i2, i, i, color1); + t += micros() - start; + // Outlines are not included in timing results + tft.drawRect(cx-i2, cy-i2, i, i, color2); + } + + return t; +} + +unsigned long testFilledCircles(uint8_t radius, uint16_t color) { + unsigned long start; + int x, y, w = tft.width(), h = tft.height(), r2 = radius * 2; + + tft.fillScreen(ILI9488_BLACK); + start = micros(); + for(x=radius; x10; i-=5) { + start = micros(); + tft.fillTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, + tft.color565(0, i, i)); + t += micros() - start; + tft.drawTriangle(cx, cy - i, cx - i, cy + i, cx + i, cy + i, + tft.color565(i, i, 0)); + } + + return t; +} + +unsigned long testRoundRects() { + unsigned long start; + int w, i, i2, + cx = tft.width() / 2 - 1, + cy = tft.height() / 2 - 1; + + tft.fillScreen(ILI9488_BLACK); + w = min(tft.width(), tft.height()); + start = micros(); + for(i=0; i20; i-=6) { + i2 = i / 2; + tft.fillRoundRect(cx-i2, cy-i2, i, i, i/8, tft.color565(0, i, 0)); + } + + return micros() - start; +} diff --git a/lib/ILI9488-jaretburkett/keywords.txt b/lib/ILI9488-jaretburkett/keywords.txt new file mode 100644 index 000000000..44c83b06f --- /dev/null +++ b/lib/ILI9488-jaretburkett/keywords.txt @@ -0,0 +1,30 @@ +####################################### +# Syntax Coloring Map +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +ILI9488 KEYWORD1 + + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +setRotation KEYWORD2 +setAddrWindow KEYWORD2 +pushColor KEYWORD2 +drawPixel KEYWORD2 +drawFastVLine KEYWORD2 +drawFastHLine KEYWORD2 +fillRect KEYWORD2 +setRotation KEYWORD2 +setRotation KEYWORD2 +height KEYWORD2 +width KEYWORD2 +invertDisplay KEYWORD2 +drawImage KEYWORD2 +setScrollArea KEYWORD2 +scroll KEYWORD2 diff --git a/lib/ILI9488-jaretburkett/library.properties b/lib/ILI9488-jaretburkett/library.properties new file mode 100644 index 000000000..a8ae043a7 --- /dev/null +++ b/lib/ILI9488-jaretburkett/library.properties @@ -0,0 +1,9 @@ +name=ILI9488 +version=1.0.2 +author=Jaret Burkett +maintainer=Jaret Burkett +sentence=Library for ILI9488 displays +paragraph=Library for ILI9488 displays +category=Display +url=https://github.com/jaretburkett/ILI9488 +architectures=* diff --git a/lib/IRremoteESP8266/examples/LGACSend/LGACSend.ino b/lib/IRremoteESP8266/examples/LGACSend/LGACSend.ino index 9139983c9..1625232ee 100644 --- a/lib/IRremoteESP8266/examples/LGACSend/LGACSend.ino +++ b/lib/IRremoteESP8266/examples/LGACSend/LGACSend.ino @@ -75,7 +75,7 @@ void Ac_Activate(unsigned int temperature, unsigned int air_flow, // calculating using other values unsigned int ac_msbits7 = (ac_msbits3 + ac_msbits4 + ac_msbits5 + - ac_msbits6) & B00001111; + ac_msbits6) & 0b00001111; ac_code_to_sent = ac_msbits1 << 4; ac_code_to_sent = (ac_code_to_sent + ac_msbits2) << 4; ac_code_to_sent = (ac_code_to_sent + ac_msbits3) << 4; diff --git a/lib/IRremoteESP8266/src/ir_Gree.cpp b/lib/IRremoteESP8266/src/ir_Gree.cpp index 2c44cfe52..38c0325fc 100644 --- a/lib/IRremoteESP8266/src/ir_Gree.cpp +++ b/lib/IRremoteESP8266/src/ir_Gree.cpp @@ -715,7 +715,7 @@ bool IRrecv::decodeGree(decode_results* results, uint16_t offset, if (used == 0) return false; offset += used; - // Block #1 footer (3 bits, B010) + // Block #1 footer (3 bits, 0b010) match_result_t data_result; data_result = matchData(&(results->rawbuf[offset]), kGreeBlockFooterBits, kGreeBitMark, kGreeOneSpace, kGreeBitMark, diff --git a/lib/IRremoteESP8266/src/ir_Kelvinator.cpp b/lib/IRremoteESP8266/src/ir_Kelvinator.cpp index c44f7c683..9d23384db 100644 --- a/lib/IRremoteESP8266/src/ir_Kelvinator.cpp +++ b/lib/IRremoteESP8266/src/ir_Kelvinator.cpp @@ -540,7 +540,7 @@ bool IRrecv::decodeKelvinator(decode_results *results, uint16_t offset, offset += used; pos += 4; - // Command data footer (3 bits, B010) + // Command data footer (3 bits, 0b010) data_result = matchData( &(results->rawbuf[offset]), kKelvinatorCmdFooterBits, kKelvinatorBitMark, kKelvinatorOneSpace, diff --git a/lib/ImprovWiFi/src/ImprovTypes.h b/lib/ImprovWiFi/src/ImprovTypes.h index 1a8a6abeb..3e77a7cf3 100644 --- a/lib/ImprovWiFi/src/ImprovTypes.h +++ b/lib/ImprovWiFi/src/ImprovTypes.h @@ -32,6 +32,8 @@ enum Error : uint8_t { ERROR_UNKNOWN_RPC = 0x02, ERROR_UNABLE_TO_CONNECT = 0x03, ERROR_NOT_AUTHORIZED = 0x04, + ERROR_INVALID_CHECKSUM = 0x05, + ERROR_EMPTY_SSID = 0x06, ERROR_UNKNOWN = 0xFF, }; diff --git a/lib/ImprovWiFi/src/ImprovWiFiLibrary.cpp b/lib/ImprovWiFi/src/ImprovWiFiLibrary.cpp index 68e240f4c..790ab47f9 100644 --- a/lib/ImprovWiFi/src/ImprovWiFiLibrary.cpp +++ b/lib/ImprovWiFi/src/ImprovWiFiLibrary.cpp @@ -71,7 +71,7 @@ bool ImprovWiFi::onCommandCallback(ImprovTypes::ImprovCommand cmd) { if (cmd.ssid.empty()) { - setError(ImprovTypes::Error::ERROR_INVALID_RPC); + setError(ImprovTypes::Error::ERROR_EMPTY_SSID); break; } @@ -298,17 +298,19 @@ ImprovTypes::ParseState ImprovWiFi::parseImprovSerial(size_t position, uint8_t b if (position == (8u + data_len + 1u)) { + /* if (computeChecksum(buffer, position - 1) != byte) { _position = 0; - onErrorCallback(ImprovTypes::Error::ERROR_INVALID_RPC); + onErrorCallback(ImprovTypes::Error::ERROR_INVALID_CHECKSUM); return ImprovTypes::ParseState::INVALID; } + */ if (type == ImprovTypes::ImprovSerialType::TYPE_RPC) { _position = 0; - auto command = parseImprovData(&buffer[9], data_len, false); + auto command = parseImprovData(&buffer[9], data_len + 1, false); return onCommandCallback(command) ? ImprovTypes::ParseState::VALID_COMPLETE : ImprovTypes::ParseState::INVALID; } } @@ -332,19 +334,23 @@ ImprovTypes::ImprovCommand ImprovWiFi::parseImprovData(const uint8_t *data, size } const ImprovTypes::Command command = (ImprovTypes::Command)data[0]; const uint8_t data_length = data[1]; + const uint8_t data_start = 2; + const size_t data_end = data_start + data_length; +// if (data_end >= length) if (data_length != (length - 2 - check_checksum)) { - return improv_command; +// return improv_command; } if (check_checksum) { - const uint8_t checksum = data[length - 1]; + const uint8_t checksum = data[data_end]; - if (computeChecksum(data, length - 1) != checksum) + if (computeChecksum(data, data_end - 1) != checksum) { improv_command.command = ImprovTypes::Command::BAD_CHECKSUM; + setError(ImprovTypes::Error::ERROR_INVALID_CHECKSUM); return improv_command; } } @@ -363,7 +369,7 @@ ImprovTypes::ImprovCommand ImprovWiFi::parseImprovData(const uint8_t *data, size const size_t pass_start = ssid_end + 1; const size_t pass_end = pass_start + pass_length; - if (pass_end >= length) { + if (pass_end > length) { return improv_command; } @@ -394,6 +400,7 @@ void ImprovWiFi::setError(ImprovTypes::Error error) const std::vector response = { error }; send(ImprovTypes::TYPE_ERROR_STATE, response); + onErrorCallback(error); } void ImprovWiFi::sendResponse(const std::vector& response) diff --git a/lib/Itho/CC1101Packet.h b/lib/Itho/CC1101Packet.h index f96390d7e..1df185876 100644 --- a/lib/Itho/CC1101Packet.h +++ b/lib/Itho/CC1101Packet.h @@ -9,7 +9,7 @@ #include #define CC1101_BUFFER_LEN 64 -#define CC1101_DATA_LEN CC1101_BUFFER_LEN - 3 +#define CC1101_DATA_LEN (CC1101_BUFFER_LEN - 3) class CC1101Packet { diff --git a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h index f7c7d132c..5f82ae504 100755 --- a/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h +++ b/lib/LiquidCrystal_I2C/LiquidCrystal_I2C.h @@ -48,9 +48,9 @@ #define LCD_BACKLIGHT 0x08 #define LCD_NOBACKLIGHT 0x00 -#define En B00000100 // Enable bit -#define Rw B00000010 // Read/Write bit -#define Rs B00000001 // Register select bit +#define En 0b00000100 // Enable bit +#define Rw 0b00000010 // Read/Write bit +#define Rs 0b00000001 // Register select bit class LiquidCrystal_I2C : public Print { public: diff --git a/lib/NeoPixelBus_wrapper/README.md b/lib/NeoPixelBus_wrapper/README.md index 8e03098e2..f8d24776b 100644 --- a/lib/NeoPixelBus_wrapper/README.md +++ b/lib/NeoPixelBus_wrapper/README.md @@ -16,6 +16,7 @@ ### Limitations - Currently only supports the most commonly used NeoPixel stripes `NEO_GRB` and `NEO_GRBW`, and the default `NEO_KHZ800` method. (That's all what is used in ESPEasy...) +- When using an ESP8266 and the used GPIO pin is *not* `GPIO2`, you can enable `# define NEOPIXEL_WRAPPER_USE_ADAFRUIT` in `NeoPixelBus_wrapper.h`, but you'll then be using the Adafruit_NeoPixel library again, as that does allow to select the GPIO pin. The `Adafruit_NeoPixel` library then has to be available for compilation! ### Support diff --git a/lib/NeoPixelBus_wrapper/library.properties b/lib/NeoPixelBus_wrapper/library.properties index b00f7c338..532e818b5 100644 --- a/lib/NeoPixelBus_wrapper/library.properties +++ b/lib/NeoPixelBus_wrapper/library.properties @@ -7,4 +7,4 @@ paragraph=Arduino wrapper library for interfacing Adafruit_NeoPixel applications category=Display url=https://github.com/tonhuisman/NeoPixelBus_wrapper architectures=* -includes=NeoPixelBus by Makuna +includes=NeoPixelBus_wrapper.h diff --git a/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.cpp b/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.cpp index b05ad349b..df772ecbd 100644 --- a/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.cpp +++ b/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.cpp @@ -5,36 +5,46 @@ NeoPixelBus_wrapper::NeoPixelBus_wrapper(uint16_t _maxPixels, int16_t _gpioPin, neoPixelType _stripType) - : numLEDs(_maxPixels) { + #ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT + : numLEDs(_maxPixels) + #else // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT + : Adafruit_NeoPixel(_maxPixels, _gpioPin, _stripType) + #endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT +{ + #ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT + if (NEO_GRBW == (_stripType & NEO_GRBW)) { - #ifdef ESP8266 + # ifdef ESP8266 neopixels_grbw = new (std::nothrow) NEOPIXEL_LIB(_maxPixels); - #endif // ifdef ESP8266 - #ifdef ESP32 + # endif // ifdef ESP8266 + # ifdef ESP32 neopixels_grbw = new (std::nothrow) NEOPIXEL_LIB(_maxPixels, _gpioPin); - #endif // ifdef ESP32 + # endif // ifdef ESP32 } else if (NEO_GRB == (_stripType & NEO_GRB)) { - #ifdef ESP8266 + # ifdef ESP8266 neopixels_grb = new (std::nothrow) NEOPIXEL_LIB(_maxPixels); - #endif // ifdef ESP8266 - #ifdef ESP32 + # endif // ifdef ESP8266 + # ifdef ESP32 neopixels_grb = new (std::nothrow) NEOPIXEL_LIB(_maxPixels, _gpioPin); - #endif // ifdef ESP32 + # endif // ifdef ESP32 } + #endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT } NeoPixelBus_wrapper::~NeoPixelBus_wrapper() { + #ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT delete neopixels_grb; neopixels_grb = nullptr; delete neopixels_grbw; neopixels_grbw = nullptr; + #endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT } +#ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT void NeoPixelBus_wrapper::begin() { if (nullptr != neopixels_grb) { neopixels_grb->Begin(); - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->Begin(); } @@ -43,8 +53,7 @@ void NeoPixelBus_wrapper::begin() { void NeoPixelBus_wrapper::show(void) { if (nullptr != neopixels_grb) { neopixels_grb->Show(); - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->Show(); } @@ -53,8 +62,7 @@ void NeoPixelBus_wrapper::show(void) { void NeoPixelBus_wrapper::setBrightness(uint8_t b) { if (nullptr != neopixels_grb) { neopixels_grb->SetBrightness(b); - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->SetBrightness(b); } @@ -66,8 +74,7 @@ void NeoPixelBus_wrapper::setPixelColor(uint16_t pxl, uint8_t b) { if (nullptr != neopixels_grb) { neopixels_grb->SetPixelColor(pxl, RgbColor(r, g, b)); - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->SetPixelColor(pxl, RgbwColor(r, g, b)); } @@ -80,8 +87,7 @@ void NeoPixelBus_wrapper::setPixelColor(uint16_t pxl, uint8_t w) { if (nullptr != neopixels_grb) { neopixels_grb->SetPixelColor(pxl, RgbColor(r, g, b)); - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->SetPixelColor(pxl, RgbwColor(r, g, b, w)); } @@ -91,8 +97,7 @@ void NeoPixelBus_wrapper::setPixelColor(uint16_t pxl, uint32_t c) { if (nullptr != neopixels_grb) { neopixels_grb->SetPixelColor(pxl, RgbColor((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF)); // Unfold the Color(r,g,b,w) static - } - + } else if (nullptr != neopixels_grbw) { neopixels_grbw->SetPixelColor(pxl, RgbwColor((c >> 16) & 0xFF, (c >> 8) & 0xFF, c & 0xFF, (c >> 24) & 0xFF)); } @@ -102,8 +107,7 @@ uint32_t NeoPixelBus_wrapper::getPixelColor(uint16_t n) { if (nullptr != neopixels_grb) { const RgbColor color = neopixels_grb->GetPixelColor(n); return Color(color.R, color.G, color.B); - } - + } else if (nullptr != neopixels_grbw) { const RgbwColor color = neopixels_grbw->GetPixelColor(n); return Color(color.R, color.G, color.B, color.W); @@ -112,4 +116,5 @@ uint32_t NeoPixelBus_wrapper::getPixelColor(uint16_t n) { return 0u; // Fall-through value... } +#endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT #endif // ifndef _NEOPIXELBUS_WRAPPER_CPP diff --git a/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.h b/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.h index 10f1b88f0..259e7c619 100644 --- a/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.h +++ b/lib/NeoPixelBus_wrapper/src/NeoPixelBus_wrapper.h @@ -1,33 +1,55 @@ #ifndef _HELPERS_NEOPIXELBUS_WRAPPER_H #define _HELPERS_NEOPIXELBUS_WRAPPER_H -#include -#include // Be sure to keep this header file when upgrading the NeoPixelBus library, - // and remove the deprecation warning if needed +#ifdef ESP8266 +# ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT + +# define NEOPIXEL_WRAPPER_USE_ADAFRUIT // Enable this line to use on ESP8266 with configurable GPIO using Adafruit_NeoPixel +# endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT +#endif // ifdef ESP8266 + +#if defined(ESP32) && defined(NEOPIXEL_WRAPPER_USE_ADAFRUIT) +# undef NEOPIXEL_WRAPPER_USE_ADAFRUIT // Shouldn't (or can't) be used on newer ESP32 chip types like -C2, -C3 and -C6 +#endif // if defined(ESP32) && defined(NEOPIXEL_WRAPPER_USE_ADAFRUIT) + +#ifdef NEOPIXEL_WRAPPER_USE_ADAFRUIT +# include +#else // ifdef NEOPIXEL_WRAPPER_USE_ADAFRUIT +# include +# include // Be sure to keep this header file when upgrading the NeoPixelBus library, + // and remove the deprecation warning if needed + // Some stuff from Adafruit_NeoPixel.h used in plugins -#ifndef NEO_GRB -# define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B -# define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W -# define NEO_KHZ800 0x0000 ///< 800 KHz data transmission -typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor -#endif // ifndef NEO_GRB +# ifndef NEO_GRB +# define NEO_GRB ((1 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B +# define NEO_GRBW ((3 << 6) | (1 << 4) | (0 << 2) | (2)) ///< Transmit as G,R,B,W +# define NEO_KHZ800 0x0000 ///< 800 KHz data transmission +typedef uint16_t neoPixelType; ///< 3rd arg to Adafruit_NeoPixel constructor +# endif // ifndef NEO_GRB -#define NEOPIXEL_LIB NeoPixelBrightnessBus // Neopixel library type -#if defined(ESP32) -# define METHOD NeoWs2812xMethod // Automatic method, user selected pin -#endif // if defined(ESP32) -#if defined(ESP8266) -# define METHOD NeoEsp8266Uart1800KbpsMethod // GPIO2 - use NeoEsp8266Uart0800KbpsMethod for GPIO1(TX) -#endif // if defined(ESP8266) +# define NEOPIXEL_LIB NeoPixelBrightnessBus // Neopixel library type +# if defined(ESP32) +# define METHOD NeoWs2812xMethod // Automatic method, user selected pin +# endif // if defined(ESP32) +# if defined(ESP8266) +# define METHOD NeoEsp8266Uart1800KbpsMethod // GPIO2 - use NeoEsp8266Uart0800KbpsMethod for GPIO1(TX) +# endif // if defined(ESP8266) -struct NeoPixelBus_wrapper { +#endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT + +struct NeoPixelBus_wrapper +#ifdef NEOPIXEL_WRAPPER_USE_ADAFRUIT + : Adafruit_NeoPixel +#endif // ifdef NEOPIXEL_WRAPPER_USE_ADAFRUIT +{ public: NeoPixelBus_wrapper(uint16_t _maxPixels, int16_t _gpioPin, neoPixelType _stripType); virtual ~NeoPixelBus_wrapper(); + #ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT void begin(); void show(void); void setBrightness(uint8_t); @@ -59,6 +81,7 @@ private: NEOPIXEL_LIB *neopixels_grb = nullptr; NEOPIXEL_LIB *neopixels_grbw = nullptr; uint16_t numLEDs = 0; + #endif // ifndef NEOPIXEL_WRAPPER_USE_ADAFRUIT }; #endif // ifndef _HELPERS_NEOPIXELBUS_WRAPPER_H diff --git a/lib/RAK12019_LTR390/src/UVlight_LTR390.cpp b/lib/RAK12019_LTR390/src/UVlight_LTR390.cpp index e06a2fec0..31a8c17e9 100644 --- a/lib/RAK12019_LTR390/src/UVlight_LTR390.cpp +++ b/lib/RAK12019_LTR390/src/UVlight_LTR390.cpp @@ -96,7 +96,7 @@ bool UVlight_LTR390::init(bool doReset) { bool UVlight_LTR390::reset(void) { uint8_t readData = readRegister(LTR390_MAIN_CTRL); - readData |= B00010000; + readData |= 0b00010000; writeRegister(LTR390_MAIN_CTRL, readData); delay(10); diff --git a/lib/ServoESP32/examples/01-SimpleServo/01-SimpleServo.ino b/lib/ServoESP32/examples/01-SimpleServo/01-SimpleServo.ino index d2ef741ce..d39fc9e1a 100644 --- a/lib/ServoESP32/examples/01-SimpleServo/01-SimpleServo.ino +++ b/lib/ServoESP32/examples/01-SimpleServo/01-SimpleServo.ino @@ -1,24 +1,24 @@ -#include - -static const int servoPin = 4; - -Servo servo1; - -void setup() { - Serial.begin(115200); - servo1.attach(servoPin); -} - -void loop() { - for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { - servo1.write(posDegrees); - Serial.println(posDegrees); - delay(20); - } - - for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { - servo1.write(posDegrees); - Serial.println(posDegrees); - delay(20); - } +#include + +static const int servoPin = 4; + +Servo servo1; + +void setup() { + Serial.begin(115200); + servo1.attach(servoPin); +} + +void loop() { + for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { + servo1.write(posDegrees); + Serial.println(posDegrees); + delay(20); + } + + for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { + servo1.write(posDegrees); + Serial.println(posDegrees); + delay(20); + } } \ No newline at end of file diff --git a/lib/ServoESP32/examples/02-ServoPotentiometer/02-ServoPotentiometer.ino b/lib/ServoESP32/examples/02-ServoPotentiometer/02-ServoPotentiometer.ino index eb7458184..08d4244f2 100644 --- a/lib/ServoESP32/examples/02-ServoPotentiometer/02-ServoPotentiometer.ino +++ b/lib/ServoESP32/examples/02-ServoPotentiometer/02-ServoPotentiometer.ino @@ -1,18 +1,18 @@ -#include - -static const int servoPin = 4; -static const int potentiometerPin = 32; - -Servo servo1; - -void setup() { - Serial.begin(115200); - servo1.attach(servoPin); -} - -void loop() { - int servoPosition = map(analogRead(potentiometerPin), 0, 4096, 0, 180); - servo1.write(servoPosition); - Serial.println(servoPosition); - delay(20); -} +#include + +static const int servoPin = 4; +static const int potentiometerPin = 32; + +Servo servo1; + +void setup() { + Serial.begin(115200); + servo1.attach(servoPin); +} + +void loop() { + int servoPosition = map(analogRead(potentiometerPin), 0, 4096, 0, 180); + servo1.write(servoPosition); + Serial.println(servoPosition); + delay(20); +} diff --git a/lib/ServoESP32/examples/03-MultipleServos/03-MultipleServos.ino b/lib/ServoESP32/examples/03-MultipleServos/03-MultipleServos.ino index 28b3eebaa..7f009ac91 100644 --- a/lib/ServoESP32/examples/03-MultipleServos/03-MultipleServos.ino +++ b/lib/ServoESP32/examples/03-MultipleServos/03-MultipleServos.ino @@ -1,37 +1,37 @@ -#include - -static const int servosPins[5] = {4, 16, 18, 19, 21}; - -Servo servos[5]; - -void setServos(int degrees) { - for(int i = 0; i < 5; ++i) { - servos[i].write((degrees + (35 * i)) % 180); - } -} - -void setup() { - Serial.begin(115200); - - for(int i = 0; i < 5; ++i) { - if(!servos[i].attach(servosPins[i])) { - Serial.print("Servo "); - Serial.print(i); - Serial.println("attach error"); - } - } -} - -void loop() { - for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { - setServos(posDegrees); - Serial.println(posDegrees); - delay(20); - } - - for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { - setServos(posDegrees); - Serial.println(posDegrees); - delay(20); - } -} +#include + +static const int servosPins[5] = {4, 16, 18, 19, 21}; + +Servo servos[5]; + +void setServos(int degrees) { + for(int i = 0; i < 5; ++i) { + servos[i].write((degrees + (35 * i)) % 180); + } +} + +void setup() { + Serial.begin(115200); + + for(int i = 0; i < 5; ++i) { + if(!servos[i].attach(servosPins[i])) { + Serial.print("Servo "); + Serial.print(i); + Serial.println("attach error"); + } + } +} + +void loop() { + for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { + setServos(posDegrees); + Serial.println(posDegrees); + delay(20); + } + + for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { + setServos(posDegrees); + Serial.println(posDegrees); + delay(20); + } +} diff --git a/lib/ServoESP32/examples/04-SimpleServoAngles/04-SimpleServoAngles.ino b/lib/ServoESP32/examples/04-SimpleServoAngles/04-SimpleServoAngles.ino index 4912f3e2c..8cad99810 100644 --- a/lib/ServoESP32/examples/04-SimpleServoAngles/04-SimpleServoAngles.ino +++ b/lib/ServoESP32/examples/04-SimpleServoAngles/04-SimpleServoAngles.ino @@ -1,34 +1,34 @@ -#include - -/* - * Description: - * Example for setting the minimal and maximal angle. - */ - -static const int servoPin = 4; - -Servo servo1; - -void setup() { - Serial.begin(115200); - servo1.attach( - servoPin, - Servo::CHANNEL_NOT_ATTACHED, - 45, - 120 - ); -} - -void loop() { - for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { - servo1.write(posDegrees); - Serial.println(posDegrees); - delay(20); - } - - for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { - servo1.write(posDegrees); - Serial.println(posDegrees); - delay(20); - } +#include + +/* + * Description: + * Example for setting the minimal and maximal angle. + */ + +static const int servoPin = 4; + +Servo servo1; + +void setup() { + Serial.begin(115200); + servo1.attach( + servoPin, + Servo::CHANNEL_NOT_ATTACHED, + 45, + 120 + ); +} + +void loop() { + for(int posDegrees = 0; posDegrees <= 180; posDegrees++) { + servo1.write(posDegrees); + Serial.println(posDegrees); + delay(20); + } + + for(int posDegrees = 180; posDegrees >= 0; posDegrees--) { + servo1.write(posDegrees); + Serial.println(posDegrees); + delay(20); + } } \ No newline at end of file diff --git a/lib/ServoESP32/src/Servo.h b/lib/ServoESP32/src/Servo.h index 8b15ea2c4..c8b7fd4a8 100644 --- a/lib/ServoESP32/src/Servo.h +++ b/lib/ServoESP32/src/Servo.h @@ -159,6 +159,7 @@ class ServoTemplate : public ServoBase { ledcSetup(_channel, frequency, TIMER_RESOLUTION); ledcAttachPin(_pin, _channel); #else + ledcDetach(_pin); // See: https://github.com/espressif/arduino-esp32/issues/9212 ledcAttach(_pin, frequency, TIMER_RESOLUTION); #endif return true; diff --git a/lib/SparkFun_ADXL345_Arduino_Library/src/SparkFun_ADXL345.cpp b/lib/SparkFun_ADXL345_Arduino_Library/src/SparkFun_ADXL345.cpp index 928d8d330..36e14a92b 100644 --- a/lib/SparkFun_ADXL345_Arduino_Library/src/SparkFun_ADXL345.cpp +++ b/lib/SparkFun_ADXL345_Arduino_Library/src/SparkFun_ADXL345.cpp @@ -199,7 +199,7 @@ void ADXL345::getRangeSetting(byte *rangeSetting) { byte _b; readFrom(ADXL345_DATA_FORMAT, 1, &_b); - *rangeSetting = _b & B00000011; + *rangeSetting = _b & 0b00000011; } void ADXL345::setRangeSetting(int val) { @@ -208,22 +208,22 @@ void ADXL345::setRangeSetting(int val) { switch (val) { case 2: - _s = B00000000; + _s = 0b00000000; break; case 4: - _s = B00000001; + _s = 0b00000001; break; case 8: - _s = B00000010; + _s = 0b00000010; break; case 16: - _s = B00000011; + _s = 0b00000011; break; default: - _s = B00000000; + _s = 0b00000000; } readFrom(ADXL345_DATA_FORMAT, 1, &_b); - _s |= (_b & B11101100); + _s |= (_b & 0b11101100); writeTo(ADXL345_DATA_FORMAT, _s); } @@ -700,7 +700,7 @@ double ADXL345::getRate() { byte _b; readFrom(ADXL345_BW_RATE, 1, &_b); - _b &= B00001111; + _b &= 0b00001111; return (pow(2, ((int)_b) - 6)) * 6.25; } @@ -716,7 +716,7 @@ void ADXL345::setRate(double rate) { if (r <= 9) { readFrom(ADXL345_BW_RATE, 1, &_b); - _s = (byte)(r + 6) | (_b & B11110000); + _s = (byte)(r + 6) | (_b & 0b11110000); writeTo(ADXL345_BW_RATE, _s); } } diff --git a/lib/VL53L0X/library.properties b/lib/VL53L0X/library.properties index cac8675fa..5c3209c25 100644 --- a/lib/VL53L0X/library.properties +++ b/lib/VL53L0X/library.properties @@ -1,9 +1,9 @@ -name=VL53L0X -version=1.3.0 -author=Pololu -maintainer=Pololu -sentence=VL53L0X distance sensor library -paragraph=This is a library for the Arduino IDE that helps interface with ST's VL53L0X distance sensor. -category=Sensors -url=https://github.com/pololu/vl53l0x-arduino -architectures=* +name=VL53L0X +version=1.3.0 +author=Pololu +maintainer=Pololu +sentence=VL53L0X distance sensor library (heavily modified for ESPEasy) +paragraph=This is a library for the Arduino IDE that helps interface with ST's VL53L0X distance sensor. +category=Sensors +url=https://github.com/pololu/vl53l0x-arduino +architectures=* diff --git a/lib/VL53L0X/src/VL53L0X.cpp b/lib/VL53L0X/src/VL53L0X.cpp index f72b38307..72ef0173e 100644 --- a/lib/VL53L0X/src/VL53L0X.cpp +++ b/lib/VL53L0X/src/VL53L0X.cpp @@ -1,1049 +1,1133 @@ -// Most of the functionality of this library is based on the VL53L0X API -// provided by ST (STSW-IMG005), and some of the explanatory comments are quoted -// or paraphrased from the API source code, API user manual (UM2039), and the -// VL53L0X datasheet. - -#include "VL53L0X.h" -#include - -// Defines ///////////////////////////////////////////////////////////////////// - -// The Arduino two-wire interface uses a 7-bit number for the address, -// and sets the last bit correctly based on reads and writes -#define ADDRESS_DEFAULT 0b0101001 - -// Record the current time to check an upcoming timeout against -#define startTimeout() (timeout_start_ms = millis()) - -// Check if timeout is enabled (set to nonzero value) and has expired -#define checkTimeoutExpired() (io_timeout > 0 && ((uint16_t)(millis() - timeout_start_ms) > io_timeout)) - -// Decode VCSEL (vertical cavity surface emitting laser) pulse period in PCLKs -// from register value -// based on VL53L0X_decode_vcsel_period() -#define decodeVcselPeriod(reg_val) (((reg_val) + 1) << 1) - -// Encode VCSEL pulse period register value from period in PCLKs -// based on VL53L0X_encode_vcsel_period() -#define encodeVcselPeriod(period_pclks) (((period_pclks) >> 1) - 1) - -// Calculate macro period in *nanoseconds* from VCSEL period in PCLKs -// based on VL53L0X_calc_macro_period_ps() -// PLL_period_ps = 1655; macro_period_vclks = 2304 -#define calcMacroPeriod(vcsel_period_pclks) ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000) - -// Constructors //////////////////////////////////////////////////////////////// - -VL53L0X::VL53L0X() - : bus(&Wire) - , address(ADDRESS_DEFAULT) - , io_timeout(0) // no timeout - , did_timeout(false) -{ -} - -// Public Methods ////////////////////////////////////////////////////////////// - -void VL53L0X::setAddress(uint8_t new_addr) -{ - writeReg(I2C_SLAVE_DEVICE_ADDRESS, new_addr & 0x7F); - address = new_addr; -} - -// Initialize sensor using sequence based on VL53L0X_DataInit(), -// VL53L0X_StaticInit(), and VL53L0X_PerformRefCalibration(). -// This function does not perform reference SPAD calibration -// (VL53L0X_PerformRefSpadManagement()), since the API user manual says that it -// is performed by ST on the bare modules; it seems like that should work well -// enough unless a cover glass is added. -// If io_2v8 (optional) is true or not given, the sensor is configured for 2V8 -// mode. -bool VL53L0X::init(bool io_2v8) -{ - initResult = F(""); // Clear any previous result - // check model ID register (value specified in datasheet) - uint8_t modelId = readReg(IDENTIFICATION_MODEL_ID); - if (modelId != 0xEE) { // Recognize VL53L0X (0xEE) - initResult = F("VL53L0X: Init: unrecognized Model-ID: 0x"); - initResult += String(modelId, HEX); - return false; - } - - // VL53L0X_DataInit() begin - - // sensor uses 1V8 mode for I/O by default; switch to 2V8 mode if necessary - if (io_2v8) - { - writeReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, - readReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); // set bit 0 - } - - // "Set I2C standard mode" - writeReg(0x88, 0x00); - - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - stop_variable = readReg(0x91); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - // disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks - writeReg(MSRC_CONFIG_CONTROL, readReg(MSRC_CONFIG_CONTROL) | 0x12); - - // set final range signal rate limit to 0.25 MCPS (million counts per second) - setSignalRateLimit(0.25); - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xFF); - - // VL53L0X_DataInit() end - - // VL53L0X_StaticInit() begin - - uint8_t spad_count; - bool spad_type_is_aperture; - if (!getSpadInfo(&spad_count, &spad_type_is_aperture)) { return false; } - - // The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in - // the API, but the same data seems to be more easily readable from - // GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there - uint8_t ref_spad_map[6]; - readMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); - - // -- VL53L0X_set_reference_spads() begin (assume NVM values are valid) - - writeReg(0xFF, 0x01); - writeReg(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); - writeReg(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); - writeReg(0xFF, 0x00); - writeReg(GLOBAL_CONFIG_REF_EN_START_SELECT, 0xB4); - - uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0; // 12 is the first aperture spad - uint8_t spads_enabled = 0; - - for (uint8_t i = 0; i < 48; i++) - { - if (i < first_spad_to_enable || spads_enabled == spad_count) - { - // This bit is lower than the first one that should be enabled, or - // (reference_spad_count) bits have already been enabled, so zero this bit - ref_spad_map[i / 8] &= ~(1 << (i % 8)); - } - else if ((ref_spad_map[i / 8] >> (i % 8)) & 0x1) - { - spads_enabled++; - } - } - - writeMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); - - // -- VL53L0X_set_reference_spads() end - - // -- VL53L0X_load_tuning_settings() begin - // DefaultTuningSettings from vl53l0x_tuning.h - - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - - writeReg(0xFF, 0x00); - writeReg(0x09, 0x00); - writeReg(0x10, 0x00); - writeReg(0x11, 0x00); - - writeReg(0x24, 0x01); - writeReg(0x25, 0xFF); - writeReg(0x75, 0x00); - - writeReg(0xFF, 0x01); - writeReg(0x4E, 0x2C); - writeReg(0x48, 0x00); - writeReg(0x30, 0x20); - - writeReg(0xFF, 0x00); - writeReg(0x30, 0x09); - writeReg(0x54, 0x00); - writeReg(0x31, 0x04); - writeReg(0x32, 0x03); - writeReg(0x40, 0x83); - writeReg(0x46, 0x25); - writeReg(0x60, 0x00); - writeReg(0x27, 0x00); - writeReg(0x50, 0x06); - writeReg(0x51, 0x00); - writeReg(0x52, 0x96); - writeReg(0x56, 0x08); - writeReg(0x57, 0x30); - writeReg(0x61, 0x00); - writeReg(0x62, 0x00); - writeReg(0x64, 0x00); - writeReg(0x65, 0x00); - writeReg(0x66, 0xA0); - - writeReg(0xFF, 0x01); - writeReg(0x22, 0x32); - writeReg(0x47, 0x14); - writeReg(0x49, 0xFF); - writeReg(0x4A, 0x00); - - writeReg(0xFF, 0x00); - writeReg(0x7A, 0x0A); - writeReg(0x7B, 0x00); - writeReg(0x78, 0x21); - - writeReg(0xFF, 0x01); - writeReg(0x23, 0x34); - writeReg(0x42, 0x00); - writeReg(0x44, 0xFF); - writeReg(0x45, 0x26); - writeReg(0x46, 0x05); - writeReg(0x40, 0x40); - writeReg(0x0E, 0x06); - writeReg(0x20, 0x1A); - writeReg(0x43, 0x40); - - writeReg(0xFF, 0x00); - writeReg(0x34, 0x03); - writeReg(0x35, 0x44); - - writeReg(0xFF, 0x01); - writeReg(0x31, 0x04); - writeReg(0x4B, 0x09); - writeReg(0x4C, 0x05); - writeReg(0x4D, 0x04); - - writeReg(0xFF, 0x00); - writeReg(0x44, 0x00); - writeReg(0x45, 0x20); - writeReg(0x47, 0x08); - writeReg(0x48, 0x28); - writeReg(0x67, 0x00); - writeReg(0x70, 0x04); - writeReg(0x71, 0x01); - writeReg(0x72, 0xFE); - writeReg(0x76, 0x00); - writeReg(0x77, 0x00); - - writeReg(0xFF, 0x01); - writeReg(0x0D, 0x01); - - writeReg(0xFF, 0x00); - writeReg(0x80, 0x01); - writeReg(0x01, 0xF8); - - writeReg(0xFF, 0x01); - writeReg(0x8E, 0x01); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - // -- VL53L0X_load_tuning_settings() end - - // "Set interrupt config to new sample ready" - // -- VL53L0X_SetGpioConfig() begin - - writeReg(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04); - writeReg(GPIO_HV_MUX_ACTIVE_HIGH, readReg(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10); // active low - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - // -- VL53L0X_SetGpioConfig() end - - measurement_timing_budget_us = getMeasurementTimingBudget(); - - // "Disable MSRC and TCC by default" - // MSRC = Minimum Signal Rate Check - // TCC = Target CentreCheck - // -- VL53L0X_SetSequenceStepEnable() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); - - // -- VL53L0X_SetSequenceStepEnable() end - - // "Recalculate timing budget" - setMeasurementTimingBudget(measurement_timing_budget_us); - - // VL53L0X_StaticInit() end - - // VL53L0X_PerformRefCalibration() begin (VL53L0X_perform_ref_calibration()) - - // -- VL53L0X_perform_vhv_calibration() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x01); - if (!performSingleRefCalibration(0x40)) { return false; } - - // -- VL53L0X_perform_vhv_calibration() end - - // -- VL53L0X_perform_phase_calibration() begin - - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); - if (!performSingleRefCalibration(0x00)) { return false; } - - // -- VL53L0X_perform_phase_calibration() end - - // "restore the previous Sequence Config" - writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); - - // VL53L0X_PerformRefCalibration() end - - return true; -} - -// Write an 8-bit register -void VL53L0X::writeReg(uint8_t reg, uint8_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write(value); - last_status = bus->endTransmission(); -} - -// Write a 16-bit register -void VL53L0X::writeReg16Bit(uint8_t reg, uint16_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write((value >> 8) & 0xFF); // value high byte - bus->write( value & 0xFF); // value low byte - last_status = bus->endTransmission(); -} - -// Write a 32-bit register -void VL53L0X::writeReg32Bit(uint8_t reg, uint32_t value) -{ - bus->beginTransmission(address); - bus->write(reg); - bus->write((value >> 24) & 0xFF); // value highest byte - bus->write((value >> 16) & 0xFF); - bus->write((value >> 8) & 0xFF); - bus->write( value & 0xFF); // value lowest byte - last_status = bus->endTransmission(); -} - -// Read an 8-bit register -uint8_t VL53L0X::readReg(uint8_t reg) -{ - uint8_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)1); - value = bus->read(); - - return value; -} - -// Read a 16-bit register -uint16_t VL53L0X::readReg16Bit(uint8_t reg) -{ - uint16_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)2); - value = (uint16_t)bus->read() << 8; // value high byte - value |= bus->read(); // value low byte - - return value; -} - -// Read a 32-bit register -uint32_t VL53L0X::readReg32Bit(uint8_t reg) -{ - uint32_t value; - - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, (uint8_t)4); - value = (uint32_t)bus->read() << 24; // value highest byte - value |= (uint32_t)bus->read() << 16; - value |= (uint16_t)bus->read() << 8; - value |= bus->read(); // value lowest byte - - return value; -} - -// Write an arbitrary number of bytes from the given array to the sensor, -// starting at the given register -void VL53L0X::writeMulti(uint8_t reg, uint8_t const * src, uint8_t count) -{ - bus->beginTransmission(address); - bus->write(reg); - - while (count-- > 0) - { - bus->write(*(src++)); - } - - last_status = bus->endTransmission(); -} - -// Read an arbitrary number of bytes from the sensor, starting at the given -// register, into the given array -void VL53L0X::readMulti(uint8_t reg, uint8_t * dst, uint8_t count) -{ - bus->beginTransmission(address); - bus->write(reg); - last_status = bus->endTransmission(); - - bus->requestFrom(address, count); - - while (count-- > 0) - { - *(dst++) = bus->read(); - } -} - -// Set the return signal rate limit check value in units of MCPS (mega counts -// per second). "This represents the amplitude of the signal reflected from the -// target and detected by the device"; setting this limit presumably determines -// the minimum measurement necessary for the sensor to report a valid reading. -// Setting a lower limit increases the potential range of the sensor but also -// seems to increase the likelihood of getting an inaccurate reading because of -// unwanted reflections from objects other than the intended target. -// Defaults to 0.25 MCPS as initialized by the ST API and this library. -bool VL53L0X::setSignalRateLimit(float limit_Mcps) -{ - if (limit_Mcps < 0 || limit_Mcps > 511.99f) { return false; } - - // Q9.7 fixed point format (9 integer bits, 7 fractional bits) - writeReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, limit_Mcps * (1 << 7)); - return true; -} - -// Get the return signal rate limit check value in MCPS -float VL53L0X::getSignalRateLimit() -{ - return (float)readReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) / (1 << 7); -} - -// Set the measurement timing budget in microseconds, which is the time allowed -// for one measurement; the ST API and this library take care of splitting the -// timing budget among the sub-steps in the ranging sequence. A longer timing -// budget allows for more accurate measurements. Increasing the budget by a -// factor of N decreases the range measurement standard deviation by a factor of -// sqrt(N). Defaults to about 33 milliseconds; the minimum is 20 ms. -// based on VL53L0X_set_measurement_timing_budget_micro_seconds() -bool VL53L0X::setMeasurementTimingBudget(uint32_t budget_us) -{ - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - uint16_t const StartOverhead = 1910; - uint16_t const EndOverhead = 960; - uint16_t const MsrcOverhead = 660; - uint16_t const TccOverhead = 590; - uint16_t const DssOverhead = 690; - uint16_t const PreRangeOverhead = 660; - uint16_t const FinalRangeOverhead = 550; - - uint32_t const MinTimingBudget = 20000; - - if (budget_us < MinTimingBudget) { return false; } - - uint32_t used_budget_us = StartOverhead + EndOverhead; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - if (enables.tcc) - { - used_budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); - } - - if (enables.dss) - { - used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); - } - else if (enables.msrc) - { - used_budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); - } - - if (enables.pre_range) - { - used_budget_us += (timeouts.pre_range_us + PreRangeOverhead); - } - - if (enables.final_range) - { - used_budget_us += FinalRangeOverhead; - - // "Note that the final range timeout is determined by the timing - // budget and the sum of all other timeouts within the sequence. - // If there is no room for the final range timeout, then an error - // will be set. Otherwise the remaining time will be applied to - // the final range." - - if (used_budget_us > budget_us) - { - // "Requested timeout too big." - return false; - } - - uint32_t final_range_timeout_us = budget_us - used_budget_us; - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) - - // "For the final range timeout, the pre-range timeout - // must be added. To do this both final and pre-range - // timeouts must be expressed in macro periods MClks - // because they have different vcsel periods." - - uint32_t final_range_timeout_mclks = - timeoutMicrosecondsToMclks(final_range_timeout_us, - timeouts.final_range_vcsel_period_pclks); - - if (enables.pre_range) - { - final_range_timeout_mclks += timeouts.pre_range_mclks; - } - - writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(final_range_timeout_mclks)); - - // set_sequence_step_timeout() end - - measurement_timing_budget_us = budget_us; // store for internal reuse - } - return true; -} - -// Get the measurement timing budget in microseconds -// based on VL53L0X_get_measurement_timing_budget_micro_seconds() -// in us -uint32_t VL53L0X::getMeasurementTimingBudget() -{ - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - uint16_t const StartOverhead = 1910; - uint16_t const EndOverhead = 960; - uint16_t const MsrcOverhead = 660; - uint16_t const TccOverhead = 590; - uint16_t const DssOverhead = 690; - uint16_t const PreRangeOverhead = 660; - uint16_t const FinalRangeOverhead = 550; - - // "Start and end overhead times always present" - uint32_t budget_us = StartOverhead + EndOverhead; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - if (enables.tcc) - { - budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); - } - - if (enables.dss) - { - budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); - } - else if (enables.msrc) - { - budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); - } - - if (enables.pre_range) - { - budget_us += (timeouts.pre_range_us + PreRangeOverhead); - } - - if (enables.final_range) - { - budget_us += (timeouts.final_range_us + FinalRangeOverhead); - } - - measurement_timing_budget_us = budget_us; // store for internal reuse - return budget_us; -} - -// Set the VCSEL (vertical cavity surface emitting laser) pulse period for the -// given period type (pre-range or final range) to the given value in PCLKs. -// Longer periods seem to increase the potential range of the sensor. -// Valid values are (even numbers only): -// pre: 12 to 18 (initialized default: 14) -// final: 8 to 14 (initialized default: 10) -// based on VL53L0X_set_vcsel_pulse_period() -bool VL53L0X::setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks) -{ - uint8_t vcsel_period_reg = encodeVcselPeriod(period_pclks); - - SequenceStepEnables enables; - SequenceStepTimeouts timeouts; - - getSequenceStepEnables(&enables); - getSequenceStepTimeouts(&enables, &timeouts); - - // "Apply specific settings for the requested clock period" - // "Re-calculate and apply timeouts, in macro periods" - - // "When the VCSEL period for the pre or final range is changed, - // the corresponding timeout must be read from the device using - // the current VCSEL period, then the new VCSEL period can be - // applied. The timeout then must be written back to the device - // using the new VCSEL period. - // - // For the MSRC timeout, the same applies - this timeout being - // dependant on the pre-range vcsel period." - - - if (type == VcselPeriodPreRange) - { - // "Set phase check limits" - switch (period_pclks) - { - case 12: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x18); - break; - - case 14: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x30); - break; - - case 16: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x40); - break; - - case 18: - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x50); - break; - - default: - // invalid period - return false; - } - writeReg(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - - // apply new VCSEL period - writeReg(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); - - // update timeouts - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) - - uint16_t new_pre_range_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.pre_range_us, period_pclks); - - writeReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(new_pre_range_timeout_mclks)); - - // set_sequence_step_timeout() end - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC) - - uint16_t new_msrc_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.msrc_dss_tcc_us, period_pclks); - - writeReg(MSRC_CONFIG_TIMEOUT_MACROP, - (new_msrc_timeout_mclks > 256) ? 255 : (new_msrc_timeout_mclks - 1)); - - // set_sequence_step_timeout() end - } - else if (type == VcselPeriodFinalRange) - { - switch (period_pclks) - { - case 8: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x10); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x02); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x30); - writeReg(0xFF, 0x00); - break; - - case 10: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x28); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - case 12: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x38); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - case 14: - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x48); - writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); - writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); - writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07); - writeReg(0xFF, 0x01); - writeReg(ALGO_PHASECAL_LIM, 0x20); - writeReg(0xFF, 0x00); - break; - - default: - // invalid period - return false; - } - - // apply new VCSEL period - writeReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); - - // update timeouts - - // set_sequence_step_timeout() begin - // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) - - // "For the final range timeout, the pre-range timeout - // must be added. To do this both final and pre-range - // timeouts must be expressed in macro periods MClks - // because they have different vcsel periods." - - uint16_t new_final_range_timeout_mclks = - timeoutMicrosecondsToMclks(timeouts.final_range_us, period_pclks); - - if (enables.pre_range) - { - new_final_range_timeout_mclks += timeouts.pre_range_mclks; - } - - writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, - encodeTimeout(new_final_range_timeout_mclks)); - - // set_sequence_step_timeout end - } - else - { - // invalid type - return false; - } - - // "Finally, the timing budget must be re-applied" - - setMeasurementTimingBudget(measurement_timing_budget_us); - - // "Perform the phase calibration. This is needed after changing on vcsel period." - // VL53L0X_perform_phase_calibration() begin - - uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); - writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); - performSingleRefCalibration(0x0); - writeReg(SYSTEM_SEQUENCE_CONFIG, sequence_config); - - // VL53L0X_perform_phase_calibration() end - - return true; -} - -// Get the VCSEL pulse period in PCLKs for the given period type. -// based on VL53L0X_get_vcsel_pulse_period() -uint8_t VL53L0X::getVcselPulsePeriod(vcselPeriodType type) -{ - if (type == VcselPeriodPreRange) - { - return decodeVcselPeriod(readReg(PRE_RANGE_CONFIG_VCSEL_PERIOD)); - } - else if (type == VcselPeriodFinalRange) - { - return decodeVcselPeriod(readReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD)); - } - else { return 255; } -} - -// Start continuous ranging measurements. If period_ms (optional) is 0 or not -// given, continuous back-to-back mode is used (the sensor takes measurements as -// often as possible); otherwise, continuous timed mode is used, with the given -// inter-measurement period in milliseconds determining how often the sensor -// takes a measurement. -// based on VL53L0X_StartMeasurement() -void VL53L0X::startContinuous(uint32_t period_ms) -{ - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, stop_variable); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - if (period_ms != 0) - { - // continuous timed mode - - // VL53L0X_SetInterMeasurementPeriodMilliSeconds() begin - - uint16_t osc_calibrate_val = readReg16Bit(OSC_CALIBRATE_VAL); - - if (osc_calibrate_val != 0) - { - period_ms *= osc_calibrate_val; - } - - writeReg32Bit(SYSTEM_INTERMEASUREMENT_PERIOD, period_ms); - - // VL53L0X_SetInterMeasurementPeriodMilliSeconds() end - - writeReg(SYSRANGE_START, 0x04); // VL53L0X_REG_SYSRANGE_MODE_TIMED - } - else - { - // continuous back-to-back mode - writeReg(SYSRANGE_START, 0x02); // VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK - } -} - -// Stop continuous measurements -// based on VL53L0X_StopMeasurement() -void VL53L0X::stopContinuous() -{ - writeReg(SYSRANGE_START, 0x01); // VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT - - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, 0x00); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); -} - -// Returns a range reading in millimeters when continuous mode is active -// (readRangeSingleMillimeters() also calls this function after starting a -// single-shot range measurement) -uint16_t VL53L0X::readRangeContinuousMillimeters() -{ - startTimeout(); - while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) - { - if (checkTimeoutExpired()) - { - did_timeout = true; - return 65535; - } - } - - // assumptions: Linearity Corrective Gain is 1000 (default); - // fractional ranging is not enabled - uint16_t range = readReg16Bit(RESULT_RANGE_STATUS + 10); - - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - return range; -} - -// Performs a single-shot range measurement and returns the reading in -// millimeters -// based on VL53L0X_PerformSingleRangingMeasurement() -uint16_t VL53L0X::readRangeSingleMillimeters() -{ - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - writeReg(0x91, stop_variable); - writeReg(0x00, 0x01); - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - writeReg(SYSRANGE_START, 0x01); - - // "Wait until start bit has been cleared" - startTimeout(); - while (readReg(SYSRANGE_START) & 0x01) - { - if (checkTimeoutExpired()) - { - did_timeout = true; - return 65535; - } - } - - return readRangeContinuousMillimeters(); -} - -// Did a timeout occur in one of the read functions since the last call to -// timeoutOccurred()? -bool VL53L0X::timeoutOccurred() -{ - bool tmp = did_timeout; - did_timeout = false; - return tmp; -} - -// Private Methods ///////////////////////////////////////////////////////////// - -// Get reference SPAD (single photon avalanche diode) count and type -// based on VL53L0X_get_info_from_device(), -// but only gets reference SPAD count and type -bool VL53L0X::getSpadInfo(uint8_t * count, bool * type_is_aperture) -{ - uint8_t tmp; - - writeReg(0x80, 0x01); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x00); - - writeReg(0xFF, 0x06); - writeReg(0x83, readReg(0x83) | 0x04); - writeReg(0xFF, 0x07); - writeReg(0x81, 0x01); - - writeReg(0x80, 0x01); - - writeReg(0x94, 0x6b); - writeReg(0x83, 0x00); - startTimeout(); - while (readReg(0x83) == 0x00) - { - if (checkTimeoutExpired()) { return false; } - } - writeReg(0x83, 0x01); - tmp = readReg(0x92); - - *count = tmp & 0x7f; - *type_is_aperture = (tmp >> 7) & 0x01; - - writeReg(0x81, 0x00); - writeReg(0xFF, 0x06); - writeReg(0x83, readReg(0x83) & ~0x04); - writeReg(0xFF, 0x01); - writeReg(0x00, 0x01); - - writeReg(0xFF, 0x00); - writeReg(0x80, 0x00); - - return true; -} - -// Get sequence step enables -// based on VL53L0X_GetSequenceStepEnables() -void VL53L0X::getSequenceStepEnables(SequenceStepEnables * enables) -{ - uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); - - enables->tcc = (sequence_config >> 4) & 0x1; - enables->dss = (sequence_config >> 3) & 0x1; - enables->msrc = (sequence_config >> 2) & 0x1; - enables->pre_range = (sequence_config >> 6) & 0x1; - enables->final_range = (sequence_config >> 7) & 0x1; -} - -// Get sequence step timeouts -// based on get_sequence_step_timeout(), -// but gets all timeouts instead of just the requested one, and also stores -// intermediate values -void VL53L0X::getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts) -{ - timeouts->pre_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodPreRange); - - timeouts->msrc_dss_tcc_mclks = readReg(MSRC_CONFIG_TIMEOUT_MACROP) + 1; - timeouts->msrc_dss_tcc_us = - timeoutMclksToMicroseconds(timeouts->msrc_dss_tcc_mclks, - timeouts->pre_range_vcsel_period_pclks); - - timeouts->pre_range_mclks = - decodeTimeout(readReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)); - timeouts->pre_range_us = - timeoutMclksToMicroseconds(timeouts->pre_range_mclks, - timeouts->pre_range_vcsel_period_pclks); - - timeouts->final_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodFinalRange); - - timeouts->final_range_mclks = - decodeTimeout(readReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)); - - if (enables->pre_range) - { - timeouts->final_range_mclks -= timeouts->pre_range_mclks; - } - - timeouts->final_range_us = - timeoutMclksToMicroseconds(timeouts->final_range_mclks, - timeouts->final_range_vcsel_period_pclks); -} - -// Decode sequence step timeout in MCLKs from register value -// based on VL53L0X_decode_timeout() -// Note: the original function returned a uint32_t, but the return value is -// always stored in a uint16_t. -uint16_t VL53L0X::decodeTimeout(uint16_t reg_val) -{ - // format: "(LSByte * 2^MSByte) + 1" - return (uint16_t)((reg_val & 0x00FF) << - (uint16_t)((reg_val & 0xFF00) >> 8)) + 1; -} - -// Encode sequence step timeout register value from timeout in MCLKs -// based on VL53L0X_encode_timeout() -uint16_t VL53L0X::encodeTimeout(uint32_t timeout_mclks) -{ - // format: "(LSByte * 2^MSByte) + 1" - - uint32_t ls_byte = 0; - uint16_t ms_byte = 0; - - if (timeout_mclks > 0) - { - ls_byte = timeout_mclks - 1; - - while ((ls_byte & 0xFFFFFF00) > 0) - { - ls_byte >>= 1; - ms_byte++; - } - - return (ms_byte << 8) | (ls_byte & 0xFF); - } - else { return 0; } -} - -// Convert sequence step timeout from MCLKs to microseconds with given VCSEL period in PCLKs -// based on VL53L0X_calc_timeout_us() -uint32_t VL53L0X::timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) -{ - uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); - - return ((timeout_period_mclks * macro_period_ns) + 500) / 1000; -} - -// Convert sequence step timeout from microseconds to MCLKs with given VCSEL period in PCLKs -// based on VL53L0X_calc_timeout_mclks() -uint32_t VL53L0X::timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) -{ - uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); - - return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns); -} - - -// based on VL53L0X_perform_single_ref_calibration() -bool VL53L0X::performSingleRefCalibration(uint8_t vhv_init_byte) -{ - writeReg(SYSRANGE_START, 0x01 | vhv_init_byte); // VL53L0X_REG_SYSRANGE_MODE_START_STOP - - startTimeout(); - while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) - { - if (checkTimeoutExpired()) { return false; } - } - - writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); - - writeReg(SYSRANGE_START, 0x00); - - return true; -} - -// Simply return the string, it is cleared and could be set during init -String VL53L0X::getInitResult() { - return initResult; +// Most of the functionality of this library is based on the VL53L0X API +// provided by ST (STSW-IMG005), and some of the explanatory comments are quoted +// or paraphrased from the API source code, API user manual (UM2039), and the +// VL53L0X datasheet. + +#include "VL53L0X.h" +#include + +// Defines ///////////////////////////////////////////////////////////////////// + +// The Arduino two-wire interface uses a 7-bit number for the address, +// and sets the last bit correctly based on reads and writes +#define ADDRESS_DEFAULT 0b0101001 + +// Record the current time to check an upcoming timeout against +#define startTimeout() (timeout_start_ms = millis()) + +// Check if timeout is enabled (set to nonzero value) and has expired +#define checkTimeoutExpired() (io_timeout > 0 && ((uint16_t)(millis() - timeout_start_ms) > io_timeout)) + +// Decode VCSEL (vertical cavity surface emitting laser) pulse period in PCLKs +// from register value +// based on VL53L0X_decode_vcsel_period() +#define decodeVcselPeriod(reg_val) (((reg_val) + 1) << 1) + +// Encode VCSEL pulse period register value from period in PCLKs +// based on VL53L0X_encode_vcsel_period() +#define encodeVcselPeriod(period_pclks) (((period_pclks) >> 1) - 1) + +// Calculate macro period in *nanoseconds* from VCSEL period in PCLKs +// based on VL53L0X_calc_macro_period_ps() +// PLL_period_ps = 1655; macro_period_vclks = 2304 +#define calcMacroPeriod(vcsel_period_pclks) ((((uint32_t)2304 * (vcsel_period_pclks) * 1655) + 500) / 1000) + +// Constructors //////////////////////////////////////////////////////////////// + +VL53L0X::VL53L0X() + : bus(&Wire) + , address(ADDRESS_DEFAULT) + , io_timeout(0) // no timeout + , did_timeout(false) +{ +} + +// Public Methods ////////////////////////////////////////////////////////////// + +void VL53L0X::setAddress(uint8_t new_addr) +{ + writeReg(I2C_SLAVE_DEVICE_ADDRESS, new_addr & 0x7F); + address = new_addr; +} + +// Initialize sensor using sequence based on VL53L0X_DataInit(), +// VL53L0X_StaticInit(), and VL53L0X_PerformRefCalibration(). +// This function does not perform reference SPAD calibration +// (VL53L0X_PerformRefSpadManagement()), since the API user manual says that it +// is performed by ST on the bare modules; it seems like that should work well +// enough unless a cover glass is added. +// If io_2v8 (optional) is true or not given, the sensor is configured for 2V8 +// mode. +bool VL53L0X::init(bool io_2v8) +{ + initResult = F(""); // Clear any previous result + // check model ID register (value specified in datasheet) + uint8_t modelId = readReg(IDENTIFICATION_MODEL_ID); + if (modelId != 0xEE) { // Recognize VL53L0X (0xEE) + initResult = F("VL53L0X: Init: unrecognized Model-ID: 0x"); + initResult += String(modelId, HEX); + return false; + } + + // VL53L0X_DataInit() begin + + // sensor uses 1V8 mode for I/O by default; switch to 2V8 mode if necessary + if (io_2v8) + { + writeReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV, + readReg(VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01); // set bit 0 + } + + // "Set I2C standard mode" + writeReg(0x88, 0x00); + + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + stop_variable = readReg(0x91); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + // disable SIGNAL_RATE_MSRC (bit 1) and SIGNAL_RATE_PRE_RANGE (bit 4) limit checks + writeReg(MSRC_CONFIG_CONTROL, readReg(MSRC_CONFIG_CONTROL) | 0x12); + + // set final range signal rate limit to 0.25 MCPS (million counts per second) + setSignalRateLimit(0.25); + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xFF); + + // VL53L0X_DataInit() end + + // VL53L0X_StaticInit() begin + + uint8_t spad_count; + bool spad_type_is_aperture; + if (!getSpadInfo(&spad_count, &spad_type_is_aperture)) { return false; } + + // The SPAD map (RefGoodSpadMap) is read by VL53L0X_get_info_from_device() in + // the API, but the same data seems to be more easily readable from + // GLOBAL_CONFIG_SPAD_ENABLES_REF_0 through _6, so read it from there + uint8_t ref_spad_map[6]; + readMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); + + // -- VL53L0X_set_reference_spads() begin (assume NVM values are valid) + + writeReg(0xFF, 0x01); + writeReg(DYNAMIC_SPAD_REF_EN_START_OFFSET, 0x00); + writeReg(DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD, 0x2C); + writeReg(0xFF, 0x00); + writeReg(GLOBAL_CONFIG_REF_EN_START_SELECT, 0xB4); + + uint8_t first_spad_to_enable = spad_type_is_aperture ? 12 : 0; // 12 is the first aperture spad + uint8_t spads_enabled = 0; + + for (uint8_t i = 0; i < 48; i++) + { + if (i < first_spad_to_enable || spads_enabled == spad_count) + { + // This bit is lower than the first one that should be enabled, or + // (reference_spad_count) bits have already been enabled, so zero this bit + ref_spad_map[i / 8] &= ~(1 << (i % 8)); + } + else if ((ref_spad_map[i / 8] >> (i % 8)) & 0x1) + { + spads_enabled++; + } + } + + writeMulti(GLOBAL_CONFIG_SPAD_ENABLES_REF_0, ref_spad_map, 6); + + // -- VL53L0X_set_reference_spads() end + + // -- VL53L0X_load_tuning_settings() begin + // DefaultTuningSettings from vl53l0x_tuning.h + + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + + writeReg(0xFF, 0x00); + writeReg(0x09, 0x00); + writeReg(0x10, 0x00); + writeReg(0x11, 0x00); + + writeReg(0x24, 0x01); + writeReg(0x25, 0xFF); + writeReg(0x75, 0x00); + + writeReg(0xFF, 0x01); + writeReg(0x4E, 0x2C); + writeReg(0x48, 0x00); + writeReg(0x30, 0x20); + + writeReg(0xFF, 0x00); + writeReg(0x30, 0x09); + writeReg(0x54, 0x00); + writeReg(0x31, 0x04); + writeReg(0x32, 0x03); + writeReg(0x40, 0x83); + writeReg(0x46, 0x25); + writeReg(0x60, 0x00); + writeReg(0x27, 0x00); + writeReg(0x50, 0x06); + writeReg(0x51, 0x00); + writeReg(0x52, 0x96); + writeReg(0x56, 0x08); + writeReg(0x57, 0x30); + writeReg(0x61, 0x00); + writeReg(0x62, 0x00); + writeReg(0x64, 0x00); + writeReg(0x65, 0x00); + writeReg(0x66, 0xA0); + + writeReg(0xFF, 0x01); + writeReg(0x22, 0x32); + writeReg(0x47, 0x14); + writeReg(0x49, 0xFF); + writeReg(0x4A, 0x00); + + writeReg(0xFF, 0x00); + writeReg(0x7A, 0x0A); + writeReg(0x7B, 0x00); + writeReg(0x78, 0x21); + + writeReg(0xFF, 0x01); + writeReg(0x23, 0x34); + writeReg(0x42, 0x00); + writeReg(0x44, 0xFF); + writeReg(0x45, 0x26); + writeReg(0x46, 0x05); + writeReg(0x40, 0x40); + writeReg(0x0E, 0x06); + writeReg(0x20, 0x1A); + writeReg(0x43, 0x40); + + writeReg(0xFF, 0x00); + writeReg(0x34, 0x03); + writeReg(0x35, 0x44); + + writeReg(0xFF, 0x01); + writeReg(0x31, 0x04); + writeReg(0x4B, 0x09); + writeReg(0x4C, 0x05); + writeReg(0x4D, 0x04); + + writeReg(0xFF, 0x00); + writeReg(0x44, 0x00); + writeReg(0x45, 0x20); + writeReg(0x47, 0x08); + writeReg(0x48, 0x28); + writeReg(0x67, 0x00); + writeReg(0x70, 0x04); + writeReg(0x71, 0x01); + writeReg(0x72, 0xFE); + writeReg(0x76, 0x00); + writeReg(0x77, 0x00); + + writeReg(0xFF, 0x01); + writeReg(0x0D, 0x01); + + writeReg(0xFF, 0x00); + writeReg(0x80, 0x01); + writeReg(0x01, 0xF8); + + writeReg(0xFF, 0x01); + writeReg(0x8E, 0x01); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + // -- VL53L0X_load_tuning_settings() end + + // "Set interrupt config to new sample ready" + // -- VL53L0X_SetGpioConfig() begin + + writeReg(SYSTEM_INTERRUPT_CONFIG_GPIO, 0x04); + writeReg(GPIO_HV_MUX_ACTIVE_HIGH, readReg(GPIO_HV_MUX_ACTIVE_HIGH) & ~0x10); // active low + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + + // -- VL53L0X_SetGpioConfig() end + + measurement_timing_budget_us = getMeasurementTimingBudget(); + + // "Disable MSRC and TCC by default" + // MSRC = Minimum Signal Rate Check + // TCC = Target CentreCheck + // -- VL53L0X_SetSequenceStepEnable() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); + + // -- VL53L0X_SetSequenceStepEnable() end + + // "Recalculate timing budget" + setMeasurementTimingBudget(measurement_timing_budget_us); + + // VL53L0X_StaticInit() end + + // VL53L0X_PerformRefCalibration() begin (VL53L0X_perform_ref_calibration()) + + // -- VL53L0X_perform_vhv_calibration() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x01); + if (!performSingleRefCalibration(0x40)) { return false; } + + // -- VL53L0X_perform_vhv_calibration() end + + // -- VL53L0X_perform_phase_calibration() begin + + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); + if (!performSingleRefCalibration(0x00)) { return false; } + + // -- VL53L0X_perform_phase_calibration() end + + // "restore the previous Sequence Config" + writeReg(SYSTEM_SEQUENCE_CONFIG, 0xE8); + + // VL53L0X_PerformRefCalibration() end + + return true; +} + +// Write an 8-bit register +void VL53L0X::writeReg(uint8_t reg, uint8_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write(value); + last_status = bus->endTransmission(); +} + +// Write a 16-bit register +void VL53L0X::writeReg16Bit(uint8_t reg, uint16_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write((value >> 8) & 0xFF); // value high byte + bus->write( value & 0xFF); // value low byte + last_status = bus->endTransmission(); +} + +// Write a 32-bit register +void VL53L0X::writeReg32Bit(uint8_t reg, uint32_t value) +{ + bus->beginTransmission(address); + bus->write(reg); + bus->write((value >> 24) & 0xFF); // value highest byte + bus->write((value >> 16) & 0xFF); + bus->write((value >> 8) & 0xFF); + bus->write( value & 0xFF); // value lowest byte + last_status = bus->endTransmission(); +} + +// Read an 8-bit register +uint8_t VL53L0X::readReg(uint8_t reg) +{ + uint8_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)1); + value = bus->read(); + + return value; +} + +// Read a 16-bit register +uint16_t VL53L0X::readReg16Bit(uint8_t reg) +{ + uint16_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)2); + value = (uint16_t)bus->read() << 8; // value high byte + value |= bus->read(); // value low byte + + return value; +} + +// Read a 32-bit register +uint32_t VL53L0X::readReg32Bit(uint8_t reg) +{ + uint32_t value; + + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, (uint8_t)4); + value = (uint32_t)bus->read() << 24; // value highest byte + value |= (uint32_t)bus->read() << 16; + value |= (uint16_t)bus->read() << 8; + value |= bus->read(); // value lowest byte + + return value; +} + +// Write an arbitrary number of bytes from the given array to the sensor, +// starting at the given register +void VL53L0X::writeMulti(uint8_t reg, uint8_t const * src, uint8_t count) +{ + bus->beginTransmission(address); + bus->write(reg); + + while (count-- > 0) + { + bus->write(*(src++)); + } + + last_status = bus->endTransmission(); +} + +// Read an arbitrary number of bytes from the sensor, starting at the given +// register, into the given array +void VL53L0X::readMulti(uint8_t reg, uint8_t * dst, uint8_t count) +{ + bus->beginTransmission(address); + bus->write(reg); + last_status = bus->endTransmission(); + + bus->requestFrom(address, count); + + while (count-- > 0) + { + *(dst++) = bus->read(); + } +} + +// Set the return signal rate limit check value in units of MCPS (mega counts +// per second). "This represents the amplitude of the signal reflected from the +// target and detected by the device"; setting this limit presumably determines +// the minimum measurement necessary for the sensor to report a valid reading. +// Setting a lower limit increases the potential range of the sensor but also +// seems to increase the likelihood of getting an inaccurate reading because of +// unwanted reflections from objects other than the intended target. +// Defaults to 0.25 MCPS as initialized by the ST API and this library. +bool VL53L0X::setSignalRateLimit(float limit_Mcps) +{ + if (limit_Mcps < 0 || limit_Mcps > 511.99f) { return false; } + + // Q9.7 fixed point format (9 integer bits, 7 fractional bits) + writeReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT, limit_Mcps * (1 << 7)); + return true; +} + +// Get the return signal rate limit check value in MCPS +float VL53L0X::getSignalRateLimit() +{ + return (float)readReg16Bit(FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT) / (1 << 7); +} + +// Set the measurement timing budget in microseconds, which is the time allowed +// for one measurement; the ST API and this library take care of splitting the +// timing budget among the sub-steps in the ranging sequence. A longer timing +// budget allows for more accurate measurements. Increasing the budget by a +// factor of N decreases the range measurement standard deviation by a factor of +// sqrt(N). Defaults to about 33 milliseconds; the minimum is 20 ms. +// based on VL53L0X_set_measurement_timing_budget_micro_seconds() +bool VL53L0X::setMeasurementTimingBudget(uint32_t budget_us) +{ + uint32_t const MinTimingBudget = 20000; + + if (budget_us < MinTimingBudget) { return false; } + + // FIXME TD-er: Following is nearly the same as VL53L0X::getMeasurementTimingBudget() + + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + uint16_t const StartOverhead = 1910; + uint16_t const EndOverhead = 960; + uint16_t const MsrcOverhead = 660; + uint16_t const TccOverhead = 590; + uint16_t const DssOverhead = 690; + uint16_t const PreRangeOverhead = 660; + uint16_t const FinalRangeOverhead = 550; + + uint32_t used_budget_us = StartOverhead + EndOverhead; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + if (enables.tcc) + { + used_budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); + } + + if (enables.dss) + { + used_budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); + } + else if (enables.msrc) + { + used_budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); + } + + if (enables.pre_range) + { + used_budget_us += (timeouts.pre_range_us + PreRangeOverhead); + } + + if (enables.final_range) + { + used_budget_us += FinalRangeOverhead; + + // "Note that the final range timeout is determined by the timing + // budget and the sum of all other timeouts within the sequence. + // If there is no room for the final range timeout, then an error + // will be set. Otherwise the remaining time will be applied to + // the final range." + + if (used_budget_us > budget_us) + { + // "Requested timeout too big." + return false; + } + + uint32_t final_range_timeout_us = budget_us - used_budget_us; + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) + + // "For the final range timeout, the pre-range timeout + // must be added. To do this both final and pre-range + // timeouts must be expressed in macro periods MClks + // because they have different vcsel periods." + + uint32_t final_range_timeout_mclks = + timeoutMicrosecondsToMclks(final_range_timeout_us, + timeouts.final_range_vcsel_period_pclks); + + if (enables.pre_range) + { + final_range_timeout_mclks += timeouts.pre_range_mclks; + } + + writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(final_range_timeout_mclks)); + + // set_sequence_step_timeout() end + + measurement_timing_budget_us = budget_us; // store for internal reuse + } + return true; +} + +// Get the measurement timing budget in microseconds +// based on VL53L0X_get_measurement_timing_budget_micro_seconds() +// in us +uint32_t VL53L0X::getMeasurementTimingBudget() +{ + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + uint16_t const StartOverhead = 1910; + uint16_t const EndOverhead = 960; + uint16_t const MsrcOverhead = 660; + uint16_t const TccOverhead = 590; + uint16_t const DssOverhead = 690; + uint16_t const PreRangeOverhead = 660; + uint16_t const FinalRangeOverhead = 550; + + // "Start and end overhead times always present" + uint32_t budget_us = StartOverhead + EndOverhead; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + if (enables.tcc) + { + budget_us += (timeouts.msrc_dss_tcc_us + TccOverhead); + } + + if (enables.dss) + { + budget_us += 2 * (timeouts.msrc_dss_tcc_us + DssOverhead); + } + else if (enables.msrc) + { + budget_us += (timeouts.msrc_dss_tcc_us + MsrcOverhead); + } + + if (enables.pre_range) + { + budget_us += (timeouts.pre_range_us + PreRangeOverhead); + } + + if (enables.final_range) + { + budget_us += (timeouts.final_range_us + FinalRangeOverhead); + } + + measurement_timing_budget_us = budget_us; // store for internal reuse + return budget_us; +} + +// Set the VCSEL (vertical cavity surface emitting laser) pulse period for the +// given period type (pre-range or final range) to the given value in PCLKs. +// Longer periods seem to increase the potential range of the sensor. +// Valid values are (even numbers only): +// pre: 12 to 18 (initialized default: 14) +// final: 8 to 14 (initialized default: 10) +// based on VL53L0X_set_vcsel_pulse_period() +bool VL53L0X::setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks) +{ + uint8_t vcsel_period_reg = encodeVcselPeriod(period_pclks); + + SequenceStepEnables enables; + SequenceStepTimeouts timeouts; + + getSequenceStepEnables(&enables); + getSequenceStepTimeouts(&enables, &timeouts); + + // "Apply specific settings for the requested clock period" + // "Re-calculate and apply timeouts, in macro periods" + + // "When the VCSEL period for the pre or final range is changed, + // the corresponding timeout must be read from the device using + // the current VCSEL period, then the new VCSEL period can be + // applied. The timeout then must be written back to the device + // using the new VCSEL period. + // + // For the MSRC timeout, the same applies - this timeout being + // dependant on the pre-range vcsel period." + + + if (type == VcselPeriodPreRange) + { + // "Set phase check limits" + switch (period_pclks) + { + case 12: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x18); + break; + + case 14: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x30); + break; + + case 16: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x40); + break; + + case 18: + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_HIGH, 0x50); + break; + + default: + // invalid period + return false; + } + writeReg(PRE_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + + // apply new VCSEL period + writeReg(PRE_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); + + // update timeouts + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_PRE_RANGE) + + uint16_t new_pre_range_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.pre_range_us, period_pclks); + + writeReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(new_pre_range_timeout_mclks)); + + // set_sequence_step_timeout() end + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_MSRC) + + uint16_t new_msrc_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.msrc_dss_tcc_us, period_pclks); + + writeReg(MSRC_CONFIG_TIMEOUT_MACROP, + (new_msrc_timeout_mclks > 256) ? 255 : (new_msrc_timeout_mclks - 1)); + + // set_sequence_step_timeout() end + } + else if (type == VcselPeriodFinalRange) + { + switch (period_pclks) + { + case 8: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x10); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x02); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x0C); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x30); + writeReg(0xFF, 0x00); + break; + + case 10: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x28); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x09); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + case 12: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x38); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x08); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + case 14: + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_HIGH, 0x48); + writeReg(FINAL_RANGE_CONFIG_VALID_PHASE_LOW, 0x08); + writeReg(GLOBAL_CONFIG_VCSEL_WIDTH, 0x03); + writeReg(ALGO_PHASECAL_CONFIG_TIMEOUT, 0x07); + writeReg(0xFF, 0x01); + writeReg(ALGO_PHASECAL_LIM, 0x20); + writeReg(0xFF, 0x00); + break; + + default: + // invalid period + return false; + } + + // apply new VCSEL period + writeReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD, vcsel_period_reg); + + // update timeouts + + // set_sequence_step_timeout() begin + // (SequenceStepId == VL53L0X_SEQUENCESTEP_FINAL_RANGE) + + // "For the final range timeout, the pre-range timeout + // must be added. To do this both final and pre-range + // timeouts must be expressed in macro periods MClks + // because they have different vcsel periods." + + uint16_t new_final_range_timeout_mclks = + timeoutMicrosecondsToMclks(timeouts.final_range_us, period_pclks); + + if (enables.pre_range) + { + new_final_range_timeout_mclks += timeouts.pre_range_mclks; + } + + writeReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI, + encodeTimeout(new_final_range_timeout_mclks)); + + // set_sequence_step_timeout end + } + else + { + // invalid type + return false; + } + + // "Finally, the timing budget must be re-applied" + + setMeasurementTimingBudget(measurement_timing_budget_us); + + // "Perform the phase calibration. This is needed after changing on vcsel period." + // VL53L0X_perform_phase_calibration() begin + + uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); + writeReg(SYSTEM_SEQUENCE_CONFIG, 0x02); + performSingleRefCalibration(0x0); + writeReg(SYSTEM_SEQUENCE_CONFIG, sequence_config); + + // VL53L0X_perform_phase_calibration() end + + return true; +} + +// Get the VCSEL pulse period in PCLKs for the given period type. +// based on VL53L0X_get_vcsel_pulse_period() +uint8_t VL53L0X::getVcselPulsePeriod(vcselPeriodType type) +{ + if (type == VcselPeriodPreRange) + { + return decodeVcselPeriod(readReg(PRE_RANGE_CONFIG_VCSEL_PERIOD)); + } + else if (type == VcselPeriodFinalRange) + { + return decodeVcselPeriod(readReg(FINAL_RANGE_CONFIG_VCSEL_PERIOD)); + } + else { return 255; } +} + +// Start continuous ranging measurements. If period_ms (optional) is 0 or not +// given, continuous back-to-back mode is used (the sensor takes measurements as +// often as possible); otherwise, continuous timed mode is used, with the given +// inter-measurement period in milliseconds determining how often the sensor +// takes a measurement. +// based on VL53L0X_StartMeasurement() +void VL53L0X::startContinuous(uint32_t period_ms) +{ + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, stop_variable); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + if (period_ms != 0) + { + // continuous timed mode + + // VL53L0X_SetInterMeasurementPeriodMilliSeconds() begin + + uint16_t osc_calibrate_val = readReg16Bit(OSC_CALIBRATE_VAL); + + if (osc_calibrate_val != 0) + { + period_ms *= osc_calibrate_val; + } + + writeReg32Bit(SYSTEM_INTERMEASUREMENT_PERIOD, period_ms); + + // VL53L0X_SetInterMeasurementPeriodMilliSeconds() end + + writeReg(SYSRANGE_START, 0x04); // VL53L0X_REG_SYSRANGE_MODE_TIMED + } + else + { + // continuous back-to-back mode + writeReg(SYSRANGE_START, 0x02); // VL53L0X_REG_SYSRANGE_MODE_BACKTOBACK + } + state = state_e::waitMeasurement; + start_timeout_ms = millis(); +} + +// Stop continuous measurements +// based on VL53L0X_StopMeasurement() +void VL53L0X::stopContinuous() +{ + writeReg(SYSRANGE_START, 0x01); // VL53L0X_REG_SYSRANGE_MODE_SINGLESHOT + + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, 0x00); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + state = state_e::initialized; + start_timeout_ms = 0; +} + +// Returns a range reading in millimeters when continuous mode is active +// (readRangeSingleMillimeters() also calls this function after starting a +// single-shot range measurement) +uint16_t VL53L0X::readRangeContinuousMillimeters() +{ + int16_t distance; + start_timeout_ms = millis(); + while (!loop(distance)) { + if (VL53L0X_NOT_WAITING == distance) { + return 65535u; + } + } + if (VL53L0X_TIMEOUT == distance) + { + return 65535u; + } + if (VL53L0X_WAITING == distance) + { + return 65534u; + } + return distance; +} + +void VL53L0X::startSingleMeasurement() +{ + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + writeReg(0x91, stop_variable); + writeReg(0x00, 0x01); + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + writeReg(SYSRANGE_START, 0x01); + + state = state_e::waitStartBitCleared; + start_timeout_ms = millis(); +} + +bool VL53L0X::asyncReadRangeSingleMillimeters(int16_t& distance) +{ + const bool res = loop(distance); + if (res || (VL53L0X_WAITING != distance)) { + state = state_e::initialized; + start_timeout_ms = 0; + return true; + } + return false; +} + +bool VL53L0X::asyncReadRangeContinuousMillimeters(int16_t& distance) +{ + return loop(distance); +} + +// Performs a single-shot range measurement and returns the reading in +// millimeters +// based on VL53L0X_PerformSingleRangingMeasurement() +uint16_t VL53L0X::readRangeSingleMillimeters() +{ + startSingleMeasurement(); + + // "Wait until start bit has been cleared" + int16_t distance; + while (!loop(distance)) { + delay(0); + } + state = state_e::initialized; + start_timeout_ms = 0; + + uint16_t res = 65535u; + if (distance >= 0) + { + res = distance; + } + return res; +} + +// Did a timeout occur in one of the read functions since the last call to +// timeoutOccurred()? +bool VL53L0X::timeoutOccurred() +{ + bool tmp = did_timeout; + did_timeout = false; + return tmp; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +// Get reference SPAD (single photon avalanche diode) count and type +// based on VL53L0X_get_info_from_device(), +// but only gets reference SPAD count and type +bool VL53L0X::getSpadInfo(uint8_t * count, bool * type_is_aperture) +{ + uint8_t tmp; + + writeReg(0x80, 0x01); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x00); + + writeReg(0xFF, 0x06); + writeReg(0x83, readReg(0x83) | 0x04); + writeReg(0xFF, 0x07); + writeReg(0x81, 0x01); + + writeReg(0x80, 0x01); + + writeReg(0x94, 0x6b); + writeReg(0x83, 0x00); + startTimeout(); + while (readReg(0x83) == 0x00) + { + if (checkTimeoutExpired()) { return false; } + } + writeReg(0x83, 0x01); + tmp = readReg(0x92); + + *count = tmp & 0x7f; + *type_is_aperture = (tmp >> 7) & 0x01; + + writeReg(0x81, 0x00); + writeReg(0xFF, 0x06); + writeReg(0x83, readReg(0x83) & ~0x04); + writeReg(0xFF, 0x01); + writeReg(0x00, 0x01); + + writeReg(0xFF, 0x00); + writeReg(0x80, 0x00); + + return true; +} + +// Get sequence step enables +// based on VL53L0X_GetSequenceStepEnables() +void VL53L0X::getSequenceStepEnables(SequenceStepEnables * enables) +{ + uint8_t sequence_config = readReg(SYSTEM_SEQUENCE_CONFIG); + + enables->tcc = (sequence_config >> 4) & 0x1; + enables->dss = (sequence_config >> 3) & 0x1; + enables->msrc = (sequence_config >> 2) & 0x1; + enables->pre_range = (sequence_config >> 6) & 0x1; + enables->final_range = (sequence_config >> 7) & 0x1; +} + +// Get sequence step timeouts +// based on get_sequence_step_timeout(), +// but gets all timeouts instead of just the requested one, and also stores +// intermediate values +void VL53L0X::getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts) +{ + timeouts->pre_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodPreRange); + + timeouts->msrc_dss_tcc_mclks = readReg(MSRC_CONFIG_TIMEOUT_MACROP) + 1; + timeouts->msrc_dss_tcc_us = + timeoutMclksToMicroseconds(timeouts->msrc_dss_tcc_mclks, + timeouts->pre_range_vcsel_period_pclks); + + timeouts->pre_range_mclks = + decodeTimeout(readReg16Bit(PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI)); + timeouts->pre_range_us = + timeoutMclksToMicroseconds(timeouts->pre_range_mclks, + timeouts->pre_range_vcsel_period_pclks); + + timeouts->final_range_vcsel_period_pclks = getVcselPulsePeriod(VcselPeriodFinalRange); + + timeouts->final_range_mclks = + decodeTimeout(readReg16Bit(FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI)); + + if (enables->pre_range) + { + timeouts->final_range_mclks -= timeouts->pre_range_mclks; + } + + timeouts->final_range_us = + timeoutMclksToMicroseconds(timeouts->final_range_mclks, + timeouts->final_range_vcsel_period_pclks); +} + +// Decode sequence step timeout in MCLKs from register value +// based on VL53L0X_decode_timeout() +// Note: the original function returned a uint32_t, but the return value is +// always stored in a uint16_t. +uint16_t VL53L0X::decodeTimeout(uint16_t reg_val) +{ + // format: "(LSByte * 2^MSByte) + 1" + return (uint16_t)((reg_val & 0x00FF) << + (uint16_t)((reg_val & 0xFF00) >> 8)) + 1; +} + +// Encode sequence step timeout register value from timeout in MCLKs +// based on VL53L0X_encode_timeout() +uint16_t VL53L0X::encodeTimeout(uint32_t timeout_mclks) +{ + // format: "(LSByte * 2^MSByte) + 1" + + uint32_t ls_byte = 0; + uint16_t ms_byte = 0; + + if (timeout_mclks > 0) + { + ls_byte = timeout_mclks - 1; + + while ((ls_byte & 0xFFFFFF00) > 0) + { + ls_byte >>= 1; + ms_byte++; + } + + return (ms_byte << 8) | (ls_byte & 0xFF); + } + else { return 0; } +} + +// Convert sequence step timeout from MCLKs to microseconds with given VCSEL period in PCLKs +// based on VL53L0X_calc_timeout_us() +uint32_t VL53L0X::timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); + + return ((timeout_period_mclks * macro_period_ns) + 500) / 1000; +} + +// Convert sequence step timeout from microseconds to MCLKs with given VCSEL period in PCLKs +// based on VL53L0X_calc_timeout_mclks() +uint32_t VL53L0X::timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks) +{ + uint32_t macro_period_ns = calcMacroPeriod(vcsel_period_pclks); + + return (((timeout_period_us * 1000) + (macro_period_ns / 2)) / macro_period_ns); +} + + +// based on VL53L0X_perform_single_ref_calibration() +bool VL53L0X::performSingleRefCalibration(uint8_t vhv_init_byte) +{ + writeReg(SYSRANGE_START, 0x01 | vhv_init_byte); // VL53L0X_REG_SYSRANGE_MODE_START_STOP + + startTimeout(); + while ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) + { + if (checkTimeoutExpired()) { return false; } + } + + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + + writeReg(SYSRANGE_START, 0x00); + + return true; +} + +// Simply return the string, it is cleared and could be set during init +String VL53L0X::getInitResult() { + return initResult; +} + + +bool VL53L0X::loop(int16_t& distance) { + distance = VL53L0X_NOT_WAITING; + + const int32_t timePassedSince = (start_timeout_ms == 0) ? 0 : (int32_t)(millis() - start_timeout_ms); + switch (state) { + case state_e::uninitialized: + case state_e::initialized: + break; + case state_e::waitStartBitCleared: + { + distance = VL53L0X_WAITING; + if (timePassedSince > io_timeout) { + distance = VL53L0X_TIMEOUT; + did_timeout = true; + state = state_e::initialized; + start_timeout_ms = 0; + return true; + } else { + if (!(readReg(SYSRANGE_START) & 0x01)) { + // start bit has been cleared, wait for measurement to complete + state = state_e::waitMeasurement; + start_timeout_ms = millis(); + } + } + + return false; + } + case state_e::waitMeasurement: + { + if ((readReg(RESULT_INTERRUPT_STATUS) & 0x07) == 0) { + if (timePassedSince > io_timeout) { + distance = VL53L0X_TIMEOUT; + did_timeout = true; + start_timeout_ms = millis(); + return true; + } + distance = VL53L0X_WAITING; + return false; + } + // assumptions: Linearity Corrective Gain is 1000 (default); + // fractional ranging is not enabled + distance = readReg16Bit(RESULT_RANGE_STATUS + 10); + + writeReg(SYSTEM_INTERRUPT_CLEAR, 0x01); + did_timeout = false; + start_timeout_ms = millis(); + return true; + } + } + return true; } \ No newline at end of file diff --git a/lib/VL53L0X/src/VL53L0X.h b/lib/VL53L0X/src/VL53L0X.h index 0b2884f34..4b2c4d3fa 100644 --- a/lib/VL53L0X/src/VL53L0X.h +++ b/lib/VL53L0X/src/VL53L0X.h @@ -1,183 +1,220 @@ -#ifndef VL53L0X_h -#define VL53L0X_h - -#include -#include - -class VL53L0X -{ - public: - // register addresses from API vl53l0x_device.h (ordered as listed there) - enum regAddr - { - SYSRANGE_START = 0x00, - - SYSTEM_THRESH_HIGH = 0x0C, - SYSTEM_THRESH_LOW = 0x0E, - - SYSTEM_SEQUENCE_CONFIG = 0x01, - SYSTEM_RANGE_CONFIG = 0x09, - SYSTEM_INTERMEASUREMENT_PERIOD = 0x04, - - SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0A, - - GPIO_HV_MUX_ACTIVE_HIGH = 0x84, - - SYSTEM_INTERRUPT_CLEAR = 0x0B, - - RESULT_INTERRUPT_STATUS = 0x13, - RESULT_RANGE_STATUS = 0x14, - - RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN = 0xBC, - RESULT_CORE_RANGING_TOTAL_EVENTS_RTN = 0xC0, - RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF = 0xD0, - RESULT_CORE_RANGING_TOTAL_EVENTS_REF = 0xD4, - RESULT_PEAK_SIGNAL_RATE_REF = 0xB6, - - ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28, - - I2C_SLAVE_DEVICE_ADDRESS = 0x8A, - - MSRC_CONFIG_CONTROL = 0x60, - - PRE_RANGE_CONFIG_MIN_SNR = 0x27, - PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56, - PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57, - PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64, - - FINAL_RANGE_CONFIG_MIN_SNR = 0x67, - FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47, - FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48, - FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44, - - PRE_RANGE_CONFIG_SIGMA_THRESH_HI = 0x61, - PRE_RANGE_CONFIG_SIGMA_THRESH_LO = 0x62, - - PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50, - PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51, - PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x52, - - SYSTEM_HISTOGRAM_BIN = 0x81, - HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT = 0x33, - HISTOGRAM_CONFIG_READOUT_CTRL = 0x55, - - FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70, - FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71, - FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x72, - CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20, - - MSRC_CONFIG_TIMEOUT_MACROP = 0x46, - - SOFT_RESET_GO2_SOFT_RESET_N = 0xBF, - IDENTIFICATION_MODEL_ID = 0xC0, - IDENTIFICATION_REVISION_ID = 0xC2, - - OSC_CALIBRATE_VAL = 0xF8, - - GLOBAL_CONFIG_VCSEL_WIDTH = 0x32, - GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xB0, - GLOBAL_CONFIG_SPAD_ENABLES_REF_1 = 0xB1, - GLOBAL_CONFIG_SPAD_ENABLES_REF_2 = 0xB2, - GLOBAL_CONFIG_SPAD_ENABLES_REF_3 = 0xB3, - GLOBAL_CONFIG_SPAD_ENABLES_REF_4 = 0xB4, - GLOBAL_CONFIG_SPAD_ENABLES_REF_5 = 0xB5, - - GLOBAL_CONFIG_REF_EN_START_SELECT = 0xB6, - DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4E, - DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4F, - POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80, - - VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV = 0x89, - - ALGO_PHASECAL_LIM = 0x30, - ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30, - }; - - enum vcselPeriodType { VcselPeriodPreRange, VcselPeriodFinalRange }; - - uint8_t last_status; // status of last I2C transmission - - VL53L0X(); - - void setBus(TwoWire * bus) { this->bus = bus; } - TwoWire * getBus() { return bus; } - - void setAddress(uint8_t new_addr); - inline uint8_t getAddress() { return address; } - - bool init(bool io_2v8 = true); - - void writeReg(uint8_t reg, uint8_t value); - void writeReg16Bit(uint8_t reg, uint16_t value); - void writeReg32Bit(uint8_t reg, uint32_t value); - uint8_t readReg(uint8_t reg); - uint16_t readReg16Bit(uint8_t reg); - uint32_t readReg32Bit(uint8_t reg); - - void writeMulti(uint8_t reg, uint8_t const * src, uint8_t count); - void readMulti(uint8_t reg, uint8_t * dst, uint8_t count); - - bool setSignalRateLimit(float limit_Mcps); - float getSignalRateLimit(); - - bool setMeasurementTimingBudget(uint32_t budget_us); - uint32_t getMeasurementTimingBudget(); - - bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks); - uint8_t getVcselPulsePeriod(vcselPeriodType type); - - void startContinuous(uint32_t period_ms = 0); - void stopContinuous(); - uint16_t readRangeContinuousMillimeters(); - uint16_t readRangeSingleMillimeters(); - - inline void setTimeout(uint16_t timeout) { io_timeout = timeout; } - inline uint16_t getTimeout() { return io_timeout; } - bool timeoutOccurred(); - String getInitResult(); - - private: - // TCC: Target CentreCheck - // MSRC: Minimum Signal Rate Check - // DSS: Dynamic Spad Selection - - struct SequenceStepEnables - { - boolean tcc, msrc, dss, pre_range, final_range; - }; - - struct SequenceStepTimeouts - { - uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks; - - uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks; - uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us; - }; - - TwoWire * bus; - uint8_t address; - uint16_t io_timeout; - bool did_timeout; - uint16_t timeout_start_ms; - - uint8_t stop_variable; // read by init and used when starting measurement; is StopVariable field of VL53L0X_DevData_t structure in API - uint32_t measurement_timing_budget_us; - - bool getSpadInfo(uint8_t * count, bool * type_is_aperture); - - void getSequenceStepEnables(SequenceStepEnables * enables); - void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts); - - bool performSingleRefCalibration(uint8_t vhv_init_byte); - - static uint16_t decodeTimeout(uint16_t value); - static uint16_t encodeTimeout(uint32_t timeout_mclks); - static uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks); - static uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks); - String initResult; -}; - -#endif - - - +#ifndef VL53L0X_h +#define VL53L0X_h + +#include +#include + +class VL53L0X +{ + public: + // register addresses from API vl53l0x_device.h (ordered as listed there) + enum regAddr + { + SYSRANGE_START = 0x00, + + SYSTEM_THRESH_HIGH = 0x0C, + SYSTEM_THRESH_LOW = 0x0E, + + SYSTEM_SEQUENCE_CONFIG = 0x01, + SYSTEM_RANGE_CONFIG = 0x09, + SYSTEM_INTERMEASUREMENT_PERIOD = 0x04, + + SYSTEM_INTERRUPT_CONFIG_GPIO = 0x0A, + + GPIO_HV_MUX_ACTIVE_HIGH = 0x84, + + SYSTEM_INTERRUPT_CLEAR = 0x0B, + + RESULT_INTERRUPT_STATUS = 0x13, + RESULT_RANGE_STATUS = 0x14, + + RESULT_CORE_AMBIENT_WINDOW_EVENTS_RTN = 0xBC, + RESULT_CORE_RANGING_TOTAL_EVENTS_RTN = 0xC0, + RESULT_CORE_AMBIENT_WINDOW_EVENTS_REF = 0xD0, + RESULT_CORE_RANGING_TOTAL_EVENTS_REF = 0xD4, + RESULT_PEAK_SIGNAL_RATE_REF = 0xB6, + + ALGO_PART_TO_PART_RANGE_OFFSET_MM = 0x28, + + I2C_SLAVE_DEVICE_ADDRESS = 0x8A, + + MSRC_CONFIG_CONTROL = 0x60, + + PRE_RANGE_CONFIG_MIN_SNR = 0x27, + PRE_RANGE_CONFIG_VALID_PHASE_LOW = 0x56, + PRE_RANGE_CONFIG_VALID_PHASE_HIGH = 0x57, + PRE_RANGE_MIN_COUNT_RATE_RTN_LIMIT = 0x64, + + FINAL_RANGE_CONFIG_MIN_SNR = 0x67, + FINAL_RANGE_CONFIG_VALID_PHASE_LOW = 0x47, + FINAL_RANGE_CONFIG_VALID_PHASE_HIGH = 0x48, + FINAL_RANGE_CONFIG_MIN_COUNT_RATE_RTN_LIMIT = 0x44, + + PRE_RANGE_CONFIG_SIGMA_THRESH_HI = 0x61, + PRE_RANGE_CONFIG_SIGMA_THRESH_LO = 0x62, + + PRE_RANGE_CONFIG_VCSEL_PERIOD = 0x50, + PRE_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x51, + PRE_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x52, + + SYSTEM_HISTOGRAM_BIN = 0x81, + HISTOGRAM_CONFIG_INITIAL_PHASE_SELECT = 0x33, + HISTOGRAM_CONFIG_READOUT_CTRL = 0x55, + + FINAL_RANGE_CONFIG_VCSEL_PERIOD = 0x70, + FINAL_RANGE_CONFIG_TIMEOUT_MACROP_HI = 0x71, + FINAL_RANGE_CONFIG_TIMEOUT_MACROP_LO = 0x72, + CROSSTALK_COMPENSATION_PEAK_RATE_MCPS = 0x20, + + MSRC_CONFIG_TIMEOUT_MACROP = 0x46, + + SOFT_RESET_GO2_SOFT_RESET_N = 0xBF, + IDENTIFICATION_MODEL_ID = 0xC0, + IDENTIFICATION_REVISION_ID = 0xC2, + + OSC_CALIBRATE_VAL = 0xF8, + + GLOBAL_CONFIG_VCSEL_WIDTH = 0x32, + GLOBAL_CONFIG_SPAD_ENABLES_REF_0 = 0xB0, + GLOBAL_CONFIG_SPAD_ENABLES_REF_1 = 0xB1, + GLOBAL_CONFIG_SPAD_ENABLES_REF_2 = 0xB2, + GLOBAL_CONFIG_SPAD_ENABLES_REF_3 = 0xB3, + GLOBAL_CONFIG_SPAD_ENABLES_REF_4 = 0xB4, + GLOBAL_CONFIG_SPAD_ENABLES_REF_5 = 0xB5, + + GLOBAL_CONFIG_REF_EN_START_SELECT = 0xB6, + DYNAMIC_SPAD_NUM_REQUESTED_REF_SPAD = 0x4E, + DYNAMIC_SPAD_REF_EN_START_OFFSET = 0x4F, + POWER_MANAGEMENT_GO1_POWER_FORCE = 0x80, + + VHV_CONFIG_PAD_SCL_SDA__EXTSUP_HV = 0x89, + + ALGO_PHASECAL_LIM = 0x30, + ALGO_PHASECAL_CONFIG_TIMEOUT = 0x30, + }; + + enum vcselPeriodType { VcselPeriodPreRange, VcselPeriodFinalRange }; + + uint8_t last_status; // status of last I2C transmission + + VL53L0X(); + + void setBus(TwoWire * bus) { this->bus = bus; } + TwoWire * getBus() { return bus; } + + void setAddress(uint8_t new_addr); + inline uint8_t getAddress() { return address; } + + bool init(bool io_2v8 = true); + + void writeReg(uint8_t reg, uint8_t value); + void writeReg16Bit(uint8_t reg, uint16_t value); + void writeReg32Bit(uint8_t reg, uint32_t value); + uint8_t readReg(uint8_t reg); + uint16_t readReg16Bit(uint8_t reg); + uint32_t readReg32Bit(uint8_t reg); + + void writeMulti(uint8_t reg, uint8_t const * src, uint8_t count); + void readMulti(uint8_t reg, uint8_t * dst, uint8_t count); + + bool setSignalRateLimit(float limit_Mcps); + float getSignalRateLimit(); + + bool setMeasurementTimingBudget(uint32_t budget_us); + uint32_t getMeasurementTimingBudget(); + + bool setVcselPulsePeriod(vcselPeriodType type, uint8_t period_pclks); + uint8_t getVcselPulsePeriod(vcselPeriodType type); + + void startContinuous(uint32_t period_ms = 0); + void stopContinuous(); + uint16_t readRangeContinuousMillimeters(); + uint16_t readRangeSingleMillimeters(); + + inline void setTimeout(uint16_t timeout) { io_timeout = timeout; } + inline uint16_t getTimeout() { return io_timeout; } + bool timeoutOccurred(); + String getInitResult(); + + private: + // TCC: Target CentreCheck + // MSRC: Minimum Signal Rate Check + // DSS: Dynamic Spad Selection + + struct SequenceStepEnables + { + boolean tcc, msrc, dss, pre_range, final_range; + }; + + struct SequenceStepTimeouts + { + uint16_t pre_range_vcsel_period_pclks, final_range_vcsel_period_pclks; + + uint16_t msrc_dss_tcc_mclks, pre_range_mclks, final_range_mclks; + uint32_t msrc_dss_tcc_us, pre_range_us, final_range_us; + }; + + TwoWire * bus; + uint8_t address; + uint16_t io_timeout; + bool did_timeout; + uint16_t timeout_start_ms; + + uint8_t stop_variable; // read by init and used when starting measurement; is StopVariable field of VL53L0X_DevData_t structure in API + uint32_t measurement_timing_budget_us; + + bool getSpadInfo(uint8_t * count, bool * type_is_aperture); + + void getSequenceStepEnables(SequenceStepEnables * enables); + void getSequenceStepTimeouts(SequenceStepEnables const * enables, SequenceStepTimeouts * timeouts); + + bool performSingleRefCalibration(uint8_t vhv_init_byte); + + static uint16_t decodeTimeout(uint16_t value); + static uint16_t encodeTimeout(uint32_t timeout_mclks); + static uint32_t timeoutMclksToMicroseconds(uint16_t timeout_period_mclks, uint8_t vcsel_period_pclks); + static uint32_t timeoutMicrosecondsToMclks(uint32_t timeout_period_us, uint8_t vcsel_period_pclks); + String initResult; + +public: + + #define VL53L0X_NOT_WAITING -1 + #define VL53L0X_WAITING -2 + #define VL53L0X_TIMEOUT -3 + +private: + + // Returns true when measurement done. + // distance < 0 when there was an error, otherwise successful measurement + bool loop(int16_t& distance); + +public: + + void startSingleMeasurement(); + + // Check if taking a sample has finished + // Return true when measurement has finished or aborted. + // Valid distance when distance >= 0 + bool asyncReadRangeSingleMillimeters(int16_t& distance); + + // Check if new sample is ready. + // Return true when measurement has finished or aborted. + // Valid distance when distance >= 0 + bool asyncReadRangeContinuousMillimeters(int16_t& distance); + +private: + + enum class state_e { + uninitialized, + initialized, + waitStartBitCleared, + waitMeasurement + }; + state_e state = state_e::uninitialized; + uint32_t start_timeout_ms = 0; +}; + +#endif + + + diff --git a/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP32/WakeOnLan-ESP32.ino b/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP32/WakeOnLan-ESP32.ino index 8c1a13525..c29312538 100644 --- a/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP32/WakeOnLan-ESP32.ino +++ b/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP32/WakeOnLan-ESP32.ino @@ -1,48 +1,48 @@ -#include -#include - -#include - -WiFiUDP UDP; -WakeOnLan WOL(UDP); - -const char* ssid = "your-ssid"; -const char* password = "your-password"; - -void wakeMyPC() { - const char *MACAddress = "01:23:45:67:89:AB"; - - WOL.sendMagicPacket(MACAddress); // Send Wake On Lan packet with the above MAC address. Default to port 9. - // WOL.sendMagicPacket(MACAddress, 7); // Change the port number -} - -void wakeOfficePC() { - const char *MACAddress = "01:23:45:67:89:AB"; - const char *secureOn = "FE:DC:BA:98:76:54"; - - WOL.sendSecureMagicPacket(MACAddress, secureOn); // Send Wake On Lan packet with the above MAC address and SecureOn feature. Default to port 9. - // WOL.sendSecureMagicPacket(MACAddress, secureOn, 7); // Change the port number -} - -void setup() -{ - WOL.setRepeat(3, 100); // Optional, repeat the packet three times with 100ms between. WARNING delay() is used between send packet function. - - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask()); // Optional => To calculate the broadcast address, otherwise 255.255.255.255 is used (which is denied in some networks). - - wakeMyPC(); - wakeOfficePC(); -} - - -void loop() -{ -} +#include +#include + +#include + +WiFiUDP UDP; +WakeOnLan WOL(UDP); + +const char* ssid = "your-ssid"; +const char* password = "your-password"; + +void wakeMyPC() { + const char *MACAddress = "01:23:45:67:89:AB"; + + WOL.sendMagicPacket(MACAddress); // Send Wake On Lan packet with the above MAC address. Default to port 9. + // WOL.sendMagicPacket(MACAddress, 7); // Change the port number +} + +void wakeOfficePC() { + const char *MACAddress = "01:23:45:67:89:AB"; + const char *secureOn = "FE:DC:BA:98:76:54"; + + WOL.sendSecureMagicPacket(MACAddress, secureOn); // Send Wake On Lan packet with the above MAC address and SecureOn feature. Default to port 9. + // WOL.sendSecureMagicPacket(MACAddress, secureOn, 7); // Change the port number +} + +void setup() +{ + WOL.setRepeat(3, 100); // Optional, repeat the packet three times with 100ms between. WARNING delay() is used between send packet function. + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask()); // Optional => To calculate the broadcast address, otherwise 255.255.255.255 is used (which is denied in some networks). + + wakeMyPC(); + wakeOfficePC(); +} + + +void loop() +{ +} diff --git a/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP8266/WakeOnLan-ESP8266.ino b/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP8266/WakeOnLan-ESP8266.ino index 8d920fc09..880a67c9a 100644 --- a/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP8266/WakeOnLan-ESP8266.ino +++ b/lib/WakeOnLan-1.1.6/examples/WakeOnLan-ESP8266/WakeOnLan-ESP8266.ino @@ -1,48 +1,48 @@ -#include -#include - -#include - -WiFiUDP UDP; -WakeOnLan WOL(UDP); - -const char* ssid = "your-ssid"; -const char* password = "your-password"; - -void wakeMyPC() { - const char *MACAddress = "01:23:45:67:89:AB"; - - WOL.sendMagicPacket(MACAddress); // Send Wake On Lan packet with the above MAC address. Default to port 9. - // WOL.sendMagicPacket(MACAddress, 7); // Change the port number -} - -void wakeOfficePC() { - const char *MACAddress = "01:23:45:67:89:AB"; - const char *secureOn = "FE:DC:BA:98:76:54"; - - WOL.sendSecureMagicPacket(MACAddress, secureOn); // Send Wake On Lan packet with the above MAC address and SecureOn feature. Default to port 9. - // WOL.sendSecureMagicPacket(MACAddress, secureOn, 7); // Change the port number -} - -void setup() -{ - WOL.setRepeat(3, 100); // Optional, repeat the packet three times with 100ms between. WARNING delay() is used between send packet function. - - WiFi.mode(WIFI_STA); - WiFi.begin(ssid, password); - - while (WiFi.status() != WL_CONNECTED) { - delay(500); - Serial.print("."); - } - - WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask()); // Optional => To calculate the broadcast address, otherwise 255.255.255.255 is used (which is denied in some networks). - - wakeMyPC(); - wakeOfficePC(); -} - - -void loop() -{ -} +#include +#include + +#include + +WiFiUDP UDP; +WakeOnLan WOL(UDP); + +const char* ssid = "your-ssid"; +const char* password = "your-password"; + +void wakeMyPC() { + const char *MACAddress = "01:23:45:67:89:AB"; + + WOL.sendMagicPacket(MACAddress); // Send Wake On Lan packet with the above MAC address. Default to port 9. + // WOL.sendMagicPacket(MACAddress, 7); // Change the port number +} + +void wakeOfficePC() { + const char *MACAddress = "01:23:45:67:89:AB"; + const char *secureOn = "FE:DC:BA:98:76:54"; + + WOL.sendSecureMagicPacket(MACAddress, secureOn); // Send Wake On Lan packet with the above MAC address and SecureOn feature. Default to port 9. + // WOL.sendSecureMagicPacket(MACAddress, secureOn, 7); // Change the port number +} + +void setup() +{ + WOL.setRepeat(3, 100); // Optional, repeat the packet three times with 100ms between. WARNING delay() is used between send packet function. + + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + WOL.calculateBroadcastAddress(WiFi.localIP(), WiFi.subnetMask()); // Optional => To calculate the broadcast address, otherwise 255.255.255.255 is used (which is denied in some networks). + + wakeMyPC(); + wakeOfficePC(); +} + + +void loop() +{ +} diff --git a/lib/WakeOnLan-1.1.6/src/WakeOnLan.h b/lib/WakeOnLan-1.1.6/src/WakeOnLan.h index 97110166b..4c4e45115 100644 --- a/lib/WakeOnLan-1.1.6/src/WakeOnLan.h +++ b/lib/WakeOnLan-1.1.6/src/WakeOnLan.h @@ -1,38 +1,38 @@ -#ifndef WakeOnLan_h -#define WakeOnLan_h - -#include -#include - -class WakeOnLan { - private: - WiFiUDP udpSock; - IPAddress broadcastAddress = IPAddress(255, 255, 255, 255); - - uint8_t repeatPacket = 1; - unsigned long delayPacket = 0; - - public: - WakeOnLan(WiFiUDP _udpSock); - - void setBroadcastAddress(IPAddress _broadcastAddress); - void setRepeat(uint8_t _repeatPacket, unsigned long _delayPacket); - - IPAddress calculateBroadcastAddress(IPAddress _ipAddress, IPAddress _subnetMask); - - bool stringToArray(uint8_t* _macAddress, const char* _macString); - - bool sendMagicPacket(String _macString, uint16_t _portNum = 9); - bool sendSecureMagicPacket(String _macString, String _secureOn, uint16_t _portNum = 9); - - bool sendMagicPacket(const char* _macAddress, uint16_t _portNum = 9); - bool sendSecureMagicPacket(const char* _macAddress, const char* _secureOn, uint16_t _portNum = 9); - - bool sendMagicPacket(uint8_t* pMacAddress, size_t sizeOfMacAddress, uint16_t portNum = 9); - bool sendSecureMagicPacket(uint8_t* pMacAddress, size_t sizeOfMacAddress, uint8_t* pSecureOn, size_t sizeOfSecureOn, uint16_t portNum = 9); - - void generateMagicPacket(uint8_t*& pMagicPacket, size_t& sizeOfMagicPacket, uint8_t* pMacAddress, size_t sizeOfMacAddress); - void generateSecureMagicPacket(uint8_t*& pMagicPacket, size_t& sizeOfMagicPacket, uint8_t* pMacAddress, size_t sizeOfMacAddress, uint8_t* pSecureOn, size_t sizeOfSecureOn); -}; - -#endif +#ifndef WakeOnLan_h +#define WakeOnLan_h + +#include +#include + +class WakeOnLan { + private: + WiFiUDP udpSock; + IPAddress broadcastAddress = IPAddress(255, 255, 255, 255); + + uint8_t repeatPacket = 1; + unsigned long delayPacket = 0; + + public: + WakeOnLan(WiFiUDP _udpSock); + + void setBroadcastAddress(IPAddress _broadcastAddress); + void setRepeat(uint8_t _repeatPacket, unsigned long _delayPacket); + + IPAddress calculateBroadcastAddress(IPAddress _ipAddress, IPAddress _subnetMask); + + bool stringToArray(uint8_t* _macAddress, const char* _macString); + + bool sendMagicPacket(String _macString, uint16_t _portNum = 9); + bool sendSecureMagicPacket(String _macString, String _secureOn, uint16_t _portNum = 9); + + bool sendMagicPacket(const char* _macAddress, uint16_t _portNum = 9); + bool sendSecureMagicPacket(const char* _macAddress, const char* _secureOn, uint16_t _portNum = 9); + + bool sendMagicPacket(uint8_t* pMacAddress, size_t sizeOfMacAddress, uint16_t portNum = 9); + bool sendSecureMagicPacket(uint8_t* pMacAddress, size_t sizeOfMacAddress, uint8_t* pSecureOn, size_t sizeOfSecureOn, uint16_t portNum = 9); + + void generateMagicPacket(uint8_t*& pMagicPacket, size_t& sizeOfMagicPacket, uint8_t* pMacAddress, size_t sizeOfMacAddress); + void generateSecureMagicPacket(uint8_t*& pMagicPacket, size_t& sizeOfMagicPacket, uint8_t* pMacAddress, size_t sizeOfMacAddress, uint8_t* pSecureOn, size_t sizeOfSecureOn); +}; + +#endif diff --git a/lib/bb_captouch/LICENSE b/lib/bb_captouch/LICENSE new file mode 100644 index 000000000..716f427d0 --- /dev/null +++ b/lib/bb_captouch/LICENSE @@ -0,0 +1,204 @@ +Copyright 2020 BitBank Software, Inc. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/lib/bb_captouch/README.md b/lib/bb_captouch/README.md new file mode 100644 index 000000000..62974e430 --- /dev/null +++ b/lib/bb_captouch/README.md @@ -0,0 +1,27 @@ +BitBank Capacitive Touch Sensor Library
+--------------------------------------- +Copyright (c) 2023 BitBank Software, Inc.
+Written by Larry Bank
+email: bitbank@pobox.com
+
+There are a growing list of development boards which include LCDs with capacitive touch plates on them. These are overwhelmingly controlled by different versions of the ESP32 MCU. The boards normally only utilize GOODiX and FocalTech capacitive touch controllers and this library supports the CST820, GT911 and FT6x36 in a generic way. Each has different capabilities and usually come pre-programmed for the specific pixel width and height of the target application. A feature supported by this library that may not be present in the device you're using is the touch area and pressure values. Some of their controllers also have built-in gesture detection. The common features of the controllers is that they will generate an interrupt signal when a touch event is occurring. This library allows you to request the latest touch information and it returns the number of active touch points (0-5) along with the coordinates (and pressure/area of each if available). The sensor type and address is auto-detected when calling the init() method. The only info that must be correctly supplied to the library are the GPIO pins used for the SDA/SCL/INT/RESET signals. Once initialized, repeatedly call getSamples() to test for and read any touch samples available.
+ +There are only 3 methods exposed by the class:
+init() - detects if a supported CT controller is available and initializes it
+getSamples() - returns touch points if available
+sensorType() - returns an enumerated value of the sensor detected<> +Here is the TOUCHINFO structure filled by the getSamples() method:
+``` +typedef struct _fttouchinfo +{ + int count; + uint16_t x[5], y[5]; + uint8_t pressure[5], area[5]; +} TOUCHINFO; +``` + +If you find this code useful, please consider becoming a sponsor or sending a donation. + +[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=SR4F44J2UR8S4) + + diff --git a/lib/bb_captouch/examples/touch_demo/touch_demo.ino b/lib/bb_captouch/examples/touch_demo/touch_demo.ino new file mode 100644 index 000000000..39b5c3842 --- /dev/null +++ b/lib/bb_captouch/examples/touch_demo/touch_demo.ino @@ -0,0 +1,138 @@ +#include +//#include +#include +#include +#include + +//ONE_BIT_DISPLAY obd; +SCD41 co2; + +//#define CYD_128C +//#define LILYGO_S3_PRO +#define LILYGO_S3_LONG + +#ifdef LILYGO_S3_LONG +#define TOUCH_SDA 15 +#define TOUCH_SCL 10 +#define TOUCH_INT 11 +// reset is 16, but it's shared with the LCD +#define TOUCH_RST -1 +#define LCD DISPLAY_T_DISPLAY_S3_LONG +#endif + +#ifdef LILYGO_S3_PRO +#define TOUCH_SDA 5 +#define TOUCH_SCL 6 +#define TOUCH_INT 7 +#define TOUCH_RST 13 +#define LCD DISPLAY_T_DISPLAY_S3_PRO +#endif + +#ifdef CYD_28C +// These defines are for a low cost 2.8" ESP32 LCD board with the GT911 touch controller +#define TOUCH_SDA 33 +#define TOUCH_SCL 32 +#define TOUCH_INT 21 +#define TOUCH_RST 25 +#define LCD DISPLAY_CYD +#endif + +#ifdef CYD_128C +// These defines are for a low cost 1.28" ESP32-C3 round LCD board with the CST816D touch controller +#define TOUCH_SDA 4 +#define TOUCH_SCL 5 +#define TOUCH_INT 0 +#define TOUCH_RST 1 +#define QWIIC_SDA 21 +#define QWIIC_SCL 20 +#define LCD DISPLAY_CYD_128 +#endif + +#ifdef ARDUINO_M5STACK_CORES3 +#define TOUCH_SDA 12 +#define TOUCH_SCL 11 +#define TOUCH_INT -1 +#define TOUCH_RST -1 +#define LCD DISPLAY_M5STACK_CORES3 +#endif // CORES3 + +BBCapTouch bbct; +BB_SPI_LCD lcd; +int iWidth, iHeight; + +const char *szNames[] = {"Unknown", "FT6x36", "GT911", "CST820", "CST226", "AXS15231"}; +void setup() { + + Serial.begin(115200); + while (!Serial) {}; + lcd.begin(LCD); + Serial.println("Starting..."); +// obd.setI2CPins(QWIIC_SDA, QWIIC_SCL); +// obd.setBitBang(true); +// obd.I2Cbegin(OLED_128x64); +// obd.fillScreen(OBD_WHITE); +// obd.setFont(FONT_12x16); +// obd.println("QWIIC OLED"); + iWidth = lcd.width(); + iHeight = lcd.height(); + Serial.printf("LCD size = %dx%d\n", iWidth, iHeight); + lcd.fillScreen(TFT_BLACK); + lcd.setTextColor(TFT_GREEN, TFT_BLACK); + lcd.setFont(FONT_8x8); + lcd.setCursor(0, 0); + lcd.println("CYD Touch Test"); + delay(1000); + Wire.end(); + //Wire1.end(); + bbct.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT); + int iType = bbct.sensorType(); + Serial.printf("Sensor type = %s\n", szNames[iType]); +#ifdef OLD_STUFF +if (co2.init(QWIIC_SDA, QWIIC_SCL, 1, 100000) == SCD41_SUCCESS) { +// Serial.println("Found SCD41 sensor!"); + co2.start(); // start sampling mode + lcd.println("SCD41 found!"); + } else { // can't find the sensor, stop + lcd.println("SCD41 sensor not found"); + lcd.println("Check your connections"); + lcd.println("\nstopping..."); + while (1) {}; + } // no sensor connected or some error +#endif +} /* setup() */ + +void loop() { +#ifndef OLD_STUFF + int i; + TOUCHINFO ti; + +while (1) { + if (bbct.getSamples(&ti)) { + for (int i=0; i -1) { + uint8_t ucsetThresh[2] = { 0x80, (uint8_t)_thresh }; // 0x80 = FT62XX_REG_THRESHOLD + I2CWrite(_iAddr, (uint8_t *)ucsetThresh, 2); + } + } // FT62x6 + else if (I2CTest(CST820_ADDR)) { // CST820 + _iType = CT_TYPE_CST820; + _iAddr = CST820_ADDR; + + if (iRST != -1) { + reset(iRST); + } + } // CST820 + else if (I2CTest(CHSC5816_ADDR)) { // CHSC5816 + _iType = CT_TYPE_CHSC5816; + _iAddr = CHSC5816_ADDR; + + if (iRST != -1) { + reset(iRST); + } + } // CHSC5816 + #ifdef ESP32 + Wire.setTimeout(orgTimeout); // ESPEasy: Restore original I2C timeout + #endif // ifdef ESP32 + + if (CT_TYPE_UNKNOWN == _iType) { + return CT_ERROR; // no device found + } + return CT_SUCCESS; +} /* init() */ + +// +// Test if an I2C device is monitoring an address +// return true if it responds, false if no response +// +bool BBCapTouch::I2CTest(uint8_t u8Addr) { + // Check if a device acknowledges the address. + Wire.beginTransmission(u8Addr); + return Wire.endTransmission(true) == 0; +} /* I2CTest() */ + +// +// Write I2C data +// quits if a NACK is received and returns 0 +// otherwise returns the number of bytes written +// +int BBCapTouch::I2CWrite(uint8_t u8Addr, uint8_t *pData, int iLen) { + int rc = 0; + + Wire.beginTransmission(u8Addr); + Wire.write(pData, (uint8_t)iLen); + rc = !Wire.endTransmission(); + return rc; +} /* I2CWrite() */ + +// +// Read N bytes starting at a specific 16-bit I2C register +// +int BBCapTouch::I2CReadRegister16(uint8_t u8Addr, uint16_t u16Register, uint8_t *pData, int iLen) { + int i = 0; + + Wire.beginTransmission(u8Addr); + Wire.write((uint8_t)(u16Register >> 8)); // high byte + Wire.write((uint8_t)u16Register); // low byte + Wire.endTransmission(); + Wire.requestFrom(u8Addr, (uint8_t)iLen); + + while (Wire.available() && i < iLen) { + pData[i++] = Wire.read(); + } + return i; +} /* I2CReadRegister16() */ + +// +// Read N bytes starting at a specific I2C internal register +// returns 1 for success, 0 for error +// +int BBCapTouch::I2CReadRegister(uint8_t u8Addr, uint8_t u8Register, uint8_t *pData, int iLen) { + int i = 0; + + Wire.beginTransmission(u8Addr); + Wire.write(u8Register); + Wire.endTransmission(); + Wire.requestFrom(u8Addr, (uint8_t)iLen); + + // i = Wire.readBytes(pData, iLen); + while (Wire.available() && i < iLen) { + pData[i++] = Wire.read(); + } + return i; +} /* I2CReadRegister() */ + +// +// Read N bytes +// +int BBCapTouch::I2CRead(uint8_t u8Addr, uint8_t *pData, int iLen) { + int i = 0; + + Wire.requestFrom(u8Addr, (uint8_t)iLen); + + while (Wire.available() && i < iLen) { + pData[i++] = Wire.read(); + } + return i; +} /* I2CRead() */ + +// +// Private function to rotate touch samples if the user +// specified a new display orientation +// +void BBCapTouch::fixSamples(TOUCHINFO *pTI) { + int i, x, y; + + for (i = 0; i < pTI->count; ++i) { + switch (_iOrientation) { + case 90: + x = pTI->y[i]; + y = _iWidth - 1 - pTI->x[i]; + pTI->x[i] = x; + pTI->y[i] = y; + break; + case 180: + pTI->x[i] = _iWidth - 1 - pTI->x[i]; + pTI->y[i] = _iHeight - 1 - pTI->y[i]; + break; + case 270: + x = _iHeight - 1 - pTI->y[i]; + y = pTI->x[i]; + pTI->x[i] = x; + pTI->y[i] = y; + break; + default: // do nothing + break; + } + } +} /* fixSamples() */ + +// +// Read the touch points +// returns 0 (none), 1 if touch points are available +// The point count and info is returned in the TOUCHINFO structure +// +int BBCapTouch::getSamples(TOUCHINFO *pTI) { + uint8_t c, *s, ucTemp[32]; + int i, j, rc; + + if (!pTI) { + return 0; + } + pTI->count = 0; + + if (_iType == CT_TYPE_AXS15231) { // AXS15231 + uint8_t ucReadCMD[8] = { 0xb5, 0xab, 0xa5, 0x5a, 0, 0, 0, 0x8 }; + I2CWrite(_iAddr, (uint8_t *)ucReadCMD, 8); + I2CRead(_iAddr, ucTemp, 14); // read up to 2 touch points + c = ucTemp[1]; // number of touch points + + if ((c == 0) || (c > 2) || (ucTemp[0] != 0)) { return 0; } + pTI->count = c; + j = 0; // buffer offset + + for (i = 0; i < c; i++) { + pTI->x[i] = ((ucTemp[j + 2] & 0xf) << 8) + ucTemp[j + 3]; + pTI->y[i] = ((ucTemp[j + 4] & 0xf) << 8) + ucTemp[j + 5]; + pTI->area[i] = 1; + j += 6; + } + + if (_iOrientation != 0) { fixSamples(pTI); } + return c > 0; + } // AXS15231 + + if (_iType == CT_TYPE_CST226) { // CST226 + i = I2CReadRegister(_iAddr, 0, ucTemp, 28); // read the whole block of regs + + if ((ucTemp[0] == 0x83) && (ucTemp[1] == 0x17) && (ucTemp[5] == 0x80)) { + // home button pressed + return 0; + } + + if (ucTemp[6] != 0xab) { return 0; } + + if (ucTemp[0] == 0xab) { return 0; } + + if (ucTemp[5] == 0x80) { return 0; } + c = ucTemp[5] & 0x7f; + + if ((c > 5) || (c == 0)) { // invalid point count + ucTemp[0] = 0; + ucTemp[1] = 0xab; + I2CWrite(_iAddr, ucTemp, 2); // reset + return 0; + } + + pTI->count = c; + j = 0; + + for (i = 0; i < c; i++) { + pTI->x[i] = (uint16_t)((ucTemp[j + 1] << 4) | ((ucTemp[j + 3] >> 4) & 0xf)); + pTI->y[i] = (uint16_t)((ucTemp[j + 2] << 4) | (ucTemp[j + 3] & 0xf)); + pTI->pressure[i] = ucTemp[j + 4]; + j = (i == 0) ? (j + 7) : (j + 5); + } + + if (_iOrientation != 0) { fixSamples(pTI); } + return c > 0; + } // CST226 + + if (_iType == CT_TYPE_CST820) { // CST820 + I2CReadRegister(_iAddr, CST820_TOUCH_REGS + 1, ucTemp, 1); // read touch count + + if ((ucTemp[0] < 1) || (ucTemp[0] > 5)) { // something went wrong + return 0; + } + + pTI->count = ucTemp[0]; + I2CReadRegister(_iAddr, CST820_TOUCH_REGS + 2, ucTemp, pTI->count * 6); + s = ucTemp; + + for (i = 0; i < pTI->count; i++) { + pTI->x[i] = ((s[0] & 0xf) << 8) | s[1]; + pTI->y[i] = ((s[2] & 0xf) << 8) | s[3]; + pTI->area[i] = 1; // no data available + s += 6; + } + + if (_iOrientation != 0) { fixSamples(pTI); } + return 1; + } // CST820 + + if (_iType == CT_TYPE_FT6X36) { // FT62x6 + if (_id == FT5316_CHIPID) { + I2CReadRegister(_iAddr, TOUCH_REG_MODE, ucTemp, 1); // Handle specific issue + + if (ucTemp[0]) { + // wrong mode + ucTemp[0] = TOUCH_REG_MODE; + ucTemp[1] = 0; + I2CWrite(_iAddr, ucTemp, 2); + } + } + rc = I2CReadRegister(_iAddr, TOUCH_REG_STATUS, ucTemp, 1); // read touch status + + if (rc == 0) { // something went wrong + return 0; + } + i = ucTemp[0]; // number of touch points available + + if (i > 0) { // get data, max. 2 points + rc = I2CReadRegister(_iAddr, TOUCH_REG_XH, ucTemp, 6 * i); // read X+Y position(s) + + if (((ucTemp[0] & 0x40) == 0) && ((ucTemp[2] & 0xf0) != 0xf0)) { // finger is down + pTI->x[0] = ((ucTemp[0] & 0xf) << 8) | ucTemp[1]; + pTI->y[0] = ((ucTemp[2] & 0xf) << 8) | ucTemp[3]; + + // get touch pressure and area + pTI->pressure[0] = ucTemp[4]; + pTI->area[0] = ucTemp[5]; + pTI->count++; + } + + if (i > 1) { // get second point + if (((ucTemp[6] & 0x40) == 0) && ((ucTemp[8] & 0xf0) != 0xf0)) { // finger is down + pTI->x[1] = ((ucTemp[6] & 0xf) << 8) | ucTemp[7]; + pTI->y[1] = ((ucTemp[8] & 0xf) << 8) | ucTemp[9]; + + // get touch pressure and area + pTI->pressure[1] = ucTemp[10]; + pTI->area[1] = ucTemp[11]; + pTI->count++; + } + } + } // if touch points available + + if (_iOrientation != 0) { fixSamples(pTI); } + return i > 0; + } // FT62x6 + else if (_iType == CT_TYPE_GT911) { // GT911 + I2CReadRegister16(_iAddr, GT911_POINT_INFO, ucTemp, 1); // get number of touch points + i = ucTemp[0] & 0xf; // number of touches + + if ((i <= 5) && ucTemp[0] & 0x80) { // if buffer status is good + >= 1 touch points + ucTemp[0] = (uint8_t)(GT911_POINT_INFO >> 8); + ucTemp[1] = (uint8_t)GT911_POINT_INFO; + ucTemp[2] = 0; // clear touch info for next time + I2CWrite(_iAddr, ucTemp, 3); + + pTI->count = i; + + for (int j = 0; j < i; j++) { // read each touch point block + I2CReadRegister16(_iAddr, GT911_POINT_1 + (j * 8), ucTemp, 7); + pTI->x[j] = ucTemp[1] + (ucTemp[2] << 8); + pTI->y[j] = ucTemp[3] + (ucTemp[4] << 8); + pTI->area[j] = ucTemp[5] + (ucTemp[6] << 8); + pTI->pressure[j] = 0; + } + + if (i && (_iOrientation != 0)) { fixSamples(pTI); } + return i > 0; + } + } // GT911 + else if (_iType == CT_TYPE_CHSC5816) { // CHSC5816 + __CHSC5816_PointReg touch; + + // CHSC5816_REG_POINT + uint8_t write_buffer[] = { 0x20, 0x00, 0x00, 0x2c }; + I2CWrite(_iAddr, write_buffer, 4); + I2CRead(_iAddr, touch.data, 8); + + if ((touch.rp.status == 0xFF) && (touch.rp.fingerNumber == 0)) { + return 0; + } + pTI->x[0] = static_cast(touch.rp.x_h4 << 8) | touch.rp.x_l8; + pTI->y[0] = static_cast(touch.rp.y_h4 << 8) | touch.rp.y_l8; + + if (_iOrientation != 0) { fixSamples(pTI); } + + return 1; + } // CHSC5816 + return 0; +} /* getSamples() */ + +int BBCapTouch::setOrientation(int iOrientation, int iWidth, int iHeight) { + if ((iOrientation != 0) && (iOrientation != 90) && (iOrientation != 180) && (iOrientation != 270)) { + return CT_ERROR; + } + _iOrientation = iOrientation; + _iWidth = iWidth; + _iHeight = iHeight; + return CT_SUCCESS; +} /* setOrientation() */ + +void BBCapTouch::sensorType(int iType) { + _iType = iType; +} + +void BBCapTouch::setI2CAddress(int i2caddr) { + _iAddr = i2caddr; +} + +/** + * Threashold is used _during_ init() so should be set before init() is called. + */ +void BBCapTouch::setThreshold(int thresh) { + _thresh = thresh; +} diff --git a/lib/bb_captouch/src/bb_captouch.h b/lib/bb_captouch/src/bb_captouch.h new file mode 100644 index 000000000..1ff43789c --- /dev/null +++ b/lib/bb_captouch/src/bb_captouch.h @@ -0,0 +1,187 @@ +// +// BitBank Capacitive Touch Sensor Library +// written by Larry Bank +// +// Copyright 2023 BitBank Software, Inc. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// =========================================================================== + +// +// Written for the many variants of ESP32 + Capacitive touch LCDs on the market +// +// 2024-06-10 tonhuisman: Add support for CHSC5816 touchscreen, code borrowed from Lewis He SensorLib at +// https://github.com/lewisxhe/SensorLib +// 2024-06-02 tonhuisman: Fix FT62x6 to not return true when no data is read +// Formatted source using Uncrustify +// Adjust I2C handling so ESPEasy stays happy, ignoring SDA/SCL pins and I2C enable/disable +// =========================================================================== + +#include +#include + +#ifndef __BB_CAPTOUCH__ +# define __BB_CAPTOUCH__ + +# define CT_SUCCESS 0 +# define CT_ERROR -1 + +enum { + CT_TYPE_UNKNOWN = 0, + CT_TYPE_FT6X36, + CT_TYPE_GT911, + CT_TYPE_CST820, + CT_TYPE_CST226, + CT_TYPE_AXS15231, + CT_TYPE_CHSC5816, + CT_TYPE_COUNT +}; + +# define GT911_ADDR1 0x5D +# define GT911_ADDR2 0x14 +# define FT6X36_ADDR 0x38 +# define CST820_ADDR 0x15 +# define CST226_ADDR 0x5A +# define AXS15231_ADDR 0x3B +# define CHSC5816_ADDR 0x2E + +// CST8xx gestures +enum { + GESTURE_NONE = 0, + GESTURE_SWIPE_UP, + GESTURE_SWIPE_DOWN, + GESTURE_SWIPE_LEFT, + GESTURE_SWIPE_RIGHT, + GESTURE_SINGLE_CLICK, + GESTURE_DOUBLE_CLICK = 0x0B, + GESTURE_LONG_PRESS = 0x0C +}; + +// CST820 registers +# define CST820_TOUCH_REGS 1 + +// GT911 registers +# define GT911_POINT_INFO 0x814E +# define GT911_POINT_1 0x814F +# define GT911_CONFIG_FRESH 0x8100 +# define GT911_CONFIG_SIZE 0xb9 +# define GT911_CONFIG_START 0x8047 + +// FT6x36 registers +# define TOUCH_REG_MODE 0x00 +# define TOUCH_REG_STATUS 0x02 +# define TOUCH_REG_XH 0x03 +# define TOUCH_REG_XL 0x04 +# define TOUCH_REG_YH 0x05 +# define TOUCH_REG_YL 0x06 +# define TOUCH_REG_WEIGHT 0x07 +# define TOUCH_REG_AREA 0x08 +# define TOUCH_REG_CHIPID 0xA3 // !< Chip selecting +# define TOUCH_REG_VENDID 0xA8 // !< FocalTech's panel ID + +// FT62x6 Vendor and Chip IDs +# define FT62XX_VENDID 0x11 // !< FocalTech's vendor ID +# define FT6206_CHIPID 0x06 // !< Chip selecting +# define FT6236_CHIPID 0x36 // !< Chip selecting +# define FT6236U_CHIPID 0x64 // !< Chip selecting +# define FT5316_CHIPID 0x0A // !< Chip selecting + +// register offset to info for the second touch point +# define PT2_OFFSET 6 + +union __CHSC5816_PointReg { + struct { + uint8_t status; + uint8_t fingerNumber; + uint8_t x_l8; + uint8_t y_l8; + uint8_t z; + uint8_t x_h4 : 4; + uint8_t y_h4 : 4; + uint8_t id : 4; + uint8_t event : 4; + uint8_t p2; + } rp; + unsigned char data[8]; +}; + +# ifndef __TOUCHINFO_STRUCT__ +# define __TOUCHINFO_STRUCT__ +typedef struct _fttouchinfo { + int count; + uint16_t x[5], y[5]; + uint8_t pressure[5], area[5]; +} TOUCHINFO; +# endif // ifndef __TOUCHINFO_STRUCT__ + +class BBCapTouch { +public: + + BBCapTouch() { + _iOrientation = 0; + } + + ~BBCapTouch() { /*Wire.end();*/ + } // ESPEasy doesn't allow this + + int init(int iSDA, + int iSCL, + int iRST = -1, + int iINT = -1, + uint32_t u32Speed = 400000); + int getSamples(TOUCHINFO *pTI); + void sensorType(int iType); + int sensorType(void) const { + return _iType; + } + + void setI2CAddress(int i2caddr); + int getI2CAddress(void) const { + return _iAddr; + } + + int setOrientation(int iOrientation, + int iWidth, + int iHeight); + void setThreshold(int tresh); + uint8_t getChipId(void) const { + return _id; // Only set for FT62x6 family + } + +protected: + + void reset(int iResetPin); + +private: + + int _iAddr; + int _iType = CT_TYPE_UNKNOWN; + int _iOrientation, _iWidth{}, _iHeight{}; + int _thresh = -1; + uint8_t _id = 0; + + void fixSamples(TOUCHINFO *pTI); + bool I2CTest(uint8_t u8Addr); + int I2CRead(uint8_t u8Addr, + uint8_t *pData, + int iLen); + int I2CReadRegister(uint8_t u8Addr, + uint8_t u8Register, + uint8_t *pData, + int iLen); + int I2CReadRegister16(uint8_t u8Addr, + uint16_t u16Register, + uint8_t *pData, + int iLen); + int I2CWrite(uint8_t u8Addr, + uint8_t *pData, + int iLen); +}; // class BBCapTouch +#endif // __BB_CAPTOUCH__ diff --git a/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp b/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp index 0fa15c8ea..5276d517a 100644 --- a/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp +++ b/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp @@ -391,9 +391,11 @@ void OLEDDisplay::drawXbm(int16_t xMove, int16_t yMove, int16_t width, int16_t h } void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, uint16_t textLength, uint16_t textWidth) { - uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); - uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); - uint16_t sizeOfJumpTable = pgm_read_byte(fontData + CHAR_NUM_POS) * JUMPTABLE_BYTES; + if (fontData == nullptr) return; + const uint8_t textHeight = pgm_read_byte(fontData + HEIGHT_POS); + const uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + const uint8_t numberOfChars = pgm_read_byte(fontData + CHAR_NUM_POS); + const uint16_t sizeOfJumpTable = static_cast(numberOfChars) * JUMPTABLE_BYTES; uint8_t cursorX = 0; uint8_t cursorY = 0; @@ -417,33 +419,37 @@ void OLEDDisplay::drawStringInternal(int16_t xMove, int16_t yMove, char* text, u if (yMove + textHeight < 0 || yMove > this->width() ) {return;} for (uint16_t j = 0; j < textLength; j++) { - int16_t xPos = xMove + cursorX; - int16_t yPos = yMove + cursorY; + const int16_t xPos = xMove + cursorX; + const int16_t yPos = yMove + cursorY; - uint8_t code = text[j]; + const uint8_t code = text[j]; if (code >= firstChar) { - uint8_t charCode = code - firstChar; + const uint8_t charCode = code - firstChar; + if (charCode < numberOfChars) { - // 4 Bytes per char code - uint8_t msbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES ); // MSB \ JumpAddress - uint8_t lsbJumpToChar = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_LSB); // LSB / - uint8_t charByteSize = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_SIZE); // Size - uint8_t currentCharWidth = pgm_read_byte( fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); // Width + // 4 Bytes per char code + const char* charOffset = fontData + JUMPTABLE_START + charCode * JUMPTABLE_BYTES; + const uint8_t msbJumpToChar = pgm_read_byte( charOffset ); // MSB \ JumpAddress + const uint8_t lsbJumpToChar = pgm_read_byte( charOffset + JUMPTABLE_LSB); // LSB / + const uint8_t charByteSize = pgm_read_byte( charOffset + JUMPTABLE_SIZE); // Size + const uint8_t currentCharWidth = pgm_read_byte( charOffset + JUMPTABLE_WIDTH); // Width - // Test if the char is drawable - if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { - // Get the position of the char data - uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); - drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + ((msbJumpToChar << 8) + lsbJumpToChar); + drawInternal(xPos, yPos, currentCharWidth, textHeight, fontData, charDataPosition, charByteSize); + } + + cursorX += currentCharWidth; } - - cursorX += currentCharWidth; } } } void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String& strUser) { + if (fontData == nullptr) return; uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); // char* text must be freed! @@ -473,6 +479,7 @@ void OLEDDisplay::drawString(int16_t xMove, int16_t yMove, const String& strUser } void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxLineWidth, const String& strUser) { + if (fontData == nullptr) return; uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); @@ -519,6 +526,7 @@ void OLEDDisplay::drawStringMaxWidth(int16_t xMove, int16_t yMove, uint16_t maxL } uint16_t OLEDDisplay::getStringWidth(const char* text, uint16_t length) { + if (fontData == nullptr) return 0; uint16_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); uint16_t stringWidth = 0; @@ -544,6 +552,7 @@ uint16_t OLEDDisplay::getStringWidth(const String& strUser) { } uint8_t OLEDDisplay::getCharWidth(const char c) { + if (fontData == nullptr) return 0; uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); if (utf8ascii(c) == 0) return 0; @@ -577,15 +586,20 @@ void OLEDDisplay::normalDisplay(void) { } void OLEDDisplay::setContrast(char contrast, char precharge, char comdetect) { - sendCommand(SETPRECHARGE); //0xD9 - sendCommand(precharge); //0xF1 default, to lower the contrast, put 1-1F - sendCommand(SETCONTRAST); - sendCommand(contrast); // 0-255 - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(comdetect); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(DISPLAYON); + const uint8_t commands[] = { + SETPRECHARGE, //0xD9 + precharge, //0xF1 default, to lower the contrast, put 1-1F + SETCONTRAST, + contrast, // 0-255 + SETVCOMDETECT, //0xDB, (additionally needed to lower the contrast) + comdetect, //0x40 default, to lower the contrast, put 0 + DISPLAYALLON_RESUME, + NORMALDISPLAY, + DISPLAYON + }; + for (uint8_t i = 0; i < sizeof(commands); ++i) { + sendCommand(commands[i]); + } } void OLEDDisplay::flipScreenVertically() { @@ -598,6 +612,7 @@ void OLEDDisplay::clear(void) { } void OLEDDisplay::drawLogBuffer(uint16_t xMove, uint16_t yMove) { + if (fontData == nullptr) return; uint16_t lineHeight = pgm_read_byte(fontData + HEIGHT_POS); // Always align left setTextAlignment(TEXT_ALIGN_LEFT); @@ -770,32 +785,36 @@ void OLEDDisplay::SetComPins(uint8_t _compins) { // Private functions void OLEDDisplay::sendInitCommands(void) { - sendCommand(DISPLAYOFF); - sendCommand(SETDISPLAYCLOCKDIV); - sendCommand(0xF0); // Increase speed of the display max ~96Hz - sendCommand(SETMULTIPLEX); - sendCommand(this->height() - 1); - sendCommand(SETDISPLAYOFFSET); - sendCommand(0x00); - sendCommand(SETSTARTLINE); - sendCommand(CHARGEPUMP); - sendCommand(0x14); - sendCommand(MEMORYMODE); - sendCommand(0x00); - sendCommand(SEGREMAP); - sendCommand(COMSCANINC); - sendCommand(SETCOMPINS); - sendCommand(0x12); // according to the adafruit lib, sometimes this may need to be 0x02 - sendCommand(SETCONTRAST); - sendCommand(0xCF); - sendCommand(SETPRECHARGE); - sendCommand(0xF1); - sendCommand(SETVCOMDETECT); //0xDB, (additionally needed to lower the contrast) - sendCommand(0x40); //0x40 default, to lower the contrast, put 0 - sendCommand(DISPLAYALLON_RESUME); - sendCommand(NORMALDISPLAY); - sendCommand(0x2e); // stop scroll - sendCommand(DISPLAYON); + const uint8_t commands[] = { + DISPLAYOFF, + SETDISPLAYCLOCKDIV, + 0xF0, // Increase speed of the display max ~96Hz + SETMULTIPLEX, + static_cast(this->height() - 1), // FIXME TD-er: should add some checks here? + SETDISPLAYOFFSET, + 0x00, + SETSTARTLINE, + CHARGEPUMP, + 0x14, + MEMORYMODE, + 0x00, + SEGREMAP, + COMSCANINC, + SETCOMPINS, + 0x12, // according to the adafruit lib, sometimes this may need to be 0x02 + SETCONTRAST, + 0xCF, + SETPRECHARGE, + 0xF1, + SETVCOMDETECT, //0xDB, (additionally needed to lower the contrast) + 0x40, //0x40 default, to lower the contrast, put 0 + DISPLAYALLON_RESUME, + NORMALDISPLAY, + 0x2e, // stop scroll + DISPLAYON}; + for (uint8_t i = 0; i < sizeof(commands); ++i) { + sendCommand(commands[i]); + } } void inline OLEDDisplay::drawInternal(int16_t xMove, int16_t yMove, int16_t width, int16_t height, const char *data, uint16_t offset, uint16_t bytesInData) { diff --git a/lib/esp8266-oled-ssd1306/OLED_SSD1306_SH1106_images.h b/lib/esp8266-oled-ssd1306/OLED_SSD1306_SH1106_images.h index 3595e6560..7e9d95bb8 100644 --- a/lib/esp8266-oled-ssd1306/OLED_SSD1306_SH1106_images.h +++ b/lib/esp8266-oled-ssd1306/OLED_SSD1306_SH1106_images.h @@ -14,25 +14,25 @@ const char espeasy_logo_bits[] PROGMEM= { 0x3e, 0x3c, 0x00, 0x3f, 0x1e, 0x1c, 0x00, 0x1e, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const char activeSymbole[] PROGMEM = { - B00000000, - B00000000, - B00011000, - B00100100, - B01000010, - B01000010, - B00100100, - B00011000 + 0b00000000, + 0b00000000, + 0b00011000, + 0b00100100, + 0b01000010, + 0b01000010, + 0b00100100, + 0b00011000 }; const char inactiveSymbole[] PROGMEM = { - B00000000, - B00000000, - B00000000, - B00000000, - B00011000, - B00011000, - B00000000, - B00000000 + 0b00000000, + 0b00000000, + 0b00000000, + 0b00000000, + 0b00011000, + 0b00011000, + 0b00000000, + 0b00000000 }; #endif diff --git a/lib/esp8266-oled-ssd1306/SH1106Wire.cpp b/lib/esp8266-oled-ssd1306/SH1106Wire.cpp new file mode 100644 index 000000000..a24398685 --- /dev/null +++ b/lib/esp8266-oled-ssd1306/SH1106Wire.cpp @@ -0,0 +1,76 @@ +#include "SH1106Wire.h" + + + + SH1106Wire::SH1106Wire(uint8_t address, uint8_t sda, uint8_t scl) : + _address(address), + _sda(sda), + _scl(scl) {} + + bool SH1106Wire::connect() { + Wire.begin(this->_sda, this->_scl); + return true; + } + + void SH1106Wire::display(void) { + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundX, minBoundY, maxBoundX, maxBoundY; + if (!getChangedBoundingBox(minBoundX, minBoundY, maxBoundX, maxBoundY)) + return; + + + // Calculate the colum offset + uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F; + uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 ); + + uint8_t k = 0; + for (uint8_t y = minBoundY; y <= maxBoundY; y++) { + sendCommand(0xB0 + y); + sendCommand(minBoundXp2H); + sendCommand(minBoundXp2L); + for (uint8_t x = minBoundX; x <= maxBoundX; x++) { + if (k == 0) { + Wire.beginTransmission(_address); + Wire.write(0x40); + } + Wire.write(buffer[x + y * DISPLAY_WIDTH]); + k++; + if (k == 16) { + Wire.endTransmission(); + k = 0; + } + } + if (k != 0) { + Wire.endTransmission(); + k = 0; + } + yield(); + } + + if (k != 0) { + Wire.endTransmission(); + } + #else + uint8_t * p = &buffer[0]; + for (uint8_t y=0; y<8; y++) { + sendCommand(0xB0+y); + sendCommand(0x02); + sendCommand(0x10); + for( uint8_t x=0; x<8; x++) { + Wire.beginTransmission(_address); + Wire.write(0x40); + for (uint8_t k = 0; k < 16; k++) { + Wire.write(*p++); + } + Wire.endTransmission(); + } + } + #endif + } + + void SH1106Wire::sendCommand(uint8_t command) { + Wire.beginTransmission(_address); + Wire.write(0x80); + Wire.write(command); + Wire.endTransmission(); + } diff --git a/lib/esp8266-oled-ssd1306/SH1106Wire.h b/lib/esp8266-oled-ssd1306/SH1106Wire.h index 447aef3eb..3f11c3868 100644 --- a/lib/esp8266-oled-ssd1306/SH1106Wire.h +++ b/lib/esp8266-oled-ssd1306/SH1106Wire.h @@ -44,80 +44,14 @@ class SH1106Wire : public OLEDDisplay { uint8_t _scl; public: - SH1106Wire(uint8_t _address, uint8_t _sda, uint8_t _scl) { - this->_address = _address; - this->_sda = _sda; - this->_scl = _scl; - } + SH1106Wire(uint8_t _address, uint8_t _sda, uint8_t _scl); - bool connect() { - Wire.begin(this->_sda, this->_scl); - return true; - } + bool connect() override; - void display(void) { - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - uint8_t minBoundX, minBoundY, maxBoundX, maxBoundY; - if (!getChangedBoundingBox(minBoundX, minBoundY, maxBoundX, maxBoundY)) - return; - - - // Calculate the colum offset - uint8_t minBoundXp2H = (minBoundX + 2) & 0x0F; - uint8_t minBoundXp2L = 0x10 | ((minBoundX + 2) >> 4 ); - - uint8_t k = 0; - for (uint8_t y = minBoundY; y <= maxBoundY; y++) { - sendCommand(0xB0 + y); - sendCommand(minBoundXp2H); - sendCommand(minBoundXp2L); - for (uint8_t x = minBoundX; x <= maxBoundX; x++) { - if (k == 0) { - Wire.beginTransmission(_address); - Wire.write(0x40); - } - Wire.write(buffer[x + y * DISPLAY_WIDTH]); - k++; - if (k == 16) { - Wire.endTransmission(); - k = 0; - } - } - if (k != 0) { - Wire.endTransmission(); - k = 0; - } - yield(); - } - - if (k != 0) { - Wire.endTransmission(); - } - #else - uint8_t * p = &buffer[0]; - for (uint8_t y=0; y<8; y++) { - sendCommand(0xB0+y); - sendCommand(0x02); - sendCommand(0x10); - for( uint8_t x=0; x<8; x++) { - Wire.beginTransmission(_address); - Wire.write(0x40); - for (uint8_t k = 0; k < 16; k++) { - Wire.write(*p++); - } - Wire.endTransmission(); - } - } - #endif - } + void display(void) override; private: - inline void sendCommand(uint8_t command) __attribute__((always_inline)){ - Wire.beginTransmission(_address); - Wire.write(0x80); - Wire.write(command); - Wire.endTransmission(); - } + void sendCommand(uint8_t command) override; }; diff --git a/lib/esp8266-oled-ssd1306/SSD1306Wire.cpp b/lib/esp8266-oled-ssd1306/SSD1306Wire.cpp new file mode 100644 index 000000000..59b7e7c33 --- /dev/null +++ b/lib/esp8266-oled-ssd1306/SSD1306Wire.cpp @@ -0,0 +1,78 @@ +#include "SSD1306Wire.h" + + SSD1306Wire::SSD1306Wire(uint8_t address, uint8_t sda, uint8_t scl, int width, int height) + : OLEDDisplay(width, height) { + this->_address = address; + this->_sda = sda; + this->_scl = scl; + } + + bool SSD1306Wire::connect() { + Wire.begin(this->_sda, this->_scl); + return true; + } + + void SSD1306Wire::display(void) { + const int x_offset = (128 - this->width()) / 2; + #ifdef OLEDDISPLAY_DOUBLE_BUFFER + uint8_t minBoundX, minBoundY, maxBoundX, maxBoundY; + if (!getChangedBoundingBox(minBoundX, minBoundY, maxBoundX, maxBoundY)) + return; + + sendCommand(COLUMNADDR); + sendCommand(x_offset + minBoundX); + sendCommand(x_offset + maxBoundX); + + sendCommand(PAGEADDR); + sendCommand(minBoundY); + sendCommand(maxBoundY); + + uint8_t k = 0; + for (uint8_t y = minBoundY; y <= maxBoundY; y++) { + for (uint8_t x = minBoundX; x <= maxBoundX; x++) { + if (k == 0) { + Wire.beginTransmission(_address); + Wire.write(0x40); + } + Wire.write(buffer[x + y * this->width()]); + k++; + if (k == 16) { + Wire.endTransmission(); + k = 0; + } + } + yield(); + } + + if (k != 0) { + Wire.endTransmission(); + } + #else + + sendCommand(COLUMNADDR); + sendCommand(x_offset); + sendCommand(x_offset + (this->width() - 1)); + + sendCommand(PAGEADDR); + sendCommand(0x0); + sendCommand((this->height() / 8) - 1); + + for (uint16_t i=0; i < DISPLAY_BUFFER_SIZE; i++) { + Wire.beginTransmission(this->_address); + Wire.write(0x40); + for (uint8_t x = 0; x < 16; x++) { + Wire.write(buffer[i]); + i++; + } + i--; + Wire.endTransmission(); + } + #endif + } + + void SSD1306Wire::sendCommand(uint8_t command) { + Wire.beginTransmission(_address); + Wire.write(0x80); + Wire.write(command); + Wire.endTransmission(); + } diff --git a/lib/esp8266-oled-ssd1306/SSD1306Wire.h b/lib/esp8266-oled-ssd1306/SSD1306Wire.h index b6129f23d..fed3dd668 100644 --- a/lib/esp8266-oled-ssd1306/SSD1306Wire.h +++ b/lib/esp8266-oled-ssd1306/SSD1306Wire.h @@ -38,83 +38,14 @@ class SSD1306Wire : public OLEDDisplay { uint8_t _scl; public: - SSD1306Wire(uint8_t _address, uint8_t _sda, uint8_t _scl, int width = DISPLAY_WIDTH, int height = DISPLAY_HEIGHT) - : OLEDDisplay(width, height) { - this->_address = _address; - this->_sda = _sda; - this->_scl = _scl; - } + SSD1306Wire(uint8_t address, uint8_t sda, uint8_t scl, int width = DISPLAY_WIDTH, int height = DISPLAY_HEIGHT); - bool connect() { - Wire.begin(this->_sda, this->_scl); - return true; - } + bool connect() override; - void display(void) { - const int x_offset = (128 - this->width()) / 2; - #ifdef OLEDDISPLAY_DOUBLE_BUFFER - uint8_t minBoundX, minBoundY, maxBoundX, maxBoundY; - if (!getChangedBoundingBox(minBoundX, minBoundY, maxBoundX, maxBoundY)) - return; - - sendCommand(COLUMNADDR); - sendCommand(x_offset + minBoundX); - sendCommand(x_offset + maxBoundX); - - sendCommand(PAGEADDR); - sendCommand(minBoundY); - sendCommand(maxBoundY); - - uint8_t k = 0; - for (uint8_t y = minBoundY; y <= maxBoundY; y++) { - for (uint8_t x = minBoundX; x <= maxBoundX; x++) { - if (k == 0) { - Wire.beginTransmission(_address); - Wire.write(0x40); - } - Wire.write(buffer[x + y * this->width()]); - k++; - if (k == 16) { - Wire.endTransmission(); - k = 0; - } - } - yield(); - } - - if (k != 0) { - Wire.endTransmission(); - } - #else - - sendCommand(COLUMNADDR); - sendCommand(x_offset); - sendCommand(x_offset + (this->width() - 1)); - - sendCommand(PAGEADDR); - sendCommand(0x0); - sendCommand((this->height() / 8) - 1); - - for (uint16_t i=0; i < DISPLAY_BUFFER_SIZE; i++) { - Wire.beginTransmission(this->_address); - Wire.write(0x40); - for (uint8_t x = 0; x < 16; x++) { - Wire.write(buffer[i]); - i++; - } - i--; - Wire.endTransmission(); - } - #endif - } + void display(void) override; private: - inline void sendCommand(uint8_t command) __attribute__((always_inline)){ - Wire.beginTransmission(_address); - Wire.write(0x80); - Wire.write(command); - Wire.endTransmission(); - } + void sendCommand(uint8_t command) override; }; diff --git a/lib/ld2410/src/ld2410.cpp b/lib/ld2410/src/ld2410.cpp index ffd987da3..5edae0993 100644 --- a/lib/ld2410/src/ld2410.cpp +++ b/lib/ld2410/src/ld2410.cpp @@ -28,7 +28,11 @@ uint16_t ld2410::serial_to_int_(uint8_t index) return (int16_t)radar_data_frame_[index] + (radar_data_frame_[index + 1] << 8); } -bool ld2410::debug_command_results_(const char *title) { +bool ld2410::debug_command_results_( + #ifdef LD2410_DEBUG + const char *title + #endif + ) { if (latest_command_success_) { radar_uart_last_packet_ = millis(); @@ -640,9 +644,9 @@ bool ld2410::parse_data_frame_() bool ld2410::parse_command_frame_() { - uint16_t intra_frame_data_length_ = serial_to_int_(4); // radar_data_frame_[4] + (radar_data_frame_[5] << 8); #if defined(LD2410_DEBUG_COMMANDS) && defined(LD2410_DEBUG) + uint16_t intra_frame_data_length_ = serial_to_int_(4); // radar_data_frame_[4] + (radar_data_frame_[5] << 8); if (debug_uart_ != nullptr) { @@ -664,11 +668,23 @@ bool ld2410::parse_command_frame_() configuration_protocol_version_ = serial_to_int_(10); // radar_data_frame_[10] + (radar_data_frame_[11] << 8); configuration_buffer_size_ = serial_to_int_(12); // radar_data_frame_[12] + (radar_data_frame_[13] << 8); } - return debug_command_results_("ACK for entering configuration mode"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for entering configuration mode" + #endif + ); case CMD_CONFIGURATION_END: - return debug_command_results_("ACK for leaving configuration mode"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for leaving configuration mode" + #endif + ); case CMD_MAX_DISTANCE_AND_UNMANNED_DURATION: - return debug_command_results_("ACK for setting max values"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for setting max values" + #endif + ); case CMD_READ_PARAMETER: if (latest_command_success_) @@ -676,24 +692,31 @@ bool ld2410::parse_command_frame_() max_gate = radar_data_frame_[11]; max_moving_gate = radar_data_frame_[12]; max_stationary_gate = radar_data_frame_[13]; - motion_sensitivity[0] = radar_data_frame_[14]; - motion_sensitivity[1] = radar_data_frame_[15]; - motion_sensitivity[2] = radar_data_frame_[16]; - motion_sensitivity[3] = radar_data_frame_[17]; - motion_sensitivity[4] = radar_data_frame_[18]; - motion_sensitivity[5] = radar_data_frame_[19]; - motion_sensitivity[6] = radar_data_frame_[20]; - motion_sensitivity[7] = radar_data_frame_[21]; - motion_sensitivity[8] = radar_data_frame_[22]; - stationary_sensitivity[0] = radar_data_frame_[23]; - stationary_sensitivity[1] = radar_data_frame_[24]; - stationary_sensitivity[2] = radar_data_frame_[25]; - stationary_sensitivity[3] = radar_data_frame_[26]; - stationary_sensitivity[4] = radar_data_frame_[27]; - stationary_sensitivity[5] = radar_data_frame_[28]; - stationary_sensitivity[6] = radar_data_frame_[29]; - stationary_sensitivity[7] = radar_data_frame_[30]; - stationary_sensitivity[8] = radar_data_frame_[31]; + // Leave optimization to the compiler... + for (uint8_t n = 0; n < LD2410_MAX_GATES; ++n) { + motion_sensitivity[n] = radar_data_frame_[14 + n]; + } + // motion_sensitivity[0] = radar_data_frame_[14]; + // motion_sensitivity[1] = radar_data_frame_[15]; + // motion_sensitivity[2] = radar_data_frame_[16]; + // motion_sensitivity[3] = radar_data_frame_[17]; + // motion_sensitivity[4] = radar_data_frame_[18]; + // motion_sensitivity[5] = radar_data_frame_[19]; + // motion_sensitivity[6] = radar_data_frame_[20]; + // motion_sensitivity[7] = radar_data_frame_[21]; + // motion_sensitivity[8] = radar_data_frame_[22]; + for (uint8_t n = 0; n < LD2410_MAX_GATES; ++n) { + stationary_sensitivity[n] = radar_data_frame_[23 + n]; + } + // stationary_sensitivity[0] = radar_data_frame_[23]; + // stationary_sensitivity[1] = radar_data_frame_[24]; + // stationary_sensitivity[2] = radar_data_frame_[25]; + // stationary_sensitivity[3] = radar_data_frame_[26]; + // stationary_sensitivity[4] = radar_data_frame_[27]; + // stationary_sensitivity[5] = radar_data_frame_[28]; + // stationary_sensitivity[6] = radar_data_frame_[29]; + // stationary_sensitivity[7] = radar_data_frame_[30]; + // stationary_sensitivity[8] = radar_data_frame_[31]; sensor_idle_time = serial_to_int_(32); // radar_data_frame_[32]; #if defined(LD2410_DEBUG_COMMANDS) && defined(LD2410_DEBUG) @@ -728,13 +751,29 @@ bool ld2410::parse_command_frame_() } else { _errorCount++; } - return debug_command_results_("ACK for current configuration"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for current configuration" + #endif + ); case CMD_ENGINEERING_ENABLE: - return debug_command_results_("ACK for enable engineering mode"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for enable engineering mode" + #endif + ); case CMD_ENGINEERING_END: - return debug_command_results_("ACK for end engineering mode"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for end engineering mode" + #endif + ); case CMD_RANGE_GATE_SENSITIVITY: - return debug_command_results_("ACK for setting sensitivity values"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for setting sensitivity values" + #endif + ); case CMD_READ_FIRMWARE_VERSION: if (latest_command_success_) @@ -746,13 +785,29 @@ bool ld2410::parse_command_frame_() firmware_bugfix_version += radar_data_frame_[16] << 16; firmware_bugfix_version += radar_data_frame_[17] << 24; } - return debug_command_results_("ACK for firmware version"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for firmware version" + #endif + ); case CMD_SET_SERIAL_PORT_BAUD: - return debug_command_results_("ACK for setting serial baud rate"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for setting serial baud rate" + #endif + ); case CMD_FACTORY_RESET: - return debug_command_results_("ACK for factory reset"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for factory reset" + #endif + ); case CMD_RESTART: - return debug_command_results_("ACK for restart"); + return debug_command_results_( + #ifdef LD2410_DEBUG + "ACK for restart" + #endif + ); default: #if (defined(LD2410_DEBUG_DATA) || defined(LD2410_DEBUG_COMMANDS) || defined(LD2410_DEBUG_PARSE)) && defined(LD2410_DEBUG) @@ -776,22 +831,48 @@ bool ld2410::parse_command_frame_() void ld2410::send_command_preamble_() { // Command preamble - radar_uart_->write((uint8_t)char(0xFD)); - radar_uart_->write((uint8_t)char(0xFC)); - radar_uart_->write((uint8_t)char(0xFB)); - radar_uart_->write((uint8_t)char(0xFA)); + const uint8_t preamble [] {0xFD,0xFC,0xFB,0xFA}; + for (uint8_t n = 0; n < 4; ++n) { + radar_uart_->write(preamble[n]); + } + // radar_uart_->write((uint8_t)char(0xFD)); + // radar_uart_->write((uint8_t)char(0xFC)); + // radar_uart_->write((uint8_t)char(0xFB)); + // radar_uart_->write((uint8_t)char(0xFA)); } void ld2410::send_command_postamble_() { // Command end - radar_uart_->write((uint8_t)char(0x04)); - radar_uart_->write((uint8_t)char(0x03)); - radar_uart_->write((uint8_t)char(0x02)); - radar_uart_->write((uint8_t)char(0x01)); + const uint8_t postamble [] { 0x04, 0x03, 0x02, 0x01 }; + for (uint8_t n = 0; n < 4; ++n) { + radar_uart_->write(postamble[n]); + } + // radar_uart_->write((uint8_t)char(0x04)); + // radar_uart_->write((uint8_t)char(0x03)); + // radar_uart_->write((uint8_t)char(0x02)); + // radar_uart_->write((uint8_t)char(0x01)); radar_uart_->flush(); } +void ld2410::send_2byte_command(uint8_t cmd_byte) { + uint8_t cmd_array [] = { 0x02, 0x00, 0xFF, 0x00 }; + cmd_array[2] = cmd_byte; + for (uint8_t n = 0; n < 4u; ++n) { + radar_uart_->write(cmd_array[n]); + } +} + +void ld2410::send_4byte_command(uint8_t cmd_byte, + uint8_t val_byte) { + uint8_t cmd_array [] = { 0x04, 0x00, 0xFF, 0x00, 0xFF, 0x00 }; + cmd_array[2] = cmd_byte; + cmd_array[4] = val_byte; + for (uint8_t n = 0; n < 6u; ++n) { + radar_uart_->write(cmd_array[n]); + } +} + /* * Wrapper to enable configuration mode for * multiple command execution @@ -834,12 +915,13 @@ bool ld2410::enter_configuration_mode_() send_command_preamble_(); // Request - radar_uart_->write((uint8_t)char(0x04)); // Command is four bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_CONFIGURATION_ENABLE)); // Request enter command mode - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x01)); - radar_uart_->write((uint8_t)char(0x00)); + send_4byte_command(CMD_CONFIGURATION_ENABLE, 0x01); // Request enter command mode + // radar_uart_->write((uint8_t)char(0x04)); // Command is four bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_CONFIGURATION_ENABLE)); // Request enter command mode + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x01)); + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); @@ -864,10 +946,11 @@ bool ld2410::leave_configuration_mode_() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_CONFIGURATION_END)); // Request leave command mode - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_CONFIGURATION_END); // Request leave command mode + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_CONFIGURATION_END)); // Request leave command mode + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); @@ -892,10 +975,11 @@ bool ld2410::requestStartEngineeringMode() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_ENGINEERING_ENABLE)); // Request enter engineering mode - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_ENGINEERING_ENABLE); // Request enter engineering mode + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_ENGINEERING_ENABLE)); // Request enter engineering mode + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_ENGINEERING_ENABLE); @@ -912,10 +996,11 @@ bool ld2410::requestEndEngineeringMode() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_ENGINEERING_END)); // Request leave engineering mode - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_ENGINEERING_END); // Request leave engineering mode + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_ENGINEERING_END)); // Request leave engineering mode + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_ENGINEERING_END); @@ -931,10 +1016,11 @@ bool ld2410::requestCurrentConfiguration() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_READ_PARAMETER)); // Request current configuration - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_READ_PARAMETER); // Request current configuration + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_READ_PARAMETER)); // Request current configuration + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_READ_PARAMETER); @@ -950,10 +1036,11 @@ bool ld2410::requestFirmwareVersion() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_READ_FIRMWARE_VERSION)); // Request firmware version - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_READ_FIRMWARE_VERSION); // Request firmware version + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_READ_FIRMWARE_VERSION)); // Request firmware version + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_READ_FIRMWARE_VERSION); @@ -969,10 +1056,11 @@ bool ld2410::requestRestart() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_RESTART)); // Request restart - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_RESTART); // Request restart + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_RESTART)); // Request restart + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_RESTART); @@ -988,10 +1076,11 @@ bool ld2410::requestFactoryReset() send_command_preamble_(); // Request firmware - radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_FACTORY_RESET)); // Request factory reset - radar_uart_->write((uint8_t)char(0x00)); + send_2byte_command(CMD_FACTORY_RESET); // Request factory reset + // radar_uart_->write((uint8_t)char(0x02)); // Command is two bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_FACTORY_RESET)); // Request factory reset + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_FACTORY_RESET); @@ -1022,12 +1111,13 @@ bool ld2410::setSerialBaudRate(uint8_t cSpeed) send_command_preamble_(); // Serial baud Rate - radar_uart_->write((uint8_t)char(0x04)); // Command is four bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_SET_SERIAL_PORT_BAUD)); - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(cSpeed)); // Set serial baud rate 1-8, 9600-460800 default=7 - radar_uart_->write((uint8_t)char(0x00)); + send_4byte_command(CMD_SET_SERIAL_PORT_BAUD, cSpeed); + // radar_uart_->write((uint8_t)char(0x04)); // Command is four bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_SET_SERIAL_PORT_BAUD)); + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(cSpeed)); // Set serial baud rate 1-8, 9600-460800 default=7 + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_SET_SERIAL_PORT_BAUD); @@ -1047,28 +1137,40 @@ bool ld2410::setMaxValues(uint16_t moving, uint16_t stationary, uint16_t inactiv { delay(50); send_command_preamble_(); - radar_uart_->write((uint8_t)char(0x14)); // Command is 20 bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_MAX_DISTANCE_AND_UNMANNED_DURATION)); // Request set max values - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x00)); // Moving gate command - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(moving & 0x00FF)); // Moving gate value - radar_uart_->write((uint8_t)char((moving & 0xFF00) >> 8)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x01)); // Stationary gate command - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(stationary & 0x00FF)); // Stationary gate value - radar_uart_->write((uint8_t)char((stationary & 0xFF00) >> 8)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x02)); // Inactivity timer command - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(inactivityTimer & 0x00FF)); // Inactivity timer - radar_uart_->write((uint8_t)char((inactivityTimer & 0xFF00) >> 8)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); + uint8_t set_zones[] = { 0x14, 0x00, CMD_MAX_DISTANCE_AND_UNMANNED_DURATION, + 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x01, 0x00, + 0xFF, 0xFF, 0x00, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0x00, 0x00 }; + set_zones[6] = moving & 0x00FF; + set_zones[7] = (moving & 0xFF00) >> 8; + set_zones[12] = stationary & 0x00FF; + set_zones[13] = (stationary & 0xFF00) >> 8; + set_zones[18] = inactivityTimer & 0x00FF; + set_zones[19] = (inactivityTimer & 0xFF00) >> 8; + for (uint8_t n = 0; n < 22u; ++n) { + radar_uart_->write(set_zones[n]); + } + // radar_uart_->write((uint8_t)char(0x14)); // Command is 20 bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_MAX_DISTANCE_AND_UNMANNED_DURATION)); // Request set max values + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x00)); // Moving gate command + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(moving & 0x00FF)); // Moving gate value + // radar_uart_->write((uint8_t)char((moving & 0xFF00) >> 8)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x01)); // Stationary gate command + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(stationary & 0x00FF)); // Stationary gate value + // radar_uart_->write((uint8_t)char((stationary & 0xFF00) >> 8)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x02)); // Inactivity timer command + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(inactivityTimer & 0x00FF)); // Inactivity timer + // radar_uart_->write((uint8_t)char((inactivityTimer & 0xFF00) >> 8)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_MAX_DISTANCE_AND_UNMANNED_DURATION); @@ -1099,36 +1201,50 @@ bool ld2410::setGateSensitivityThreshold(uint8_t gate, uint8_t moving, uint8_t s { delay(50); send_command_preamble_(); - radar_uart_->write((uint8_t)char(0x14)); // Command is 20 bytes long - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(CMD_RANGE_GATE_SENSITIVITY)); // Request set sensitivity values - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x00)); // Gate command - radar_uart_->write((uint8_t)char(0x00)); - - if (gate == 255) { - radar_uart_->write((uint8_t)char(0xFF)); // Gate value - radar_uart_->write((uint8_t)char(0xFF)); - radar_uart_->write((uint8_t)char(0xFF)); - radar_uart_->write((uint8_t)char(0xFF)); - } else { - radar_uart_->write((uint8_t)char(gate)); // Gate value - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); + uint8_t set_sens[] = { 0x14, 0x00, CMD_RANGE_GATE_SENSITIVITY, + 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x00, + 0xFF, 0x00, 0x00, 0x00, 0x02, 0x00, 0xFF, 0x00, 0x00, 0x00 }; + set_sens[6] = gate; + if (255 == gate) { + set_sens[7] = gate; + set_sens[8] = gate; + set_sens[9] = gate; } - radar_uart_->write((uint8_t)char(0x01)); // Motion sensitivity command - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(moving)); // Motion sensitivity value - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x02)); // Stationary sensitivity command - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(stationary)); // Stationary sensitivity value - radar_uart_->write((uint8_t)char(0x00)); - radar_uart_->write((uint8_t)char(0x00)); // Spacer - radar_uart_->write((uint8_t)char(0x00)); + set_sens[12] = moving; + set_sens[18] = stationary; + for (uint8_t n = 0; n < 22u; ++n) { + radar_uart_->write(set_sens[n]); + } + // radar_uart_->write((uint8_t)char(0x14)); // Command is 20 bytes long + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(CMD_RANGE_GATE_SENSITIVITY)); // Request set sensitivity values + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x00)); // Gate command + // radar_uart_->write((uint8_t)char(0x00)); + + // if (gate == 255) { + // radar_uart_->write((uint8_t)char(0xFF)); // Gate value + // radar_uart_->write((uint8_t)char(0xFF)); + // radar_uart_->write((uint8_t)char(0xFF)); + // radar_uart_->write((uint8_t)char(0xFF)); + // } else { + // radar_uart_->write((uint8_t)char(gate)); // Gate value + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); + // } + // radar_uart_->write((uint8_t)char(0x01)); // Motion sensitivity command + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(moving)); // Motion sensitivity value + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x02)); // Stationary sensitivity command + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(stationary)); // Stationary sensitivity value + // radar_uart_->write((uint8_t)char(0x00)); + // radar_uart_->write((uint8_t)char(0x00)); // Spacer + // radar_uart_->write((uint8_t)char(0x00)); send_command_postamble_(); radar_uart_last_command_ = millis(); return wait_for_command_ack_(CMD_RANGE_GATE_SENSITIVITY); diff --git a/lib/ld2410/src/ld2410.h b/lib/ld2410/src/ld2410.h index 6007d57bd..102da8418 100644 --- a/lib/ld2410/src/ld2410.h +++ b/lib/ld2410/src/ld2410.h @@ -12,6 +12,12 @@ * Released under LGPL-2.1 see https://github.com/ncmreynolds/ld2410/LICENSE for full license * */ + +/** Changelog: + * 2024-01-13 tonhuisman: Replace separate serial->write() commands and c-style casts, by loops using an uint8_t array, + * saving ca. 1100 bytes on ESP8266 and ca. 700 bytes on ESP32 binaries + */ + #pragma once #include @@ -308,7 +314,11 @@ private: /* * feature management functions */ uint16_t serial_to_int_(uint8_t index); // Unpack bytes - bool debug_command_results_(const char *title); + bool debug_command_results_( + #ifdef LD2410_DEBUG + const char *title + #endif + ); bool wait_for_command_ack_(uint8_t command); bool isProtocolDataFrame_(); // Command -Determine type of Frame bool isReportingDataFrame_(); // Data - Determine type of Frame @@ -319,6 +329,9 @@ private: void print_frame_(); // Print the frame for debugging void send_command_preamble_(); // Commands have the same preamble void send_command_postamble_(); // Commands have the same postamble + void send_2byte_command(uint8_t cmd_byte); // 2-byte commands have a single command byte + void send_4byte_command(uint8_t cmd_byte, + uint8_t val_byte); // 4-byte commands have a singel command byte and a single value byte bool enter_configuration_mode_(); // Necessary before sending any command bool leave_configuration_mode_(); // Will not read values without leaving command mode }; diff --git a/lib/pubsubclient/src/PubSubClient.cpp b/lib/pubsubclient/src/PubSubClient.cpp index 5976062b6..30948c57f 100644 --- a/lib/pubsubclient/src/PubSubClient.cpp +++ b/lib/pubsubclient/src/PubSubClient.cpp @@ -1,772 +1,841 @@ -/* - PubSubClient.cpp - A simple client for MQTT. - Nick O'Leary - http://knolleary.net -*/ - -#include "PubSubClient.h" -#include - -#ifdef ESP32 -#include -#endif - -#ifdef USE_SECOND_HEAP - #include -#endif - - -PubSubClient::PubSubClient() { - this->_state = MQTT_DISCONNECTED; - this->_client = NULL; - this->stream = NULL; - setCallback(NULL); -} - -PubSubClient::PubSubClient(Client& client) { - this->_state = MQTT_DISCONNECTED; - setClient(client); - this->stream = NULL; -} - -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(addr, port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(addr,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(ip, port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(ip,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setClient(client); - setStream(stream); -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - this->stream = NULL; -} -PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { - this->_state = MQTT_DISCONNECTED; - setServer(domain,port); - setCallback(callback); - setClient(client); - setStream(stream); -} - -PubSubClient::~PubSubClient() -{ - if (buffer != nullptr) { - free(buffer); - buffer = nullptr; - } -} - -boolean PubSubClient::connect(const char *id) { - return connect(id,NULL,NULL,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { - return connect(id,user,pass,0,0,0,0,1); -} - -boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { - return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); -} - -boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { - if (!initBuffer()) { - return false; - } - - if (!connected()) { - int result = 0; - - if (_client == nullptr) { - return false; - } - if (_client->connected()) { - result = 1; - } else { - if (domain.length() != 0) { -#ifdef ESP32 - WiFiClient* wfc = (WiFiClient*)_client; - result = wfc->connect(this->domain.c_str(), this->port, ESP32_CONNECTION_TIMEOUT); -#else - result = _client->connect(this->domain.c_str(), this->port); -#endif - } else { -#ifdef ESP32 - WiFiClient* wfc = (WiFiClient*)_client; - result = wfc->connect(this->ip, this->port, ESP32_CONNECTION_TIMEOUT); -#else - result = _client->connect(this->ip, this->port); -#endif - } - } - if (result == 1) { - nextMsgId = 1; - // Leave room in the buffer for header and variable length field - uint16_t length = MQTT_MAX_HEADER_SIZE; - unsigned int j; - -#if MQTT_VERSION == MQTT_VERSION_3_1 - uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 9 -#elif MQTT_VERSION == MQTT_VERSION_3_1_1 - uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; -#define MQTT_HEADER_VERSION_LENGTH 7 -#endif - for (j = 0;j>1); - } - } - - buffer[length++] = v; - - buffer[length++] = ((MQTT_KEEPALIVE) >> 8); - buffer[length++] = ((MQTT_KEEPALIVE) & 0xFF); - - CHECK_STRING_LENGTH(length,id) - length = writeString(id,buffer,length); - if (willTopic) { - CHECK_STRING_LENGTH(length,willTopic) - length = writeString(willTopic,buffer,length); - CHECK_STRING_LENGTH(length,willMessage) - length = writeString(willMessage,buffer,length); - } - - if(user != NULL) { - CHECK_STRING_LENGTH(length,user) - length = writeString(user,buffer,length); - if(pass != NULL) { - CHECK_STRING_LENGTH(length,pass) - length = writeString(pass,buffer,length); - } - } - - write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE); - - lastInActivity = lastOutActivity = millis(); - pingOutstanding = false; // See: https://github.com/knolleary/pubsubclient/pull/802 - - while (!_client->available()) { - delay(0); // Prevent watchdog crashes - unsigned long t = millis(); - if (t-lastInActivity >= ((int32_t) MQTT_SOCKET_TIMEOUT*1000UL)) { - _state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } - } - uint8_t llen; - uint16_t len = readPacket(&llen); - - if (len == 4) { - if (buffer[3] == 0) { - lastInActivity = millis(); - pingOutstanding = false; - _state = MQTT_CONNECTED; - return true; - } else { - _state = buffer[3]; - } - } - _client->stop(); - } else { - _state = MQTT_CONNECT_FAILED; - } - return false; - } - return true; -} - -// reads a byte into result -boolean PubSubClient::readByte(uint8_t * result) { - if (_client == nullptr) { - return false; - } - uint32_t previousMillis = millis(); - while(!_client->available()) { - delay(1); // Prevent watchdog crashes - uint32_t currentMillis = millis(); - if(currentMillis - previousMillis >= ((int32_t) MQTT_SOCKET_TIMEOUT * 1000)){ - return false; - } - } - *result = _client->read(); - return true; -} - -// reads a byte into result[*index] and increments index -boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ - uint16_t current_index = *index; - uint8_t * write_address = &(result[current_index]); - if(readByte(write_address)){ - *index = current_index + 1; - return true; - } - return false; -} - -uint16_t PubSubClient::readPacket(uint8_t* lengthLength) { - if (!initBuffer()) { - return 0; - } - - uint16_t len = 0; - if(!readByte(buffer, &len)) return 0; - bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH; - uint32_t multiplier = 1; - uint16_t length = 0; - uint8_t digit = 0; - uint16_t skip = 0; - uint8_t start = 0; - - do { - if (len == 5) { - // Invalid remaining length encoding - kill the connection - _state = MQTT_DISCONNECTED; - _client->stop(); - return 0; - } - if(!readByte(&digit)) return 0; - buffer[len++] = digit; - length += (digit & 127) * multiplier; - multiplier *= 128; - } while ((digit & 128) != 0 && len < (MQTT_MAX_PACKET_SIZE -2)); - *lengthLength = len-1; - - if (isPublish) { - // Read in topic length to calculate bytes to skip over for Stream writing - if(!readByte(buffer, &len)) return 0; - if(!readByte(buffer, &len)) return 0; - skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2]; - start = 2; - if (buffer[0]&MQTTQOS1) { - // skip message id - skip += 2; - } - } - - for (uint16_t i = start;istream) { - if (isPublish && len-*lengthLength-2>skip) { - this->stream->write(digit); - } - } - if (len < MQTT_MAX_PACKET_SIZE) { - buffer[len] = digit; - } - len++; - } - - if (!this->stream && len > MQTT_MAX_PACKET_SIZE) { - len = 0; // This will cause the packet to be ignored. - } - - return len; -} - -bool PubSubClient::loop_read() { - if (!initBuffer()) { - return false; - } - - if (_client == nullptr) { - return false; - } - if (!_client->available()) { - return false; - } - uint8_t llen; - uint16_t len = readPacket(&llen); - if (len == 0) { - return false; - } - unsigned long t = millis(); - lastInActivity = t; - uint8_t type = buffer[0]&0xF0; - - switch(type) { - case MQTTPUBLISH: - { - if (callback) { - const bool msgId_present = (buffer[0]&0x06) == MQTTQOS1; - const uint16_t tl_offset = llen+1; - const uint16_t tl = (buffer[tl_offset]<<8)+buffer[tl_offset+1]; /* topic length in bytes */ - const uint16_t topic_offset = tl_offset+2; - const uint16_t msgId_offset = topic_offset+tl; - const uint16_t payload_offset = msgId_present ? msgId_offset+2 : msgId_offset; - if (payload_offset >= MQTT_MAX_PACKET_SIZE) return false; - if (len < payload_offset) return false; - // Need to move the topic 1 byte to insert a '\0' at the end of the topic. - memmove(buffer+topic_offset-1,buffer+topic_offset,tl); /* move topic inside buffer 1 byte to front */ - buffer[topic_offset-1+tl] = 0; /* end the topic as a 'C' string with \x00 */ - char *topic = (char*) buffer+topic_offset-1; - uint8_t *payload; - // msgId only present for QOS>0 - if (msgId_present) { - const uint16_t msgId = (buffer[msgId_offset]<<8)+buffer[msgId_offset+1]; - payload = buffer+payload_offset; - callback(topic,payload,len-payload_offset); - if (_client->connected()) { - buffer[0] = MQTTPUBACK; - buffer[1] = 2; - buffer[2] = (msgId >> 8); - buffer[3] = (msgId & 0xFF); - if (_client->write(buffer,4) != 0) { - lastOutActivity = t; - } - } - } else { - // No msgId - payload = buffer+payload_offset; - callback(topic,payload,len-payload_offset); - } - } - break; - } - case MQTTPINGREQ: - { - if (_client->connected()) { - buffer[0] = MQTTPINGRESP; - buffer[1] = 0; - _client->write(buffer,2); - } - break; - } - case MQTTPINGRESP: - { - pingOutstanding = false; - break; - } - default: - return false; - } - return true; -} - -boolean PubSubClient::loop() { - loop_read(); - if (connected()) { - unsigned long t = millis(); - if ((t - lastInActivity > MQTT_KEEPALIVE*1000UL) || (t - lastOutActivity > MQTT_KEEPALIVE*1000UL)) { - if (pingOutstanding) { - this->_state = MQTT_CONNECTION_TIMEOUT; - _client->stop(); - return false; - } else { - buffer[0] = MQTTPINGREQ; - buffer[1] = 0; - if (_client->write(buffer,2) != 0) { - lastOutActivity = t; - lastInActivity = t; - } - pingOutstanding = true; - } - } - return true; - } - return false; -} - -boolean PubSubClient::publish(const char* topic, const char* payload) { - size_t plength = (payload != nullptr) ? strlen(payload) : 0; - return publish(topic,(const uint8_t*)payload,plength,false); -} - -boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { - size_t plength = (payload != nullptr) ? strlen(payload) : 0; - return publish(topic,(const uint8_t*)payload,plength,retained); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { - return publish(topic, payload, plength, false); -} - -boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { - if (!beginPublish(topic, plength, retained)) { - return false; - } - for (unsigned int i=0;iwrite(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); - if (rc > 0) { - lastOutActivity = millis(); - } - return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); - } - return false; -} - -int PubSubClient::endPublish() { - flushBuffer(); - return 1; -} - -size_t PubSubClient::write(uint8_t data) { - if (_client == nullptr) { - lastOutActivity = millis(); - return 0; - } - size_t rc = appendBuffer(data); - if (rc != 0) { - lastOutActivity = millis(); - } - return rc; -} - -size_t PubSubClient::write(const uint8_t *data, size_t size) { - if (_client == nullptr) { - lastOutActivity = millis(); - return 0; - } - size_t rc = appendBuffer(data,size); - if (rc != 0) { - lastOutActivity = millis(); - } - return rc; -} - -size_t PubSubClient::write(const String& message) { - return write(reinterpret_cast(message.c_str()), message.length()); -} - - -size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint32_t length) { - uint8_t lenBuf[4]; - uint8_t llen = 0; - uint8_t digit; - uint8_t pos = 0; - uint32_t len = length; - do { - digit = len % 128; - len = len / 128; - if (len > 0) { - digit |= 0x80; - } - lenBuf[pos++] = digit; - llen++; - } while(len>0 && pos < 4); - - buf[4-llen] = header; - for (int i=0;i 0) && result) { - delay(0); // Prevent watchdog crashes - bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; - rc = _client->write(writeBuf,bytesToWrite); - result = (rc == bytesToWrite); - bytesRemaining -= rc; - writeBuf += rc; - } - return result; -#else - rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); - if (rc != 0) { - lastOutActivity = millis(); - } - return (rc == hlen+length); -#endif -} - -boolean PubSubClient::subscribe(const char* topic) { - return subscribe(topic, 0); -} - -boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { - if (qos > 1) { - return false; - } - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - 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; - } - buffer[length++] = (nextMsgId >> 8); - buffer[length++] = (nextMsgId & 0xFF); - length = writeString((char*)topic, buffer,length); - buffer[length++] = qos; - return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -boolean PubSubClient::unsubscribe(const char* topic) { - if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { - // Too long - return false; - } - if (connected()) { - uint16_t length = MQTT_MAX_HEADER_SIZE; - nextMsgId++; - if (nextMsgId == 0) { - nextMsgId = 1; - } - buffer[length++] = (nextMsgId >> 8); - buffer[length++] = (nextMsgId & 0xFF); - length = writeString(topic, buffer,length); - return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); - } - return false; -} - -void PubSubClient::disconnect() { - if (_state == MQTT_DISCONNECTED || !initBuffer()) { - _state = MQTT_DISCONNECTED; - lastInActivity = lastOutActivity = millis(); - - return; - } - - buffer[0] = MQTTDISCONNECT; - buffer[1] = 0; - if (_client != nullptr) { - _client->write(buffer,2); - _client->flush(); - _client->stop(); - } - _state = MQTT_DISCONNECTED; - lastInActivity = lastOutActivity = millis(); -} - -uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { - const char* idp = string; - uint16_t i = 0; - pos += 2; - while (*idp && pos < (MQTT_MAX_PACKET_SIZE - 2)) { - buf[pos++] = *idp++; - i++; - } - buf[pos-i-2] = (i >> 8); - buf[pos-i-1] = (i & 0xFF); - return pos; -} - -size_t PubSubClient::appendBuffer(uint8_t data) { - if (!initBuffer()) { - return 0; - } - - buffer[_bufferWritePos] = data; - ++_bufferWritePos; - if (_bufferWritePos >= MQTT_MAX_PACKET_SIZE) { - if (flushBuffer() == 0) return 0; - } - return 1; -} - -size_t PubSubClient::appendBuffer(const uint8_t *data, size_t size) { - for (size_t i = 0; i < size; ++i) { - if (appendBuffer(data[i]) == 0) return i; - } - return size; -} - -size_t PubSubClient::flushBuffer() { - size_t rc = 0; - if (_bufferWritePos > 0) { - if (connected()) { - rc = _client->write(buffer, _bufferWritePos); - if (rc != 0) { - lastOutActivity = millis(); - } - } - _bufferWritePos = 0; - } - return rc; -} - -bool PubSubClient::initBuffer() -{ - if (buffer == nullptr) { -#ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; -#endif - buffer = (uint8_t*) malloc(sizeof(uint8_t) * MQTT_MAX_PACKET_SIZE); - } - return buffer != nullptr; -} - -boolean PubSubClient::connected() { - if (_client == NULL ) { - this->_state = MQTT_DISCONNECTED; - return false; - } - if (_client->connected() == 0) { - bool lastStateConnected = this->_state == MQTT_CONNECTED; - this->disconnect(); - if (lastStateConnected) { - this->_state = MQTT_CONNECTION_LOST; - } - return false; - } - return this->_state == MQTT_CONNECTED; -} - -PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { - IPAddress addr(ip[0],ip[1],ip[2],ip[3]); - return setServer(addr,port); -} - -PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { - this->ip = ip; - this->port = port; - this->domain = ""; - return *this; -} - -PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { - this->domain = domain; - this->port = port; - return *this; -} - -PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { - this->callback = callback; - return *this; -} - -PubSubClient& PubSubClient::setClient(Client& client){ - this->_client = &client; - return *this; -} - -PubSubClient& PubSubClient::setStream(Stream& stream){ - this->stream = &stream; - return *this; -} - -int PubSubClient::state() { - return this->_state; -} +/* + PubSubClient.cpp - A simple client for MQTT. + Nick O'Leary + http://knolleary.net +*/ + +#include "PubSubClient.h" +#include + +#ifdef ESP32 +#include +#endif + +#ifdef USE_SECOND_HEAP + #include +#endif + + +PubSubClient::PubSubClient() { + this->_state = MQTT_DISCONNECTED; + this->_client = NULL; + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; + + setCallback(NULL); +} + +PubSubClient::PubSubClient(Client& client) { + this->_state = MQTT_DISCONNECTED; + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(addr, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(IPAddress addr, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(addr,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(ip, port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(uint8_t *ip, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(ip,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + this->stream = NULL; + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} +PubSubClient::PubSubClient(const char* domain, uint16_t port, MQTT_CALLBACK_SIGNATURE, Client& client, Stream& stream) { + this->_state = MQTT_DISCONNECTED; + setServer(domain,port); + setCallback(callback); + setClient(client); + setStream(stream); + this->keepAlive_sec = MQTT_KEEPALIVE; + this->socketTimeout_msec = MQTT_SOCKET_TIMEOUT * 1000; +} + +PubSubClient::~PubSubClient() +{ + if (buffer != nullptr) { + free(buffer); + buffer = nullptr; + } +} + +boolean PubSubClient::connect(const char *id) { + return connect(id,NULL,NULL,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass) { + return connect(id,user,pass,0,0,0,0,1); +} + +boolean PubSubClient::connect(const char *id, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,NULL,NULL,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage) { + return connect(id,user,pass,willTopic,willQos,willRetain,willMessage,1); +} + +boolean PubSubClient::connect(const char *id, const char *user, const char *pass, const char* willTopic, uint8_t willQos, boolean willRetain, const char* willMessage, boolean cleanSession) { + if (!initBuffer()) { + return false; + } + + if (!connected()) { + int result = 0; + + if (_client == nullptr) { + return false; + } + if (_client->connected()) { + result = 1; + } else { + if (domain.length() != 0) { +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR < 5 + WiFiClient* wfc = (WiFiClient*)_client; + result = wfc->connect(this->domain.c_str(), this->port, _client->getTimeout()); +#else + result = _client->connect(this->domain.c_str(), this->port); +#endif + } else { +#if defined(ESP32) && ESP_IDF_VERSION_MAJOR < 5 + WiFiClient* wfc = (WiFiClient*)_client; + result = wfc->connect(this->ip, this->port, _client->getTimeout()); +#else + result = _client->connect(this->ip, this->port); +#endif + } + } + if (result == 1) { + nextMsgId = 1; + // Leave room in the buffer for header and variable length field + uint16_t length = MQTT_MAX_HEADER_SIZE; + unsigned int j; + +#if MQTT_VERSION == MQTT_VERSION_3_1 + uint8_t d[9] = {0x00,0x06,'M','Q','I','s','d','p', MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 9 +#elif MQTT_VERSION == MQTT_VERSION_3_1_1 + uint8_t d[7] = {0x00,0x04,'M','Q','T','T',MQTT_VERSION}; +#define MQTT_HEADER_VERSION_LENGTH 7 +#endif + for (j = 0;j>1); + } + } + + buffer[length++] = v; + + buffer[length++] = ((keepAlive_sec) >> 8); + buffer[length++] = ((keepAlive_sec) & 0xFF); + + CHECK_STRING_LENGTH(length,id) + length = writeString(id,buffer,length); + if (willTopic) { + CHECK_STRING_LENGTH(length,willTopic) + length = writeString(willTopic,buffer,length); + CHECK_STRING_LENGTH(length,willMessage) + length = writeString(willMessage,buffer,length); + } + + if(user != NULL) { + CHECK_STRING_LENGTH(length,user) + length = writeString(user,buffer,length); + if(pass != NULL) { + CHECK_STRING_LENGTH(length,pass) + length = writeString(pass,buffer,length); + } + } + + write(MQTTCONNECT,buffer,length-MQTT_MAX_HEADER_SIZE); + + lastInActivity = lastOutActivity = millis(); + pingOutstanding = false; // See: https://github.com/knolleary/pubsubclient/pull/802 + + while (!_client->available()) { + delay(0); // Prevent watchdog crashes + unsigned long t = millis(); + if ((int32_t)(t - lastInActivity) >= socketTimeout_msec) { + _state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } + } + uint8_t llen; + uint16_t len = readPacket(&llen); + + if (len == 4) { + if (buffer[3] == 0) { + lastInActivity = millis(); + pingOutstanding = false; + _state = MQTT_CONNECTED; + return true; + } else { + _state = buffer[3]; + } + } + _client->stop(); + } else { + _state = MQTT_CONNECT_FAILED; + } + return false; + } + return true; +} + +// reads a byte into result +boolean PubSubClient::readByte(uint8_t * result) { + if (_client == nullptr) { + return false; + } + uint32_t previousMillis = millis(); + + while(!_client->available() && _client->connected()) { + delay(1); // Prevent watchdog crashes + + if((int32_t)(millis() - previousMillis) >= socketTimeout_msec){ + return false; + } + } + *result = _client->read(); + return true; +} + +// reads a byte into result[*index] and increments index +boolean PubSubClient::readByte(uint8_t * result, uint16_t * index){ + uint16_t current_index = *index; + uint8_t * write_address = &(result[current_index]); + if(readByte(write_address)){ + *index = current_index + 1; + return true; + } + return false; +} + +uint16_t PubSubClient::readPacket(uint8_t* lengthLength) { + if (!initBuffer()) { + return 0; + } + + uint16_t len = 0; + if(!readByte(buffer, &len)) return 0; + bool isPublish = (buffer[0]&0xF0) == MQTTPUBLISH; + uint32_t multiplier = 1; + uint16_t length = 0; + uint8_t digit = 0; + uint16_t skip = 0; + uint8_t start = 0; + + do { + if (len == 5) { + // Invalid remaining length encoding - kill the connection + _state = MQTT_DISCONNECTED; + _client->stop(); + return 0; + } + if(!readByte(&digit)) return 0; + buffer[len++] = digit; + length += (digit & 127) * multiplier; + multiplier *= 128; + } while ((digit & 128) != 0 && len < (MQTT_MAX_PACKET_SIZE -2)); + *lengthLength = len-1; + + if (isPublish) { + // Read in topic length to calculate bytes to skip over for Stream writing + if(!readByte(buffer, &len)) return 0; + if(!readByte(buffer, &len)) return 0; + skip = (buffer[*lengthLength+1]<<8)+buffer[*lengthLength+2]; + start = 2; + if (buffer[0]&MQTTQOS1) { + // skip message id + skip += 2; + } + } + + for (uint16_t i = start;istream) { + if (isPublish && len-*lengthLength-2>skip) { + this->stream->write(digit); + } + } + if (len < MQTT_MAX_PACKET_SIZE) { + buffer[len] = digit; + } + len++; + } + + if (!this->stream && len > MQTT_MAX_PACKET_SIZE) { + len = 0; // This will cause the packet to be ignored. + } + + return len; +} + +bool PubSubClient::loop_read() { + if (!initBuffer()) { + return false; + } + + if (_client == nullptr) { + return false; + } + if (!_client->available()) { + return false; + } + uint8_t llen; + uint16_t len = readPacket(&llen); + if (len == 0) { + return false; + } + unsigned long t = millis(); + lastInActivity = t; + uint8_t type = buffer[0]&0xF0; + + switch(type) { + case MQTTPUBLISH: + { + if (callback) { + const bool msgId_present = (buffer[0]&0x06) == MQTTQOS1; + const uint16_t tl_offset = llen+1; + const uint16_t tl = (buffer[tl_offset]<<8)+buffer[tl_offset+1]; /* topic length in bytes */ + const uint16_t topic_offset = tl_offset+2; + const uint16_t msgId_offset = topic_offset+tl; + const uint16_t payload_offset = msgId_present ? msgId_offset+2 : msgId_offset; + if (payload_offset >= MQTT_MAX_PACKET_SIZE) return false; + if (len < payload_offset) return false; + // Need to move the topic 1 byte to insert a '\0' at the end of the topic. + memmove(buffer+topic_offset-1,buffer+topic_offset,tl); /* move topic inside buffer 1 byte to front */ + buffer[topic_offset-1+tl] = 0; /* end the topic as a 'C' string with \x00 */ + char *topic = (char*) buffer+topic_offset-1; + uint8_t *payload; + // msgId only present for QOS>0 + if (msgId_present) { + const uint16_t msgId = (buffer[msgId_offset]<<8)+buffer[msgId_offset+1]; + payload = buffer+payload_offset; + callback(topic,payload,len-payload_offset); + if (_client->connected()) { + buffer[0] = MQTTPUBACK; + buffer[1] = 2; + buffer[2] = (msgId >> 8); + buffer[3] = (msgId & 0xFF); + if (_client->write(buffer,4) != 0) { + lastOutActivity = t; + } + } + } else { + // No msgId + payload = buffer+payload_offset; + callback(topic,payload,len-payload_offset); + } + } + break; + } + case MQTTPINGREQ: + { + if (_client->connected()) { + buffer[0] = MQTTPINGRESP; + buffer[1] = 0; + if (_client->write(buffer,2) != 0) { + lastOutActivity = t; + } + } + break; + } + case MQTTPINGRESP: + { + pingOutstanding = false; + break; + } + default: + return false; + } + return true; +} + +boolean PubSubClient::loop() { + loop_read(); + if (connected()) { + unsigned long t = millis(); + // Send message at 2/3 of keepalive interval + // Wait for server-sent keep-alive till 3/2 of keepalive interval + // Just to make sure the broker will not disconnect us + const int32_t keepalive_66pct = pingOutstanding + ? keepAlive_sec * 1500 + : keepAlive_sec * 666; + if (((int32_t)(t - lastInActivity) > keepalive_66pct) || + ((int32_t)(t - lastOutActivity) > keepalive_66pct)) { + if (pingOutstanding) { + this->_state = MQTT_CONNECTION_TIMEOUT; + _client->stop(); + return false; + } else { + buffer[0] = MQTTPINGREQ; + buffer[1] = 0; + if (_client->write(buffer,2) != 0) { + lastOutActivity = t; + lastInActivity = t; + } + pingOutstanding = true; + } + } + return true; + } + return false; +} + +boolean PubSubClient::publish(const char* topic, const char* payload) { + size_t plength = (payload != nullptr) ? strlen(payload) : 0; + return publish(topic,(const uint8_t*)payload,plength,false); +} + +boolean PubSubClient::publish(const char* topic, const char* payload, boolean retained) { + size_t plength = (payload != nullptr) ? strlen(payload) : 0; + return publish(topic,(const uint8_t*)payload,plength,retained); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength) { + return publish(topic, payload, plength, false); +} + +boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) { + if (!beginPublish(topic, plength, retained)) { + return false; + } + for (unsigned int i=0;iwrite(buffer+(MQTT_MAX_HEADER_SIZE-hlen),length-(MQTT_MAX_HEADER_SIZE-hlen)); + if (rc > 0) { + lastOutActivity = millis(); + } + return (rc == (length-(MQTT_MAX_HEADER_SIZE-hlen))); + } + return false; +} + +int PubSubClient::endPublish() { + flushBuffer(); + return 1; +} + +size_t PubSubClient::write(uint8_t data) { + if (_client == nullptr) { + lastOutActivity = millis(); + return 0; + } + size_t rc = appendBuffer(data); + if (rc != 0) { + lastOutActivity = millis(); + } + return rc; +} + +size_t PubSubClient::write(const uint8_t *data, size_t size) { + if (_client == nullptr) { + lastOutActivity = millis(); + return 0; + } + size_t rc = appendBuffer(data,size); + if (rc != 0) { + lastOutActivity = millis(); + } + return rc; +} + +size_t PubSubClient::write(const String& message) { + return write(reinterpret_cast(message.c_str()), message.length()); +} + + +size_t PubSubClient::buildHeader(uint8_t header, uint8_t* buf, uint32_t length) { + uint8_t lenBuf[4]; + uint8_t llen = 0; + uint8_t digit; + uint8_t pos = 0; + uint32_t len = length; + do { + digit = len % 128; + len = len / 128; + if (len > 0) { + digit |= 0x80; + } + lenBuf[pos++] = digit; + llen++; + } while(len>0 && pos < 4); + + buf[4-llen] = header; + for (int i=0;i 0) && result) { + delay(0); // Prevent watchdog crashes + bytesToWrite = (bytesRemaining > MQTT_MAX_TRANSFER_SIZE)?MQTT_MAX_TRANSFER_SIZE:bytesRemaining; + rc = _client->write(writeBuf,bytesToWrite); + result = (rc == bytesToWrite); + bytesRemaining -= rc; + writeBuf += rc; + if (rc != 0) { + lastOutActivity = millis(); + } + } + return result; +#else + rc = _client->write(buf+(MQTT_MAX_HEADER_SIZE-hlen),length+hlen); + if (rc != 0) { + lastOutActivity = millis(); + } + return (rc == hlen+length); +#endif +} + +boolean PubSubClient::subscribe(const char* topic) { + return subscribe(topic, 0); +} + +boolean PubSubClient::subscribe(const char* topic, uint8_t qos) { + if (qos > 1) { + return false; + } + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + 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; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString((char*)topic, buffer,length); + buffer[length++] = qos; + return write(MQTTSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +boolean PubSubClient::unsubscribe(const char* topic) { + if (MQTT_MAX_PACKET_SIZE < 9 + strlen(topic)) { + // Too long + return false; + } + if (connected()) { + uint16_t length = MQTT_MAX_HEADER_SIZE; + nextMsgId++; + if (nextMsgId == 0) { + nextMsgId = 1; + } + buffer[length++] = (nextMsgId >> 8); + buffer[length++] = (nextMsgId & 0xFF); + length = writeString(topic, buffer,length); + return write(MQTTUNSUBSCRIBE|MQTTQOS1,buffer,length-MQTT_MAX_HEADER_SIZE); + } + return false; +} + +void PubSubClient::disconnect() { + if (_state == MQTT_DISCONNECTED || !initBuffer()) { + _state = MQTT_DISCONNECTED; + lastInActivity = lastOutActivity = millis(); + + return; + } + + buffer[0] = MQTTDISCONNECT; + buffer[1] = 0; + if (_client != nullptr) { + _client->write(buffer,2); + _client->flush(); + _client->stop(); + } + _state = MQTT_DISCONNECTED; + lastInActivity = lastOutActivity = millis(); +} + +uint16_t PubSubClient::writeString(const char* string, uint8_t* buf, uint16_t pos) { + const char* idp = string; + uint16_t i = 0; + pos += 2; + while (*idp && pos < (MQTT_MAX_PACKET_SIZE - 2)) { + buf[pos++] = *idp++; + i++; + } + buf[pos-i-2] = (i >> 8); + buf[pos-i-1] = (i & 0xFF); + return pos; +} + +size_t PubSubClient::appendBuffer(uint8_t data) { + if (!initBuffer()) { + return 0; + } + + buffer[_bufferWritePos] = data; + ++_bufferWritePos; + if (_bufferWritePos >= MQTT_MAX_PACKET_SIZE) { + if (flushBuffer() == 0) return 0; + } + return 1; +} + +size_t PubSubClient::appendBuffer(const uint8_t *data, size_t size) { + for (size_t i = 0; i < size; ++i) { + if (appendBuffer(data[i]) == 0) return i; + } + return size; +} + +size_t PubSubClient::flushBuffer() { + size_t rc = 0; + if (_bufferWritePos > 0) { + if (connected()) { + rc = _client->write(buffer, _bufferWritePos); + if (rc != 0) { + lastOutActivity = millis(); + } + } + _bufferWritePos = 0; + } + return rc; +} + +bool PubSubClient::initBuffer() +{ + constexpr size_t size = sizeof(uint8_t) * MQTT_MAX_PACKET_SIZE; + if (buffer == nullptr) { +#ifdef ESP32 + buffer = (uint8_t*) heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (buffer == nullptr) { + buffer = (uint8_t*) malloc(size); + } +#else + { +#ifdef USE_SECOND_HEAP + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +#endif + buffer = (uint8_t*) malloc(size); + if (buffer != nullptr) return true; + } +#ifdef USE_SECOND_HEAP + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif + buffer = (uint8_t*) malloc(size); +#endif + } + return buffer != nullptr; +} + +boolean PubSubClient::connected() { + if (_client == NULL ) { + this->_state = MQTT_DISCONNECTED; + return false; + } + if (_client->connected() == 0) { + bool lastStateConnected = this->_state == MQTT_CONNECTED; + this->disconnect(); + if (lastStateConnected) { + this->_state = MQTT_CONNECTION_LOST; + } + return false; + } + return this->_state == MQTT_CONNECTED; +} + +PubSubClient& PubSubClient::setServer(uint8_t * ip, uint16_t port) { + IPAddress addr(ip[0],ip[1],ip[2],ip[3]); + return setServer(addr,port); +} + +PubSubClient& PubSubClient::setServer(IPAddress ip, uint16_t port) { + this->ip = ip; + this->port = port; + this->domain = ""; + return *this; +} + +PubSubClient& PubSubClient::setServer(const char * domain, uint16_t port) { + this->domain = domain; + this->port = port; + return *this; +} + +PubSubClient& PubSubClient::setCallback(MQTT_CALLBACK_SIGNATURE) { + this->callback = callback; + return *this; +} + +PubSubClient& PubSubClient::setClient(Client& client){ + this->_client = &client; + return *this; +} + +PubSubClient& PubSubClient::setStream(Stream& stream){ + this->stream = &stream; + return *this; +} + +int PubSubClient::state() { + return this->_state; +} + +PubSubClient& PubSubClient::setKeepAlive(uint16_t keepAlive_sec) { + this->keepAlive_sec = keepAlive_sec; + return *this; +} + +PubSubClient& PubSubClient::setSocketTimeout(uint16_t timeout_ms) { + this->socketTimeout_msec = timeout_ms; + return *this; +} \ No newline at end of file diff --git a/lib/pubsubclient/src/PubSubClient.h b/lib/pubsubclient/src/PubSubClient.h index 199e243bf..4a59af578 100644 --- a/lib/pubsubclient/src/PubSubClient.h +++ b/lib/pubsubclient/src/PubSubClient.h @@ -100,10 +100,10 @@ class PubSubClient : public Print { private: Client* _client; uint8_t *buffer = nullptr; - uint16_t nextMsgId; - unsigned long lastOutActivity; - unsigned long lastInActivity; - bool pingOutstanding; + uint16_t nextMsgId = 0; + unsigned long lastOutActivity = 0; + unsigned long lastInActivity = 0; + bool pingOutstanding = false; MQTT_CALLBACK_SIGNATURE; // Try to read from the client whatever is available. bool loop_read(); @@ -127,10 +127,12 @@ private: IPAddress ip; String domain; - uint16_t port; + uint16_t port = 0; Stream* stream; - int _state; + int _state = MQTT_DISCONNECTED; int _bufferWritePos = 0; + int16_t keepAlive_sec = MQTT_KEEPALIVE; + int16_t socketTimeout_msec = MQTT_SOCKET_TIMEOUT*1000; public: PubSubClient(); PubSubClient(Client& client); @@ -194,6 +196,9 @@ public: boolean loop(); boolean connected(); int state(); + + PubSubClient& setKeepAlive(uint16_t keepAlive_sec); + PubSubClient& setSocketTimeout(uint16_t timeout_ms); }; diff --git a/lib/supertinycron/supertinycron.c b/lib/supertinycron/supertinycron.c deleted file mode 100644 index 30e54145f..000000000 --- a/lib/supertinycron/supertinycron.c +++ /dev/null @@ -1,237 +0,0 @@ - -#define _ISOC99_SOURCE - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ccronexpr.h" - -#ifndef VERSION - #define VERSION "dev-build" -#endif - -typedef struct { char *shell, *cmd, *schedule; int verbose; } TinyCronJob; - -void output(const char *msg) { - printf("[supertinycron] %s\n", msg); -} - -void sigchld_handler(int signo) { - (void) signo; - /*while (waitpid(-1, NULL, WNOHANG) > 0);*/ -} - -void sig_handler(int signo) { - if (signo == SIGTERM || signo == SIGINT) { - output("terminated"); - _exit(0); - } -} - -int cron_system(const char *shell, const char *command) { - int stdout_pipe[2], stderr_pipe[2]; - pid_t pid; - - if (pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0) { - perror("pipe"); - return -1; - } - - pid = fork(); - if (pid == 0) { - close(stdout_pipe[0]); - close(stderr_pipe[0]); - - if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) { - perror("dup2 stdout"); - return -1; - } - - if (dup2(stderr_pipe[1], STDERR_FILENO) == -1) { - perror("dup2 stderr"); - return -1; - } - - close(stdout_pipe[1]); - close(stderr_pipe[1]); - - execl(shell, shell, "-c", command, NULL); - perror("execl"); - exit(EXIT_FAILURE); - } else if (pid < 0) { - perror("fork"); - return -1; - } else { - char buffer[4096]; - int nbytes; - - close(stdout_pipe[1]); - close(stderr_pipe[1]); - - while ((nbytes = read(stdout_pipe[0], buffer, sizeof(buffer))) > 0) { - write(STDOUT_FILENO, buffer, nbytes); - } - - while ((nbytes = read(stderr_pipe[0], buffer, sizeof(buffer))) > 0) { - write(STDERR_FILENO, buffer, nbytes); - } - - close(stdout_pipe[0]); - close(stderr_pipe[0]); - - int status; - pid_t wpid = waitpid(pid, &status, 0); - if (wpid == -1) { - perror("waitpid"); - return -1; - } - if (WIFEXITED(status)) return WEXITSTATUS(status); - else if (WIFSIGNALED(status)) return -WTERMSIG(status); - return -1; - } -} - -TinyCronJob optsFromEnv() { - TinyCronJob opts = {0, 0, 0, 0}; - if (getenv("TINYCRON_VERBOSE") != NULL) opts.verbose = 1; - opts.shell = getenv("SHELL"); - if (!opts.shell) opts.shell = (char *)"/bin/sh"; - return opts; -} - -void usage() { - printf("Usage: supertinycron [expression] [command]\n"); - exit(EXIT_FAILURE); -} - -void message(const char *err, const char *msg) { - if (strlen(msg) == 0) output(err); - else { - char errMsg[512]; - snprintf(errMsg, sizeof(errMsg), "%s %s", msg, err); - output(errMsg); - } -} - -void messageInt(int err, const char *msg) { - if (err) message(strerror(err), msg); -} - -void exitOnErr(int err, const char *msg) { - if (err) { - messageInt(err, msg); - exit(EXIT_FAILURE); - } -} - -void run(TinyCronJob *job) { - if (job->verbose) message(job->cmd, "running job:"); - - messageInt(cron_system(job->shell, job->cmd), "job failed:"); -} - -int nap(TinyCronJob *job) { - time_t current_time = time(NULL), next_run; - - cron_expr expr; - const char* err = NULL; - cron_parse_expr(job->schedule, &expr, &err); - - if (err) { - message(err, "error parsing cron expression:"); - return 1; - } - - next_run = cron_next(&expr, current_time); - - if (job->verbose) { - char msg[512]; - struct tm *time_info = localtime(&next_run); - strftime(msg, sizeof(msg), "%Y-%m-%d %H:%M:%S", time_info); - message(msg, "next job scheduled for"); - } - - int sleep_duration = next_run - current_time; - sleep(sleep_duration); - return 0; -} - -char* find_nth(const char* str, char ch, int n) { - int count = 0; - while (*str) { - if (*str == ch && ++count == n) return (char*)str; - str++; - } - return NULL; -} - -void parse_line(char *line, TinyCronJob *job, int count) { - job->schedule = line; - job->cmd = find_nth(line, ' ', line[0] == '@' ? 1 : count); - - if (!job->cmd) { - messageInt(1, "incomplete cron expression"); - exit(EXIT_FAILURE); - } - *job->cmd = '\0'; - ++job->cmd; -} -/* -int main(int argc, char *argv[]) { - //signal(SIGCHLD, sigchld_handler); - signal(SIGTERM, sig_handler); - signal(SIGINT, sig_handler); - - if (argc < 2 || strcmp(argv[1], "help") == 0) usage(); - - if (strcmp(argv[1], "version") == 0) { - printf("supertinycron version %s\n", VERSION); - return EXIT_SUCCESS; - } - - TinyCronJob job = optsFromEnv(); - - int i, line_len = 0; - for (i = 1; i < argc; i++) { - line_len += strlen(argv[i]); - } - - line_len += argc - 3; - line_len += 1; - - char *line = (char *)malloc(line_len); - if (!line) { - perror("malloc"); - return EXIT_FAILURE; - } - - strcpy(line, argv[1]); - - for (i = 2; i < argc; i++) { - strcat(line, " "); - strcat(line, argv[i]); - } - - if (job.verbose) message(line, "line"); - - parse_line(line, &job, 7); - - while (1) { - if (nap(&job)) { - perror("error creating job"); - break; - } - run(&job); - } - - free(line); - - return EXIT_SUCCESS; -} -*/ \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 1024041df..276591276 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,106 +1,108 @@ -; -; PlatformIO Project Configuration File -; -; Please make sure to read documentation with examples first -; http://docs.platformio.org/en/stable/projectconf.html -; - -; *********************************************************************; -; You can uncomment or add here Your favorite environment you want to work on at the moment -; (uncomment only one !) -; *********************************************************************; - -[platformio] -description = Firmware for ESP82xx/ESP32/ESP32-S2/ESP32-S3/ESP32-C3 for easy IoT deployment of sensors. -boards_dir = boards -lib_dir = lib -extra_configs = - platformio_core_defs.ini - platformio_esp82xx_base.ini - 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_esp32c6_envs.ini - -;default_envs = normal_ESP32_4M -default_envs = max_ESP32_16M8M_LittleFS -; default_envs = custom_ESP8266_4M1M - -;default_envs = normal_ESP8266_4M1M -;default_envs = test_beta_ESP8266_4M1M -; ..etc -;build_cache_dir = $PROJECT_DIR\.buildcache - - -; add these: -; -Werror -Wall -Wextra -pedantic -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op -; -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel -; -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option -; thanks @chouffe103 -[compiler_warnings] -build_flags = -Wall -Wno-parentheses -fdiagnostics-show-option - - -[minimal_size] -build_flags = - -Os - -ffunction-sections - -fdata-sections - -Wl,--gc-sections - -s - - -[espota] -upload_protocol = espota -; each flag in a new line -; Do not use port 8266 for OTA, since that's used for ESPeasy p2p -upload_flags_esp8266 = - --port=18266 -upload_flags_esp32 = - --port=3232 -build_flags = -DFEATURE_ARDUINO_OTA=1 -upload_port = 192.168.1.152 - - -[debug_flags] -;build_flags = -Wstack-usage=300 -build_flags = - -[mqtt_flags] -build_flags = -DMQTT_MAX_PACKET_SIZE=1024 - -[extra_scripts_default] -extra_scripts = pre:tools/pio/set-ci-defines.py - pre:tools/pio/generate-compiletime-defines.py - tools/pio/copy_files.py - -[extra_scripts_esp8266] -extra_scripts = tools/pio/gzip-firmware.py - pre:tools/pio/concat_cpp_files.py - post:tools/pio/remove_concat_cpp_files.py - ${extra_scripts_default.extra_scripts} - - -[common] -lib_archive = false -lib_ldf_mode = chain -lib_compat_mode = strict -shared_libdeps_dir = lib -framework = arduino -upload_speed = 115200 -monitor_speed = 115200 -;targets = size, checkprogsize -targets = -src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands_tmp/> -<*/ControllerQueue_tmp/> -<*/DataStructs_tmp/> -<*/DataTypes_tmp/> -<*/ESPEasyCore_tmp/> -<*/Globals_tmp/> -<*/Helpers_tmp/> -<*/PluginStructs_tmp/> -<*/WebServer_tmp/> - -; Backwards compatibility: https://github.com/platformio/platformio-core/issues/4270 -;build_src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands/> -<*/ControllerQueue/> -<*/DataStructs/> -<*/DataTypes/> -<*/Globals/> -<*/Helpers/> -<*/PluginStructs/> -<*/WebServer/> - - -[env] -extends = common +; +; PlatformIO Project Configuration File +; +; Please make sure to read documentation with examples first +; http://docs.platformio.org/en/stable/projectconf.html +; + +; *********************************************************************; +; You can uncomment or add here Your favorite environment you want to work on at the moment +; (uncomment only one !) +; *********************************************************************; + +[platformio] +description = Firmware for ESP82xx/ESP32/ESP32-S2/ESP32-S3/ESP32-C3 for easy IoT deployment of sensors. +boards_dir = boards +lib_dir = lib +extra_configs = + platformio_core_defs.ini + platformio_esp82xx_base.ini + 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_esp32c6_envs.ini + +;default_envs = normal_ESP32_4M +default_envs = max_ESP32_16M8M_LittleFS_ETH +;default_envs = normal_ESP32c6_4M316k_LittleFS_CDC +; default_envs = custom_ESP8266_4M1M + +;default_envs = normal_ESP8266_4M1M +;default_envs = test_beta_ESP8266_4M1M +; ..etc +;build_cache_dir = $PROJECT_DIR\.buildcache + + +; add these: +; -Werror -Wall -Wextra -pedantic -Wcast-align -Wcast-qual -Wctor-dtor-privacy -Wdisabled-optimization -Wformat=2 -Winit-self -Wlogical-op +; -Wmissing-include-dirs -Wnoexcept -Wold-style-cast -Woverloaded-virtual -Wredundant-decls -Wshadow -Wsign-promo -Wstrict-null-sentinel +; -Wstrict-overflow=5 -Wundef -Wno-unused -Wno-variadic-macros -Wno-parentheses -fdiagnostics-show-option +; thanks @chouffe103 +[compiler_warnings] +build_flags = -Wall -Wno-parentheses -fdiagnostics-show-option + + +[minimal_size] +build_flags = + -Os + -ffunction-sections + -fdata-sections + -Wl,--gc-sections + -s + + +[espota] +upload_protocol = espota +; each flag in a new line +; Do not use port 8266 for OTA, since that's used for ESPeasy p2p +upload_flags_esp8266 = + --port=18266 +upload_flags_esp32 = + --port=3232 +build_flags = -DFEATURE_ARDUINO_OTA=1 +upload_port = 192.168.1.152 + + +[debug_flags] +;build_flags = -Wstack-usage=300 +build_flags = + +[mqtt_flags] +build_flags = -DMQTT_MAX_PACKET_SIZE=1024 + +[extra_scripts_default] +extra_scripts = pre:tools/pio/install-requirements.py + pre:tools/pio/set-ci-defines.py + pre:tools/pio/generate-compiletime-defines.py + tools/pio/copy_files.py + +[extra_scripts_esp8266] +extra_scripts = tools/pio/gzip-firmware.py + pre:tools/pio/remove_concat_cpp_files.py + pre:tools/pio/concat_cpp_files.py + ${extra_scripts_default.extra_scripts} + + +[common] +lib_archive = false +lib_ldf_mode = chain +lib_compat_mode = strict +shared_libdeps_dir = lib +framework = arduino +upload_speed = 115200 +monitor_speed = 115200 +;targets = size, checkprogsize +targets = +src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands_tmp/> -<*/ControllerQueue_tmp/> -<*/DataStructs_tmp/> -<*/DataTypes_tmp/> -<*/ESPEasyCore_tmp/> -<*/Globals_tmp/> -<*/Helpers_tmp/> -<*/PluginStructs_tmp/> -<*/WebServer_tmp/> + +; Backwards compatibility: https://github.com/platformio/platformio-core/issues/4270 +;build_src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands/> -<*/ControllerQueue/> -<*/DataStructs/> -<*/DataTypes/> -<*/Globals/> -<*/Helpers/> -<*/PluginStructs/> -<*/WebServer/> + + +[env] +extends = common diff --git a/platformio_core_defs.ini b/platformio_core_defs.ini index bc8809450..f0d789957 100644 --- a/platformio_core_defs.ini +++ b/platformio_core_defs.ini @@ -1,234 +1,235 @@ -; ********************************************************************* - -; **** Definition cheat sheet: -; board_build.flash_mode in terms of performance: QIO > QOUT > DIO > DOUT -; for lib_ldf_mode, see http://docs.platformio.org/en/latest/librarymanager/ldf.html;ldf - -; **** Frequently used build flags: -; Use custom.h file to override default settings for ESPeasy: -D USE_CUSTOM_H -; Set VCC mode to measure Vcc of ESP chip : -D FEATURE_ADC_VCC=1 - -; Build Flags: -; -DUSE_CONFIG_OVERRIDE -; lwIP 1.4 (Default) -; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH -; lwIP 2 - Low Memory -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -; lwIP 2 - Higher Bandwitdh -; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -; VTABLES in Flash (default) -; -DVTABLES_IN_FLASH -; VTABLES in Heap -; -DVTABLES_IN_DRAM -; VTABLES in IRAM -; -DVTABLES_IN_IRAM -; NO_EXTRA_4K_HEAP - this forces the default NONOS-SDK user's heap location -; Default currently overlaps cont stack (Arduino) with sys stack (System) -; to save up-to 4 kB of heap. (starting core_2.4.2) -; ESP8266_DISABLE_EXTRA4K - Calls disable_extra4k_at_link_time() from setup -; to force the linker keep user's stack in user ram. -; CONT_STACKSIZE to set the 'cont' (Arduino) stack size. Default = 4096 -; -mtarget-align see: https://github.com/arendst/Sonoff-Tasmota/issues/3678#issuecomment-419712437 - -[esp82xx_defaults] -build_flags = -D NDEBUG - -lstdc++ -lsupc++ - -mtarget-align - -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY - -DVTABLES_IN_FLASH - -DPUYA_SUPPORT=1 - -DDISABLE_SC16IS752_SPI - -DCRON_USE_LOCAL_TIME - -fno-strict-aliasing - -I$PROJECT_DIR/src/include - -include "ESPEasy_config.h" - -lib_ignore = ESP32_ping - ESP32WebServer - ESP32HTTPUpdateServer - ServoESP32 - IRremoteESP8266 - HeatpumpIR - TinyWireM - ESP8266SdFat - SD(esp8266) - SD - SDFS - LittleFS(esp8266) - LittleFS - ArduinoOTA - ESP8266mDNS - I2C AXP192 Power management -; EspSoftwareSerial - - - -; Keep optimization flag to -O2 -; See: https://github.com/platformio/platform-espressif8266/issues/288 -; For "-fno-strict-aliasing" -; See: https://github.com/esp8266/Arduino/issues/8261 -[esp82xx_2_7_x] -build_flags = -DNDEBUG - -mtarget-align - -DVTABLES_IN_FLASH - -fno-exceptions - -lstdc++ - -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH - -DPUYA_SUPPORT=1 - -DCORE_POST_2_5_0 - -DDISABLE_SC16IS752_SPI - -DCRON_USE_LOCAL_TIME - -fno-strict-aliasing - -DLIBRARIES_NO_LOG=1 - -DNO_GLOBAL_I2S - -I$PROJECT_DIR/src/include - -include "ESPEasy_config.h" - -O2 - -s - -DBEARSSL_SSL_BASIC - -DCORE_POST_2_6_0 - ; remove the 4-bytes alignment for PSTR() - -DPSTR_ALIGN=1 - -Werror=return-type -build_unflags = ${esp82xx_common.build_unflags} -lib_ignore = ${esp82xx_defaults.lib_ignore} - EspSoftwareSerial - - -[esp82xx_3_0_x] -build_flags = ${esp82xx_2_7_x.build_flags} - -DCORE_POST_3_0_0 - -Wno-deprecated-declarations -; -flto=auto -; -Wl,-flto -build_unflags = -DDEBUG_ESP_PORT - -fexceptions - -Wall -; -fno-lto -lib_ignore = ${esp82xx_defaults.lib_ignore} -extra_scripts = pre:tools/pio/pre_custom_esp8266_toolchain.py - - - -; See for SDK flags: https://github.com/esp8266/Arduino/blob/master/tools/platformio-build.py - -[core_2_7_4] -extends = esp82xx_2_7_x -platform = espressif8266@2.6.3 -platform_packages = - framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.7.4 -build_flags = ${esp82xx_2_7_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=0 - -Wno-deprecated-declarations - -DLIBRARIES_NO_LOG=1 -lib_ignore = ${esp82xx_2_7_x.lib_ignore} -build_unflags = ${esp82xx_2_7_x.build_unflags} -extra_scripts = - - -[core_stage] -extends = esp82xx_3_0_x -platform = espressif8266@4.2.1 -platform_packages = -build_flags = ${esp82xx_3_0_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 - -DLIBRARIES_NO_LOG=1 - -DPHASE_LOCKED_WAVEFORM -build_unflags = ${esp82xx_3_0_x.build_unflags} -lib_ignore = ${esp82xx_defaults.lib_ignore} -;extra_scripts = ${esp82xx_3_0_x.extra_scripts} -extra_scripts = - - - -; See: https://arduino-esp8266.readthedocs.io/en/latest/mmu.html -[core_stage_2ndheap] -extends = esp82xx_3_0_x -platform = espressif8266@4.2.1 -platform_packages = -build_flags = ${esp82xx_3_0_x.build_flags} - -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 - -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 - -DLIBRARIES_NO_LOG=1 - -DPHASE_LOCKED_WAVEFORM - -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED - -DUSE_SECOND_HEAP -build_unflags = ${esp82xx_3_0_x.build_unflags} -lib_ignore = ${core_stage.lib_ignore} -extra_scripts = ${esp82xx_3_0_x.extra_scripts} - - - -; Updated ESP-IDF to the latest stable 4.0.1 -; See: https://github.com/platformio/platform-espressif32/releases -; IDF 4.4 = platform-espressif32 3.4.x = espressif/arduino-esp32 tag 2.0.4 -; Just for those who lost track of the extremely confusing numbering schema. -; For MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS See: https://github.com/espressif/arduino-esp32/pull/6676 -[core_esp32_IDF4_4__2_0_14] -;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip - -; debug boot log enabled -; See: https://github.com/letscontrolit/ESPEasy/pull/4200#issuecomment-1216929859 -;platform = https://github.com/Jason2866/platform-espressif32.git -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/936/framework-arduinoespressif32-443_esp421-9ce849ce72.tar.gz - -; debug boot log disabled -;platform = https://github.com/Jason2866/platform-espressif32.git -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/938/framework-arduinoespressif32-443_esp421-10ab11e815.tar.gz - -;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.2/platform-espressif32-2.0.5.2.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2022.12.2/platform-espressif32.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.01/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1212/framework-arduinoespressif32-release_v4.4-fb9a7685e1.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.02/platform-espressif32.zip -;platform_packages = -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.02.00/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1243/framework-arduinoespressif32-lwip_timeout-ed6742e7f0.zip - -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.05.03/platform-espressif32.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.04/platform-espressif32.zip -platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32.zip -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1645/framework-arduinoespressif32-release_v4.4_spiffs-e3fc63b439.zip -build_flags = -DESP32_STAGE - -DESP_IDF_VERSION_MAJOR=4 - -DMUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - -DLIBRARIES_NO_LOG=1 - -DDISABLE_SC16IS752_SPI - -DCONFIG_PM_ENABLE - -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 - -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 - -DNEOPIXEL_ESP32_RMT_DEFAULT - -DCRON_USE_LOCAL_TIME - -I$PROJECT_DIR/src/include - -include "sdkconfig.h" - -include "ESPEasy_config.h" - -include "esp32x_fixes.h" -lib_ignore = - -; ESP_IDF 5.1 -[core_esp32_IDF5_1__3_0_0] -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.12/platform-espressif32.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1787/framework-arduinoespressif32-release_v5.1-f61c914469.zip -;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.11.11/platform-espressif32.zip -;platform_packages = -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1818/framework-arduinoespressif32-release_v5.1-e5ff26581f.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1846/framework-arduinoespressif32-release_v5.1-29db12e.zip -;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1847/framework-arduinoespressif32-release_v5.1-29db12e.zip -platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.12.10/platform-espressif32.zip -platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1877/framework-arduinoespressif32-release_v5.1-88d1438.zip -build_flags = -DESP32_STAGE - -DESP_IDF_VERSION_MAJOR=5 - -DLIBRARIES_NO_LOG=1 - -DDISABLE_SC16IS752_SPI - -DCONFIG_PM_ENABLE - -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 - -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 - -DNEOPIXEL_ESP32_RMT_DEFAULT - -DCRON_USE_LOCAL_TIME - -I$PROJECT_DIR/src/include - -include "sdkconfig.h" - -include "ESPEasy_config.h" - -include "esp32x_fixes.h" -lib_ignore = +; ********************************************************************* + +; **** Definition cheat sheet: +; board_build.flash_mode in terms of performance: QIO > QOUT > DIO > DOUT +; for lib_ldf_mode, see http://docs.platformio.org/en/latest/librarymanager/ldf.html;ldf + +; **** Frequently used build flags: +; Use custom.h file to override default settings for ESPeasy: -D USE_CUSTOM_H +; Set VCC mode to measure Vcc of ESP chip : -D FEATURE_ADC_VCC=1 + +; Build Flags: +; -DUSE_CONFIG_OVERRIDE +; lwIP 1.4 (Default) +; -DPIO_FRAMEWORK_ARDUINO_LWIP_HIGHER_BANDWIDTH +; lwIP 2 - Low Memory +; -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY +; lwIP 2 - Higher Bandwitdh +; -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH +; VTABLES in Flash (default) +; -DVTABLES_IN_FLASH +; VTABLES in Heap +; -DVTABLES_IN_DRAM +; VTABLES in IRAM +; -DVTABLES_IN_IRAM +; NO_EXTRA_4K_HEAP - this forces the default NONOS-SDK user's heap location +; Default currently overlaps cont stack (Arduino) with sys stack (System) +; to save up-to 4 kB of heap. (starting core_2.4.2) +; ESP8266_DISABLE_EXTRA4K - Calls disable_extra4k_at_link_time() from setup +; to force the linker keep user's stack in user ram. +; CONT_STACKSIZE to set the 'cont' (Arduino) stack size. Default = 4096 +; -mtarget-align see: https://github.com/arendst/Sonoff-Tasmota/issues/3678#issuecomment-419712437 + +[esp82xx_defaults] +build_flags = -D NDEBUG + -lstdc++ -lsupc++ + -mtarget-align + -DPIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY + -DVTABLES_IN_FLASH + -DPUYA_SUPPORT=1 + -DDISABLE_SC16IS752_SPI + -DCRON_USE_LOCAL_TIME + -fno-strict-aliasing + -I$PROJECT_DIR/src/include + -include "ESPEasy_config.h" + +lib_ignore = ESP32_ping + ESP32WebServer + ESP32HTTPUpdateServer + ServoESP32 + IRremoteESP8266 + HeatpumpIR + TinyWireM + ESP8266SdFat + SD(esp8266) + SD + SDFS + LittleFS(esp8266) + LittleFS + ArduinoOTA + ESP8266mDNS + I2C AXP192 Power management +; EspSoftwareSerial + + + +; Keep optimization flag to -O2 +; See: https://github.com/platformio/platform-espressif8266/issues/288 +; For "-fno-strict-aliasing" +; See: https://github.com/esp8266/Arduino/issues/8261 +[esp82xx_2_7_x] +build_flags = -DNDEBUG + -mtarget-align + -DVTABLES_IN_FLASH + -fno-exceptions + -lstdc++ + -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH + -DPUYA_SUPPORT=1 + -DCORE_POST_2_5_0 + -DDISABLE_SC16IS752_SPI + -DCRON_USE_LOCAL_TIME + -fno-strict-aliasing + -DLIBRARIES_NO_LOG=1 + -DNO_GLOBAL_I2S + -I$PROJECT_DIR/src/include + -include "ESPEasy_config.h" + -O2 + -s + -DBEARSSL_SSL_BASIC + -DCORE_POST_2_6_0 + ; remove the 4-bytes alignment for PSTR() + -DPSTR_ALIGN=1 + -Werror=return-type +build_unflags = ${esp82xx_common.build_unflags} +lib_ignore = ${esp82xx_defaults.lib_ignore} + EspSoftwareSerial + + +[esp82xx_3_0_x] +build_flags = ${esp82xx_2_7_x.build_flags} + -DCORE_POST_3_0_0 + -Wno-deprecated-declarations +; -flto=auto +; -Wl,-flto +build_unflags = -DDEBUG_ESP_PORT + -fexceptions + -Wall +; -fno-lto +lib_ignore = ${esp82xx_defaults.lib_ignore} +extra_scripts = pre:tools/pio/pre_custom_esp8266_toolchain.py + + + +; See for SDK flags: https://github.com/esp8266/Arduino/blob/master/tools/platformio-build.py + +[core_2_7_4] +extends = esp82xx_2_7_x +platform = espressif8266@2.6.3 +platform_packages = + framework-arduinoespressif8266 @ https://github.com/esp8266/Arduino.git#2.7.4 +build_flags = ${esp82xx_2_7_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK22x_190703 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=0 + -Wno-deprecated-declarations + -DLIBRARIES_NO_LOG=1 +lib_ignore = ${esp82xx_2_7_x.lib_ignore} +build_unflags = ${esp82xx_2_7_x.build_unflags} +extra_scripts = ${esp82xx_common.extra_scripts} + + +[core_stage] +extends = esp82xx_3_0_x +platform = espressif8266@4.2.1 +platform_packages = +build_flags = ${esp82xx_3_0_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 + -DLIBRARIES_NO_LOG=1 + -DPHASE_LOCKED_WAVEFORM +build_unflags = ${esp82xx_3_0_x.build_unflags} +lib_ignore = ${esp82xx_defaults.lib_ignore} +extra_scripts = ${esp82xx_common.extra_scripts} + + + +; See: https://arduino-esp8266.readthedocs.io/en/latest/mmu.html +[core_stage_2ndheap] +extends = esp82xx_3_0_x +platform = espressif8266@4.2.1 +platform_packages = +build_flags = ${esp82xx_3_0_x.build_flags} + -DPIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK3 + -DUSES_LATEST_SOFTWARE_SERIAL_LIBRARY=1 + -DLIBRARIES_NO_LOG=1 + -DPHASE_LOCKED_WAVEFORM + -DPIO_FRAMEWORK_ARDUINO_MMU_CACHE16_IRAM48_SECHEAP_SHARED + -DUSE_SECOND_HEAP +build_unflags = ${esp82xx_3_0_x.build_unflags} +lib_ignore = ${core_stage.lib_ignore} +extra_scripts = ${esp82xx_common.extra_scripts} + + + +; Updated ESP-IDF to the latest stable 4.0.1 +; See: https://github.com/platformio/platform-espressif32/releases +; IDF 4.4 = platform-espressif32 3.4.x = espressif/arduino-esp32 tag 2.0.4 +; Just for those who lost track of the extremely confusing numbering schema. +; For MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS See: https://github.com/espressif/arduino-esp32/pull/6676 +[core_esp32_IDF4_4__2_0_14] +;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.4.1/platform-espressif32-2.0.4.1.zip + +; debug boot log enabled +; See: https://github.com/letscontrolit/ESPEasy/pull/4200#issuecomment-1216929859 +;platform = https://github.com/Jason2866/platform-espressif32.git +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/936/framework-arduinoespressif32-443_esp421-9ce849ce72.tar.gz + +; debug boot log disabled +;platform = https://github.com/Jason2866/platform-espressif32.git +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/938/framework-arduinoespressif32-443_esp421-10ab11e815.tar.gz + +;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.5.2/platform-espressif32-2.0.5.2.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2022.12.2/platform-espressif32.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.01/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1212/framework-arduinoespressif32-release_v4.4-fb9a7685e1.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.01.02/platform-espressif32.zip +;platform_packages = +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.02.00/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1243/framework-arduinoespressif32-lwip_timeout-ed6742e7f0.zip + +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.05.03/platform-espressif32.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.06.04/platform-espressif32.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/2023.10.03/platform-espressif32.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1645/framework-arduinoespressif32-release_v4.4_spiffs-e3fc63b439.zip +build_flags = -DESP32_STAGE + -DESP_IDF_VERSION_MAJOR=4 + -DMUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + -DLIBRARIES_NO_LOG=1 + -DDISABLE_SC16IS752_SPI + -DCONFIG_PM_ENABLE + -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 + -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 + -DNEOPIXEL_ESP32_RMT_DEFAULT + -DCRON_USE_LOCAL_TIME + -I$PROJECT_DIR/src/include + -include "sdkconfig.h" + -include "ESPEasy_config.h" + -include "esp32x_fixes.h" + -Wnull-dereference +lib_ignore = + +; ESP_IDF 5.1 +[core_esp32_IDF5_1__3_0_0] +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.11/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2286/framework-arduinoespressif32-all-release_v5.1-11140aa.zip +;platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.04.14/platform-espressif32.zip +;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2386/framework-arduinoespressif32-all-release_v5.1-324fdc1.zip +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.07.10/platform-espressif32.zip +platform_packages = +build_flags = -DESP32_STAGE + -DESP_IDF_VERSION_MAJOR=5 + -DLIBRARIES_NO_LOG=1 + -DDISABLE_SC16IS752_SPI + -DCONFIG_PM_ENABLE +; -DCONFIG_LWIP_L2_TO_L3_COPY +; -DETH_SPI_SUPPORTS_NO_IRQ=1 + -DCONFIG_FREERTOS_USE_TICKLESS_IDLE=1 + -DCONFIG_FREERTOS_IDLE_TIME_BEFORE_SLEEP=3 + -DNEOPIXEL_ESP32_RMT_DEFAULT + -DCRON_USE_LOCAL_TIME + -I$PROJECT_DIR/src/include + -include "sdkconfig.h" + -include "ESPEasy_config.h" + -include "esp32x_fixes.h" + -Wnull-dereference +lib_ignore = + diff --git a/platformio_esp32_envs.ini b/platformio_esp32_envs.ini index 5b13b43d1..105a56172 100644 --- a/platformio_esp32_envs.ini +++ b/platformio_esp32_envs.ini @@ -1,558 +1,572 @@ -;;; ESP32 test build ********************************************************************; -; Status of the ESP32 support is still considered "beta" ; -; Most plugins work just fine on ESP32. ; -; Especially some plugins using serial may not run very well (GPS does run fine). ; -; ***************************************************************************************; - - -[esp32_base] -extends = common, core_esp32_IDF4_4__2_0_14 -upload_speed = 460800 -upload_before_reset = default_reset -upload_after_reset = hard_reset -extra_scripts = post:tools/pio/post_esp32.py - ${extra_scripts_default.extra_scripts} -; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable -; tools/pio/extra_linker_flags.py -; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin -; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ -; pre:tools/pio/apply_patches.py -build_unflags = -Wall -; -fno-lto -build_flags = ${core_esp32_IDF4_4__2_0_14.build_flags} -; ${mqtt_flags.build_flags} - -DMQTT_MAX_PACKET_SIZE=2048 - -DCONFIG_FREERTOS_ASSERT_DISABLE - -DCONFIG_LWIP_ESP_GRATUITOUS_ARP - -fno-strict-aliasing -; -flto - -Wswitch - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE -monitor_filters = esp32_exception_decoder -lib_ignore = - ${core_esp32_IDF4_4__2_0_14.lib_ignore} - - -[esp32_base_idf5] -extends = common, core_esp32_IDF5_1__3_0_0 -upload_speed = 460800 -upload_before_reset = default_reset -upload_after_reset = hard_reset -extra_scripts = post:tools/pio/post_esp32.py - ${extra_scripts_default.extra_scripts} -; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable -; tools/pio/extra_linker_flags.py -; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin -; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ -; pre:tools/pio/apply_patches.py - -; When using LTO, make sure NOT to use -mtext-section-literals -; -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_1__3_0_0.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 - -Wswitch - -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE - -DLWIP_IPV6 -monitor_filters = esp32_exception_decoder -lib_ignore = - ${core_esp32_IDF5_1__3_0_0.lib_ignore} - - -; -flto cannot be used for ESP32 C3! -; See: https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1014857366 -; TD-er: 2022-01-20: Disabled for now as it also resulted in obscure linker errors on ESP32-S2 and ESP32 running custom builds. -;build_flags = ${esp32_base.build_flags} -; -flto -;build_unflags = ${esp32_base.build_unflags} -; -fexceptions -; -fno-lto - - -[esp32_always] -lib_ignore = ESP8266Ping - ESP8266HTTPUpdateServer - ESP8266WiFi - ESP8266WebServer - ESP8266mDNS - ESPEasy_ESP8266Ping - RABurton ESP8266 Mutex - TinyWireM - LittleFS_esp32 - ${esp32_base.lib_ignore} - - -[esp32_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DESP32_CLASSIC -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} -; -mtext-section-literals - -DESP32_CLASSIC - -DUSE_LITTLEFS -build_unflags = ${esp32_base_idf5.build_unflags} -extra_scripts = ${esp32_common.extra_scripts} -board_build.filesystem = littlefs -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ESP32 BLE Arduino - ${core_esp32_IDF5_1__3_0_0.lib_ignore} - - -[esp32_IRExt] -extends = esp32_common -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_NORMAL_IRext - -DCOLLECTION_USE_RTTTL -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/ir_build_check.py - - -[esp32_custom_base] -extends = esp32_common -build_flags = ${esp32_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[esp32_custom_base_LittleFS] -extends = esp32_common_LittleFS -build_flags = ${esp32_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_ESP32_4M316k] -extends = esp32_custom_base -board = esp32_4M - -[env:custom_ESP32_4M316k_LittleFS] -extends = esp32_custom_base_LittleFS -board = esp32_4M - -[env:custom_ESP32_16M8M_LittleFS] -extends = esp32_custom_base_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB - -[env:custom_IR_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32_IR.py - pre:tools/pio/ir_build_check.py - -[env:custom_ESP32_4M2M_NO_OTA_LittleFS] -extends = esp32_custom_base_LittleFS -board = esp32_4M2M -build_flags = ${esp32_custom_base_LittleFS.build_flags} - -DNO_HTTP_UPDATER - - -[env:normal_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -lib_ignore = ${esp32_common.lib_ignore} - ${no_ir.lib_ignore} - -[env:normal_ESP32_4M316k_LittleFS] -extends = esp32_common_LittleFS -board = esp32_4M -lib_ignore = ${esp32_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - -[env:collection_A_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:collection_A_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -[env:collection_B_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -[env:collection_C_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -[env:collection_D_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -[env:collection_E_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -[env:collection_F_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -[env:collection_G_ESP32_IRExt_4M316k] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -[env:energy_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_ENERGY_COLLECTION - -[env:display_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32_4M316k] -extends = esp32_common -board = esp32_4M -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -D PLUGIN_NEOPIXEL_COLLECTION - - -[env:custom_ESP32_4M316k_ETH] -extends = env:custom_ESP32_4M316k -build_flags = ${env:custom_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:custom_IR_ESP32_4M316k_ETH] -extends = env:custom_IR_ESP32_4M316k -build_flags = ${env:custom_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 -extra_scripts = ${env:custom_ESP32_4M316k.extra_scripts} - -[env:custom_IR_ESP32_16M8M_LittleFS_ETH] -extends = esp32_common_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB -build_flags = ${esp32_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR - -DFEATURE_ETHERNET=1 -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} -extra_scripts = ${esp32_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - pre:tools/pio/ir_build_check.py - -[env:normal_ESP32_4M316k_ETH] -extends = env:normal_ESP32_4M316k -build_flags = ${env:normal_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:normal_ESP32_4M316k_LittleFS_ETH] -extends = esp32_common_LittleFS -board = esp32_4M -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} -build_flags = ${esp32_common_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - - -[env:normal_ESP32_IRExt_4M316k_ETH] -extends = esp32_IRExt -board = esp32_4M -build_flags = ${esp32_IRExt.build_flags} - -DFEATURE_ETHERNET=1 - -[env:collection_A_ESP32_4M316k_ETH] -extends = env:collection_A_ESP32_4M316k -build_flags = ${env:collection_A_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32_4M316k_ETH] -extends = env:collection_B_ESP32_4M316k -build_flags = ${env:collection_B_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32_4M316k_ETH] -extends = env:collection_C_ESP32_4M316k -build_flags = ${env:collection_C_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32_4M316k_ETH] -extends = env:collection_D_ESP32_4M316k -build_flags = ${env:collection_D_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32_4M316k_ETH] -extends = env:collection_E_ESP32_4M316k -build_flags = ${env:collection_E_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32_4M316k_ETH] -extends = env:collection_F_ESP32_4M316k -build_flags = ${env:collection_F_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32_4M316k_ETH] -extends = env:collection_G_ESP32_4M316k -build_flags = ${env:collection_G_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -DCOLLECTION_USE_RTTTL - -[env:energy_ESP32_4M316k_ETH] -extends = env:energy_ESP32_4M316k -build_flags = ${env:energy_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:display_ESP32_4M316k_ETH] -extends = env:display_ESP32_4M316k -build_flags = ${env:display_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:climate_ESP32_4M316k_ETH] -extends = env:climate_ESP32_4M316k -build_flags = ${env:climate_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -[env:neopixel_ESP32_4M316k_ETH] -extends = env:neopixel_ESP32_4M316k -build_flags = ${env:neopixel_ESP32_4M316k.build_flags} - -DFEATURE_ETHERNET=1 - -; [env:collection_A_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_B_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_B_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_C_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_C_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_D_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_D_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_E_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_E_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_F_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_F_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:collection_G_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_SET_COLLECTION_G_ESP32 -; -DFEATURE_ETHERNET=1 - -; [env:energy_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_ENERGY_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:display_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_DISPLAY_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:climate_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -DPLUGIN_CLIMATE_COLLECTION -; -DFEATURE_ETHERNET=1 - -; [env:neopixel_ESP32_IRExt_4M316k_ETH] -; extends = esp32_IRExt -; board = esp32_4M -; build_flags = ${esp32_IRExt.build_flags} -; -D PLUGIN_NEOPIXEL_COLLECTION -; -DFEATURE_ETHERNET=1 - - -; ESP32 MAX builds 16M flash ------------------------------ - -; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using the default (SPIFFS) filesystem -[env:max_ESP32_16M1M] -extends = esp32_common -board = esp32_16M1M -board_upload.flash_size = 16MB -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -build_flags = ${esp32_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED - -[env:max_ESP32_16M1M_ETH] -extends = env:max_ESP32_16M1M -build_flags = ${env:max_ESP32_16M1M.build_flags} - -DFEATURE_ETHERNET=1 - - -; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem -[env:max_ESP32_16M8M_LittleFS] -extends = esp32_common_LittleFS -board = esp32_16M8M -board_upload.flash_size = 16MB -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${esp32_common_LittleFS.lib_ignore} -build_flags = ${esp32_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32_common.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] -extends = env:max_ESP32_16M8M_LittleFS -board = ${env:max_ESP32_16M8M_LittleFS.board} -build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} - -DFEATURE_ETHERNET=1 - - - - - - - - - - +;;; ESP32 test build ********************************************************************; +; Status of the ESP32 support is still considered "beta" ; +; Most plugins work just fine on ESP32. ; +; Especially some plugins using serial may not run very well (GPS does run fine). ; +; ***************************************************************************************; + + +[esp32_base] +extends = common, core_esp32_IDF4_4__2_0_14 +upload_speed = 460800 +upload_before_reset = default_reset +upload_after_reset = hard_reset +extra_scripts = post:tools/pio/post_esp32.py + ${extra_scripts_default.extra_scripts} +; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable +; tools/pio/extra_linker_flags.py +; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin +; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ +; pre:tools/pio/apply_patches.py +build_unflags = -Wall +; -fno-lto +build_flags = ${core_esp32_IDF4_4__2_0_14.build_flags} +; ${mqtt_flags.build_flags} + -DMQTT_MAX_PACKET_SIZE=2048 + -DCONFIG_FREERTOS_ASSERT_DISABLE + -DCONFIG_LWIP_ESP_GRATUITOUS_ARP + -fno-strict-aliasing +; -flto + -Wswitch + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_NONE +monitor_filters = esp32_exception_decoder +lib_ignore = + ${core_esp32_IDF4_4__2_0_14.lib_ignore} + + +[esp32_base_idf5] +extends = common, core_esp32_IDF5_1__3_0_0 +upload_speed = 460800 +upload_before_reset = default_reset +upload_after_reset = hard_reset +extra_scripts = post:tools/pio/post_esp32.py + ${extra_scripts_default.extra_scripts} +; you can disable debug linker flag to reduce binary size (comment out line below), but the backtraces will become less readable +; tools/pio/extra_linker_flags.py +; fix the platform package to use gcc-ar and gcc-ranlib to enable lto linker plugin +; more detail: https://embeddedartistry.com/blog/2020/04/13/prefer-gcc-ar-to-ar-in-your-buildsystems/ +; pre:tools/pio/apply_patches.py + +; When using LTO, make sure NOT to use -mtext-section-literals +; -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_1__3_0_0.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 + -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 +monitor_filters = esp32_exception_decoder +lib_ignore = + ${core_esp32_IDF5_1__3_0_0.lib_ignore} + + +; -flto cannot be used for ESP32 C3! +; See: https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1014857366 +; TD-er: 2022-01-20: Disabled for now as it also resulted in obscure linker errors on ESP32-S2 and ESP32 running custom builds. +;build_flags = ${esp32_base.build_flags} +; -flto +;build_unflags = ${esp32_base.build_unflags} +; -fexceptions +; -fno-lto + + +[esp32_always] +lib_ignore = ESP8266Ping + ESP8266HTTPUpdateServer + ESP8266WiFi + ESP8266WebServer + ESP8266mDNS + ESPEasy_ESP8266Ping + RABurton ESP8266 Mutex + TinyWireM + LittleFS_esp32 + Adafruit NeoPixel + ${esp32_base.lib_ignore} + + +[esp32_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DESP32_CLASSIC +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} +; -mtext-section-literals + -DESP32_CLASSIC + -DUSE_LITTLEFS +build_unflags = ${esp32_base_idf5.build_unflags} +extra_scripts = ${esp32_common.extra_scripts} +board_build.filesystem = littlefs +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ESP32 BLE Arduino + ${core_esp32_IDF5_1__3_0_0.lib_ignore} + + +[esp32_IRExt] +extends = esp32_common +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_NORMAL_IRext + -DCOLLECTION_USE_RTTTL +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/ir_build_check.py + + +[esp32_custom_base] +extends = esp32_common +build_flags = ${esp32_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[esp32_custom_base_LittleFS] +extends = esp32_common_LittleFS +build_flags = ${esp32_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_ESP32_4M316k] +extends = esp32_custom_base +board = esp32_4M + +; [env:custom_ESP32_4M316k_LittleFS] +; extends = esp32_custom_base_LittleFS +; board = esp32_4M + +[env:custom_ESP32_16M8M_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + +[env:custom_IR_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32_common.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 = esp32_custom_base_LittleFS +board = esp32_4M2M +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DNO_HTTP_UPDATER + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +lib_ignore = ${esp32_common.lib_ignore} + ${no_ir.lib_ignore} + +; [env:normal_ESP32_4M316k_LittleFS] +; extends = esp32_common_LittleFS +; board = esp32_4M +; lib_ignore = ${esp32_common_LittleFS.lib_ignore} +; ${no_ir.lib_ignore} + +[env:collection_A_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:collection_A_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + +[env:collection_B_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + +[env:collection_C_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + +[env:collection_D_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + +[env:collection_E_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + +[env:collection_F_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + +[env:collection_G_ESP32_IRExt_4M316k] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + +[env:energy_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_ENERGY_COLLECTION + +[env:display_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32_4M316k] +extends = esp32_common +board = esp32_4M +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -D PLUGIN_NEOPIXEL_COLLECTION + + +[env:custom_ESP32_4M316k_ETH] +extends = env:custom_ESP32_4M316k +build_flags = ${env:custom_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:custom_ESP32_4M316k_LittleFS_ETH] +extends = esp32_custom_base_LittleFS +board = esp32_4M +build_flags = ${esp32_custom_base_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:custom_IR_ESP32_4M316k_ETH] +extends = env:custom_IR_ESP32_4M316k +build_flags = ${env:custom_IR_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 +extra_scripts = ${env:custom_IR_ESP32_4M316k.extra_scripts} + +[env:custom_IR_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +build_flags = ${esp32_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} +extra_scripts = ${esp32_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +[env:normal_ESP32_4M316k_ETH] +extends = env:normal_ESP32_4M316k +build_flags = ${env:normal_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_4M316k_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_4M +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + + +[env:normal_ESP32_IRExt_4M316k_ETH] +extends = esp32_IRExt +board = esp32_4M +build_flags = ${esp32_IRExt.build_flags} + -DFEATURE_ETHERNET=1 + +[env:collection_A_ESP32_4M316k_ETH] +extends = env:collection_A_ESP32_4M316k +build_flags = ${env:collection_A_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32_4M316k_ETH] +extends = env:collection_B_ESP32_4M316k +build_flags = ${env:collection_B_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32_4M316k_ETH] +extends = env:collection_C_ESP32_4M316k +build_flags = ${env:collection_C_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32_4M316k_ETH] +extends = env:collection_D_ESP32_4M316k +build_flags = ${env:collection_D_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32_4M316k_ETH] +extends = env:collection_E_ESP32_4M316k +build_flags = ${env:collection_E_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32_4M316k_ETH] +extends = env:collection_F_ESP32_4M316k +build_flags = ${env:collection_F_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32_4M316k_ETH] +extends = env:collection_G_ESP32_4M316k +build_flags = ${env:collection_G_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + -DCOLLECTION_USE_RTTTL + +[env:energy_ESP32_4M316k_ETH] +extends = env:energy_ESP32_4M316k +build_flags = ${env:energy_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:display_ESP32_4M316k_ETH] +extends = env:display_ESP32_4M316k +build_flags = ${env:display_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:climate_ESP32_4M316k_ETH] +extends = env:climate_ESP32_4M316k +build_flags = ${env:climate_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +[env:neopixel_ESP32_4M316k_ETH] +extends = env:neopixel_ESP32_4M316k +build_flags = ${env:neopixel_ESP32_4M316k.build_flags} + -DFEATURE_ETHERNET=1 + +; [env:collection_A_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_B_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_B_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_C_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_C_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_D_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_D_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_E_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_E_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_F_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_F_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:collection_G_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_SET_COLLECTION_G_ESP32 +; -DFEATURE_ETHERNET=1 + +; [env:energy_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_ENERGY_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:display_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_DISPLAY_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:climate_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -DPLUGIN_CLIMATE_COLLECTION +; -DFEATURE_ETHERNET=1 + +; [env:neopixel_ESP32_IRExt_4M316k_ETH] +; extends = esp32_IRExt +; board = esp32_4M +; build_flags = ${esp32_IRExt.build_flags} +; -D PLUGIN_NEOPIXEL_COLLECTION +; -DFEATURE_ETHERNET=1 + + +; ESP32 MAX builds 16M flash ------------------------------ + +; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using the default (SPIFFS) filesystem +[env:max_ESP32_16M1M] +extends = esp32_common +board = esp32_16M1M +board_upload.flash_size = 16MB +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +build_flags = ${esp32_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + +[env:max_ESP32_16M1M_ETH] +extends = env:max_ESP32_16M1M +build_flags = ${env:max_ESP32_16M1M.build_flags} + -DFEATURE_ETHERNET=1 + + +; A Lolin D32 PRO with 16MB Flash, allowing 4MB sketch size, and file storage using LittleFS filesystem +[env:max_ESP32_16M8M_LittleFS_ETH] +extends = esp32_common_LittleFS +board = esp32_16M8M +board_upload.flash_size = 16MB +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${esp32_common_LittleFS.lib_ignore} +build_flags = ${esp32_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32_common.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] +; extends = env:max_ESP32_16M8M_LittleFS +; board = ${env:max_ESP32_16M8M_LittleFS.board} +; build_flags = ${env:max_ESP32_16M8M_LittleFS.build_flags} +; -DFEATURE_ETHERNET=1 + + + + + + + + + + diff --git a/platformio_esp32_solo1.ini b/platformio_esp32_solo1.ini index ce4168c35..b27d256b8 100644 --- a/platformio_esp32_solo1.ini +++ b/platformio_esp32_solo1.ini @@ -1,57 +1,63 @@ - - - -; IDF 4.4 -[esp32_solo1_common] -extends = esp32_base -platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1646/framework-arduinoespressif32-solo1-release_v4.4_spiffs-e3fc63b439.zip -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -; IDF 5.1.2 -[esp32_solo1_common_LittleFS] -extends = esp32_base_idf5 -platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1878/framework-arduinoespressif32-solo1-release_v5.1-88d1438.zip -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32solo1_4M316k_LittleFS] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:normal_ESP32solo1_4M316k_LittleFS] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -lib_ignore = ${esp32_solo1_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - - -[env:energy_ESP32solo1_4M316k_LittleFS] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:climate_ESP32solo1_4M316k_LittleFS] -extends = esp32_solo1_common_LittleFS -board = esp32_solo1_4M -build_flags = ${esp32_solo1_common_LittleFS.build_flags} - -D PLUGIN_CLIMATE_COLLECTION + + + +; IDF 4.4 +[esp32_solo1_common] +extends = esp32_base +platform_packages = framework-arduino-solo1 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/1646/framework-arduinoespressif32-solo1-release_v4.4_spiffs-e3fc63b439.zip +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +; IDF 5.1.2 +[esp32_solo1_common_LittleFS] +extends = esp32_base_idf5 +platform = https://github.com/tasmota/platform-espressif32/releases/download/2024.06.11/platform-espressif32.zip +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/2525/framework-arduinoespressif32-solo1-release_v5.1-e9a74b6.zip +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32_solo1_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:normal_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32_solo1_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + + +[env:energy_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -D PLUGIN_ENERGY_COLLECTION + -DFEATURE_ETHERNET=1 + +[env:climate_ESP32solo1_4M316k_LittleFS_ETH] +extends = esp32_solo1_common_LittleFS +board = esp32_solo1_4M +build_flags = ${esp32_solo1_common_LittleFS.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + -DFEATURE_ETHERNET=1 diff --git a/platformio_esp32c2_envs.ini b/platformio_esp32c2_envs.ini index 732859392..7f8919bf5 100644 --- a/platformio_esp32c2_envs.ini +++ b/platformio_esp32c2_envs.ini @@ -1,50 +1,51 @@ - - -[esp32c2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} - NeoPixelBus - NeoPixelBus_wrapper - Adafruit NeoMatrix via NeoPixelBus - - -[env:safeboot_ESP32c2_4M_LittleFS] -extends = esp32c2_common_LittleFS -board = esp32c2 -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_SAFEBOOT -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_safeboot_esp32c2.py -lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} - - -[env:custom_ESP32c2_2M320k_LittleFS_noOTA] -extends = esp32c2_common_LittleFS -board = esp32c2_2M -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c2.py - -[env:custom_ESP32c2_4M316k_LittleFS] -extends = esp32c2_common_LittleFS -board = esp32c2 -build_flags = ${esp32c2_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c2.py - -[env:normal_ESP32c2_4M316k_LittleFS] -extends = esp32c2_common_LittleFS -board = esp32c2 -lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} +; 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} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C2 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5.lib_ignore} + NeoPixelBus + NeoPixelBus_wrapper + Adafruit NeoMatrix via NeoPixelBus + + +[env:safeboot_ESP32c2_4M_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_SAFEBOOT +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_safeboot_esp32c2.py +lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} + + +[env:custom_ESP32c2_2M320k_LittleFS_noOTA] +extends = esp32c2_common_LittleFS +board = esp32c2_2M +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c2.py + +[env:custom_ESP32c2_4M316k_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c2.py + +[env:normal_ESP32c2_4M316k_LittleFS] +extends = esp32c2_common_LittleFS +board = esp32c2 +build_flags = ${esp32c2_common_LittleFS.build_flags} +lib_ignore = ${esp32c2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} diff --git a/platformio_esp32c3_envs.ini b/platformio_esp32c3_envs.ini index bb8162a03..f4a36b912 100644 --- a/platformio_esp32c3_envs.ini +++ b/platformio_esp32c3_envs.ini @@ -1,147 +1,174 @@ - - - -[esp32c3_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32C3 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32c3_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32c3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_IR_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32c3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -lib_ignore = ${esp32_common.lib_ignore} - ${no_ir.lib_ignore} - - -[env:normal_ESP32c3_4M316k_LittleFS_CDC] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -lib_ignore = ${esp32c3_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - -[env:collection_A_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:display_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32c3_4M316k_CDC] -extends = esp32c3_common -board = esp32c3cdc -build_flags = ${esp32c3_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -[env:neopixel_ESP32c3_4M316k_LittleFS_CDC] -extends = esp32c3_common_LittleFS -board = esp32c3cdc -build_flags = ${esp32c3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION + + + +[esp32c3_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32C3 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32c3_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C3 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32c3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_IR_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32c3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + +; [env:custom_ESP32c3_4M316k_LittleFS_CDC] +; extends = esp32c3_common_LittleFS +; board = esp32c3cdc +; build_flags = ${esp32c3_common_LittleFS.build_flags} +; -DPLUGIN_BUILD_CUSTOM +; extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} +; pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32c3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + + +[env:normal_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +lib_ignore = ${esp32_common.lib_ignore} + ${no_ir.lib_ignore} + + +[env:normal_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32c3_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + +[env:collection_A_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:energy_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -D PLUGIN_ENERGY_COLLECTION + -DFEATURE_ETHERNET=1 + +[env:display_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32c3_4M316k_CDC] +extends = esp32c3_common +board = esp32c3cdc +build_flags = ${esp32c3_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32c3_4M316k_LittleFS_CDC_ETH] +extends = esp32c3_common_LittleFS +board = esp32c3cdc +build_flags = ${esp32c3_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DFEATURE_ETHERNET=1 + -DPLUGIN_NEOPIXEL_COLLECTION diff --git a/platformio_esp32c6_envs.ini b/platformio_esp32c6_envs.ini index 4d07f4a11..af5e25d2c 100644 --- a/platformio_esp32c6_envs.ini +++ b/platformio_esp32c6_envs.ini @@ -1,31 +1,55 @@ - - -[esp32c6_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32C6 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs -lib_ignore = ${esp32_base_idf5.lib_ignore} - NeoPixelBus - NeoPixelBus_wrapper - Adafruit NeoMatrix via NeoPixelBus -board = esp32c6cdc - - -[env:custom_ESP32c6_4M316k_LittleFS_CDC] -extends = esp32c6_common_LittleFS -build_flags = ${esp32c6_common_LittleFS.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} - pre:tools/pio/pre_custom_esp32c6.py - - -[env:normal_ESP32c6_4M316k_LittleFS_CDC] -extends = esp32c6_common_LittleFS -lib_ignore = ${esp32c6_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} + + +[esp32c6_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32C6 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs +lib_ignore = ${esp32_base_idf5.lib_ignore} +board = esp32c6cdc + + +[env:custom_ESP32c6_4M316k_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32c6.py + + +[env:normal_ESP32c6_4M316k_LittleFS_CDC_ETH] +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:max_ESP32c6_8M1M_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +board = esp32c6cdc-8M +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + + +[env:max_ESP32c6_16M8M_LittleFS_CDC_ETH] +extends = esp32c6_common_LittleFS +board = esp32c6cdc-16M +build_flags = ${esp32c6_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32c6_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp32s2_envs.ini b/platformio_esp32s2_envs.ini index d7ea411f9..4419571ba 100644 --- a/platformio_esp32s2_envs.ini +++ b/platformio_esp32s2_envs.ini @@ -1,150 +1,167 @@ - - - - -[esp32s2_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32S2 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32s2_common_LittleFS] -extends = esp32_base_idf5 -build_flags = ${esp32_base_idf5.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32S2 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DESP_CONSOLE_USB_CDC=y -extra_scripts = ${esp32s2_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:neopixel_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - -[env:neopixel_ESP32s2_4M316k_LittleFS_CDC] -extends = esp32s2_common_LittleFS -board = esp32s2cdc -build_flags = ${esp32s2_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - - -[env:custom_IR_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32s2_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -lib_ignore = ${esp32s2_common.lib_ignore} - ${no_ir.lib_ignore} - - -[env:normal_ESP32s2_4M316k_LittleFS_CDC] -extends = esp32s2_common_LittleFS -board = esp32s2cdc -lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} - ${no_ir.lib_ignore} - -[env:collection_A_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:display_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32s2_4M316k_CDC] -extends = esp32s2_common -board = esp32s2cdc -build_flags = ${esp32s2_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - + + + + +[esp32s2_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32S2 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32s2_common_LittleFS] +extends = esp32_base_idf5 +build_flags = ${esp32_base_idf5.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32S2 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DESP_CONSOLE_USB_CDC=y +extra_scripts = ${esp32s2_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:neopixel_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + -DFEATURE_ETHERNET=1 + + +[env:custom_IR_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32s2_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + + + +[env:normal_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +lib_ignore = ${esp32s2_common.lib_ignore} + ${no_ir.lib_ignore} + +[env:custom_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DESP_CONSOLE_USB_CDC=y + -DFEATURE_ETHERNET=1 +extra_scripts = ${esp32s2_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + + +[env:normal_ESP32s2_4M316k_LittleFS_CDC_ETH] +extends = esp32s2_common_LittleFS +board = esp32s2cdc +build_flags = ${esp32s2_common_LittleFS.build_flags} + -DESP_CONSOLE_USB_CDC=y + -DFEATURE_ETHERNET=1 +lib_ignore = ${esp32s2_common_LittleFS.lib_ignore} + ${no_ir.lib_ignore} + +[env:collection_A_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:display_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32s2_4M316k_CDC] +extends = esp32s2_common +board = esp32s2cdc +build_flags = ${esp32s2_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + diff --git a/platformio_esp32s3_envs.ini b/platformio_esp32s3_envs.ini index 6ff40a7f2..59ce3af29 100644 --- a/platformio_esp32s3_envs.ini +++ b/platformio_esp32s3_envs.ini @@ -1,208 +1,220 @@ - - - - -[esp32s3_common] -extends = esp32_base -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping - ${no_ir.lib_ignore} - ESP32 BLE Arduino -build_flags = ${esp32_base.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DESP32S3 -extra_scripts = ${esp32_base.extra_scripts} -build_unflags = ${esp32_base.build_unflags} - -fexceptions - -[esp32s3_common_LittleFS] -extends = esp32_base_idf5 -lib_ignore = ${esp32_common_LittleFS.lib_ignore} - ESP32_ping - ${esp32_base_idf5.lib_ignore} -build_flags = ${esp32_base_idf5.build_flags} -; -mtext-section-literals - -DFEATURE_ARDUINO_OTA=1 - -DUSE_LITTLEFS - -DESP32S3 -extra_scripts = ${esp32_base_idf5.extra_scripts} -build_unflags = ${esp32_base_idf5.build_unflags} - -fexceptions -board_build.filesystem = littlefs - - -[env:custom_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - - -[env:custom_IR_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR -lib_ignore = ${esp32_always.lib_ignore} - ESP32_ping -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - pre:tools/pio/ir_build_check.py - - - -[env:normal_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -lib_ignore = ${esp32s3_common.lib_ignore} - ${no_ir.lib_ignore} - - -[env:collection_A_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_B_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_B_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_C_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_C_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_D_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_D_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_E_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_E_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_F_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_F_ESP32 - -DCOLLECTION_USE_RTTTL - -[env:collection_G_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DPLUGIN_SET_COLLECTION_G_ESP32 - -DCOLLECTION_USE_RTTTL - - -[env:energy_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_ENERGY_COLLECTION - -[env:display_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_DISPLAY_COLLECTION - -[env:climate_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -D PLUGIN_CLIMATE_COLLECTION - -[env:neopixel_ESP32s3_4M316k_CDC] -extends = esp32s3_common -board = esp32s3cdc-qio_qspi -build_flags = ${esp32s3_common.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DFEATURE_SD=1 - -DPLUGIN_NEOPIXEL_COLLECTION - - -[env:custom_ESP32s3_8M1M_LittleFS_CDC] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-8M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_CUSTOM - -DFEATURE_SD=1 -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:custom_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC] -extends = env:custom_ESP32s3_8M1M_LittleFS_CDC -board = esp32s3cdc-qio_opi-8M - - -[env:max_ESP32s3_8M1M_LittleFS_CDC] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-8M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32_common.extra_scripts} - - -[env:max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC] -extends = env:max_ESP32s3_8M1M_LittleFS_CDC -board = esp32s3cdc-qio_opi-8M - - -[env:custom_ESP32s3_16M8M_LittleFS_CDC] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_CUSTOM - -DPLUGIN_BUILD_IR_EXTENDED - -DFEATURE_SD=1 -extra_scripts = ${esp32s3_common.extra_scripts} - pre:tools/pio/pre_custom_esp32.py - -[env:custom_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC] -extends = env:custom_ESP32s3_16M8M_LittleFS_CDC -board = esp32s3cdc-qio_opi-16M - - -[env:max_ESP32s3_16M8M_LittleFS_CDC] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_qspi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DUSE_LITTLEFS - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32_common.extra_scripts} - - -[env:max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC] -extends = esp32s3_common_LittleFS -board = esp32s3cdc-qio_opi-16M -build_flags = ${esp32s3_common_LittleFS.build_flags} - -DUSE_LITTLEFS - -DFEATURE_ARDUINO_OTA=1 - -DPLUGIN_BUILD_MAX_ESP32 - -DPLUGIN_BUILD_IR_EXTENDED -extra_scripts = ${esp32_common.extra_scripts} - - + + + + +[esp32s3_common] +extends = esp32_base +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping + ${no_ir.lib_ignore} + ESP32 BLE Arduino +build_flags = ${esp32_base.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DESP32S3 +extra_scripts = ${esp32_base.extra_scripts} +build_unflags = ${esp32_base.build_unflags} + -fexceptions + +[esp32s3_common_LittleFS] +extends = esp32_base_idf5 +lib_ignore = ${esp32_common_LittleFS.lib_ignore} + ESP32_ping + ${esp32_base_idf5.lib_ignore} +build_flags = ${esp32_base_idf5.build_flags} +; -mtext-section-literals + -DFEATURE_ARDUINO_OTA=1 + -DUSE_LITTLEFS + -DESP32S3 +extra_scripts = ${esp32_base_idf5.extra_scripts} +build_unflags = ${esp32_base_idf5.build_unflags} + -fexceptions +board_build.filesystem = littlefs + + +[env:custom_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM +extra_scripts = ${esp32s3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + + +[env:custom_IR_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ${esp32_always.lib_ignore} + ESP32_ping +extra_scripts = ${esp32s3_common.extra_scripts} + pre:tools/pio/pre_custom_esp32_IR.py + pre:tools/pio/ir_build_check.py + + + +[env:normal_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +lib_ignore = ${esp32s3_common.lib_ignore} + ${no_ir.lib_ignore} + + +[env:collection_A_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_B_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_B_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_C_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_C_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_D_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_D_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_E_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_E_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_F_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_F_ESP32 + -DCOLLECTION_USE_RTTTL + +[env:collection_G_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DPLUGIN_SET_COLLECTION_G_ESP32 + -DCOLLECTION_USE_RTTTL + + +[env:energy_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_ENERGY_COLLECTION + +[env:display_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_DISPLAY_COLLECTION + +[env:climate_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -D PLUGIN_CLIMATE_COLLECTION + +[env:neopixel_ESP32s3_4M316k_CDC] +extends = esp32s3_common +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + +[env:neopixel_ESP32s3_4M316k_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ARDUINO_OTA=1 + -DFEATURE_SD=1 + -DPLUGIN_NEOPIXEL_COLLECTION + -DFEATURE_ETHERNET=1 + + +[env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-8M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_CUSTOM + -DFEATURE_SD=1 +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:custom_ESP32s3_8M1M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-8M + + +[env:max_ESP32s3_8M1M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-8M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + +[env:max_ESP32s3_8M1M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:max_ESP32s3_8M1M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-8M + + +[env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR_EXTENDED + -DFEATURE_SD=1 +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + pre:tools/pio/pre_custom_esp32.py + +[env:custom_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = env:custom_ESP32s3_16M8M_LittleFS_CDC_ETH +board = esp32s3cdc-qio_opi-16M + + +[env:max_ESP32s3_16M8M_LittleFS_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_qspi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + +[env:max_ESP32s3_16M8M_LittleFS_OPI_PSRAM_CDC_ETH] +extends = esp32s3_common_LittleFS +board = esp32s3cdc-qio_opi-16M +build_flags = ${esp32s3_common_LittleFS.build_flags} + -DFEATURE_ETHERNET=1 + -DFEATURE_ARDUINO_OTA=1 + -DPLUGIN_BUILD_MAX_ESP32 + -DPLUGIN_BUILD_IR_EXTENDED +extra_scripts = ${esp32s3_common_LittleFS.extra_scripts} + + diff --git a/platformio_esp82xx_base.ini b/platformio_esp82xx_base.ini index eaf09d7a7..90a1b999e 100644 --- a/platformio_esp82xx_base.ini +++ b/platformio_esp82xx_base.ini @@ -9,7 +9,7 @@ platform = ${core_2_7_4.platform} platform_packages = ${core_2_7_4.platform_packages} lib_ignore = ${core_2_7_4.lib_ignore} build_unflags = ${core_2_7_4.build_unflags} -extra_scripts = +extra_scripts = ${core_2_7_4.extra_scripts} [core312_platform] @@ -26,6 +26,7 @@ platform = ${core_2_7_4.platform} platform_packages = ${core_2_7_4.platform_packages} lib_ignore = ${core_2_7_4.lib_ignore} build_unflags = ${core_2_7_4.build_unflags} +extra_scripts = ${core_2_7_4.extra_scripts} [beta_platform_2ndheap] @@ -76,8 +77,8 @@ build_flags = ${minimal_size.build_flags} build_unflags = -DDEBUG_ESP_PORT -fexceptions monitor_filters = esp8266_exception_decoder -extra_scripts = pre:tools/pio/pre_default_check.py - ${extra_scripts_esp8266.extra_scripts} +extra_scripts = ${extra_scripts_esp8266.extra_scripts} + pre:tools/pio/pre_default_check.py src_filter = +<*> -<.git/> -<.svn/> - - - - -<*/Commands/> -<*/ControllerQueue/> -<*/DataStructs/> -<*/DataTypes/> -<*/ESPEasyCore/> -<*/Globals/> -<*/Helpers/> -<*/PluginStructs/> -<*/WebServer/> diff --git a/platformio_esp82xx_envs.ini b/platformio_esp82xx_envs.ini index 60ee1b7d2..d459c27b2 100644 --- a/platformio_esp82xx_envs.ini +++ b/platformio_esp82xx_envs.ini @@ -16,7 +16,7 @@ build_flags = ${core_stage.build_flags} -DPLUGIN_BUILD_CUSTOM -DFEATURE_DEFINE_SERIAL_CONSOLE_PORT=0 extra_scripts = ${extra_scripts_esp8266.extra_scripts} - ${core_stage.extra_scripts} + [esp8266_custom_common_274] @@ -31,6 +31,8 @@ lib_ignore = ESP32_ping TinyWireM I2C AXP192 Power management EspSoftwareSerial + LittleFS + LittleFS(esp8266) extra_scripts = pre:tools/pio/pre_custom_esp82xx.py ${extra_scripts_esp8266.extra_scripts} @@ -45,9 +47,11 @@ lib_ignore = ESP32_ping ${no_ir.lib_ignore} TinyWireM I2C AXP192 Power management + LittleFS + LittleFS(esp8266) extra_scripts = pre:tools/pio/pre_custom_esp82xx.py ${extra_scripts_esp8266.extra_scripts} - ${core_stage.extra_scripts} + ; Custom: 4M1M version -------------------------- @@ -78,6 +82,39 @@ lib_ignore = ESP32_ping Adafruit ILI9341 ESPEasy adafruit/Adafruit BusIO Adafruit NeoPixel + NeoPixelBus_wrapper + NeoPixelBus by Makuna + Adafruit NeoMatrix via NeoPixelBus + Adafruit Motor Shield V2 Library + Adafruit_ST77xx + Adafruit NeoMatrix + I2C AXP192 Power management + EspSoftwareSerial +extra_scripts = pre:tools/pio/pre_custom_esp82xx_IR.py + ${extra_scripts_esp8266.extra_scripts} + pre:tools/pio/ir_build_check.py + +[env:custom_IR_ESP8266_1M] +extends = esp8266_1M +platform = ${ir.platform} +platform_packages = ${ir.platform_packages} +build_flags = ${ir.build_flags} + ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER + -DPLUGIN_BUILD_CUSTOM + -DPLUGIN_BUILD_IR +lib_ignore = ESP32_ping + ESP32WebServer + ServoESP32 + ESP32HTTPUpdateServer + adafruit/Adafruit GFX Library@^1.11.1 + LOLIN_EPD + Adafruit ILI9341 ESPEasy + adafruit/Adafruit BusIO + Adafruit NeoPixel + NeoPixelBus_wrapper + NeoPixelBus by Makuna + Adafruit NeoMatrix via NeoPixelBus Adafruit Motor Shield V2 Library Adafruit_ST77xx Adafruit NeoMatrix @@ -131,7 +168,13 @@ platform_packages = ${esp8266_custom_common_312.platform_packages} build_flags = ${esp8266_custom_common_312.build_flags} ${esp8266_4M1M.build_flags} -DPLUGIN_BUILD_CUSTOM -lib_ignore = ${esp8266_custom_common_312.lib_ignore} +lib_ignore = ESP32_ping + ESP32WebServer + ESP32HTTPUpdateServer + ServoESP32 + ${no_ir.lib_ignore} + TinyWireM + I2C AXP192 Power management extra_scripts = ${esp8266_custom_common_312.extra_scripts} @@ -142,6 +185,7 @@ platform = ${esp8266_custom_common_274.platform} platform_packages = ${esp8266_custom_common_274.platform_packages} build_flags = ${esp8266_custom_common_274.build_flags} ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER -DPLUGIN_BUILD_CUSTOM lib_ignore = ${esp8266_custom_common_274.lib_ignore} ESP8266SdFat @@ -159,6 +203,7 @@ platform = ${beta_platform.platform} platform_packages = ${beta_platform.platform_packages} build_flags = ${beta_platform.build_flags} ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER -DPLUGIN_BUILD_CUSTOM lib_ignore = ${esp8266_custom_common_312.lib_ignore} ESP8266SdFat @@ -193,6 +238,7 @@ platform = ${normal.platform} platform_packages = ${normal.platform_packages} build_flags = ${normal.build_flags} ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER lib_ignore = ${normal.lib_ignore} @@ -210,6 +256,7 @@ platform = ${normal.platform} platform_packages = ${normal.platform_packages} build_flags = ${normal.build_flags} ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER -D FEATURE_ADC_VCC=1 lib_ignore = ${normal.lib_ignore} @@ -304,8 +351,6 @@ extends = esp8266_1M_OTA, core274_platform build_flags = ${core274_platform.build_flags} ${minimal_OTA_domoticz.build_flags} lib_ignore = ${core274_platform.lib_ignore} -extra_scripts = ${core274_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} [env:minimal_core_274_ESP8266_1M_OTA_Domoticz_MQTT] @@ -313,8 +358,6 @@ extends = esp8266_1M_OTA, core274_platform build_flags = ${core274_platform.build_flags} ${minimal_OTA_domoticz_MQTT.build_flags} lib_ignore = ${core274_platform.lib_ignore} -extra_scripts = ${core274_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} [env:minimal_core_274_ESP8266_1M_OTA_FHEM_HA] @@ -322,9 +365,6 @@ extends = esp8266_1M_OTA, core274_platform build_flags = ${core274_platform.build_flags} ${minimal_OTA_FHEM_HA.build_flags} lib_ignore = ${core274_platform.lib_ignore} -extra_scripts = ${core274_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} - [env:minimal_core_312_ESP8266_1M_OTA_Domoticz] @@ -332,8 +372,6 @@ extends = esp8266_1M_OTA, core312_platform build_flags = ${core312_platform.build_flags} ${minimal_OTA_domoticz.build_flags} build_unflags = ${core312_platform.build_unflags} -extra_scripts = ${core312_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} [env:minimal_core_312_ESP8266_1M_OTA_Domoticz_MQTT] @@ -341,8 +379,6 @@ extends = esp8266_1M_OTA, core312_platform build_flags = ${core312_platform.build_flags} ${minimal_OTA_domoticz_MQTT.build_flags} build_unflags = ${core312_platform.build_unflags} -extra_scripts = ${core312_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} [env:minimal_core_312_ESP8266_1M_OTA_FHEM_HA] @@ -350,8 +386,6 @@ extends = esp8266_1M_OTA, core312_platform build_flags = ${core312_platform.build_flags} ${minimal_OTA_FHEM_HA.build_flags} build_unflags = ${core312_platform.build_unflags} -extra_scripts = ${core312_platform.extra_scripts} - ${esp8266_1M_OTA.extra_scripts} @@ -359,20 +393,21 @@ extra_scripts = ${core312_platform.extra_scripts} ; IR builds ; ; ********************************************************************* +; TD-er: disabled as it no longer fits in 1M builds ; Minimal IR: 1024k version -------------------------- ; Build including IR libraries, including extended AC commands ; Minimal set of other plugins -[env:minimal_IRext_ESP8266_1M] -extends = esp8266_1M -platform = ${ir.platform} -platform_packages = ${ir.platform_packages} -lib_ignore = ${ir.lib_ignore} -build_flags = ${minimal_ir_extended.build_flags} - ${esp8266_1M.build_flags} -build_unflags = ${esp8266_1M_OTA.build_unflags} -DPLUGIN_BUILD_NORMAL_IR -extra_scripts = ${esp8266_1M.extra_scripts} - pre:tools/pio/ir_build_check.py +;[env:minimal_IRext_ESP8266_1M] +;extends = esp8266_1M +;platform = ${ir.platform} +;platform_packages = ${ir.platform_packages} +;lib_ignore = ${ir.lib_ignore} +;build_flags = ${minimal_ir_extended.build_flags} +; ${esp8266_1M.build_flags} +;build_unflags = ${esp8266_1M_OTA.build_unflags} -DPLUGIN_BUILD_NORMAL_IR +;extra_scripts = ${esp8266_1M.extra_scripts} +; pre:tools/pio/ir_build_check.py ; Minimal IR: 4096k version -------------------------- @@ -414,7 +449,8 @@ build_flags = ${normal_ir_extended_no_rx.build_flags} ${esp8266_4M2M.build_flags} ${limited_build_size.build_flags} -DLIMIT_BUILD_SIZE - -DKEEP_RTTTL + -DFEATURE_TARSTREAM_SUPPORT=0 + ; -DKEEP_RTTTL extra_scripts = ${esp8266_4M2M.extra_scripts} pre:tools/pio/ir_build_check.py @@ -534,6 +570,7 @@ extends = esp8266_4M1M, regular_platform build_flags = ${regular_platform.build_flags} ${esp8266_4M1M.build_flags} -D PLUGIN_ENERGY_COLLECTION + -D LIMIT_BUILD_SIZE -D WEBSERVER_USE_CDN_JS_CSS lib_ignore = ${regular_platform.lib_ignore} ESP8266SdFat @@ -541,6 +578,7 @@ lib_ignore = ${regular_platform.lib_ignore} SD SDFS LittleFS(esp8266) +extra_scripts = ${regular_platform.extra_scripts} ; display : 4096k version ---------------------------- @@ -658,13 +696,16 @@ lib_ignore = ${regular_platform.lib_ignore} ; GPIO13 Blue Led (0 = On, 1 = Off) [env:hard_SONOFF_POW_4M1M] extends = esp8266_4M1M, hard_esp82xx -platform = ${hard_esp82xx.platform} -platform_packages = ${hard_esp82xx.platform_packages} -build_flags = ${hard_esp82xx.build_flags} +platform = ${core_2_7_4.platform} +platform_packages = ${core_2_7_4.platform_packages} +build_flags = ${core_2_7_4.build_flags} ${esp8266_4M1M.build_flags} + -DBUILD_NO_DEBUG + -DPLUGIN_BUILD_CUSTOM -DPLUGIN_SET_SONOFF_POW -DFEATURE_IMPROV=0 -lib_ignore = ${hard_esp82xx.lib_ignore} + -DPLUGIN_STATS_NR_ELEMENTS=64 +lib_ignore = ${esp8266_custom_common_274.lib_ignore} @@ -723,6 +764,7 @@ platform = ${hard_esp82xx.platform} platform_packages = ${hard_esp82xx.platform_packages} build_flags = ${hard_esp82xx.build_flags} ${esp8266_1M.build_flags} + -D NO_HTTP_UPDATER -D PLUGIN_SET_LC_TECH_RELAY_X2 lib_ignore = ${hard_esp82xx.lib_ignore} diff --git a/requirements.txt b/requirements.txt index 7a0b8be49..58f057ee7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ platformio>=6.1.9 pygit2>=1.10.1 cryptography==38.0.3 +setuptools \ No newline at end of file diff --git a/src/Custom-sample.h b/src/Custom-sample.h index f0528361c..c68af15ba 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -1,570 +1,588 @@ -#ifndef ESPEASY_CUSTOM_H -#define ESPEASY_CUSTOM_H - -/* - To modify the stock configuration without changing the EspEasy.ino file : - 1) rename this file to "Custom.h" (It is ignored by Git) - 2) define your own settings below - 3) define USE_CUSTOM_H as a build flags. ie : export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" - */ - - -/* - ####################################################################################################### - Your Own Default Settings - ####################################################################################################### - You can basically ovveride ALL macro defined in ESPEasy.ino. - Don't forget to first #undef each existing #define that you add below. - But since this Custom.h is included before other defines are made, you don't have to undef a lot of defines. - Here are some examples: - */ - -// --- Feature Flagging --------------------------------------------------------- -// Can be set to 1 to enable, 0 to disable, or not set to use the default (usually via define_plugin_sets.h) - -#define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules -#define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol -#define FEATURE_ARDUINO_OTA 1 //enables the Arduino OTA capabilities -// #define FEATURE_SD 1 // Enable SD card support -// #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url - -#ifdef BUILD_GIT -# undef BUILD_GIT -#endif // ifdef BUILD_GIT - -#define BUILD_GIT "My Build: " __DATE__ " " __TIME__ - - -#define DEFAULT_NAME "MyEspEasyDevice" // Enter your device friendly name -#define UNIT 0 // Unit Number -#define DEFAULT_DELAY 60 // Sleep Delay in seconds - -// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- -#define DEFAULT_AP_IP 192, 168, 4, 1 // Enter IP address (comma separated) for AP (config) mode -#define DEFAULT_AP_SUBNET 255, 255, 255, 0 // Enter IP address (comma separated) for AP (config) mode -#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode - -// --- Wifi Client Mode ----------------------------------------------------------------------------- -#define DEFAULT_SSID "MyHomeSSID" // Enter your network SSID -#define DEFAULT_KEY "MySuperSecretPassword" // Enter your network WPA key -#define DEFAULT_SSID2 "" // Enter your fallback network SSID -#define DEFAULT_KEY2 "" // Enter your fallback network WPA key -#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs -#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP -#define DEFAULT_IP "192.168.0.50" // Enter your IP address -#define DEFAULT_DNS "192.168.0.1" // Enter your DNS -#define DEFAULT_GW "192.168.0.1" // Enter your Gateway -#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet -#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver -#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver -#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: -// ONLY_IP_RANGE_ALLOWED -#define DEFAULT_ADMIN_USERNAME "admin" -#define DEFAULT_ADMIN_PASS "" - -#define DEFAULT_WIFI_CONNECTION_TIMEOUT 10000 // minimum timeout in ms for WiFi to be connected. -#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) -#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. -#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. -#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power -// used and need reboot to reset mode) -#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP - // packets to announce itself. -#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line - // 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_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 ------------------------------------------------------------------------------ -#define DEFAULT_CONTROLLER false // true or false enabled or disabled, set 1st controller - // defaults -#define DEFAULT_CONTROLLER_ENABLED true // Enable default controller by default -#define DEFAULT_CONTROLLER_USER "" // Default controller user -#define DEFAULT_CONTROLLER_PASS "" // Default controller Password - -// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! -#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub -#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub -#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address -#define DEFAULT_SERVER_HOST "" // Server hostname -#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP -#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers - -#define DEFAULT_PORT 8080 // Enter your Server port value -#define DEFAULT_CONTROLLER_TIMEOUT 100 // Default timeout in msec - -#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications - // 0 = Stand-alone (no controller set) - // 1 = Domoticz HTTP - // 2 = Domoticz MQTT - // 3 = Nodo Telnet - // 4 = ThingSpeak - // 5 = Home Assistant (openHAB) MQTT - // 6 = PiDome MQTT - // 7 = EmonCMS - // 8 = Generic HTTP - // 9 = FHEM HTTP - -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SDA 4 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SDA -1 // Undefined -#endif -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SCL 5 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SCL -1 // Undefined -#endif -#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips -#define FEATURE_I2C_DEVICE_SCAN 1 - -#define DEFAULT_SPI 0 //0=disabled 1=enabled and for ESP32 there is option 2 =HSPI - -#define DEFAULT_PIN_STATUS_LED (-1) -#define DEFAULT_PIN_STATUS_LED_INVERSED true - -#define DEFAULT_PIN_RESET_BUTTON (-1) - - -#define DEFAULT_USE_RULES false // (true|false) Enable Rules? -#define DEFAULT_RULES_OLDENGINE true - -#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? -#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full -#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment -#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages -#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic -#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message -#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message -#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 - -#define DEFAULT_USE_NTP false // (true|false) Use NTP Server -#define DEFAULT_NTP_HOST "" // NTP Server Hostname -#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) -#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving - -#define DEFAULT_LATITUDE 0.0f // Default Latitude -#define DEFAULT_LONGITUDE 0.0f // Default Longitude - -#define DEFAULT_SYSLOG_IP "" // Syslog IP Address -#define DEFAULT_SYSLOG_PORT 0 // Standard syslog port: 514 -#define DEFAULT_SYSLOG_FACILITY 0 // kern -#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level -#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level -#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level -#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level -#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card - -#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port -#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate - -#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) - - -#define BUILD_NO_DEBUG - -// Custom built-in url for hosting JavaScript and CSS files. -#define CUSTOM_BUILD_CDN_URL "https://cdn.jsdelivr.net/gh/letscontrolit/ESPEasy@mega/static/" - - - -// Special SSID/key setup only to be used in custom builds. - -// Deployment SSID will be used only when the configured SSIDs are not reachable and/or no credentials are set. -// This to make deployment of large number of nodes easier -#define CUSTOM_DEPLOYMENT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease deployment -#define CUSTOM_DEPLOYMENT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease deployment -#define CUSTOM_SUPPORT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease support -#define CUSTOM_SUPPORT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease support - - -// Emergency fallback SSID will only be attempted in the first 10 minutes after reboot. -// When found, the unit will connect to it and depending on the built in flag, it will either just connect to it, or clear set credentials. -// Use case: User connects to a public AP which does need to agree on an agreement page for the rules of conduct (e.g. open APs) -// This is seen as a valid connection, so the unit will not reconnect to another node and thus becomes inaccessible. -#define CUSTOM_EMERGENCY_FALLBACK_SSID "" // Enter SSID not shown in UI, to be used to regain access to the node -#define CUSTOM_EMERGENCY_FALLBACK_KEY "" // Enter key not shown in UI, to be used to regain access to the node - -#define CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS false -#define CUSTOM_EMERGENCY_FALLBACK_START_AP false - -#define CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME 10 - -// Allow for remote provisioning of a node. -// This is only allowed for custom builds. -// To setup the configuration of the provisioning file, one must also define FEATURE_SETTINGS_ARCHIVE -// Default setting is to not allow to configure a node remotely, unless explicitly enabled. -// #define FEATURE_CUSTOM_PROVISIONING 1 - -#define FEATURE_SSDP 1 - -#define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307 - -#define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data -#ifdef ESP8266 -# define PLUGIN_STATS_NR_ELEMENTS 16 -#endif // ifdef ESP8266 -# ifdef ESP32 -# define PLUGIN_STATS_NR_ELEMENTS 64 -#endif // ifdef ESP32 -#define FEATURE_CHART_JS 1 // Support for drawing charts, like PluginStats historic data - -// Optional alternative CDN links: -// Chart.js: (only used when FEATURE_CHART_JS is enabled) -// #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.1.2/dist/chart.umd.min.js" -// JQuery: -// #define CDN_URL_JQUERY "https://code.jquery.com/jquery-3.6.0.min.js" - - -// #define FEATURE_SETTINGS_ARCHIVE 1 -// #define FEATURE_I2CMULTIPLEXER 1 -// #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 -// #define PLUGIN_USES_ADAFRUITGFX // Used by Display plugins using Adafruit GFX library -// #define ADAGFX_ARGUMENT_VALIDATION 0 // Disable argument validation in AdafruitGFX_helper -// #define ADAGFX_SUPPORT_7COLOR 0 // Disable the support of 7-color eInk displays by AdafruitGFX_helper -// #define FEATURE_SEND_TO_HTTP 1 // Enable availability of the SendToHTTP command -// #define FEATURE_POST_TO_HTTP 1 // Enable availability of the PostToHTTP command -// #define FEATURE_PUT_TO_HTTP 1 // Enable availability of the PutToHTTP command -// #define FEATURE_I2C_DEVICE_CHECK 0 // Disable the I2C Device check feature -// #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching the I2C address from I2C plugins. Will be enabled when FEATURE_I2C_DEVICE_CHECK is enabled -// #define FEATURE_RTTTL 1 // Enable rtttl command -// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling -// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode -// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense - -#if FEATURE_CUSTOM_PROVISIONING -// For device models, see src/src/DataTypes/DeviceModel.h -// #ifdef ESP32 -// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default -// #endif -// #ifdef ESP8266 -// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default -// #endif -// #define DEFAULT_PROVISIONING_FETCH_RULES1 false -// #define DEFAULT_PROVISIONING_FETCH_RULES2 false -// #define DEFAULT_PROVISIONING_FETCH_RULES3 false -// #define DEFAULT_PROVISIONING_FETCH_RULES4 false -// #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false -// #define DEFAULT_PROVISIONING_FETCH_SECURITY false -// #define DEFAULT_PROVISIONING_FETCH_CONFIG false -// #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false -// #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false -// #define DEFAULT_PROVISIONING_SAVE_URL false -// #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false -// #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false -// #define DEFAULT_PROVISIONING_URL "" -// #define DEFAULT_PROVISIONING_USER "" -// #define DEFAULT_PROVISIONING_PASS "" -#endif - - - -#define FEATURE_SSDP 1 - -/* - ####################################################################################################### - Defining web interface - ####################################################################################################### - */ - -#define MENU_INDEX_MAIN_VISIBLE true -/* -#define MENU_INDEX_CONFIG_VISIBLE false -#define MENU_INDEX_CONTROLLERS_VISIBLE false -#define MENU_INDEX_HARDWARE_VISIBLE false -#define MENU_INDEX_DEVICES_VISIBLE false -#define MENU_INDEX_RULES_VISIBLE false -#define MENU_INDEX_NOTIFICATIONS_VISIBLE false -#define MENU_INDEX_TOOLS_VISIBLE false -*/ - -#define MAIN_PAGE_SHOW_SYSINFO_BUTTON true -#define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON true -#define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false - -#define MAIN_PAGE_SHOW_NODE_LIST_BUILD true -#define MAIN_PAGE_SHOW_NODE_LIST_TYPE true - -#define SETUP_PAGE_SHOW_CONFIG_BUTTON true - -// #define FEATURE_AUTO_DARK_MODE 0 // 0 = Disable auto-dark mode -// #define FEATURE_EXTENDED_TASK_VALUE_TYPES 0 // 0 = Disable extra task value types like 64 bit ints, double, etc. in Dummy tasks -// #define FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE 0 // 0 = switch to float as floating point type for rules/formula processing. - -//#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON - -#define SHOW_SYSINFO_JSON 1 //Enables the sysinfo_json page (by default is enabled when WEBSERVER_NEW_UI is enabled too) - -/* - ####################################################################################################### - CSS / template - ####################################################################################################### - */ -/* -#define WEBPAGE_TEMPLATE_DEFAULT_HEADER "

ESP Easy Mega: {{title}}


" -#define WEBPAGE_TEMPLATE_DEFAULT_FOOTER "" -#define WEBPAGE_TEMPLATE_AP_HEADER "

Welcome to ESP Easy Mega AP

" -#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON -*/ -// Embed Custom CSS in Custom.h: -/* -#define WEBSERVER_EMBED_CUSTOM_CSS -static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { -... -,0}; -*/ - - -/* - ####################################################################################################### - Special settings (rendering settings incompatible with other builds) - ####################################################################################################### - */ - -// #define FEATURE_NON_STANDARD_24_TASKS 1 - -/* - ####################################################################################################### - Your Own selection of plugins and controllers - ####################################################################################################### - */ - -#define CONTROLLER_SET_NONE -#define NOTIFIER_SET_NONE -#define PLUGIN_SET_NONE - - -/* - ####################################################################################################### - ########### Plugins - ####################################################################################################### - */ - -// #define FEATURE_SERVO 1 // Uncomment and set to 0 to explicitly disable SERVO support - - -// #define USES_P001 // Switch -// #define USES_P002 // ADC -// #define USES_P003 // Pulse -// #define USES_P004 // 1-Wire Temperature (Dallas/Maxim DS18B20) -// #define USES_P005 // DHT11/12/22 SONOFF2301/7021/MS01 -// #define USES_P006 // BMP085/180 -// #define USES_P007 // PCF8591 -// #define USES_P008 // Wiegand (RFID) -// #define USES_P009 // MCP23017 - -// #define USES_P010 // BH1750 -// #define USES_P011 // ProMini Extender -// #define USES_P012 // LCD2004 -// #define USES_P013 // HC-SR04/RCW-0001 -// #define USES_P014 // SI70xx/HTU21D -// #define USES_P015 // TSL2561 -// #define USES_P017 // PN532 -// #define USES_P018 // GP2Y10 -// #define USES_P019 // PCF8574 - -// #define USES_P020 // Ser2Net -// #define USES_P021 // Level Control -// #define USES_P022 // PCA9685 -// #define USES_P023 // OLED SSD1306 -// #define USES_P024 // MLX90614 -// #define USES_P025 // ADS1x15 -// #define USES_P026 // SysInfo -// #define USES_P027 // INA219 -// #define USES_P028 // BMx280 -// #define USES_P029 // Domoticz MQTT Helper - -// #define USES_P031 // SHT1x -// #define USES_P032 // MS5611 (GY-63) -// #define USES_P033 // Dummy Device -// #define USES_P034 // DHT12 -// #define USES_P036 // OLED SSD1306/SH1106 Framed -// #define P036_FEATURE_DISPLAY_PREVIEW 1 // Enable Preview feature, shows on-display content on Devices overview page -// #define P036_FEATURE_ALIGN_PREVIEW 1 // Enable center/right-align feature when preview is enabled (auto-disabled for 1M builds) -// #define P036_ENABLE_TICKER 1 // Enable ticker function -// #define USES_P037 // MQTT Import -// #define P037_MAPPING_SUPPORT 1 // Enable Value mapping support -// #define P037_FILTER_SUPPORT 1 // Enable filtering support -// #define P037_JSON_SUPPORT 1 // Enable Json support -// #define USES_P038 // NeoPixel -// #define USES_P039 // Thermocouple - -// #define USES_P040 // RFID - ID12LA/RDM6300 -// #define USES_P041 // NeoPixel (Word Clock) -// #define USES_P042 // NeoPixel (Candle) -// #define USES_P043 // ClkOutput -// #define USES_P044 // P1 Wifi Gateway -// #define USES_P045 // MPU6050 -// #define USES_P046 // Ventus W266 -// #define USES_P047 // Soil moisture sensor -// #define USES_P048 // Motoshield v2 -// #define USES_P049 // MH-Z19 - -// #define USES_P050 // TCS34725 RGB Color Sensor with IR filter and White LED -// #define USES_P051 // AM2320 -// #define USES_P052 // SenseAir -// #define USES_P053 // PMSx003 / PMSx003ST -// #define USES_P054 // DMX512 -// #define USES_P055 // Chiming -// #define USES_P056 // SDS011/018/198 -// #define USES_P057 // HT16K33_LED -// #define USES_P058 // HT16K33_KeyPad -// #define USES_P059 // Rotary Encoder - -// #define USES_P060 // MCP3221 -// #define USES_P061 // PCF8574 / MCP23017 / PCA8575 -// #define USES_P062 // MPR121 -// #define USES_P063 // TTP229 -// #define USES_P064 // APDS9960 Gesture -// #define USES_P065 // DRF0299 -// #define USES_P066 // VEML6040 -// #define USES_P067 // HX711_Load_Cell -// #define USES_P068 // SHT3x -// #define USES_P069 // LM75A - -// #define USES_P070 // NeoPixel_Clock -// #define USES_P071 // Kamstrup401 -// #define USES_P072 // HDC1000/HDC1008/HDC1010/HDC1050/HDC1080 -// #define USES_P073 // 7-segment display -// #define USES_P074 // TSL2591 -// #define USES_P075 // Nextion -// #define USES_P076 // HLW8012/BL0937 (Shelly Plug S, Sonoff POW R1, Huafan SS, KMC 70011, Aplic WDP303075, SK03 Outdoor, BlitzWolf SHP, Teckin, Teckin US, Gosund SP1 v23) -// #define USES_P077 // CSE7766 (Sonoff S31, Sonoff POW R2, Sonoff POW R3xx(D), Sonoff Dual R3) -// #define USES_P078 // Eastron SDMxxx Modbus -// #define USES_P079 // Wemos / Lolin Motorshield - -// #define USES_P080 // iButton Sensor DS1990A -// #define USES_P081 // Cron -// #define USES_P082 // GPS -// #define USES_P083 // SGP30 TVOC -// #define USES_P084 // VEML6070 -// #define USES_P085 // AcuDC24x -// #define USES_P086 // Receiving values according Homie convention. Works together with C014 Homie controller -// #define USES_P087 // Serial Proxy -// #define USES_P088 // HeatpumpIR -// #define USES_P089 // Ping - -// #define USES_P090 // CCS811 TVOC -// #define USES_P091 // Serial MCU controlled switch -// #define USES_P092 // DLbus -// #define USES_P093 // Mitsubishi Heat Pump -// #define USES_P094 // CUL Reader -// #define USES_P095 // ILI934x / ILI948x -// #define USES_P096 // eInk -// #define USES_P097 // ESP32 Touch -// #define USES_P098 // PWM Motor -// #define USES_P099 // XPT2046 touchscreen - -// #define USES_P100 // DS2423 counter -// #define USES_P101 // Wake On Lan -// #define USES_P102 // PZEM-004Tv30-Multiple -// #define USES_P103 // Atlas Scientific EZO Sensors (pH, ORP, EZO, DO) -// #define USES_P104 // MAX7219 dot matrix -// #define USES_P105 // AHT10/AHT2x -// #define USES_P106 // BME68x -// #define USES_P107 // SI1145 -// #define USES_P108 // DDS238-x ZN Modbus energy meters -// #define USES_P109 // ThermoOLED - -// #define USES_P110 // VL53L0X Time of Flight sensor -// #define USES_P111 // MFRC522 RFID reader -// #define USES_P112 // AS7265x -// #define USES_P113 // VL53L1X ToF -// #define USES_P114 // VEML6075 -// #define USES_P115 // MAX1704x -// #define USES_P116 // ST77xx -// #define USES_P117 // SCD30 -// #define USES_P118 // Itho -// #define USES_P119 // ITG3205 Gyro - -// #define USES_P120 // ADXL345 I2C Acceleration / Gravity -// #define USES_P121 // HMC5883L -// #define USES_P122 // SHT2x -// #define USES_P124 // I2C Multi Relay -// #define USES_P125 // ADXL345 SPI Acceleration / Gravity -// #define USES_P126 // 74HC595 Shift register -// #define USES_P127 // CDM7160 -// #define USES_P128 // NeoPixel (BusFX) -// #define P128_USES_GRB // Default -// #define P128_USES_GRBW // Select 1 option, only first one enabled from this list will be used -// #define P128_USES_RGB -// #define P128_USES_RGBW -// #define P128_USES_BRG -// #define P128_USES_BGR -// #define P128_USES_RBG -// #define P128_ENABLE_FAKETV 1 // Enable(1)/Disable(0) FakeTV effect, disabled by default on ESP8266 (.bin size issue), enabled by default on ESP32 -// #define USES_P129 // 74HC165 Input shiftregisters - -// #define USES_P131 // NeoPixel Matrix -// #define USES_P132 // INA3221 -// #define USES_P133 // LTR390 UV -// #define USES_P134 // A02YYUW -// #define USES_P135 // SCD4x -// #define P135_FEATURE_RESET_COMMANDS 1 // Enable/Disable quite spacious (~950 bytes) 'selftest' and 'factoryreset' subcommands -// #define USES_P137 // AXP192 -// #define USES_P138 // IP5306 - -// #define USES_P141 // PCD8544 Nokia 5110 LCD -// #define USES_P143 // I2C Rotary encoders -// #define P143_FEATURE_INCLUDE_M5STACK 0 // Enabled by default, can be turned off here -// #define P143_FEATURE_INCLUDE_DFROBOT 0 // Enabled by default, can be turned off here -// #define P143_FEATURE_COUNTER_COLORMAPPING 0 // Enabled by default, can be turned off here - -// #define USES_P144 // PM1006(K) (Vindriktning) -// #define USES_P145 // MQxxx (MQ135 CO2, MQ3 Alcohol) -// #define USES_P146 // Cache Reader -// #define USES_P147 // SGP4x -// #define P147_FEATURE_GASINDEXALGORITHM 0 // Enabled by default, can be turned off here - -// #define USES_P148 // POWR3xxD/THR3xxD -// #define USES_P150 // TMP117 Temperature -// #define USES_P151 // Honeywell Pressure -// #define USES_P152 // ESP32 DAC -// #define USES_P153 // SHT4x -// #define USES_P154 // BMP3xx - -// #define USES_P159 // Presence - LD2410 Radar detection - -/* - ####################################################################################################### - ########### Controllers - ####################################################################################################### - */ - - -// #define USES_C001 // Domoticz HTTP -// #define USES_C002 // Domoticz MQTT -// #define USES_C003 // Nodo telnet -// #define USES_C004 // ThingSpeak -// #define USES_C005 // Home Assistant (openHAB) MQTT -// #define USES_C006 // PiDome MQTT -// #define USES_C007 // Emoncms -// #define USES_C008 // Generic HTTP -// #define USES_C009 // FHEM HTTP -// #define USES_C010 // Generic UDP -// #define USES_C011 // Generic HTTP Advanced -// #define USES_C012 // Blynk HTTP -// #define USES_C013 // ESPEasy P2P network -// #define USES_C014 // homie 3 & 4dev MQTT -// #define USES_C015 // Blynk -// #define USES_C016 // Cache controller -// #define USES_C017 // Zabbix -// #define USES_C018 // TTN/RN2483 - - -/* - ####################################################################################################### - ########### Notifiers - ####################################################################################################### - */ - - -// #define USES_N001 // Email -// #define USES_N002 // Buzzer - - -#endif // ESPEASY_CUSTOM_H +#ifndef ESPEASY_CUSTOM_H +#define ESPEASY_CUSTOM_H + +/* + To modify the stock configuration without changing the EspEasy.ino file : + 1) rename this file to "Custom.h" (It is ignored by Git) + 2) define your own settings below + 3) define USE_CUSTOM_H as a build flags. ie : export PLATFORMIO_BUILD_FLAGS="'-DUSE_CUSTOM_H'" + */ + + +/* + ####################################################################################################### + Your Own Default Settings + ####################################################################################################### + You can basically ovveride ALL macro defined in ESPEasy.ino. + Don't forget to first #undef each existing #define that you add below. + But since this Custom.h is included before other defines are made, you don't have to undef a lot of defines. + Here are some examples: + */ + +// --- Feature Flagging --------------------------------------------------------- +// Can be set to 1 to enable, 0 to disable, or not set to use the default (usually via define_plugin_sets.h) + +#define FEATURE_RULES_EASY_COLOR_CODE 1 // Use code highlighting, autocompletion and command suggestions in Rules +#define FEATURE_ESPEASY_P2P 1 // (1/0) enables the ESP Easy P2P protocol +#define FEATURE_ARDUINO_OTA 1 // enables the Arduino OTA capabilities +#define FEATURE_THINGSPEAK_EVENT 1 // generate an event when requesting last value of a field in thingspeak via SendToHTTP(e.g. sendToHTTP,api.thingspeak.com,80,/channels/1667332/fields/5/last) +// #define FEATURE_SD 1 // Enable SD card support +// #define FEATURE_DOWNLOAD 1 // Enable downloading a file from an url + +#ifdef BUILD_GIT +# undef BUILD_GIT +#endif // ifdef BUILD_GIT + +#define BUILD_GIT "My Build: " __DATE__ " " __TIME__ + + +#define DEFAULT_NAME "MyEspEasyDevice" // Enter your device friendly name +#define UNIT 0 // Unit Number +#define DEFAULT_DELAY 60 // Sleep Delay in seconds + +// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- +#define DEFAULT_AP_IP 192, 168, 4, 1 // Enter IP address (comma separated) for AP (config) mode +#define DEFAULT_AP_SUBNET 255, 255, 255, 0 // Enter IP address (comma separated) for AP (config) mode +#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode + +// --- Wifi Client Mode ----------------------------------------------------------------------------- +#define DEFAULT_SSID "MyHomeSSID" // Enter your network SSID +#define DEFAULT_KEY "MySuperSecretPassword" // Enter your network WPA key +#define DEFAULT_SSID2 "" // Enter your fallback network SSID +#define DEFAULT_KEY2 "" // Enter your fallback network WPA key +#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs +#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP +#define DEFAULT_IP "192.168.0.50" // Enter your IP address +#define DEFAULT_DNS "192.168.0.1" // Enter your DNS +#define DEFAULT_GW "192.168.0.1" // Enter your Gateway +#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet +#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver +#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver +#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: +// ONLY_IP_RANGE_ALLOWED +#define DEFAULT_ADMIN_USERNAME "admin" +#define DEFAULT_ADMIN_PASS "" + +#define DEFAULT_WIFI_CONNECTION_TIMEOUT 10000 // minimum timeout in ms for WiFi to be connected. +#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) +#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. +#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. +#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power +// used and need reboot to reset mode) +#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP + // packets to announce itself. +#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line + // 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_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 ------------------------------------------------------------------------------ +#define DEFAULT_CONTROLLER false // true or false enabled or disabled, set 1st controller + // defaults +#define DEFAULT_CONTROLLER_ENABLED true // Enable default controller by default +#define DEFAULT_CONTROLLER_USER "" // Default controller user +#define DEFAULT_CONTROLLER_PASS "" // Default controller Password + +// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! +#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub +#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub +#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address +#define DEFAULT_SERVER_HOST "" // Server hostname +#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP +#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers + +#define DEFAULT_PORT 8080 // Enter your Server port value +#define DEFAULT_CONTROLLER_TIMEOUT 100 // Default timeout in msec + +#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications + // 0 = Stand-alone (no controller set) + // 1 = Domoticz HTTP + // 2 = Domoticz MQTT + // 3 = Nodo Telnet + // 4 = ThingSpeak + // 5 = Home Assistant (openHAB) MQTT + // 6 = PiDome MQTT + // 7 = EmonCMS + // 8 = Generic HTTP + // 9 = FHEM HTTP + +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SDA 4 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SDA -1 // Undefined +#endif +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SCL 5 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SCL -1 // Undefined +#endif +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips +#define FEATURE_I2C_DEVICE_SCAN 1 + +#define DEFAULT_SPI 0 //0=disabled 1=enabled and for ESP32 there is option 2 =HSPI + +#define DEFAULT_PIN_STATUS_LED (-1) +#define DEFAULT_PIN_STATUS_LED_INVERSED true + +#define DEFAULT_PIN_RESET_BUTTON (-1) + + +#define DEFAULT_USE_RULES false // (true|false) Enable Rules? +#define DEFAULT_RULES_OLDENGINE true + +#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? +#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full +#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment +#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages +#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic +#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message +#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message +#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 + +#define DEFAULT_USE_NTP false // (true|false) Use NTP Server +#define DEFAULT_NTP_HOST "" // NTP Server Hostname +#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) +#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving + +#define DEFAULT_LATITUDE 0.0f // Default Latitude +#define DEFAULT_LONGITUDE 0.0f // Default Longitude + +#define DEFAULT_SYSLOG_IP "" // Syslog IP Address +#define DEFAULT_SYSLOG_PORT 0 // Standard syslog port: 514 +#define DEFAULT_SYSLOG_FACILITY 0 // kern +#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level +#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level +#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level +#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level +#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card + +#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port +#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate + +#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) + + +// Factory Reset defaults +#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true +#define DEFAULT_FACTORY_RESET_KEEP_WIFI true +#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true +#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true +#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true + + +#define BUILD_NO_DEBUG + +// Custom built-in url for hosting JavaScript and CSS files. +#define CUSTOM_BUILD_CDN_URL "https://cdn.jsdelivr.net/gh/letscontrolit/ESPEasy@mega/static/" + + + +// Special SSID/key setup only to be used in custom builds. + +// Deployment SSID will be used only when the configured SSIDs are not reachable and/or no credentials are set. +// This to make deployment of large number of nodes easier +#define CUSTOM_DEPLOYMENT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease deployment +#define CUSTOM_DEPLOYMENT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease deployment +#define CUSTOM_SUPPORT_SSID "" // Enter SSID not shown in UI, to be used on custom builds to ease support +#define CUSTOM_SUPPORT_KEY "" // Enter key not shown in UI, to be used on custom builds to ease support + + +// Emergency fallback SSID will only be attempted in the first 10 minutes after reboot. +// When found, the unit will connect to it and depending on the built in flag, it will either just connect to it, or clear set credentials. +// Use case: User connects to a public AP which does need to agree on an agreement page for the rules of conduct (e.g. open APs) +// This is seen as a valid connection, so the unit will not reconnect to another node and thus becomes inaccessible. +#define CUSTOM_EMERGENCY_FALLBACK_SSID "" // Enter SSID not shown in UI, to be used to regain access to the node +#define CUSTOM_EMERGENCY_FALLBACK_KEY "" // Enter key not shown in UI, to be used to regain access to the node + +#define CUSTOM_EMERGENCY_FALLBACK_RESET_CREDENTIALS false +#define CUSTOM_EMERGENCY_FALLBACK_START_AP false + +#define CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME 10 + +// Allow for remote provisioning of a node. +// This is only allowed for custom builds. +// To setup the configuration of the provisioning file, one must also define FEATURE_SETTINGS_ARCHIVE +// Default setting is to not allow to configure a node remotely, unless explicitly enabled. +// #define FEATURE_CUSTOM_PROVISIONING 1 + +#define FEATURE_SSDP 1 + +#define FEATURE_EXT_RTC 1 // Support for external RTC clock modules like PCF8563/PCF8523/DS3231/DS1307 + +#define FEATURE_PLUGIN_STATS 1 // Support collecting historic data + computing stats on historic data +#ifdef ESP8266 +# define PLUGIN_STATS_NR_ELEMENTS 16 +#endif // ifdef ESP8266 +# ifdef ESP32 +# define PLUGIN_STATS_NR_ELEMENTS 64 +#endif // ifdef ESP32 +#define FEATURE_CHART_JS 1 // Support for drawing charts, like PluginStats historic data + +// Optional alternative CDN links: +// Chart.js: (only used when FEATURE_CHART_JS is enabled) +// #define CDN_URL_CHART_JS "https://cdn.jsdelivr.net/npm/chart.js@4.1.2/dist/chart.umd.min.js" +// JQuery: +// #define CDN_URL_JQUERY "https://code.jquery.com/jquery-3.6.0.min.js" + + +// #define FEATURE_SETTINGS_ARCHIVE 1 +// #define FEATURE_I2CMULTIPLEXER 1 +// #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 1 +// #define PLUGIN_USES_ADAFRUITGFX // Used by Display plugins using Adafruit GFX library +// #define ADAGFX_ARGUMENT_VALIDATION 0 // Disable argument validation in AdafruitGFX_helper +// #define ADAGFX_SUPPORT_7COLOR 0 // Disable the support of 7-color eInk displays by AdafruitGFX_helper +// #define FEATURE_SEND_TO_HTTP 1 // Enable availability of the SendToHTTP command +// #define FEATURE_POST_TO_HTTP 1 // Enable availability of the PostToHTTP command +// #define FEATURE_PUT_TO_HTTP 1 // Enable availability of the PutToHTTP command +// #define FEATURE_I2C_DEVICE_CHECK 0 // Disable the I2C Device check feature +// #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching the I2C address from I2C plugins. Will be enabled when FEATURE_I2C_DEVICE_CHECK is enabled +// #define FEATURE_RTTTL 1 // Enable rtttl command +// #define FEATURE_ANYRTTTL_LIB 1 // Use AnyRttl library for RTTTL handling +// #define FEATURE_ANYRTTTL_ASYNC 1 // When AnyRttl enabled, use Async (nonblocking) mode instead of the default Blocking mode +// #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense + +#if FEATURE_CUSTOM_PROVISIONING +// For device models, see src/src/DataTypes/DeviceModel.h +// #ifdef ESP32 +// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default +// #endif +// #ifdef ESP8266 +// #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default +// #endif +// #define DEFAULT_PROVISIONING_FETCH_RULES1 false +// #define DEFAULT_PROVISIONING_FETCH_RULES2 false +// #define DEFAULT_PROVISIONING_FETCH_RULES3 false +// #define DEFAULT_PROVISIONING_FETCH_RULES4 false +// #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false +// #define DEFAULT_PROVISIONING_FETCH_SECURITY false +// #define DEFAULT_PROVISIONING_FETCH_CONFIG false +// #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false +// #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false +// #define DEFAULT_PROVISIONING_SAVE_URL false +// #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false +// #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false +// #define DEFAULT_PROVISIONING_URL "" +// #define DEFAULT_PROVISIONING_USER "" +// #define DEFAULT_PROVISIONING_PASS "" +#endif + + + + +/* + ####################################################################################################### + Defining web interface + ####################################################################################################### + */ + +#define MENU_INDEX_MAIN_VISIBLE true +/* +#define MENU_INDEX_CONFIG_VISIBLE false +#define MENU_INDEX_CONTROLLERS_VISIBLE false +#define MENU_INDEX_HARDWARE_VISIBLE false +#define MENU_INDEX_DEVICES_VISIBLE false +#define MENU_INDEX_RULES_VISIBLE false +#define MENU_INDEX_NOTIFICATIONS_VISIBLE false +#define MENU_INDEX_TOOLS_VISIBLE false +*/ + +#define MAIN_PAGE_SHOW_SYSINFO_BUTTON true +#define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON true +#define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false + +#define MAIN_PAGE_SHOW_NODE_LIST_BUILD true +#define MAIN_PAGE_SHOW_NODE_LIST_TYPE true + +#define SETUP_PAGE_SHOW_CONFIG_BUTTON true + +// #define FEATURE_AUTO_DARK_MODE 0 // 0 = Disable auto-dark mode +// #define FEATURE_EXTENDED_TASK_VALUE_TYPES 0 // 0 = Disable extra task value types like 64 bit ints, double, etc. in Dummy tasks +// #define FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE 0 // 0 = switch to float as floating point type for rules/formula processing. + +//#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON + +#define SHOW_SYSINFO_JSON 1 //Enables the sysinfo_json page (by default is enabled when WEBSERVER_NEW_UI is enabled too) + +/* + ####################################################################################################### + CSS / template + ####################################################################################################### + */ +/* +#define WEBPAGE_TEMPLATE_DEFAULT_HEADER "

ESP Easy Mega: {{title}}


" +#define WEBPAGE_TEMPLATE_DEFAULT_FOOTER "" +#define WEBPAGE_TEMPLATE_AP_HEADER "

Welcome to ESP Easy Mega AP

" +#define WEBPAGE_TEMPLATE_HIDE_HELP_BUTTON +*/ +// Embed Custom CSS in Custom.h: +/* +#define WEBSERVER_EMBED_CUSTOM_CSS +static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { +... +,0}; +*/ + + +/* + ####################################################################################################### + Special settings (rendering settings incompatible with other builds) + ####################################################################################################### + */ + +// #define FEATURE_NON_STANDARD_24_TASKS 1 + +/* + ####################################################################################################### + Your Own selection of plugins and controllers + ####################################################################################################### + */ + +#define CONTROLLER_SET_NONE +#define NOTIFIER_SET_NONE +#define PLUGIN_SET_NONE + + +/* + ####################################################################################################### + ########### Plugins + ####################################################################################################### + */ + +// #define FEATURE_SERVO 1 // Uncomment and set to 0 to explicitly disable SERVO support + + +// #define USES_P001 // Switch +// #define USES_P002 // ADC +// #define USES_P003 // Pulse +// #define USES_P004 // 1-Wire Temperature (Dallas/Maxim DS18B20) +// #define USES_P005 // DHT11/12/22 SONOFF2301/7021/MS01 +// #define USES_P006 // BMP085/180 +// #define USES_P007 // PCF8591 +// #define USES_P008 // Wiegand (RFID) +// #define USES_P009 // MCP23017 + +// #define USES_P010 // BH1750 +// #define USES_P011 // ProMini Extender +// #define USES_P012 // LCD2004 +// #define USES_P013 // HC-SR04/RCW-0001 +// #define USES_P014 // SI70xx/HTU21D +// #define USES_P015 // TSL2561 +// #define USES_P017 // PN532 +// #define USES_P018 // GP2Y10 +// #define USES_P019 // PCF8574 + +// #define USES_P020 // Ser2Net +// #define USES_P021 // Level Control +// #define USES_P022 // PCA9685 +// #define USES_P023 // OLED SSD1306 +// #define USES_P024 // MLX90614 +// #define USES_P025 // ADS1x15 +// #define USES_P026 // SysInfo +// #define USES_P027 // INA219 +// #define USES_P028 // BMx280 +// #define USES_P029 // Domoticz MQTT Helper + +// #define USES_P031 // SHT1x +// #define USES_P032 // MS5611 (GY-63) +// #define USES_P033 // Dummy Device +// #define USES_P034 // DHT12 +// #define USES_P036 // OLED SSD1306/SH1106 Framed +// #define P036_FEATURE_DISPLAY_PREVIEW 1 // Enable Preview feature, shows on-display content on Devices overview page +// #define P036_FEATURE_ALIGN_PREVIEW 1 // Enable center/right-align feature when preview is enabled (auto-disabled for 1M builds) +// #define P036_ENABLE_TICKER 1 // Enable ticker function +// #define USES_P037 // MQTT Import +// #define P037_MAPPING_SUPPORT 1 // Enable Value mapping support +// #define P037_FILTER_SUPPORT 1 // Enable filtering support +// #define P037_JSON_SUPPORT 1 // Enable Json support +// #define USES_P038 // NeoPixel +// #define USES_P039 // Thermocouple + +// #define USES_P040 // RFID - ID12LA/RDM6300 +// #define USES_P041 // NeoPixel (Word Clock) +// #define USES_P042 // NeoPixel (Candle) +// #define USES_P043 // ClkOutput +// #define USES_P044 // P1 Wifi Gateway +// #define USES_P045 // MPU6050 +// #define USES_P046 // Ventus W266 +// #define USES_P047 // Soil moisture sensor +// #define USES_P048 // Motoshield v2 +// #define USES_P049 // MH-Z19 + +// #define USES_P050 // TCS34725 RGB Color Sensor with IR filter and White LED +// #define USES_P051 // AM2320 +// #define USES_P052 // SenseAir +// #define USES_P053 // PMSx003 / PMSx003ST +// #define USES_P054 // DMX512 +// #define USES_P055 // Chiming +// #define USES_P056 // SDS011/018/198 +// #define USES_P057 // HT16K33_LED +// #define USES_P058 // HT16K33_KeyPad +// #define USES_P059 // Rotary Encoder + +// #define USES_P060 // MCP3221 +// #define USES_P061 // PCF8574 / MCP23017 / PCA8575 +// #define USES_P062 // MPR121 +// #define USES_P063 // TTP229 +// #define USES_P064 // APDS9960 Gesture +// #define USES_P065 // DRF0299 +// #define USES_P066 // VEML6040 +// #define USES_P067 // HX711_Load_Cell +// #define USES_P068 // SHT3x +// #define USES_P069 // LM75A + +// #define USES_P070 // NeoPixel_Clock +// #define USES_P071 // Kamstrup401 +// #define USES_P072 // HDC1000/HDC1008/HDC1010/HDC1050/HDC1080 +// #define USES_P073 // 7-segment display +// #define USES_P074 // TSL2591 +// #define USES_P075 // Nextion +// #define USES_P076 // HLW8012/BL0937 (Shelly Plug S, Sonoff POW R1, Huafan SS, KMC 70011, Aplic WDP303075, SK03 Outdoor, BlitzWolf SHP, Teckin, Teckin US, Gosund SP1 v23) +// #define USES_P077 // CSE7766 (Sonoff S31, Sonoff POW R2, Sonoff POW R3xx(D), Sonoff Dual R3) +// #define USES_P078 // Eastron SDMxxx Modbus +// #define USES_P079 // Wemos / Lolin Motorshield + +// #define USES_P080 // iButton Sensor DS1990A +// #define USES_P081 // Cron +// #define USES_P082 // GPS +// #define USES_P083 // SGP30 TVOC +// #define USES_P084 // VEML6070 +// #define USES_P085 // AcuDC24x +// #define USES_P086 // Receiving values according Homie convention. Works together with C014 Homie controller +// #define USES_P087 // Serial Proxy +// #define USES_P088 // HeatpumpIR +// #define USES_P089 // Ping + +// #define USES_P090 // CCS811 TVOC +// #define USES_P091 // Serial MCU controlled switch +// #define USES_P092 // DLbus +// #define USES_P093 // Mitsubishi Heat Pump +// #define USES_P094 // CUL Reader +// #define USES_P095 // ILI934x / ILI948x +// #define USES_P096 // eInk +// #define USES_P097 // ESP32 Touch +// #define USES_P098 // PWM Motor +// #define USES_P099 // XPT2046 touchscreen + +// #define USES_P100 // DS2423 counter +// #define USES_P101 // Wake On Lan +// #define USES_P102 // PZEM-004Tv30-Multiple +// #define USES_P103 // Atlas Scientific EZO Sensors (pH, ORP, EZO, DO) +// #define USES_P104 // MAX7219 dot matrix +// #define USES_P105 // AHT10/AHT2x +// #define USES_P106 // BME68x +// #define USES_P107 // SI1145 +// #define USES_P108 // DDS238-x ZN Modbus energy meters +// #define USES_P109 // ThermoOLED + +// #define USES_P110 // VL53L0X Time of Flight sensor +// #define USES_P111 // MFRC522 RFID reader +// #define USES_P112 // AS7265x +// #define USES_P113 // VL53L1X ToF +// #define USES_P114 // VEML6075 +// #define USES_P115 // MAX1704x +// #define USES_P116 // ST77xx +// #define USES_P117 // SCD30 +// #define USES_P118 // Itho +// #define USES_P119 // ITG3205 Gyro + +// #define USES_P120 // ADXL345 I2C Acceleration / Gravity +// #define USES_P121 // HMC5883L +// #define USES_P122 // SHT2x +// #define USES_P123 // I2C Touchscreens +// #define USES_P124 // I2C Multi Relay +// #define USES_P125 // ADXL345 SPI Acceleration / Gravity +// #define USES_P126 // 74HC595 Shift register +// #define USES_P127 // CDM7160 +// #define USES_P128 // NeoPixel (BusFX) +// #define P128_USES_GRB // Default +// #define P128_USES_GRBW // Select 1 option, only first one enabled from this list will be used +// #define P128_USES_RGB +// #define P128_USES_RGBW +// #define P128_USES_BRG +// #define P128_USES_BGR +// #define P128_USES_RBG +// #define P128_ENABLE_FAKETV 1 // Enable(1)/Disable(0) FakeTV effect, disabled by default on ESP8266 (.bin size issue), enabled by default on ESP32 +// #define USES_P129 // 74HC165 Input shiftregisters + +// #define USES_P131 // NeoPixel Matrix +// #define USES_P132 // INA3221 +// #define USES_P133 // LTR390 UV +// #define USES_P134 // A02YYUW +// #define USES_P135 // SCD4x +// #define P135_FEATURE_RESET_COMMANDS 1 // Enable/Disable quite spacious (~950 bytes) 'selftest' and 'factoryreset' subcommands +// #define USES_P137 // AXP192 +// #define USES_P138 // IP5306 + +// #define USES_P141 // PCD8544 Nokia 5110 LCD +// #define USES_P143 // I2C Rotary encoders +// #define P143_FEATURE_INCLUDE_M5STACK 0 // Enabled by default, can be turned off here +// #define P143_FEATURE_INCLUDE_DFROBOT 0 // Enabled by default, can be turned off here +// #define P143_FEATURE_COUNTER_COLORMAPPING 0 // Enabled by default, can be turned off here + +// #define USES_P144 // PM1006(K) (Vindriktning) +// #define USES_P145 // MQxxx (MQ135 CO2, MQ3 Alcohol) +// #define USES_P146 // Cache Reader +// #define USES_P147 // SGP4x +// #define P147_FEATURE_GASINDEXALGORITHM 0 // Enabled by default, can be turned off here + +// #define USES_P148 // POWR3xxD/THR3xxD +// #define USES_P150 // TMP117 Temperature +// #define USES_P151 // Honeywell Pressure +// #define USES_P152 // ESP32 DAC +// #define USES_P153 // SHT4x +// #define USES_P154 // BMP3xx I2C + +// #define USES_P159 // Presence - LD2410 Radar detection + +// #define USES_P162 // Output - MCP42xxx Digipot +// #define USES_P164 // Gases - ENS16x TVOC/eCO2 +// #define USES_P166 // Output - GP8403 Dual channel DAC (Digital Analog Converter) +// #define USES_P167 // Environment - Sensirion SEN5x / Ikea Vindstyrka +// #define USES_P168 // Light - VEML6030/VEML7700 +// #define USES_P169 // Environment - AS3935 Lightning Detector +// #define USES_P170 // Input - I2C Liquid level sensor +// #define USES_P172 // BMP3xx SPI. + +/* + ####################################################################################################### + ########### Controllers + ####################################################################################################### + */ + + +// #define USES_C001 // Domoticz HTTP +// #define USES_C002 // Domoticz MQTT +// #define USES_C003 // Nodo telnet +// #define USES_C004 // ThingSpeak +// #define USES_C005 // Home Assistant (openHAB) MQTT +// #define USES_C006 // PiDome MQTT +// #define USES_C007 // Emoncms +// #define USES_C008 // Generic HTTP +// #define USES_C009 // FHEM HTTP +// #define USES_C010 // Generic UDP +// #define USES_C011 // Generic HTTP Advanced +// #define USES_C012 // Blynk HTTP +// #define USES_C013 // ESPEasy P2P network +// #define USES_C014 // homie 3 & 4dev MQTT +// #define USES_C015 // Blynk +// #define USES_C016 // Cache controller +// #define USES_C017 // Zabbix +// #define USES_C018 // TTN/RN2483 + + +/* + ####################################################################################################### + ########### Notifiers + ####################################################################################################### + */ + + +// #define USES_N001 // Email +// #define USES_N002 // Buzzer + + +#endif // ESPEASY_CUSTOM_H diff --git a/src/CustomIR-sample.h b/src/CustomIR-sample.h index ebf76e1b6..59b00b15f 100644 --- a/src/CustomIR-sample.h +++ b/src/CustomIR-sample.h @@ -24,6 +24,9 @@ // Set flags to enable (1) or disable (0) the DECODE_ and/or SEND_ feature for a specific IR device // To limit ESPEasy build-size you can disable DECODE_ or SEND_ flags for devices not needed +// Decode any arbitrary IR message into a 32-bit code value: +// #define DECODE_HASH 0 // Instead of decoding using a standard encoding scheme, This will give a unique value for each different code (probably), for most code systems + // SEND-ONLY protocols: // #define SEND_GLOBALCACHE 0 // Is used by many sending protocols, so should probably be left to default // #define SEND_PRONTO 0 @@ -34,6 +37,7 @@ // Standard: Use defaults for up to library version 2.8.2 // Change as desired after copying CustomIR-sample.h to CustomIR.h +// #define DECODE_HASH 0 // #define DECODE_RC5 0 // #define SEND_RC5 0 // #define DECODE_RC6 0 diff --git a/src/ESPEasy.ino b/src/ESPEasy.ino index f6e2e3906..d0fb77761 100644 --- a/src/ESPEasy.ino +++ b/src/ESPEasy.ino @@ -1,151 +1,151 @@ - -#ifdef CONTINUOUS_INTEGRATION -# pragma GCC diagnostic error "-Wall" -#else // ifdef CONTINUOUS_INTEGRATION -# pragma GCC diagnostic warning "-Wall" -#endif // ifdef CONTINUOUS_INTEGRATION - -// Include this as first, to make sure all defines are active during the entire compile. -// See: https://www.letscontrolit.com/forum/viewtopic.php?f=4&t=7980 -// If Custom.h build from Arduino IDE is needed, uncomment #define USE_CUSTOM_H in ESPEasy_common.h -#include "ESPEasy_common.h" - -#ifdef USE_CUSTOM_H - -// make the compiler show a warning to confirm that this file is inlcuded - # warning "**** Using Settings from Custom.h File ***" -#endif // ifdef USE_CUSTOM_H - - -// Needed due to preprocessor issues. -#ifdef PLUGIN_SET_GENERIC_ESP32 - # ifndef ESP32 - # define ESP32 - # endif // ifndef ESP32 -#endif // ifdef PLUGIN_SET_GENERIC_ESP32 - - -/****************************************************************************************************************************\ - * Arduino project "ESP Easy" © Copyright www.letscontrolit.com - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You received a copy of the GNU General Public License along with this program in file 'License.txt'. - * - * IDE download : https://www.arduino.cc/en/Main/Software - * ESP8266 Package : https://github.com/esp8266/Arduino - * - * Source Code : https://github.com/ESP8266nu/ESPEasy - * Support : http://www.letscontrolit.com - * Discussion : http://www.letscontrolit.com/forum/ - * - * Additional information about licensing can be found at : http://www.gnu.org/licenses - \*************************************************************************************************************************/ - -// This file incorporates work covered by the following copyright and permission notice: - -/****************************************************************************************************************************\ - * Arduino project "Nodo" © Copyright 2010..2015 Paul Tonkes - * - * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * You received a copy of the GNU General Public License along with this program in file 'License.txt'. - * - * Voor toelichting op de licentievoorwaarden zie : http://www.gnu.org/licenses - * Uitgebreide documentatie is te vinden op : http://www.nodo-domotica.nl - * Compiler voor deze programmacode te downloaden op : http://arduino.cc - \*************************************************************************************************************************/ - -// Simple Arduino sketch for ESP module, supporting: -// ================================================================================= -// Simple switch inputs and direct GPIO output control to drive relays, mosfets, etc -// Analog input (ESP-7/12 only) -// Pulse counters -// Dallas OneWire DS18b20 temperature sensors -// DHT11/22/12 humidity sensors -// BMP085 I2C Barometric Pressure sensor -// PCF8591 4 port Analog to Digital converter (I2C) -// RFID Wiegand-26 reader -// MCP23017 I2C IO Expanders -// BH1750 I2C Luminosity sensor -// Arduino Pro Mini with IO extender sketch, connected through I2C -// LCD I2C display 4x20 chars -// HC-SR04 Ultrasonic distance sensor -// SI7021 I2C temperature/humidity sensors -// TSL2561 I2C Luminosity sensor -// TSOP4838 IR receiver -// PN532 RFID reader -// Sharp GP2Y10 dust sensor -// PCF8574 I2C IO Expanders -// PCA9685 I2C 16 channel PWM driver -// OLED I2C display with SSD1306 driver -// MLX90614 I2C IR temperature sensor -// ADS1115 I2C ADC -// INA219 I2C voltage/current sensor -// BME280 I2C temp/hum/baro sensor -// MSP5611 I2C temp/baro sensor -// BMP280 I2C Barometric Pressure sensor -// SHT1X temperature/humidity sensors -// Ser2Net server -// DL-Bus (Technische Alternative) - -// Define globals before plugin sets to allow a personal override of the selected plugins -#include "ESPEasy-Globals.h" - -// Must be included after all the defines, since it is using TASKS_MAX -#include "_Plugin_Helper.h" - -// Plugin helper needs the defined controller sets, thus include after 'define_plugin_sets.h' -#include "src/Helpers/_CPlugin_Helper.h" - - -#include "src/ESPEasyCore/ESPEasy_setup.h" -#include "src/ESPEasyCore/ESPEasy_loop.h" - - -#ifdef PHASE_LOCKED_WAVEFORM -# include -#endif // ifdef PHASE_LOCKED_WAVEFORM - -#if FEATURE_ADC_VCC -ADC_MODE(ADC_VCC); -#endif // if FEATURE_ADC_VCC - - - -#ifdef CORE_POST_2_5_0 - -/*********************************************************************************************\ -* Pre-init -\*********************************************************************************************/ -void preinit(); -void preinit() { - system_phy_set_powerup_option(3); - // Global WiFi constructors are not called yet - // (global class instances like WiFi, Serial... are not yet initialized).. - // No global object methods or C++ exceptions can be called in here! - // The below is a static class method, which is similar to a function, so it's ok. - #ifndef CORE_POST_3_0_0 - //ESP8266WiFiClass::preinitWiFiOff(); - #endif - - // Prevent RF calibration on power up. - // TD-er: disabled on 2021-06-07 as it may cause several issues with some boards. - // It cannot be made a setting as we can't read anything of our own settings. - //system_phy_set_powerup_option(RF_NO_CAL); -} - -#endif // ifdef CORE_POST_2_5_0 - - -void setup() { - ESPEasy_setup(); -} - -void loop() { - ESPEasy_loop(); -} + +#ifdef CONTINUOUS_INTEGRATION +# pragma GCC diagnostic error "-Wall" +#else // ifdef CONTINUOUS_INTEGRATION +# pragma GCC diagnostic warning "-Wall" +#endif // ifdef CONTINUOUS_INTEGRATION + +// Include this as first, to make sure all defines are active during the entire compile. +// See: https://www.letscontrolit.com/forum/viewtopic.php?f=4&t=7980 +// If Custom.h build from Arduino IDE is needed, uncomment #define USE_CUSTOM_H in ESPEasy_common.h +#include "ESPEasy_common.h" + +#ifdef USE_CUSTOM_H + +// make the compiler show a warning to confirm that this file is inlcuded + # warning "**** Using Settings from Custom.h File ***" +#endif // ifdef USE_CUSTOM_H + + +// Needed due to preprocessor issues. +#ifdef PLUGIN_SET_GENERIC_ESP32 + # ifndef ESP32 + # define ESP32 + # endif // ifndef ESP32 +#endif // ifdef PLUGIN_SET_GENERIC_ESP32 + + +/****************************************************************************************************************************\ + * Arduino project "ESP Easy" © Copyright www.letscontrolit.com + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You received a copy of the GNU General Public License along with this program in file 'License.txt'. + * + * IDE download : https://www.arduino.cc/en/Main/Software + * ESP8266 Package : https://github.com/esp8266/Arduino + * + * Source Code : https://github.com/ESP8266nu/ESPEasy + * Support : http://www.letscontrolit.com + * Discussion : http://www.letscontrolit.com/forum/ + * + * Additional information about licensing can be found at : http://www.gnu.org/licenses + \*************************************************************************************************************************/ + +// This file incorporates work covered by the following copyright and permission notice: + +/****************************************************************************************************************************\ + * Arduino project "Nodo" © Copyright 2010..2015 Paul Tonkes + * + * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * You received a copy of the GNU General Public License along with this program in file 'License.txt'. + * + * Voor toelichting op de licentievoorwaarden zie : http://www.gnu.org/licenses + * Uitgebreide documentatie is te vinden op : http://www.nodo-domotica.nl + * Compiler voor deze programmacode te downloaden op : http://arduino.cc + \*************************************************************************************************************************/ + +// Simple Arduino sketch for ESP module, supporting: +// ================================================================================= +// Simple switch inputs and direct GPIO output control to drive relays, mosfets, etc +// Analog input (ESP-7/12 only) +// Pulse counters +// Dallas OneWire DS18b20 temperature sensors +// DHT11/22/12 humidity sensors +// BMP085 I2C Barometric Pressure sensor +// PCF8591 4 port Analog to Digital converter (I2C) +// RFID Wiegand-26 reader +// MCP23017 I2C IO Expanders +// BH1750 I2C Luminosity sensor +// Arduino Pro Mini with IO extender sketch, connected through I2C +// LCD I2C display 4x20 chars +// HC-SR04 Ultrasonic distance sensor +// SI7021 I2C temperature/humidity sensors +// TSL2561 I2C Luminosity sensor +// TSOP4838 IR receiver +// PN532 RFID reader +// Sharp GP2Y10 dust sensor +// PCF8574 I2C IO Expanders +// PCA9685 I2C 16 channel PWM driver +// OLED I2C display with SSD1306 driver +// MLX90614 I2C IR temperature sensor +// ADS1115 I2C ADC +// INA219 I2C voltage/current sensor +// BME280 I2C temp/hum/baro sensor +// MSP5611 I2C temp/baro sensor +// BMP280 I2C Barometric Pressure sensor +// SHT1X temperature/humidity sensors +// Ser2Net server +// DL-Bus (Technische Alternative) + +// Define globals before plugin sets to allow a personal override of the selected plugins +#include "ESPEasy-Globals.h" + +// Must be included after all the defines, since it is using TASKS_MAX +#include "_Plugin_Helper.h" + +// Plugin helper needs the defined controller sets, thus include after 'define_plugin_sets.h' +#include "src/Helpers/_CPlugin_Helper.h" + + +#include "src/ESPEasyCore/ESPEasy_setup.h" +#include "src/ESPEasyCore/ESPEasy_loop.h" + + +#ifdef PHASE_LOCKED_WAVEFORM +# include +#endif // ifdef PHASE_LOCKED_WAVEFORM + +#if FEATURE_ADC_VCC +ADC_MODE(ADC_VCC); +#endif // if FEATURE_ADC_VCC + + + +#ifdef CORE_POST_2_5_0 + +/*********************************************************************************************\ +* Pre-init +\*********************************************************************************************/ +void preinit(); +void preinit() { + system_phy_set_powerup_option(3); + // Global WiFi constructors are not called yet + // (global class instances like WiFi, Serial... are not yet initialized).. + // No global object methods or C++ exceptions can be called in here! + // The below is a static class method, which is similar to a function, so it's ok. + #ifndef CORE_POST_3_0_0 + //ESP8266WiFiClass::preinitWiFiOff(); + #endif + + // Prevent RF calibration on power up. + // TD-er: disabled on 2021-06-07 as it may cause several issues with some boards. + // It cannot be made a setting as we can't read anything of our own settings. + //system_phy_set_powerup_option(RF_NO_CAL); +} + +#endif // ifdef CORE_POST_2_5_0 + + +void setup() { + ESPEasy_setup(); +} + +void loop() { + ESPEasy_loop(); +} diff --git a/src/_C001.cpp b/src/_C001.cpp index ff5f4b147..46ce884e6 100644 --- a/src/_C001.cpp +++ b/src/_C001.cpp @@ -1,156 +1,156 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C001 - -# include "src/Helpers/_CPlugin_DomoticzHelper.h" - -// ####################################################################################################### -// ########################### Controller Plugin 001: Domoticz HTTP ###################################### -// ####################################################################################################### - -# define CPLUGIN_001 -# define CPLUGIN_ID_001 1 -# define CPLUGIN_NAME_001 "Domoticz HTTP" - - -bool CPlugin_001(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_001; - proto.usesMQTT = false; - proto.usesAccount = true; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 8080; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_001); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c001_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c001_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C001_DelayHandler == nullptr || !validTaskIndex(event->TaskIndex)) { - break; - } - if (C001_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - if (event->idx != 0) - { - // We now create a URI for the request - const Sensor_VType sensorType = event->getSensorType(); - String url; - const size_t expectedSize = sensorType == Sensor_VType::SENSOR_TYPE_STRING ? 64 + event->String2.length() : 128; - - 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) - { - url += F("switchlight&idx="); - url += event->idx; - url += F("&switchcmd="); - - if (essentiallyZero(UserVar[event->BaseVarIndex])) { - url += F("Off"); - } else { - if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH) { - url += F("On"); - } else { - url += F("Set%20Level&level="); - url += UserVar[event->BaseVarIndex]; - } - } - } else { - url += F("udevice&idx="); - url += event->idx; - url += F("&nvalue=0"); - url += F("&svalue="); - url += formatDomoticzSensorType(event); - } - - // Add WiFi reception quality - url += F("&rssi="); - url += mapRSSItoDomoticz(); - # if FEATURE_ADC_VCC - url += F("&battery="); - url += mapVccToDomoticz(); - # endif // if FEATURE_ADC_VCC - - std::unique_ptr element(new C001_queue_element(event->ControllerIndex, event->TaskIndex, std::move(url))); - - success = C001_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE, - C001_DelayHandler->getNextScheduleTime()); - } - } // if ixd !=0 - else - { - addLog(LOG_LEVEL_ERROR, F("HTTP : IDX cannot be zero!")); - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c001_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c001_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C001_queue_element& element = static_cast(element_base); - -// *INDENT-ON* - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, element.txt); - } - # endif // ifndef BUILD_NO_DEBUG - - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - element.txt, - F("GET"), - EMPTY_STRING, - EMPTY_STRING, - httpCode); - return (httpCode >= 100) && (httpCode < 300); -} - -#endif // ifdef USES_C001 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C001 + +# include "src/Helpers/_CPlugin_DomoticzHelper.h" + +// ####################################################################################################### +// ########################### Controller Plugin 001: Domoticz HTTP ###################################### +// ####################################################################################################### + +# define CPLUGIN_001 +# define CPLUGIN_ID_001 1 +# define CPLUGIN_NAME_001 "Domoticz HTTP" + + +bool CPlugin_001(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_001; + proto.usesMQTT = false; + proto.usesAccount = true; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 8080; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_001); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c001_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c001_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C001_DelayHandler == nullptr || !validTaskIndex(event->TaskIndex)) { + break; + } + if (C001_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + if (event->idx != 0) + { + // We now create a URI for the request + const Sensor_VType sensorType = event->getSensorType(); + String url; + const size_t expectedSize = sensorType == Sensor_VType::SENSOR_TYPE_STRING ? 64 + event->String2.length() : 128; + + 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) + { + url += F("switchlight&idx="); + url += event->idx; + url += F("&switchcmd="); + + if (essentiallyZero(UserVar[event->BaseVarIndex])) { + url += F("Off"); + } else { + if (sensorType == Sensor_VType::SENSOR_TYPE_SWITCH) { + url += F("On"); + } else { + url += F("Set%20Level&level="); + url += UserVar[event->BaseVarIndex]; + } + } + } else { + url += F("udevice&idx="); + url += event->idx; + url += F("&nvalue=0"); + url += F("&svalue="); + url += formatDomoticzSensorType(event); + } + + // Add WiFi reception quality + url += F("&rssi="); + url += mapRSSItoDomoticz(); + # if FEATURE_ADC_VCC + url += F("&battery="); + url += mapVccToDomoticz(); + # endif // if FEATURE_ADC_VCC + + std::unique_ptr element(new (std::nothrow) C001_queue_element(event->ControllerIndex, event->TaskIndex, std::move(url))); + + success = C001_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE, + C001_DelayHandler->getNextScheduleTime()); + } + } // if ixd !=0 + else + { + addLog(LOG_LEVEL_ERROR, F("HTTP : IDX cannot be zero!")); + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c001_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c001_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C001_queue_element& element = static_cast(element_base); + +// *INDENT-ON* + # ifndef BUILD_NO_DEBUG + + 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); +} + +#endif // ifdef USES_C001 diff --git a/src/_C002.cpp b/src/_C002.cpp index deeccf0f2..164586669 100644 --- a/src/_C002.cpp +++ b/src/_C002.cpp @@ -7,6 +7,11 @@ // ########################### Controller Plugin 002: Domoticz MQTT ###################################### // ####################################################################################################### +/** Changelog: + * 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) + */ + # define CPLUGIN_002 # define CPLUGIN_ID_002 2 # define CPLUGIN_NAME_002 "Domoticz MQTT" @@ -88,15 +93,16 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& constexpr pluginID_t PLUGIN_ID_DOMOTICZ_HELPER(29); # if defined(USES_P088) constexpr pluginID_t PLUGIN_ID_HEATPUMP_IR(88); - # endif + # endif // if defined(USES_P088) + if (Settings.TaskDeviceEnabled[x] && (Settings.TaskDeviceSendData[ControllerID][x] - || (Settings.getPluginID_for_task(x) == PLUGIN_ID_DOMOTICZ_HELPER) // Domoticz helper doesn't have controller checkboxes... + || (Settings.getPluginID_for_task(x) == PLUGIN_ID_DOMOTICZ_HELPER) // Domoticz helper doesn't have controller checkboxes... # if defined(USES_P088) - || (Settings.getPluginID_for_task(x) == PLUGIN_ID_HEATPUMP_IR) // Heatpump IR doesn't have controller checkboxes... + || (Settings.getPluginID_for_task(x) == PLUGIN_ID_HEATPUMP_IR) // Heatpump IR doesn't have controller checkboxes... # endif // if defined(USES_P088) ) && - (Settings.TaskDeviceID[ControllerID][x] == idx)) // get idx for our controller index + (Settings.TaskDeviceID[ControllerID][x] == idx)) // get idx for our controller index { String action; bool mustSendEvent = false; @@ -104,7 +110,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& switch (Settings.getPluginID_for_task(x).value) { case 1: // temp solution, if input switch, update state { - action = strformat(F("inputSwitchState,%u,%.2f"), x, nvalue); + action = strformat(F("gpio,%d,%d"), x, static_cast(nvalue)); break; } case 29: // temp solution, if plugin 029, set gpio @@ -117,7 +123,7 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& switch (static_cast(nvalue)) { case 0: // Off - pwmValue = 0; + pwmValue = 0; UserVar.setFloat(x, 0, pwmValue); break; case 1: // On @@ -135,20 +141,25 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& action = strformat(F("pwm,%d,%d"), Settings.TaskDevicePin1[x], pwmValue); } } else { - mustSendEvent = true; - UserVar.setFloat(x, 0, nvalue); + mustSendEvent = true; + int ivalue = static_cast(nvalue); + + if (1 == Settings.TaskDevicePluginConfig[x][0]) { // PCONFIG(0) = Invert On/Off value + ivalue = (1 == ivalue ? 0 : 1); + } + UserVar.setFloat(x, 0, ivalue); if (checkValidPortRange(PLUGIN_GPIO, Settings.TaskDevicePin1[x])) { - action = strformat(F("gpio,%d,%d"), Settings.TaskDevicePin1[x], static_cast(nvalue)); + action = strformat(F("gpio,%d,%d"), Settings.TaskDevicePin1[x], ivalue); } } break; } -# if defined(USES_P088) // || defined(USES_P115) - case 88: // Send heatpump IR (P088) if IDX matches +# if defined(USES_P088) // || defined(USES_P115) + case 88: // Send heatpump IR (P088) if IDX matches // case 115: // Send heatpump IR (P115) if IDX matches { - action = concat(F("heatpumpir,"),svalue1); // svalue1 is like 'gree,1,1,0,22,0,0' + action = concat(F("heatpumpir,"), svalue1); // svalue1 is like 'gree,1,1,0,22,0,0' break; } # endif // USES_P088 || USES_P115 @@ -162,7 +173,14 @@ bool CPlugin_002(CPlugin::Function function, struct EventStruct *event, String& mustSendEvent = true; // Try plugin and internal - ExecuteCommand(x, EventValueSource::Enum::VALUE_SOURCE_MQTT, action.c_str(), true, true, false); + ExecuteCommandArgs args( + x, + EventValueSource::Enum::VALUE_SOURCE_MQTT, + action.c_str(), + true, + true, + false); + ExecuteCommand(std::move(args), true); } if (mustSendEvent) { diff --git a/src/_C003.cpp b/src/_C003.cpp index 56781de8b..0405f090b 100644 --- a/src/_C003.cpp +++ b/src/_C003.cpp @@ -1,156 +1,156 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C003 - -// ####################################################################################################### -// ########################### Controller Plugin 003: Nodo Telnet ####################################### -// ####################################################################################################### - -# define CPLUGIN_003 -# define CPLUGIN_ID_003 3 -# define CPLUGIN_NAME_003 "Nodo Telnet" - -bool CPlugin_003(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_003; - proto.usesMQTT = false; - proto.usesAccount = false; - proto.usesPassword = true; - proto.defaultPort = 23; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_003); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c003_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c003_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C003_DelayHandler == nullptr) { - break; - } - if (C003_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - // We now create a URI for the request - String url = strformat( - F("variableset %d,%s\n"), - event->idx, - formatUserVarNoCheck(event, 0).c_str()); - std::unique_ptr element( - new C003_queue_element( - event->ControllerIndex, - event->TaskIndex, - std::move(url))); - - success = C003_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE, C003_DelayHandler->getNextScheduleTime()); - - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c003_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c003_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C003_queue_element& element = static_cast(element_base); -// *INDENT-ON* - bool success = false; - - // Use WiFiClient class to create TCP connections - WiFiClient client; - - if (!try_connect_host(controller_number, client, ControllerSettings, F("TELNT: "))) - { - return success; - } - - // strcpy_P(log, PSTR("TELNT: Sending enter")); - // addLog(LOG_LEVEL_ERROR, log); - client.print(" \n"); - - unsigned long timer = millis() + 200; - - while (!client_available(client) && !timeOutReached(timer)) { - delay(1); - } - - timer = millis() + 1000; - - while (client_available(client) && !timeOutReached(timer) && !success) - { - // String line = client.readStringUntil('\n'); - String line; - safeReadStringUntil(client, line, '\n'); - - if (line.startsWith(F("Enter your password:"))) - { - success = true; - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("TELNT: Password request ok")); - #endif - } - delay(1); - } - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("TELNT: Sending pw")); - #endif - client.println(getControllerPass(element._controller_idx, ControllerSettings)); - delay(100); - - while (client_available(client)) { - client.read(); - } - - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("TELNT: Sending cmd")); - #endif - client.print(element.txt); - delay(10); - - while (client_available(client)) { - client.read(); - } - - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("TELNT: closing connection")); - #endif - - client.stop(); - return success; -} - -#endif // ifdef USES_C003 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C003 + +// ####################################################################################################### +// ########################### Controller Plugin 003: Nodo Telnet ####################################### +// ####################################################################################################### + +# define CPLUGIN_003 +# define CPLUGIN_ID_003 3 +# define CPLUGIN_NAME_003 "Nodo Telnet" + +bool CPlugin_003(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_003; + proto.usesMQTT = false; + proto.usesAccount = false; + proto.usesPassword = true; + proto.defaultPort = 23; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_003); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c003_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c003_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C003_DelayHandler == nullptr) { + break; + } + if (C003_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + // We now create a URI for the request + String url = strformat( + F("variableset %d,%s\n"), + event->idx, + formatUserVarNoCheck(event, 0).c_str()); + std::unique_ptr element( + new (std::nothrow) C003_queue_element( + event->ControllerIndex, + event->TaskIndex, + std::move(url))); + + success = C003_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE, C003_DelayHandler->getNextScheduleTime()); + + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c003_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c003_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C003_queue_element& element = static_cast(element_base); +// *INDENT-ON* + bool success = false; + + // Use WiFiClient class to create TCP connections + WiFiClient client; + + if (!try_connect_host(cpluginID, client, ControllerSettings, F("TELNT: "))) + { + return success; + } + + // strcpy_P(log, PSTR("TELNT: Sending enter")); + // addLog(LOG_LEVEL_ERROR, log); + client.print(" \n"); + + unsigned long timer = millis() + 200; + + while (!client_available(client) && !timeOutReached(timer)) { + delay(1); + } + + timer = millis() + 1000; + + while (client_available(client) && !timeOutReached(timer) && !success) + { + // String line = client.readStringUntil('\n'); + String line; + safeReadStringUntil(client, line, '\n'); + + if (line.startsWith(F("Enter your password:"))) + { + success = true; + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("TELNT: Password request ok")); + #endif + } + delay(1); + } + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("TELNT: Sending pw")); + #endif + client.println(getControllerPass(element._controller_idx, ControllerSettings)); + delay(100); + + while (client_available(client)) { + client.read(); + } + + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("TELNT: Sending cmd")); + #endif + client.print(element.txt); + delay(10); + + while (client_available(client)) { + client.read(); + } + + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("TELNT: closing connection")); + #endif + + client.stop(); + return success; +} + +#endif // ifdef USES_C003 diff --git a/src/_C004.cpp b/src/_C004.cpp index 0c2f5328e..5e32ca2eb 100644 --- a/src/_C004.cpp +++ b/src/_C004.cpp @@ -1,136 +1,136 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C004 - -// ####################################################################################################### -// ########################### Controller Plugin 004: ThingSpeak ######################################### -// ####################################################################################################### - -# define CPLUGIN_004 -# define CPLUGIN_ID_004 4 -# define CPLUGIN_NAME_004 "ThingSpeak" - -bool CPlugin_004(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_004; - proto.usesMQTT = false; - proto.usesAccount = true; - proto.usesPassword = true; - proto.defaultPort = 80; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_004); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c004_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c004_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: - { - success = true; - - switch (event->idx) { - case ControllerSettingsStruct::CONTROLLER_USER: - string = F("ThingHTTP Name"); - break; - case ControllerSettingsStruct::CONTROLLER_PASS: - string = F("API Key"); - break; - default: - success = false; - break; - } - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C004_DelayHandler == nullptr) { - break; - } - if (C004_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - std::unique_ptr element(new C004_queue_element(event)); - - success = C004_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE, C004_DelayHandler->getNextScheduleTime()); - - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c004_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c004_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C004_queue_element& element = static_cast(element_base); -// *INDENT-ON* - String postDataStr = F("api_key="); - - postDataStr += getControllerPass(element._controller_idx, ControllerSettings); // used for API key - - if (element.sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - postDataStr += F("&status="); - postDataStr += element.txt[0]; // FIXME TD-er: Is this correct? - // See: https://nl.mathworks.com/help/thingspeak/writedata.html - } else { - for (uint8_t x = 0; x < element.valueCount; x++) - { - postDataStr += F("&field"); - postDataStr += element.idx + x; - postDataStr += '='; - postDataStr += element.txt[x]; - } - } - if (!ControllerSettings.UseDNS) { - // Patch the ControllerSettings to make sure we're using a hostname instead of an IP address - ControllerSettings.setHostname(F("api.thingspeak.com")); // PM_CZ: HTTP requests must contain host headers. - ControllerSettings.UseDNS = true; - } - - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - F("/update"), // uri - F("POST"), - F("Content-Type: application/x-www-form-urlencoded\r\n"), - postDataStr, - httpCode); - return (httpCode >= 100) && (httpCode < 300); -} - -#endif // ifdef USES_C004 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C004 + +// ####################################################################################################### +// ########################### Controller Plugin 004: ThingSpeak ######################################### +// ####################################################################################################### + +# define CPLUGIN_004 +# define CPLUGIN_ID_004 4 +# define CPLUGIN_NAME_004 "ThingSpeak" + +bool CPlugin_004(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_004; + proto.usesMQTT = false; + proto.usesAccount = true; + proto.usesPassword = true; + proto.defaultPort = 80; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_004); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c004_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c004_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: + { + success = true; + + switch (event->idx) { + case ControllerSettingsStruct::CONTROLLER_USER: + string = F("ThingHTTP Name"); + break; + case ControllerSettingsStruct::CONTROLLER_PASS: + string = F("API Key"); + break; + default: + success = false; + break; + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C004_DelayHandler == nullptr) { + break; + } + if (C004_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + std::unique_ptr element(new (std::nothrow) C004_queue_element(event)); + + success = C004_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE, C004_DelayHandler->getNextScheduleTime()); + + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c004_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c004_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C004_queue_element& element = static_cast(element_base); +// *INDENT-ON* + String postDataStr = F("api_key="); + + postDataStr += getControllerPass(element._controller_idx, ControllerSettings); // used for API key + + if (element.sensorType == Sensor_VType::SENSOR_TYPE_STRING) { + postDataStr += F("&status="); + postDataStr += element.txt[0]; // FIXME TD-er: Is this correct? + // See: https://nl.mathworks.com/help/thingspeak/writedata.html + } else { + for (uint8_t x = 0; x < element.valueCount; x++) + { + postDataStr += F("&field"); + postDataStr += element.idx + x; + postDataStr += '='; + postDataStr += element.txt[x]; + } + } + if (!ControllerSettings.UseDNS) { + // Patch the ControllerSettings to make sure we're using a hostname instead of an IP address + ControllerSettings.setHostname(F("api.thingspeak.com")); // PM_CZ: HTTP requests must contain host headers. + ControllerSettings.UseDNS = true; + } + + int httpCode = -1; + send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + F("/update"), // uri + F("POST"), + F("Content-Type: application/x-www-form-urlencoded\r\n"), + postDataStr, + httpCode); + return (httpCode >= 100) && (httpCode < 300); +} + +#endif // ifdef USES_C004 diff --git a/src/_C005.cpp b/src/_C005.cpp index ff746da6b..3769c077c 100644 --- a/src/_C005.cpp +++ b/src/_C005.cpp @@ -84,8 +84,9 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& } - String pubname = CPlugin_005_pubname; - bool mqtt_retainFlag = CPlugin_005_mqtt_retainFlag; + String pubname = CPlugin_005_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + bool mqtt_retainFlag = CPlugin_005_mqtt_retainFlag; parseControllerVariables(pubname, event, false); @@ -94,37 +95,47 @@ bool CPlugin_005(CPlugin::Function function, struct EventStruct *event, String& for (uint8_t x = 0; x < valueCount; x++) { // MFD: skip publishing for values with empty labels (removes unnecessary publishing of unwanted values) - if (getTaskValueName(event->TaskIndex, x).isEmpty()) { + if (Cache.getTaskDeviceValueName(event->TaskIndex, x).isEmpty()) { continue; // we skip values with empty labels } String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); + + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } String value; + if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - value = event->String2.substring(0, 20); // For the log +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + value = event->String2.substring(0, 20); // For the log + } +# endif } else { value = formatUserVarNoCheck(event, x); } # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat( - F("MQTT : %s %s"), - tmppubname.c_str(), - value.c_str())); + addLogMove(LOG_LEVEL_DEBUG, + strformat( + F("MQTT : %s %s"), + tmppubname.c_str(), + value.c_str())); } # endif // ifndef BUILD_NO_DEBUG // Small optimization so we don't try to copy potentially large strings if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, tmppubname.c_str(), event->String2.c_str(), mqtt_retainFlag)) { success = true; + } } else { // Publish using move operator, thus tmppubname and value are empty after this call - if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), mqtt_retainFlag)) + if (MQTTpublish(event->ControllerIndex, event->TaskIndex, std::move(tmppubname), std::move(value), mqtt_retainFlag)) { success = true; + } } } break; @@ -150,10 +161,10 @@ bool C005_parse_command(struct EventStruct *event) { // Topic : event->String1 // Message: event->String2 String cmd; - bool validTopic = false; - const int lastindex = event->String1.lastIndexOf('/'); - const String lastPartTopic = event->String1.substring(lastindex + 1); - const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; + bool validTopic = false; + const int lastindex = event->String1.lastIndexOf('/'); + const String lastPartTopic = event->String1.substring(lastindex + 1); + const bool has_cmd_arg_index = event->String1.lastIndexOf(F("cmd_arg")) != -1; if (equals(lastPartTopic, F("cmd"))) { // Example: @@ -171,26 +182,30 @@ bool C005_parse_command(struct EventStruct *event) { // Message: 14 // Full command: gpio,14,0 - uint8_t topic_index = 1; - String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); + uint8_t topic_index = 1; + String topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); - while(!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { + while (!topic_folder.startsWith(F("cmd_arg")) && !topic_folder.isEmpty()) { ++topic_index; topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); } + if (!topic_folder.isEmpty()) { int32_t cmd_arg_nr = -1; + if (validIntFromString(topic_folder.substring(7), cmd_arg_nr)) { int constructed_cmd_arg_nr = 0; ++topic_index; topic_folder = parseStringKeepCase(event->String1, topic_index, '/'); bool msg_added = false; - while(!topic_folder.isEmpty()) { + + while (!topic_folder.isEmpty()) { if (constructed_cmd_arg_nr != 0) { cmd += ','; } + if (constructed_cmd_arg_nr == cmd_arg_nr) { - cmd += event->String2; + cmd += event->String2; msg_added = true; } else { cmd += topic_folder; @@ -199,11 +214,13 @@ bool C005_parse_command(struct EventStruct *event) { } ++constructed_cmd_arg_nr; } + if (!msg_added) { cmd += ','; cmd += event->String2; } - //addLog(LOG_LEVEL_INFO, String(F("MQTT cmd: ")) + cmd); + + // addLog(LOG_LEVEL_INFO, concat(F("MQTT cmd: "), cmd)); validTopic = true; } @@ -216,7 +233,7 @@ bool C005_parse_command(struct EventStruct *event) { if (lastindex > 0) { // Topic has at least one separator int32_t lastPartTopic_int; - float value_f; + float value_f; if (validFloatFromString(event->String2, value_f) && validIntFromString(lastPartTopic, lastPartTopic_int)) { @@ -226,8 +243,8 @@ bool C005_parse_command(struct EventStruct *event) { F("%s,%d,%s"), event->String1.substring(prevLastindex + 1, lastindex).c_str(), lastPartTopic_int, - event->String2.c_str() // Just use the original format - ); + event->String2.c_str() // Just use the original format + ); validTopic = true; } } @@ -253,19 +270,22 @@ bool C005_parse_command(struct EventStruct *event) { // Example: "myEvent,1,2,3", which needs to be converted to "myEvent=1,2,3" // N.B. This may contain the first eventvalue too // e.g. "myEvent=1,2,3" => "myEvent=1" - String eventName = parseStringKeepCase(cmd, 1); - String eventValues = parseStringToEndKeepCase(cmd, 2); + String eventName = parseStringKeepCase(cmd, 1); + String eventValues = parseStringToEndKeepCase(cmd, 2); const int equal_pos = eventName.indexOf('='); + if (equal_pos != -1) { // We found an '=' character, so the actual event name is everything before that char. - eventName = cmd.substring(0, equal_pos); + eventName = cmd.substring(0, equal_pos); eventValues = cmd.substring(equal_pos + 1); // Rest of the event, after the '=' char } + if (eventValues.startsWith(F(","))) { // Need to reconstruct the event to get rid of calls like these: // myevent=,1,2 eventValues = eventValues.substring(1); } + // Now reconstruct the complete event // Without event values: "myEvent" (no '=' char) // With event values: "myEvent=1,2,3" @@ -273,18 +293,20 @@ bool C005_parse_command(struct EventStruct *event) { // Re-using the 'cmd' String as that has pre-allocated memory which is // known to be large enough to hold the entire event. cmd = eventName; + if (eventValues.length() > 0) { // Only append an = if there are eventvalues. cmd += '='; cmd += eventValues; } } + // Check for duplicates, as sometimes a node may have multiple subscriptions to the same topic. // Then it may add several of the same events in a burst. eventQueue.addMove(std::move(cmd), true); } } else { - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str()); + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd) }, true); } } return validTopic; diff --git a/src/_C006.cpp b/src/_C006.cpp index 9d4f5061e..9aa07449b 100644 --- a/src/_C006.cpp +++ b/src/_C006.cpp @@ -103,7 +103,7 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& { cmd += event->String2; // Par2 } - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str()); + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd)}, true); } break; } @@ -114,20 +114,23 @@ bool CPlugin_006(CPlugin::Function function, struct EventStruct *event, String& break; } - String pubname = CPlugin_006_pubname; - bool mqtt_retainFlag = CPlugin_006_mqtt_retainFlag; + String pubname = CPlugin_006_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + bool mqtt_retainFlag = CPlugin_006_mqtt_retainFlag; statusLED(true); //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed parseControllerVariables(pubname, event, false); - uint8_t valueCount = getValueCountForTask(event->TaskIndex); + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); for (uint8_t x = 0; x < valueCount; x++) { String tmppubname = pubname; - parseSingleControllerVariable(tmppubname, event, x, false); + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } // Small optimization so we don't try to copy potentially large strings if (event->sensorType == Sensor_VType::SENSOR_TYPE_STRING) { diff --git a/src/_C007.cpp b/src/_C007.cpp index f5dce1f02..8d2846cb9 100644 --- a/src/_C007.cpp +++ b/src/_C007.cpp @@ -1,131 +1,131 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C007 - -# include "src/ESPEasyCore/Serial.h" - -// ####################################################################################################### -// ########################### Controller Plugin 007: Emoncms ############################################ -// ####################################################################################################### - -# define CPLUGIN_007 -# define CPLUGIN_ID_007 7 -# define CPLUGIN_NAME_007 "Emoncms" - - -bool CPlugin_007(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_007; - proto.usesMQTT = false; - proto.usesAccount = false; - proto.usesPassword = true; - proto.defaultPort = 80; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_007); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c007_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c007_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C007_DelayHandler == nullptr) { - break; - } - if (C007_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - - if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { - addLog(LOG_LEVEL_ERROR, F("emoncms : No support for Sensor_VType::SENSOR_TYPE_STRING")); - break; - } - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - if ((valueCount == 0) || (valueCount > VARS_PER_TASK)) { - addLog(LOG_LEVEL_ERROR, F("emoncms : Unknown sensortype or too many sensor values")); - break; - } - - std::unique_ptr element(new C007_queue_element(event)); - success = C007_DelayHandler->addToQueue(std::move(element)); - - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE, C007_DelayHandler->getNextScheduleTime()); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c007_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c007_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C007_queue_element& element = static_cast(element_base); -// *INDENT-ON* - String url = F("/emoncms/input/post.json?node="); - - url += Settings.Unit; - url += F("&json="); - - for (uint8_t i = 0; i < element.valueCount; ++i) { - url += (i == 0) ? '{' : ','; - url += F("field"); - url += element.idx + i; - url += ':'; - url += element.txt[i]; - } - url += '}'; - url += F("&apikey="); - url += getControllerPass(element._controller_idx, ControllerSettings); // "0UDNN17RW6XAS2E5" // api key - -#ifndef BUILD_NO_DEBUG - if (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE) { - serialPrintln(url); - } -#endif - - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - url, - F("GET"), - EMPTY_STRING, - EMPTY_STRING, - httpCode); - return (httpCode >= 100) && (httpCode < 300); -} - -#endif // ifdef USES_C007 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C007 + +# include "src/ESPEasyCore/Serial.h" + +// ####################################################################################################### +// ########################### Controller Plugin 007: Emoncms ############################################ +// ####################################################################################################### + +# define CPLUGIN_007 +# define CPLUGIN_ID_007 7 +# define CPLUGIN_NAME_007 "Emoncms" + + +bool CPlugin_007(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_007; + proto.usesMQTT = false; + proto.usesAccount = false; + proto.usesPassword = true; + proto.defaultPort = 80; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_007); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c007_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c007_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C007_DelayHandler == nullptr) { + break; + } + if (C007_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + + if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { + addLog(LOG_LEVEL_ERROR, F("emoncms : No support for Sensor_VType::SENSOR_TYPE_STRING")); + break; + } + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + if ((valueCount == 0) || (valueCount > VARS_PER_TASK)) { + addLog(LOG_LEVEL_ERROR, F("emoncms : Unknown sensortype or too many sensor values")); + break; + } + + std::unique_ptr element(new (std::nothrow) C007_queue_element(event)); + success = C007_DelayHandler->addToQueue(std::move(element)); + + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE, C007_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c007_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c007_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C007_queue_element& element = static_cast(element_base); +// *INDENT-ON* + String url = F("/emoncms/input/post.json?node="); + + url += Settings.Unit; + url += F("&json="); + + for (uint8_t i = 0; i < element.valueCount; ++i) { + url += (i == 0) ? '{' : ','; + url += F("field"); + url += element.idx + i; + url += ':'; + url += element.txt[i]; + } + url += '}'; + url += F("&apikey="); + url += getControllerPass(element._controller_idx, ControllerSettings); // "0UDNN17RW6XAS2E5" // api key + +#ifndef BUILD_NO_DEBUG + if (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE) { + serialPrintln(url); + } +#endif + + int httpCode = -1; + send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + url, + F("GET"), + EMPTY_STRING, + EMPTY_STRING, + httpCode); + return (httpCode >= 100) && (httpCode < 300); +} + +#endif // ifdef USES_C007 diff --git a/src/_C008.cpp b/src/_C008.cpp index 7101a10c1..3c21e9743 100644 --- a/src/_C008.cpp +++ b/src/_C008.cpp @@ -1,172 +1,175 @@ -#include "src/Helpers/_CPlugin_Helper.h" - -#ifdef USES_C008 - -// ####################################################################################################### -// ########################### Controller Plugin 008: Generic HTTP ####################################### -// ####################################################################################################### - -# define CPLUGIN_008 -# define CPLUGIN_ID_008 8 -# define CPLUGIN_NAME_008 "Generic HTTP" - -bool CPlugin_008(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_008; - proto.usesMQTT = false; - proto.usesTemplate = true; - proto.usesAccount = true; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 80; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_008); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c008_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c008_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = String(); - event->String2 = F("demo.php?name=%sysname%&task=%tskname%&valuename=%valname%&value=%value%"); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C008_DelayHandler == nullptr) { - break; - } - if (C008_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - - String pubname; - { - // Place the ControllerSettings in a scope to free the memory as soon as we got all relevant information. - MakeControllerSettings(ControllerSettings); //-V522 - - if (!AllocatedControllerSettings()) { - addLog(LOG_LEVEL_ERROR, F("C008 : Generic HTTP - Cannot send, out of RAM")); - break; - } - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - pubname = ControllerSettings->Publish; - } - - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - std::unique_ptr element(new C008_queue_element(event, valueCount)); - success = C008_DelayHandler->addToQueue(std::move(element)); - - if (success) { - // Element was added. - // Now we try to append to the existing element - // and thus preventing the need to create a long string only to copy it to a queue element. - C008_queue_element& element = static_cast(*(C008_DelayHandler->sendQueue.back())); - - // Collect the values at the same run, to make sure all are from the same sample - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - parseControllerVariables(pubname, event, true); - - for (uint8_t x = 0; x < valueCount; x++) - { - bool isvalid; - const String formattedValue = formatUserVar(event, x , isvalid); - - if (isvalid) { - // First store in a temporary string, so we can use move_special to allocate on the best heap - String txt; - txt += '/'; - txt += pubname; - parseSingleControllerVariable(txt, event, x, true); - -# ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, strformat( - F("C008 : pubname: %s value: %s"), - pubname.c_str(), - formattedValue.c_str() - )); - } -#endif - txt.replace(F("%value%"), formattedValue); - move_special(element.txt[x], std::move(txt)); -# ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLog(LOG_LEVEL_DEBUG_MORE, concat(F("C008 : "), element.txt[x])); - } -# endif // ifndef BUILD_NO_DEBUG - } - } - } - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE, C008_DelayHandler->getNextScheduleTime()); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c008_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Generic HTTP get request -// ******************************************************************************** - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c008_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C008_queue_element& element = static_cast(element_base); -// *INDENT-ON* - while (element.txt[element.valuesSent].isEmpty()) { - // A non valid value, which we are not going to send. - // Increase sent counter until a valid value is found. - if (element.checkDone(true)) { - return true; - } - } - - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - element.txt[element.valuesSent], - F("GET"), - EMPTY_STRING, - EMPTY_STRING, - httpCode); - return element.checkDone((httpCode >= 100) && (httpCode < 300)); -} - -#endif // ifdef USES_C008 +#include "src/Helpers/_CPlugin_Helper.h" + +#ifdef USES_C008 + +// ####################################################################################################### +// ########################### Controller Plugin 008: Generic HTTP ####################################### +// ####################################################################################################### + +# define CPLUGIN_008 +# define CPLUGIN_ID_008 8 +# define CPLUGIN_NAME_008 "Generic HTTP" + +bool CPlugin_008(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_008; + proto.usesMQTT = false; + proto.usesTemplate = true; + proto.usesAccount = true; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 80; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_008); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c008_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c008_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = String(); + event->String2 = F("demo.php?name=%sysname%&task=%tskname%&valuename=%valname%&value=%value%"); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C008_DelayHandler == nullptr) { + break; + } + if (C008_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + + String pubname; + { + // Place the ControllerSettings in a scope to free the memory as soon as we got all relevant information. + MakeControllerSettings(ControllerSettings); //-V522 + + if (!AllocatedControllerSettings()) { + addLog(LOG_LEVEL_ERROR, F("C008 : Generic HTTP - Cannot send, out of RAM")); + break; + } + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + pubname = ControllerSettings->Publish; + } + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + std::unique_ptr element(new (std::nothrow) C008_queue_element(event, valueCount)); + success = C008_DelayHandler->addToQueue(std::move(element)); + + if (success) { + // Element was added. + // Now we try to append to the existing element + // and thus preventing the need to create a long string only to copy it to a queue element. + C008_queue_element& element = static_cast(*(C008_DelayHandler->sendQueue.back())); + + // Collect the values at the same run, to make sure all are from the same sample + //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed + parseControllerVariables(pubname, event, true); + + for (uint8_t x = 0; x < valueCount; x++) + { + bool isvalid; + const String formattedValue = formatUserVar(event, x , isvalid); + + if (isvalid) { + // First store in a temporary string, so we can use move_special to allocate on the best heap + String txt; + txt += '/'; + txt += pubname; + if (contains_valname) { + parseSingleControllerVariable(txt, event, x, true); + } + +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("C008 : pubname: %s value: %s"), + pubname.c_str(), + formattedValue.c_str() + )); + } +#endif + txt.replace(F("%value%"), formattedValue); + move_special(element.txt[x], std::move(txt)); +# ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, concat(F("C008 : "), element.txt[x])); + } +# endif // ifndef BUILD_NO_DEBUG + } + } + } + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE, C008_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c008_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Generic HTTP get request +// ******************************************************************************** + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c008_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C008_queue_element& element = static_cast(element_base); +// *INDENT-ON* + while (element.txt[element.valuesSent].isEmpty()) { + // A non valid value, which we are not going to send. + // Increase sent counter until a valid value is found. + if (element.checkDone(true)) { + return true; + } + } + + int httpCode = -1; + send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + element.txt[element.valuesSent], + F("GET"), + EMPTY_STRING, + EMPTY_STRING, + httpCode); + return element.checkDone((httpCode >= 100) && (httpCode < 300)); +} + +#endif // ifdef USES_C008 diff --git a/src/_C009.cpp b/src/_C009.cpp index 7171bff33..bcca8adf5 100644 --- a/src/_C009.cpp +++ b/src/_C009.cpp @@ -1,217 +1,217 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C009 - -#include "src/DataTypes/NodeTypeID.h" -#include "src/Helpers/StringProvider.h" -#include "src/CustomBuild/ESPEasy_buildinfo.h" - -// ####################################################################################################### -// ########################### Controller Plugin 009: FHEM HTTP ########################################## -// ####################################################################################################### - -/******************************************************************************* - * Copyright 2016-2017 dev0 - * Contact: https://forum.fhem.de/index.php?action=profile;u=7465 - * https://github.com/ddtlabs/ - * - * Release notes: - - v1.0 - - changed switch and dimmer setreading cmds - - v1.01 - - added json content to http requests - - v1.02 - - some optimizations as requested by mvdbro - - fixed JSON TaskDeviceValueDecimals handling - - ArduinoJson Library v5.6.4 required (as used by stable R120) - - parse for HTTP errors 400, 401 - - moved on/off translation for Sensor_VType::SENSOR_TYPE_SWITCH/DIMMER to FHEM module - - v1.03 - - changed http request from GET to POST (RFC conform) - - removed obsolete http get url code - - v1.04 - - added build options and node_type_id to JSON/device - ******************************************************************************/ - -# define CPLUGIN_009 -# define CPLUGIN_ID_009 9 -# define CPLUGIN_NAME_009 "FHEM HTTP" - -bool CPlugin_009(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_009; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = true; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.usesID = false; - proto.defaultPort = 8383; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_009); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c009_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c009_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C009_DelayHandler != nullptr) { - if (C009_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - std::unique_ptr element(new C009_queue_element(event)); - success = C009_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE, C009_DelayHandler->getNextScheduleTime()); - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c009_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -/*********************************************************************************************\ -* FHEM HTTP request -\*********************************************************************************************/ - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c009_delay_queue(int controller_number, 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 += '{'; - { - 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\":{"); - { - 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"), String(Settings.Unit)); - jsonString += ','; - jsonString += to_json_object_value(F("version"), String(Settings.Version)); - jsonString += ','; - jsonString += to_json_object_value(F("build"), String(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"), String(NODE_TYPE_ID)); - jsonString += ','; - jsonString += to_json_object_value(F("sleep"), String(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]); - 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("\":{"); - { - jsonString += to_json_object_value(F("deviceName"), getTaskDeviceName(element._taskIndex)); - jsonString += ','; - jsonString += to_json_object_value(F("valueName"), getTaskValueName(element._taskIndex, x)); - jsonString += ','; - jsonString += to_json_object_value(F("type"), String(static_cast(element.sensorType))); - jsonString += ','; - jsonString += to_json_object_value(F("value"), element.txt[x]); - } - jsonString += '}'; // End "sensor value N" - } - } - jsonString += '}'; // End "SENSOR" - } - jsonString += '}'; // End "data" - } - jsonString += '}'; // End JSON structure - } - - if (expectedJsonLength < jsonString.length()) { - expectedJsonLength = jsonString.length(); - } - - // addLog(LOG_LEVEL_INFO, F("C009 Test JSON:")); - // addLog(LOG_LEVEL_INFO, jsonString); - - int httpCode = -1; - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - F("/ESPEasy"), - F("POST"), - EMPTY_STRING, - jsonString, - httpCode); - return (httpCode >= 100) && (httpCode < 300); -} - -#endif // ifdef USES_C009 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C009 + +#include "src/DataTypes/NodeTypeID.h" +#include "src/Helpers/StringProvider.h" +#include "src/CustomBuild/ESPEasy_buildinfo.h" + +// ####################################################################################################### +// ########################### Controller Plugin 009: FHEM HTTP ########################################## +// ####################################################################################################### + +/******************************************************************************* + * Copyright 2016-2017 dev0 + * Contact: https://forum.fhem.de/index.php?action=profile;u=7465 + * https://github.com/ddtlabs/ + * + * Release notes: + - v1.0 + - changed switch and dimmer setreading cmds + - v1.01 + - added json content to http requests + - v1.02 + - some optimizations as requested by mvdbro + - fixed JSON TaskDeviceValueDecimals handling + - ArduinoJson Library v5.6.4 required (as used by stable R120) + - parse for HTTP errors 400, 401 + - moved on/off translation for Sensor_VType::SENSOR_TYPE_SWITCH/DIMMER to FHEM module + - v1.03 + - changed http request from GET to POST (RFC conform) + - removed obsolete http get url code + - v1.04 + - added build options and node_type_id to JSON/device + ******************************************************************************/ + +# define CPLUGIN_009 +# define CPLUGIN_ID_009 9 +# define CPLUGIN_NAME_009 "FHEM HTTP" + +bool CPlugin_009(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_009; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = true; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.usesID = false; + proto.defaultPort = 8383; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_009); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c009_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c009_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C009_DelayHandler != nullptr) { + if (C009_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + std::unique_ptr element(new (std::nothrow) C009_queue_element(event)); + success = C009_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE, C009_DelayHandler->getNextScheduleTime()); + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c009_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +/*********************************************************************************************\ +* FHEM HTTP request +\*********************************************************************************************/ + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +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 += '{'; + { + 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\":{"); + { + 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)); + + // 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("\":{"); + { + 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" + } + } + jsonString += '}'; // End "SENSOR" + } + jsonString += '}'; // End "data" + } + jsonString += '}'; // End JSON structure + } + + if (expectedJsonLength < jsonString.length()) { + expectedJsonLength = jsonString.length(); + } + + // 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); +} + +#endif // ifdef USES_C009 diff --git a/src/_C010.cpp b/src/_C010.cpp index 8b361fe4a..ff8921c1c 100644 --- a/src/_C010.cpp +++ b/src/_C010.cpp @@ -1,163 +1,165 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C010 - -// ####################################################################################################### -// ########################### Controller Plugin 010: Generic UDP ######################################## -// ####################################################################################################### - -# define CPLUGIN_010 -# define CPLUGIN_ID_010 10 -# define CPLUGIN_NAME_010 "Generic UDP" - -bool CPlugin_010(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_010; - proto.usesMQTT = false; - proto.usesTemplate = true; - proto.usesAccount = false; - proto.usesPassword = false; - proto.defaultPort = 514; - proto.usesID = false; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_010); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = String(); - event->String2 = F("%sysname%_%tskname%_%valname%=%value%"); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c010_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c010_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C010_DelayHandler == nullptr) { - break; - } - if (C010_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - if (valueCount == 0) { - break; - } - - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - - std::unique_ptr element(new C010_queue_element(event, valueCount)); - - - { - String pubname; - { - MakeControllerSettings(ControllerSettings); //-V522 - - if (!AllocatedControllerSettings()) { - break; - } - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - pubname = ControllerSettings->Publish; - } - - parseControllerVariables(pubname, event, false); - - for (uint8_t x = 0; x < valueCount; x++) - { - bool isvalid; - const String formattedValue = formatUserVar(event, x, isvalid); - - if (isvalid) { - String txt; - txt = pubname; - parseSingleControllerVariable(txt, event, x, false); - txt.replace(F("%value%"), formattedValue); - move_special(element->txt[x], std::move(txt)); -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - addLog(LOG_LEVEL_DEBUG_MORE, element->txt[x]); -#endif - } - } - } - - success = C010_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE, C010_DelayHandler->getNextScheduleTime()); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c010_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Generic UDP message -// ******************************************************************************** - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c010_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C010_queue_element& element = static_cast(element_base); -// *INDENT-ON* - while (element.txt[element.valuesSent].isEmpty()) { - // A non valid value, which we are not going to send. - // Increase sent counter until a valid value is found. - if (element.checkDone(true)) { - return true; - } - } - WiFiUDP C010_portUDP; - - if (!beginWiFiUDP_randomPort(C010_portUDP)) { return false; } - - if (!try_connect_host(controller_number, C010_portUDP, ControllerSettings)) { - return false; - } - - C010_portUDP.write( - reinterpret_cast(element.txt[element.valuesSent].c_str()), - element.txt[element.valuesSent].length()); - bool reply = C010_portUDP.endPacket(); - - C010_portUDP.stop(); - - if (ControllerSettings.MustCheckReply) { - return element.checkDone(reply); - } - return element.checkDone(true); -} - -#endif // ifdef USES_C010 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C010 + +// ####################################################################################################### +// ########################### Controller Plugin 010: Generic UDP ######################################## +// ####################################################################################################### + +# define CPLUGIN_010 +# define CPLUGIN_ID_010 10 +# define CPLUGIN_NAME_010 "Generic UDP" + +bool CPlugin_010(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_010; + proto.usesMQTT = false; + proto.usesTemplate = true; + proto.usesAccount = false; + proto.usesPassword = false; + proto.defaultPort = 514; + proto.usesID = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_010); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = String(); + event->String2 = F("%sysname%_%tskname%_%valname%=%value%"); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c010_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c010_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C010_DelayHandler == nullptr) { + break; + } + if (C010_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + if (valueCount == 0) { + break; + } + + //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed + + std::unique_ptr element(new (std::nothrow) C010_queue_element(event, valueCount)); + + + { + String pubname; + { + MakeControllerSettings(ControllerSettings); //-V522 + + if (!AllocatedControllerSettings()) { + break; + } + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + pubname = ControllerSettings->Publish; + } + parseControllerVariables(pubname, event, false); + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; + + for (uint8_t x = 0; x < valueCount; x++) + { + bool isvalid; + const String formattedValue = formatUserVar(event, x, isvalid); + + if (isvalid) { + String txt; + txt = pubname; + if (contains_valname) { + parseSingleControllerVariable(txt, event, x, false); + } + txt.replace(F("%value%"), formattedValue); + move_special(element->txt[x], std::move(txt)); +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + addLog(LOG_LEVEL_DEBUG_MORE, element->txt[x]); +#endif + } + } + } + + success = C010_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE, C010_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c010_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Generic UDP message +// ******************************************************************************** + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c010_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C010_queue_element& element = static_cast(element_base); +// *INDENT-ON* + while (element.txt[element.valuesSent].isEmpty()) { + // A non valid value, which we are not going to send. + // Increase sent counter until a valid value is found. + if (element.checkDone(true)) { + return true; + } + } + WiFiUDP C010_portUDP; + + if (!beginWiFiUDP_randomPort(C010_portUDP)) { return false; } + + if (!try_connect_host(cpluginID, C010_portUDP, ControllerSettings)) { + return false; + } + + C010_portUDP.write( + reinterpret_cast(element.txt[element.valuesSent].c_str()), + element.txt[element.valuesSent].length()); + bool reply = C010_portUDP.endPacket(); + + C010_portUDP.stop(); + + if (ControllerSettings.MustCheckReply) { + return element.checkDone(reply); + } + return element.checkDone(true); +} + +#endif // ifdef USES_C010 diff --git a/src/_C011.cpp b/src/_C011.cpp index 60effe70d..b4d1aab50 100644 --- a/src/_C011.cpp +++ b/src/_C011.cpp @@ -1,370 +1,370 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C011 - -// ####################################################################################################### -// ########################### Controller Plugin 011: Generic HTTP Advanced ############################## -// ####################################################################################################### - -# define CPLUGIN_011 -# define CPLUGIN_ID_011 11 -# define CPLUGIN_NAME_011 "Generic HTTP Advanced" - -# define C011_HTTP_METHOD_MAX_LEN 16 -# define C011_HTTP_URI_MAX_LEN 240 -# define C011_HTTP_HEADER_MAX_LEN 256 -# define C011_HTTP_BODY_MAX_LEN 512 - - -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; - HttpHeader[C011_HTTP_HEADER_MAX_LEN - 1] = 0; - HttpBody[C011_HTTP_BODY_MAX_LEN - 1] = 0; - } - - char HttpMethod[C011_HTTP_METHOD_MAX_LEN] = { 0 }; - char HttpUri[C011_HTTP_URI_MAX_LEN] = { 0 }; - char HttpHeader[C011_HTTP_HEADER_MAX_LEN] = { 0 }; - char HttpBody[C011_HTTP_BODY_MAX_LEN] = { 0 }; -}; - - -// Forward declarations -bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody); -boolean Create_schedule_HTTP_C011(struct EventStruct *event); -void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted); -void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary); - - - -bool CPlugin_011(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_011; - proto.usesMQTT = false; - proto.usesAccount = true; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 80; - proto.usesID = false; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_011); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - { - MakeControllerSettings(ControllerSettings); //-V522 - - if (AllocatedControllerSettings()) { - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - C011_sendBinary = ControllerSettings->sendBinary(); - } - } - success = init_c011_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c011_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: - { - { - String HttpMethod; - String HttpUri; - String HttpHeader; - String HttpBody; - - if (!load_C011_ConfigStruct(event->ControllerIndex, HttpMethod, HttpUri, HttpHeader, HttpBody)) - { - return false; - } - addTableSeparator(F("HTTP Config"), 2, 3); - { - uint8_t choice = 0; - const __FlashStringHelper * methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; - - for (uint8_t i = 0; i < 5; i++) - { - if (HttpMethod.equals(methods[i])) { - choice = i; - } - } - addFormSelector(F("Method"), F("P011httpmethod"), 5, methods, nullptr, choice); - } - - addFormTextBox(F("URI"), F("P011httpuri"), HttpUri, C011_HTTP_URI_MAX_LEN - 1); - { - htmlEscape(HttpHeader); - addFormTextArea(F("Header"), F("P011httpheader"), HttpHeader, C011_HTTP_HEADER_MAX_LEN - 1, 4, 50); - } - { - htmlEscape(HttpBody); - addFormTextArea(F("Body"), F("P011httpbody"), HttpBody, C011_HTTP_BODY_MAX_LEN - 1, 8, 50); - } - } - { - // Place in scope to delete ControllerSettings as soon as it is no longer needed - MakeControllerSettings(ControllerSettings); //-V522 - - if (!AllocatedControllerSettings()) { - addHtmlError(F("Out of memory, cannot load page")); - } else { - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - addControllerParameterForm(*ControllerSettings, event->ControllerIndex, ControllerSettingsStruct::CONTROLLER_SEND_BINARY); - addFormNote(F("Do not 'percent escape' body when send binary checked")); - } - } - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - { - std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); - - if (customConfig) { - uint8_t choice = 0; - String methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; - - for (uint8_t i = 0; i < 5; i++) - { - if (methods[i].equals(customConfig->HttpMethod)) { - choice = i; - } - } - - int httpmethod = getFormItemInt(F("P011httpmethod"), choice); - String httpuri = webArg(F("P011httpuri")); - String httpheader = webArg(F("P011httpheader")); - String httpbody = webArg(F("P011httpbody")); - - strlcpy(customConfig->HttpMethod, methods[httpmethod].c_str(), sizeof(customConfig->HttpMethod)); - strlcpy(customConfig->HttpUri, httpuri.c_str(), sizeof(customConfig->HttpUri)); - strlcpy(customConfig->HttpHeader, httpheader.c_str(), sizeof(customConfig->HttpHeader)); - strlcpy(customConfig->HttpBody, httpbody.c_str(), sizeof(customConfig->HttpBody)); - customConfig->zero_last(); - SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); - } - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C011_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - success = Create_schedule_HTTP_C011(event); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c011_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Generic HTTP request -// ******************************************************************************** - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c011_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C011_queue_element& element = static_cast(element_base); -// *INDENT-ON* - - if (!NetworkConnected()) { return false; } - - int httpCode = -1; - - send_via_http( - controller_number, - ControllerSettings, - element._controller_idx, - element.uri, - element.HttpMethod, - element.header, - element.postStr, - httpCode); - - // HTTP codes: - // 1xx Informational response - // 2xx Success - return httpCode >= 100 && httpCode < 300; -} - -bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody) { - // Just copy the needed strings and destruct the C011_ConfigStruct as soon as possible - std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); - - if (!customConfig) { - return false; - } - LoadCustomControllerSettings(ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); - customConfig->zero_last(); - move_special(HttpMethod, String(customConfig->HttpMethod)); - move_special(HttpUri , String(customConfig->HttpUri)); - move_special(HttpHeader, String(customConfig->HttpHeader)); - move_special(HttpBody , String(customConfig->HttpBody)); - return true; -} - -// ******************************************************************************** -// Create request -// ******************************************************************************** -boolean Create_schedule_HTTP_C011(struct EventStruct *event) -{ - if (C011_DelayHandler == nullptr) { - addLog(LOG_LEVEL_ERROR, F("No C011_DelayHandler")); - return false; - } - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - - // Add a new element to the queue with the minimal payload - std::unique_ptr element(new C011_queue_element(event)); - bool success = C011_DelayHandler->addToQueue(std::move(element)); - - if (success) { - // Element was added. - // Now we try to append to the existing element - // and thus preventing the need to create a long string only to copy it to a queue element. - C011_queue_element& element = static_cast(*(C011_DelayHandler->sendQueue.back())); - - - if (!load_C011_ConfigStruct(event->ControllerIndex, element.HttpMethod, element.uri, element.header, element.postStr)) - { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat( - F("C011 : %s %s %s %s"), - element.HttpMethod.c_str(), - element.uri.c_str(), - element.header.c_str(), - element.postStr.c_str())); - } - C011_DelayHandler->sendQueue.pop_back(); - return false; - } - - ReplaceTokenByValue(element.uri, event, false); - ReplaceTokenByValue(element.header, event, false); - - if (element.postStr.length() > 0) - { - ReplaceTokenByValue(element.postStr, event, C011_sendBinary); - } - } else { - addLog(LOG_LEVEL_ERROR, F("C011 : Could not add to delay handler")); - } - - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE, C011_DelayHandler->getNextScheduleTime()); - return success; -} - -// parses the string and returns only the the number of name/values we want -// according to the parameter numberOfValuesWanted -void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted) -{ - numberOfValuesWanted++; - - for (uint8_t i = 1; i < 5; i++) - { - const String startToken(strformat(F("%%%d%%"), i)); - const String endToken(strformat(F("%%/%d%%"), i)); - - // do we want to keep this one? - if (i < numberOfValuesWanted) - { - // yes, so just remove the tokens - s.replace(startToken, EMPTY_STRING); - s.replace(endToken, EMPTY_STRING); - } - else - { - // remove all the whole strings including tokes - int startIndex = s.indexOf(startToken); - int endIndex = s.indexOf(endToken); - - while (startIndex != -1 && endIndex != -1 && endIndex > startIndex) - { - String p = s.substring(startIndex, endIndex + 4); - - // remove the whole string including tokens - s.replace(p, EMPTY_STRING); - - // find next ones - startIndex = s.indexOf(startToken); - endIndex = s.indexOf(endToken); - } - } - } -} - -// ******************************************************************************** -// Replace the token in a string by real value. -// -// Example: -// %1%%vname1%____%tskname%____%val1%%/1%%2%%__%%vname2%____%tskname%____%val2%%/2% -// will become in case of a sensor with 1 value: -// SENSORVALUENAME1____TASKNAME1____VALUE1 <- everything not between %1% and %/1% will be discarded -// in case of a sensor with 2 values: -// SENSORVALUENAME1____TASKNAME1____VALUE1__SENSORVALUENAME2____TASKNAME2____VALUE2 -// ******************************************************************************** -void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary) -{ - // example string: - // write?db=testdb&type=%1%%vname1%%/1%%2%;%vname2%%/2%%3%;%vname3%%/3%%4%;%vname4%%/4%&value=%1%%val1%%/1%%2%;%val2%%/2%%3%;%val3%%/3%%4%;%val4%%/4% - // %1%%vname1%,Standort=%tskname% Wert=%val1%%/1%%2%%LF%%vname2%,Standort=%tskname% Wert=%val2%%/2%%3%%LF%%vname3%,Standort=%tskname% - // Wert=%val3%%/3%%4%%LF%%vname4%,Standort=%tskname% Wert=%val4%%/4% - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP before parsing: ")); - addLog(LOG_LEVEL_DEBUG_MORE, s); - } - #endif - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - DeleteNotNeededValues(s, valueCount); - - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after parsing: ")); - addLog(LOG_LEVEL_DEBUG_MORE, s); - } - #endif - - parseControllerVariables(s, event, !sendBinary); - - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after replacements: ")); - addLog(LOG_LEVEL_DEBUG_MORE, s); - } - #endif -} - -#endif // ifdef USES_C011 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C011 + +// ####################################################################################################### +// ########################### Controller Plugin 011: Generic HTTP Advanced ############################## +// ####################################################################################################### + +# define CPLUGIN_011 +# define CPLUGIN_ID_011 11 +# define CPLUGIN_NAME_011 "Generic HTTP Advanced" + +# define C011_HTTP_METHOD_MAX_LEN 16 +# define C011_HTTP_URI_MAX_LEN 240 +# define C011_HTTP_HEADER_MAX_LEN 256 +# define C011_HTTP_BODY_MAX_LEN 512 + + +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; + HttpHeader[C011_HTTP_HEADER_MAX_LEN - 1] = 0; + HttpBody[C011_HTTP_BODY_MAX_LEN - 1] = 0; + } + + char HttpMethod[C011_HTTP_METHOD_MAX_LEN] = { 0 }; + char HttpUri[C011_HTTP_URI_MAX_LEN] = { 0 }; + char HttpHeader[C011_HTTP_HEADER_MAX_LEN] = { 0 }; + char HttpBody[C011_HTTP_BODY_MAX_LEN] = { 0 }; +}; + + +// Forward declarations +bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody); +boolean Create_schedule_HTTP_C011(struct EventStruct *event); +void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted); +void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary); + + + +bool CPlugin_011(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_011; + proto.usesMQTT = false; + proto.usesAccount = true; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 80; + proto.usesID = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_011); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + { + MakeControllerSettings(ControllerSettings); //-V522 + + if (AllocatedControllerSettings()) { + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + C011_sendBinary = ControllerSettings->sendBinary(); + } + } + success = init_c011_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c011_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + { + String HttpMethod; + String HttpUri; + String HttpHeader; + String HttpBody; + + if (!load_C011_ConfigStruct(event->ControllerIndex, HttpMethod, HttpUri, HttpHeader, HttpBody)) + { + return false; + } + addTableSeparator(F("HTTP Config"), 2, 3); + { + uint8_t choice = 0; + const __FlashStringHelper * methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; + + for (uint8_t i = 0; i < 5; i++) + { + if (HttpMethod.equals(methods[i])) { + choice = i; + } + } + addFormSelector(F("Method"), F("P011httpmethod"), 5, methods, nullptr, choice); + } + + addFormTextBox(F("URI"), F("P011httpuri"), HttpUri, C011_HTTP_URI_MAX_LEN - 1); + { + htmlEscape(HttpHeader); + addFormTextArea(F("Header"), F("P011httpheader"), HttpHeader, C011_HTTP_HEADER_MAX_LEN - 1, 4, 50); + } + { + htmlEscape(HttpBody); + addFormTextArea(F("Body"), F("P011httpbody"), HttpBody, C011_HTTP_BODY_MAX_LEN - 1, 8, 50); + } + } + { + // Place in scope to delete ControllerSettings as soon as it is no longer needed + MakeControllerSettings(ControllerSettings); //-V522 + + if (!AllocatedControllerSettings()) { + addHtmlError(F("Out of memory, cannot load page")); + } else { + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + addControllerParameterForm(*ControllerSettings, event->ControllerIndex, ControllerSettingsStruct::CONTROLLER_SEND_BINARY); + addFormNote(F("Do not 'percent escape' body when send binary checked")); + } + } + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); + + if (customConfig) { + uint8_t choice = 0; + String methods[] = { F("GET"), F("POST"), F("PUT"), F("HEAD"), F("PATCH") }; + + for (uint8_t i = 0; i < 5; i++) + { + if (methods[i].equals(customConfig->HttpMethod)) { + choice = i; + } + } + + int httpmethod = getFormItemInt(F("P011httpmethod"), choice); + String httpuri = webArg(F("P011httpuri")); + String httpheader = webArg(F("P011httpheader")); + String httpbody = webArg(F("P011httpbody")); + + strlcpy(customConfig->HttpMethod, methods[httpmethod].c_str(), sizeof(customConfig->HttpMethod)); + strlcpy(customConfig->HttpUri, httpuri.c_str(), sizeof(customConfig->HttpUri)); + strlcpy(customConfig->HttpHeader, httpheader.c_str(), sizeof(customConfig->HttpHeader)); + strlcpy(customConfig->HttpBody, httpbody.c_str(), sizeof(customConfig->HttpBody)); + customConfig->zero_last(); + SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C011_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + success = Create_schedule_HTTP_C011(event); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c011_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Generic HTTP request +// ******************************************************************************** + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c011_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C011_queue_element& element = static_cast(element_base); +// *INDENT-ON* + + if (!NetworkConnected()) { return false; } + + int httpCode = -1; + + send_via_http( + cpluginID, + ControllerSettings, + element._controller_idx, + element.uri, + element.HttpMethod, + element.header, + element.postStr, + httpCode); + + // HTTP codes: + // 1xx Informational response + // 2xx Success + return httpCode >= 100 && httpCode < 300; +} + +bool load_C011_ConfigStruct(controllerIndex_t ControllerIndex, String& HttpMethod, String& HttpUri, String& HttpHeader, String& HttpBody) { + // Just copy the needed strings and destruct the C011_ConfigStruct as soon as possible + std::shared_ptr customConfig(new (std::nothrow) C011_ConfigStruct); + + if (!customConfig) { + return false; + } + LoadCustomControllerSettings(ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C011_ConfigStruct)); + customConfig->zero_last(); + move_special(HttpMethod, String(customConfig->HttpMethod)); + move_special(HttpUri , String(customConfig->HttpUri)); + move_special(HttpHeader, String(customConfig->HttpHeader)); + move_special(HttpBody , String(customConfig->HttpBody)); + return true; +} + +// ******************************************************************************** +// Create request +// ******************************************************************************** +boolean Create_schedule_HTTP_C011(struct EventStruct *event) +{ + if (C011_DelayHandler == nullptr) { + addLog(LOG_LEVEL_ERROR, F("No C011_DelayHandler")); + return false; + } + //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed + + // Add a new element to the queue with the minimal payload + std::unique_ptr element(new (std::nothrow) C011_queue_element(event)); + bool success = C011_DelayHandler->addToQueue(std::move(element)); + + if (success) { + // Element was added. + // Now we try to append to the existing element + // and thus preventing the need to create a long string only to copy it to a queue element. + C011_queue_element& element = static_cast(*(C011_DelayHandler->sendQueue.back())); + + + if (!load_C011_ConfigStruct(event->ControllerIndex, element.HttpMethod, element.uri, element.header, element.postStr)) + { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat( + F("C011 : %s %s %s %s"), + element.HttpMethod.c_str(), + element.uri.c_str(), + element.header.c_str(), + element.postStr.c_str())); + } + C011_DelayHandler->sendQueue.pop_back(); + return false; + } + + ReplaceTokenByValue(element.uri, event, false); + ReplaceTokenByValue(element.header, event, false); + + if (element.postStr.length() > 0) + { + ReplaceTokenByValue(element.postStr, event, C011_sendBinary); + } + } else { + addLog(LOG_LEVEL_ERROR, F("C011 : Could not add to delay handler")); + } + + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE, C011_DelayHandler->getNextScheduleTime()); + return success; +} + +// parses the string and returns only the the number of name/values we want +// according to the parameter numberOfValuesWanted +void DeleteNotNeededValues(String& s, uint8_t numberOfValuesWanted) +{ + numberOfValuesWanted++; + + for (uint8_t i = 1; i < 5; i++) + { + const String startToken(strformat(F("%%%d%%"), i)); + const String endToken(strformat(F("%%/%d%%"), i)); + + // do we want to keep this one? + if (i < numberOfValuesWanted) + { + // yes, so just remove the tokens + s.replace(startToken, EMPTY_STRING); + s.replace(endToken, EMPTY_STRING); + } + else + { + // remove all the whole strings including tokes + int startIndex = s.indexOf(startToken); + int endIndex = s.indexOf(endToken); + + while (startIndex != -1 && endIndex != -1 && endIndex > startIndex) + { + String p = s.substring(startIndex, endIndex + 4); + + // remove the whole string including tokens + s.replace(p, EMPTY_STRING); + + // find next ones + startIndex = s.indexOf(startToken); + endIndex = s.indexOf(endToken); + } + } + } +} + +// ******************************************************************************** +// Replace the token in a string by real value. +// +// Example: +// %1%%vname1%____%tskname%____%val1%%/1%%2%%__%%vname2%____%tskname%____%val2%%/2% +// will become in case of a sensor with 1 value: +// SENSORVALUENAME1____TASKNAME1____VALUE1 <- everything not between %1% and %/1% will be discarded +// in case of a sensor with 2 values: +// SENSORVALUENAME1____TASKNAME1____VALUE1__SENSORVALUENAME2____TASKNAME2____VALUE2 +// ******************************************************************************** +void ReplaceTokenByValue(String& s, struct EventStruct *event, bool sendBinary) +{ + // example string: + // write?db=testdb&type=%1%%vname1%%/1%%2%;%vname2%%/2%%3%;%vname3%%/3%%4%;%vname4%%/4%&value=%1%%val1%%/1%%2%;%val2%%/2%%3%;%val3%%/3%%4%;%val4%%/4% + // %1%%vname1%,Standort=%tskname% Wert=%val1%%/1%%2%%LF%%vname2%,Standort=%tskname% Wert=%val2%%/2%%3%%LF%%vname3%,Standort=%tskname% + // Wert=%val3%%/3%%4%%LF%%vname4%,Standort=%tskname% Wert=%val4%%/4% + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP before parsing: ")); + addLog(LOG_LEVEL_DEBUG_MORE, s); + } + #endif + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + DeleteNotNeededValues(s, valueCount); + + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after parsing: ")); + addLog(LOG_LEVEL_DEBUG_MORE, s); + } + #endif + + parseControllerVariables(s, event, !sendBinary); + + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, F("HTTP after replacements: ")); + addLog(LOG_LEVEL_DEBUG_MORE, s); + } + #endif +} + +#endif // ifdef USES_C011 diff --git a/src/_C012.cpp b/src/_C012.cpp index 09387ef50..94d639cf6 100644 --- a/src/_C012.cpp +++ b/src/_C012.cpp @@ -1,125 +1,125 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C012 - -// ####################################################################################################### -// ########################### Controller Plugin 012: Blynk ############################################# -// ####################################################################################################### - -# include "src/Commands/Blynk.h" - -# define CPLUGIN_012 -# define CPLUGIN_ID_012 12 -# define CPLUGIN_NAME_012 "Blynk HTTP" - -bool CPlugin_012(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_012; - proto.usesMQTT = false; - proto.usesAccount = false; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 80; - proto.usesID = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_012); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c012_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c012_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C012_DelayHandler == nullptr) { - break; - } - if (C012_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed - - // Collect the values at the same run, to make sure all are from the same sample - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - std::unique_ptr element(new C012_queue_element(event, valueCount)); - - for (uint8_t x = 0; x < valueCount; x++) - { - bool isvalid; - const String formattedValue = formatUserVar(event, x, isvalid); - - if (isvalid) { - move_special(element->txt[x], strformat( - F("update/V%d?value=%s"), - event->idx + x, - formattedValue.c_str())); - - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLog(LOG_LEVEL_DEBUG_MORE, element->txt[x]); - } - #endif - } - } - - - success = C012_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE, C012_DelayHandler->getNextScheduleTime()); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c012_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Process Queued Blynk request, with data set to NULL -// ******************************************************************************** - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c012_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C012_queue_element& element = static_cast(element_base); -// *INDENT-ON* - while (element.txt[element.valuesSent].isEmpty()) { - // A non valid value, which we are not going to send. - // Increase sent counter until a valid value is found. - if (element.checkDone(true)) { - return true; - } - } - - if (!NetworkConnected()) { - return false; - } - return element.checkDone(Blynk_get(element.txt[element.valuesSent], element._controller_idx)); -} - -#endif // ifdef USES_C012 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C012 + +// ####################################################################################################### +// ########################### Controller Plugin 012: Blynk ############################################# +// ####################################################################################################### + +# include "src/Commands/Blynk.h" + +# define CPLUGIN_012 +# define CPLUGIN_ID_012 12 +# define CPLUGIN_NAME_012 "Blynk HTTP" + +bool CPlugin_012(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_012; + proto.usesMQTT = false; + proto.usesAccount = false; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 80; + proto.usesID = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_012); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c012_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c012_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C012_DelayHandler == nullptr) { + break; + } + if (C012_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + //LoadTaskSettings(event->TaskIndex); // FIXME TD-er: This can probably be removed + + // Collect the values at the same run, to make sure all are from the same sample + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + std::unique_ptr element(new (std::nothrow) C012_queue_element(event, valueCount)); + + for (uint8_t x = 0; x < valueCount; x++) + { + bool isvalid; + const String formattedValue = formatUserVar(event, x, isvalid); + + if (isvalid) { + move_special(element->txt[x], strformat( + F("update/V%d?value=%s"), + event->idx + x, + formattedValue.c_str())); + + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLog(LOG_LEVEL_DEBUG_MORE, element->txt[x]); + } + #endif + } + } + + + success = C012_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE, C012_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c012_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Process Queued Blynk request, with data set to NULL +// ******************************************************************************** + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c012_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C012_queue_element& element = static_cast(element_base); +// *INDENT-ON* + while (element.txt[element.valuesSent].isEmpty()) { + // A non valid value, which we are not going to send. + // Increase sent counter until a valid value is found. + if (element.checkDone(true)) { + return true; + } + } + + if (!NetworkConnected()) { + return false; + } + return element.checkDone(Blynk_get(element.txt[element.valuesSent], element._controller_idx)); +} + +#endif // ifdef USES_C012 diff --git a/src/_C013.cpp b/src/_C013.cpp index c9855e882..56243a081 100644 --- a/src/_C013.cpp +++ b/src/_C013.cpp @@ -1,369 +1,424 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C013 - -# if FEATURE_ESPEASY_P2P == 0 - # error "Controller C013 ESPEasy P2P requires the FEATURE_ESPEASY_P2P enabled" -# endif // if FEATURE_ESPEASY_P2P == 0 - - -# include "src/Globals/Nodes.h" -# include "src/DataStructs/C013_p2p_dataStructs.h" -# include "src/ESPEasyCore/ESPEasyRules.h" -# include "src/Helpers/Misc.h" -# include "src/Helpers/Network.h" - -// ####################################################################################################### -// ########################### Controller Plugin 013: ESPEasy P2P network ################################ -// ####################################################################################################### - -# define CPLUGIN_013 -# define CPLUGIN_ID_013 13 -# define CPLUGIN_NAME_013 "ESPEasy P2P Networking" - - -// Forward declarations -void C013_SendUDPTaskInfo(uint8_t destUnit, - uint8_t sourceTaskIndex, - uint8_t destTaskIndex); -void C013_SendUDPTaskData(struct EventStruct *event, - uint8_t destUnit, - uint8_t destTaskIndex); -void C013_sendUDP(uint8_t unit, - const uint8_t *data, - uint8_t size); -void C013_Receive(struct EventStruct *event); - - -bool CPlugin_013(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_013; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesHost = false; - proto.defaultPort = 8266; - proto.usesID = false; - proto.Custom = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_013); - break; - } - - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: - { - C013_SendUDPTaskInfo(0, event->TaskIndex, event->TaskIndex); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - C013_SendUDPTaskData(event, 0, event->TaskIndex); - success = true; - break; - } - - case CPlugin::Function::CPLUGIN_UDP_IN: - { - C013_Receive(event); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - string = F("-"); - break; - } - - /* - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c013_delay_queue(event->ControllerIndex); - delay(0); - break; - } - */ - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Generic UDP message -// ******************************************************************************** -void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex) -{ - if (!NetworkConnected(10)) { - return; - } - - if (!validTaskIndex(sourceTaskIndex) || !validTaskIndex(destTaskIndex)) { - return; - } - pluginID_t pluginID = Settings.getPluginID_for_task(sourceTaskIndex); - - if (!validPluginID_fullcheck(pluginID)) { - return; - } - - struct C013_SensorInfoStruct infoReply; - - infoReply.sourceUnit = Settings.Unit; - infoReply.sourceTaskIndex = sourceTaskIndex; - infoReply.destTaskIndex = destTaskIndex; - infoReply.deviceNumber = pluginID; - safe_strncpy(infoReply.taskName, getTaskDeviceName(infoReply.sourceTaskIndex), sizeof(infoReply.taskName)); - - for (uint8_t x = 0; x < VARS_PER_TASK; x++) { - safe_strncpy(infoReply.ValueNames[x], getTaskValueName(infoReply.sourceTaskIndex, x), sizeof(infoReply.ValueNames[x])); - } - - if (destUnit != 0) - { - infoReply.destUnit = destUnit; - C013_sendUDP(destUnit, reinterpret_cast(&infoReply), sizeof(C013_SensorInfoStruct)); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - infoReply.destUnit = it->first; - C013_sendUDP(it->first, reinterpret_cast(&infoReply), sizeof(C013_SensorInfoStruct)); - } - } - } -} - -void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex) -{ - if (!NetworkConnected(10)) { - return; - } - struct C013_SensorDataStruct dataReply; - - 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(); - - const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); - - if (taskValues != nullptr) { - for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) - { - dataReply.values.copyValue(*taskValues, x, dataReply.sensorType); - } - } - - if (destUnit != 0) - { - dataReply.destUnit = destUnit; - C013_sendUDP(destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - dataReply.destUnit = it->first; - C013_sendUDP(it->first, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); - } - } - } -} - -/*********************************************************************************************\ - Send UDP message (unit 255=broadcast) -\*********************************************************************************************/ -void C013_sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) -{ - if (!NetworkConnected(10)) { - return; - } - - const IPAddress remoteNodeIP = getIPAddressForUnit(unit); - - -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( - F("C013 : Send UDP message to %d (%s)"), - unit, - remoteNodeIP.toString().c_str())); - } -# endif // ifndef BUILD_NO_DEBUG - - statusLED(true); - - WiFiUDP C013_portUDP; - - if (!beginWiFiUDP_randomPort(C013_portUDP)) { return; } - - FeedSW_watchdog(); - - if (C013_portUDP.beginPacket(remoteNodeIP, Settings.UDPPort) == 0) { return; } - C013_portUDP.write(data, size); - C013_portUDP.endPacket(); - C013_portUDP.stop(); - FeedSW_watchdog(); - delay(0); -} - -void C013_Receive(struct EventStruct *event) { - if (event->Par2 < 6) { return; } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - if ((event->Data != nullptr) && - (event->Data[1] > 1) && (event->Data[1] < 6)) - { - String log = (F("C013 : msg ")); - - for (uint8_t x = 1; x < 6; x++) - { - log += ' '; - log += static_cast(event->Data[x]); - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } -# endif // ifndef BUILD_NO_DEBUG - - switch (event->Data[1]) { - case 2: // sensor info pull request - { - // SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); - break; - } - - case 3: // sensor info - { - struct C013_SensorInfoStruct infoReply; - int structSize = sizeof(C013_SensorInfoStruct); - - if (event->Par2 < structSize) { structSize = event->Par2; } - - memcpy(reinterpret_cast(&infoReply), event->Data, structSize); - - if (infoReply.isValid()) { - // 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. - if (!validPluginID_fullcheck(Settings.getPluginID_for_task(infoReply.destTaskIndex)) && - 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 - - constexpr pluginID_t DUMMY_PLUGIN_ID{33}; - 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); - } - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; - } - 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])); - } - ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; - SaveTaskSettings(infoReply.destTaskIndex); - SaveSettings(); - } - } - break; - } - - case 4: // sensor data pull request - { - // SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); - break; - } - - case 5: // sensor data - { - struct C013_SensorDataStruct dataReply; - int structSize = sizeof(C013_SensorDataStruct); - - if (event->Par2 < structSize) { structSize = event->Par2; } - memcpy(reinterpret_cast(&dataReply), event->Data, structSize); - - // 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.isValid()) { - // only if this task has a remote feed, update values - const uint8_t remoteFeed = Settings.TaskDeviceDataFeed[dataReply.destTaskIndex]; - - if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) - { - // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) - // See: https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 - // Thus should not be checked - // - // 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. - bool mustMatch = false; - NodeStruct *sourceNode = Nodes.getNode(dataReply.sourceUnit); - if (sourceNode != nullptr) { - mustMatch = sourceNode->build >= 20460; - } - - if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { - // Mismatch in plugin ID from sending node - 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); - } - } else { - 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 (taskValues != nullptr) { - for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) - { - taskValues->copyValue(dataReply.values, x, sensorType); - } - } - - SensorSendTask(&TempEvent); - } else { - // 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); - } - } - } - } - } - break; - } - } -} - -#endif // ifdef USES_C013 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C013 + +# if FEATURE_ESPEASY_P2P == 0 + # error "Controller C013 ESPEasy P2P requires the FEATURE_ESPEASY_P2P enabled" +# endif // if FEATURE_ESPEASY_P2P == 0 + + +# include "src/Globals/Nodes.h" +# include "src/DataStructs/C013_p2p_SensorDataStruct.h" +# include "src/DataStructs/C013_p2p_SensorInfoStruct.h" +# include "src/ESPEasyCore/ESPEasyRules.h" +# include "src/Helpers/Misc.h" +# include "src/Helpers/Network.h" + +// ####################################################################################################### +// ########################### Controller Plugin 013: ESPEasy P2P network ################################ +// ####################################################################################################### + +# define CPLUGIN_013 +# define CPLUGIN_ID_013 13 +# define CPLUGIN_NAME_013 "ESPEasy P2P Networking" + + +// Forward declarations +void C013_SendUDPTaskInfo(uint8_t destUnit, + uint8_t sourceTaskIndex, + uint8_t destTaskIndex); +void C013_SendUDPTaskData(struct EventStruct *event, + uint8_t destUnit, + uint8_t destTaskIndex); +void C013_sendUDP(uint8_t unit, + const uint8_t *data, + size_t size); +void C013_Receive(struct EventStruct *event); + + +bool CPlugin_013(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_013; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesHost = false; + proto.defaultPort = 8266; + proto.usesID = false; + proto.Custom = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_013); + break; + } + + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: + { + C013_SendUDPTaskInfo(0, event->TaskIndex, event->TaskIndex); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + C013_SendUDPTaskData(event, 0, event->TaskIndex); + success = true; + break; + } + + case CPlugin::Function::CPLUGIN_UDP_IN: + { + C013_Receive(event); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + + /* + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c013_delay_queue(event->ControllerIndex); + delay(0); + break; + } + */ + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Generic UDP message +// ******************************************************************************** +void C013_SendUDPTaskInfo(uint8_t destUnit, uint8_t sourceTaskIndex, uint8_t destTaskIndex) +{ + if (!NetworkConnected(10)) { + return; + } + + if (!validTaskIndex(sourceTaskIndex) || !validTaskIndex(destTaskIndex)) { + return; + } + pluginID_t pluginID = Settings.getPluginID_for_task(sourceTaskIndex); + + if (!validPluginID_fullcheck(pluginID)) { + return; + } + + struct C013_SensorInfoStruct infoReply; + + 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; + } + size_t sizeToSend{}; + + if (infoReply.prepareForSend(sizeToSend)) { + C013_sendUDP(infoReply.destUnit, reinterpret_cast(&infoReply), sizeToSend); + } +} + +void C013_SendUDPTaskData(struct EventStruct *event, uint8_t destUnit, uint8_t destTaskIndex) +{ + if (!NetworkConnected(10)) { + return; + } + struct C013_SensorDataStruct dataReply; + + 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(); + + const TaskValues_Data_t *taskValues = UserVar.getRawTaskValues_Data(event->TaskIndex); + + if (taskValues != nullptr) { + for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) + { + dataReply.values.copyValue(*taskValues, x, dataReply.sensorType); + } + } + dataReply.destUnit = destUnit; + + if (destUnit == 0) + { + // Send to broadcast address + dataReply.destUnit = 255; + } + dataReply.prepareForSend(); + C013_sendUDP(dataReply.destUnit, reinterpret_cast(&dataReply), sizeof(C013_SensorDataStruct)); +} + +/*********************************************************************************************\ + Send UDP message (unit 255=broadcast) +\*********************************************************************************************/ +void C013_sendUDP(uint8_t unit, const uint8_t *data, size_t size) +{ + START_TIMER + + if (!NetworkConnected(10)) { + return; + } + + const IPAddress remoteNodeIP = getIPAddressForUnit(unit); + + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + addLogMove(LOG_LEVEL_DEBUG_MORE, strformat( + F("C013 : Send UDP message to %d (%s)"), + unit, + remoteNodeIP.toString().c_str())); + } +# endif // ifndef BUILD_NO_DEBUG + + statusLED(true); + + WiFiUDP C013_portUDP; + + if (!beginWiFiUDP_randomPort(C013_portUDP)) { + STOP_TIMER(C013_SEND_UDP_FAIL); + return; + } + + FeedSW_watchdog(); + + if (C013_portUDP.beginPacket(remoteNodeIP, Settings.UDPPort) == 0) { + STOP_TIMER(C013_SEND_UDP_FAIL); + return; + } + C013_portUDP.write(data, size); + C013_portUDP.endPacket(); + C013_portUDP.stop(); + FeedSW_watchdog(); + delay(0); + STOP_TIMER(C013_SEND_UDP); +} + +void C013_Receive(struct EventStruct *event) { + if (event->Par2 < 6) { return; } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { + if ((event->Data != nullptr) && + (event->Data[1] > 1) && (event->Data[1] < 6)) + { + String log = (F("C013 : msg ")); + + for (uint8_t x = 1; x < 6; x++) + { + log += ' '; + log += static_cast(event->Data[x]); + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } +# endif // ifndef BUILD_NO_DEBUG + + START_TIMER + + switch (event->Data[1]) { + case 2: // sensor info pull request + { + // SendUDPTaskInfo(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 3: // sensor info + { + bool mustSave = false; + taskIndex_t taskIndex = INVALID_TASK_INDEX; + { + // 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)) { + // 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); + bool mustUpdateCurrentTask = false; + + 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]) { + mustUpdateCurrentTask = true; + } + } + + if ((mustUpdateCurrentTask || !validPluginID_fullcheck(currentPluginID)) && + 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 + + if (mustUpdateCurrentTask) { + 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)) { + // Received a dummy device and the sensor type is actually set + Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][0] = static_cast(infoReply.sensorType); + } + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + Settings.TaskDeviceSendData[x][infoReply.destTaskIndex] = false; + } + 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])); + } + + 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]; + } + + for (uint8_t x = 0; x < PLUGIN_CONFIGVAR_MAX; ++x) { + Settings.TaskDevicePluginConfig[infoReply.destTaskIndex][x] = infoReply.TaskDevicePluginConfig[x]; + } + } + + ExtraTaskSettings.TaskIndex = infoReply.destTaskIndex; + taskIndex = infoReply.destTaskIndex; + mustSave = true; + } + } + } + + if (mustSave) { + SaveTaskSettings(taskIndex); + SaveSettings(); + + if (Settings.TaskDeviceEnabled[taskIndex]) { + struct EventStruct TempEvent(taskIndex); + TempEvent.Source = EventValueSource::Enum::VALUE_SOURCE_UDP; + + String dummy; + PluginCall(PLUGIN_INIT, &TempEvent, dummy); + } + } + break; + } + + case 4: // sensor data pull request + { + // SendUDPTaskData(packetBuffer[2], packetBuffer[5], packetBuffer[4]); + break; + } + + case 5: // sensor data + { + struct C013_SensorDataStruct dataReply; + + // 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]; + + if ((remoteFeed != 0) && (remoteFeed == dataReply.sourceUnit)) + { + // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) + // See: + // https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 + // Thus should not be checked + // + // 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; + + if (mustMatch && !dataReply.matchesPluginID(Settings.getPluginID_for_task(dataReply.destTaskIndex))) { + // Mismatch in plugin ID from sending node + 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); + } + } else { + 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 (taskValues != nullptr) { + for (taskVarIndex_t x = 0; x < VARS_PER_TASK; ++x) + { + taskValues->copyValue(dataReply.values, x, sensorType); + } + } + STOP_TIMER(C013_RECEIVE_SENSOR_DATA); + + 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); + } else { + SensorSendTask(&TempEvent); + } + } else { + // 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); + } + } + } + } + } + + break; + } + } +} + +#endif // ifdef USES_C013 diff --git a/src/_C014.cpp b/src/_C014.cpp index 60d813507..95aa7d275 100644 --- a/src/_C014.cpp +++ b/src/_C014.cpp @@ -865,31 +865,8 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& eventQueue.addMove(std::move(newEvent)); } } else { // not an event - String log; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("C014 :"); - } - // FIXME TD-er: Command is not parsed, should we call ExecuteCommand here? - if (ExecuteCommand_internal(EventValueSource::Enum::VALUE_SOURCE_MQTT, cmd.c_str())) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" Internal Command: OK!"); - } - } else if (PluginCall(PLUGIN_WRITE, &TempEvent, cmd)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" PluginCall: OK!"); - } - } else { - remoteConfig(&TempEvent, cmd); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" Plugin/Internal command failed! remoteConfig?"); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, log); - } + ExecuteCommand_all_config({EventValueSource::Enum::VALUE_SOURCE_MQTT, std::move(cmd)}, true); } } } @@ -904,6 +881,7 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& } String pubname = CPlugin_014_pubname; + const bool contains_valname = pubname.indexOf(F("%valname%")) != -1; bool mqtt_retainFlag = CPlugin_014_mqtt_retainFlag; statusLED(true); @@ -917,7 +895,9 @@ bool CPlugin_014(CPlugin::Function function, struct EventStruct *event, String& { String tmppubname = pubname; String value; - parseSingleControllerVariable(tmppubname, event, x, false); + if (contains_valname) { + parseSingleControllerVariable(tmppubname, event, x, false); + } // Small optimization so we don't try to copy potentially large strings if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { diff --git a/src/_C015.cpp b/src/_C015.cpp index 6443a2439..e06d6edda 100644 --- a/src/_C015.cpp +++ b/src/_C015.cpp @@ -1,492 +1,494 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C015 - -# include "src/Globals/CPlugins.h" -# include "src/Commands/Common.h" -# include "src/ESPEasyCore/ESPEasy_backgroundtasks.h" - -// ####################################################################################################### -// ########################### Controller Plugin 015: Blynk ############################################# -// ####################################################################################################### - -// This plugin provides blynk native protocol. This makes possible receive callbacks from user -// like button press, slider move etc. -// This require much more ESP resources, than use of blynk http API. -// So, use C012 Blynk HTTP plugin when you don't need blynk calbacks. -// -// Only one blynk controller instance is supported. -// -// https://www.youtube.com/watch?v=5_V_DibOypE - -// Uncomment this to use ssl connection. This requires more device resources than unencrypted one. -// Also it requires valid server thumbprint string to be entered in plugin settings. -// #define CPLUGIN_015_SSL - -# define CPLUGIN_015 -# define CPLUGIN_ID_015 15 -# define _BLYNK_USE_DEFAULT_FREE_RAM -# define BLYNK_TIMEOUT_MS 2000UL -# define BLYNK_HEARTBEAT 30 -# define CPLUGIN_015_RECONNECT_INTERVAL 60000 - -# ifdef CPLUGIN_015_SSL - #ifdef ESP8266 - # include - #endif - #ifdef ESP32 - # include - #endif - # define CPLUGIN_NAME_015 "Blynk SSL" - -// Current official blynk server thumbprint - # define CPLUGIN_015_DEFAULT_THUMBPRINT "FD C0 7D 8D 47 97 F7 E3 07 05 D3 4E E3 BB 8E 3D C0 EA BE 1C" - # define C015_LOG_PREFIX "BL (ssl): " -# else // ifdef CPLUGIN_015_SSL - #ifdef ESP8266 - # include - #endif - #ifdef ESP32 - # include - #endif - # define CPLUGIN_NAME_015 "Blynk" - # define C015_LOG_PREFIX "BL: " -# endif // ifdef CPLUGIN_015_SSL - - -// Forward declarations: -boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout); -boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings); - - -static unsigned long _C015_LastConnectAttempt[CONTROLLER_MAX] = { 0, 0, 0 }; - -void CPlugin_015_handleInterrupt() { - // This cplugin uses modified blynk library. - // It includes support of calling this during time-wait operations - // like blynk connection process to keep espeasy stability. - backgroundtasks(); -} - -void Blynk_Run_c015() { - // user callbacks processing. Called from run10TimesPerSecond. - if (Blynk.connected()) { - Blynk.run(); - } -} - -bool CPlugin_015(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_015; - proto.usesMQTT = false; - proto.usesAccount = false; - proto.usesPassword = true; - proto.usesExtCreds = true; - proto.defaultPort = 80; - proto.usesID = false; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_015); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c015_delay_queue(event->ControllerIndex); - - // when connected to another server and user has changed settings - if (success && Blynk.connected()) { - addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "disconnect from server")); - Blynk.disconnect(); - } - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c015_delay_queue(); - break; - } - - # ifdef CPLUGIN_015_SSL - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: - { - char thumbprint[60] = {0}; - LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); - - if (strlen(thumbprint) != 59) { - strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); - } - addFormTextBox(F("Server thumbprint string"), F("c015_thumbprint"), thumbprint, 60); - success = true; - break; - } - # endif // ifdef CPLUGIN_015_SSL - - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - { - success = true; - - if (isFormItemChecked(F("controllerenabled"))) { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); - - if (validProtocolIndex(ProtocolIndex)) { - const cpluginID_t number = getCPluginID_from_ProtocolIndex(ProtocolIndex); - if ((i != event->ControllerIndex) && (number == 15) && Settings.ControllerEnabled[i]) { - success = false; - - // FIXME: this will only show a warning message and not uncheck "enabled" in webform. - // Webserver object is not checking result of "success" var :( - addHtmlError(F("Only one enabled instance of blynk controller is supported")); - break; - } - } - } - - // force to connect without delay when webform saved - _C015_LastConnectAttempt[event->ControllerIndex] = 0; - - # ifdef CPLUGIN_015_SSL - char thumbprint[60] = {0}; - String error = F("Specify server thumbprint with exactly 59 symbols string like " CPLUGIN_015_DEFAULT_THUMBPRINT); - - if (!safe_strncpy(thumbprint, webArg("c015_thumbprint"), 60) || (strlen(thumbprint) != 59)) { - addHtmlError(error); - } - SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); - # endif // ifdef CPLUGIN_015_SSL - } - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C015_DelayHandler == nullptr) { - break; - } - if (C015_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - if (!Settings.ControllerEnabled[event->ControllerIndex]) { - break; - } - - // Collect the values at the same run, to make sure all are from the same sample - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - std::unique_ptr element(new C015_queue_element(event, valueCount)); - success = C015_DelayHandler->addToQueue(std::move(element)); - - if (success) { - // Element was added. - // Now we try to append to the existing element - // and thus preventing the need to create a long string only to copy it to a queue element. - C015_queue_element& element = static_cast(*(C015_DelayHandler->sendQueue.back())); - - for (uint8_t x = 0; x < valueCount; x++) - { - bool isvalid; - String formattedValue = formatUserVar(event, x, isvalid); - - if (!isvalid) { - // send empty string to Blynk in case of error - formattedValue = String(); - } - - const String valueName = getTaskValueName(event->TaskIndex, x); - const String valueFullName = strformat( - F("%s.%s"), - getTaskDeviceName(event->TaskIndex).c_str(), - valueName.c_str()); - const String vPinNumberStr = valueName.substring(1, 4); - int vPinNumber = vPinNumberStr.toInt(); - - if ((vPinNumber < 0) || (vPinNumber > 255)) { - vPinNumber = -1; - } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F(C015_LOG_PREFIX); - log += Blynk.connected() ? F("(online): ") : F("(offline): "); - - if ((vPinNumber > 0) && (vPinNumber < 256)) { - log += strformat( - F("send %s = %s to blynk pin v%d"), - valueFullName.c_str(), - formattedValue.c_str(), - vPinNumber); - } else { - log += strformat( - F("error got vPin number for %s, got not valid value: %s"), - valueFullName.c_str(), - vPinNumberStr.c_str()); - } - addLogMove(LOG_LEVEL_INFO, log); - } - element.vPin[x] = vPinNumber; - move_special(element.txt[x], std::move(formattedValue)); - } - } - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE, C015_DelayHandler->getNextScheduleTime()); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Process Queued Blynk request, with data set to NULL -// ******************************************************************************** -// controller_plugin_number = 015 because of C015 - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c015_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C015_queue_element& element = static_cast(element_base); -// *INDENT-ON* - if (!Settings.ControllerEnabled[element._controller_idx]) { - // controller has been disabled. Answer true to flush queue. - return true; - } - - if (!NetworkConnected()) { - return false; - } - - if (!Blynk_keep_connection_c015(element._controller_idx, ControllerSettings)) { - return false; - } - - while (element.vPin[element.valuesSent] == -1) { - // A non valid value, which we are not going to send. - // answer ok and skip real sending - if (element.checkDone(true)) { - return true; - } - } - - bool sendSuccess = Blynk_send_c015( - element.txt[element.valuesSent], - element.vPin[element.valuesSent], - ControllerSettings.ClientTimeout); - - return element.checkDone(sendSuccess); -} - -boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings) { - if (!NetworkConnected()) { - return false; - } - - if (!Blynk.connected()) { - String auth = getControllerPass(controllerIndex, ControllerSettings); - boolean connectDefault = false; - - if (timePassedSince(_C015_LastConnectAttempt[controllerIndex]) < CPLUGIN_015_RECONNECT_INTERVAL) { - // "skip connect to blynk server too often. Wait a little..."; - return false; - } - _C015_LastConnectAttempt[controllerIndex] = millis(); - - # ifdef CPLUGIN_015_SSL - char thumbprint[60] = {0}; - LoadCustomControllerSettings(controllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); - - if (strlen(thumbprint) != 59) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Saved thumprint value is not correct:")); - addLog(LOG_LEVEL_INFO, thumbprint); - } - strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "using default one:")); - addLog(LOG_LEVEL_INFO, thumbprint); - } - } - # endif // ifdef CPLUGIN_015_SSL - - String log = F(C015_LOG_PREFIX); - - if (ControllerSettings.UseDNS) { - String hostName = ControllerSettings.getHost(); - - if (!hostName.isEmpty()) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("Connecting to custom blynk server "); - log += ControllerSettings.getHostPortString(); - } - Blynk.config(auth.c_str(), - CPlugin_015_handleInterrupt, - hostName.c_str(), - ControllerSettings.Port - # ifdef CPLUGIN_015_SSL - , thumbprint - # endif // ifdef CPLUGIN_015_SSL - ); - } - else { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("Custom blynk server name not specified. "); - } - connectDefault = true; - } - } - else { - IPAddress ip = ControllerSettings.getIP(); - - if ((ip[0] + ip[1] + ip[2] + ip[3]) > 0) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("Connecting to custom blynk server "); - log += ControllerSettings.getHostPortString(); - } - Blynk.config(auth.c_str(), - CPlugin_015_handleInterrupt, - ip, - ControllerSettings.Port - # ifdef CPLUGIN_015_SSL - , thumbprint - # endif // ifdef CPLUGIN_015_SSL - ); - } - else { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("Custom blynk server ip not specified. "); - } - connectDefault = true; - } - } - addLogMove(LOG_LEVEL_INFO, log); - - if (connectDefault) { - addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Connecting to default server")); - Blynk.config(auth.c_str(), - CPlugin_015_handleInterrupt, - BLYNK_DEFAULT_DOMAIN - # ifdef CPLUGIN_015_SSL - , BLYNK_DEFAULT_PORT_SSL - , thumbprint - # else // ifdef CPLUGIN_015_SSL - , BLYNK_DEFAULT_PORT - # endif // ifdef CPLUGIN_015_SSL - ); - } - - # ifdef CPLUGIN_015_SSL - - if (!Blynk.connect()) { - if (!_blynkWifiClient.verify(thumbprint, BLYNK_DEFAULT_DOMAIN)) { - addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "thumbprint check FAILED! Check thumbprint in device settings and server thumbprint")); - addLog(LOG_LEVEL_INFO, thumbprint); - } - } - # else // ifdef CPLUGIN_015_SSL - Blynk.connect(); - # endif // ifdef CPLUGIN_015_SSL - } - - return Blynk.connected(); -} - -String Command_Blynk_Set_c015(struct EventStruct *event, const char *Line) { - // todo add multicontroller support and chek it is connected and enabled - if (!Blynk.connected()) { - return F("Not connected to blynk server"); - } - - int vPin = event->Par1; - - if ((vPin < 0) || (vPin > 255)) { - return concat(F("Not correct blynk vPin number "), vPin); - } - - String data = parseString(Line, 3); - - if (data.isEmpty()) { - return concat(F("Skip sending empty data to blynk vPin "), vPin); - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F(C015_LOG_PREFIX "(online): send blynk pin v%d = %s"), - vPin, - data.c_str())); - } - - Blynk.virtualWrite(vPin, data); - return return_command_success(); -} - -boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout) -{ - Blynk.virtualWrite(vPin, value); - - unsigned long timer = millis() + clientTimeout; - - while (!timeOutReached(timer)) { - backgroundtasks(); - } - return true; -} - -// This is called for all virtual pins, that don't have BLYNK_WRITE handler -BLYNK_WRITE_DEFAULT() { - const unsigned int vPin = request.pin; - const float pinValue = param.asFloat(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F(C015_LOG_PREFIX "server set v%u to %f"), - vPin, - pinValue)); - } - - if (Settings.UseRules) { - eventQueue.addMove(strformat( - F("blynkv%d=%f"), - vPin, - pinValue)); - } -} - -BLYNK_CONNECTED() { - // Your code here when hardware connects to Blynk Cloud or private server. - // It’s common to call sync functions inside of this function. - // Requests all stored on the server latest values for all widgets. - if (Settings.UseRules) { - eventQueue.add(F("blynk_connected")); - } - - // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "connected handler")); -} - -// This is called when Smartphone App is opened -BLYNK_APP_CONNECTED() { - if (Settings.UseRules) { - eventQueue.add(F("blynk_app_connected")); - } - - // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app connected handler")); -} - -// This is called when Smartphone App is closed -BLYNK_APP_DISCONNECTED() { - if (Settings.UseRules) { - eventQueue.add(F("blynk_app_disconnected")); - } - - // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app disconnected handler")); -} - +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C015 + +# include "src/Globals/CPlugins.h" +# include "src/Commands/Common.h" +# include "src/ESPEasyCore/ESPEasy_backgroundtasks.h" + +// ####################################################################################################### +// ########################### Controller Plugin 015: Blynk ############################################# +// ####################################################################################################### + +// This plugin provides blynk native protocol. This makes possible receive callbacks from user +// like button press, slider move etc. +// This require much more ESP resources, than use of blynk http API. +// So, use C012 Blynk HTTP plugin when you don't need blynk calbacks. +// +// Only one blynk controller instance is supported. +// +// https://www.youtube.com/watch?v=5_V_DibOypE + +// Uncomment this to use ssl connection. This requires more device resources than unencrypted one. +// Also it requires valid server thumbprint string to be entered in plugin settings. +// #define CPLUGIN_015_SSL + +# define CPLUGIN_015 +# define CPLUGIN_ID_015 15 +# define _BLYNK_USE_DEFAULT_FREE_RAM +# define BLYNK_TIMEOUT_MS 2000UL +# define BLYNK_HEARTBEAT 30 +# define CPLUGIN_015_RECONNECT_INTERVAL 60000 + +# ifdef CPLUGIN_015_SSL + #ifdef ESP8266 + # include + #endif + #ifdef ESP32 + # include + #endif + # define CPLUGIN_NAME_015 "Blynk SSL" + +// Current official blynk server thumbprint + # define CPLUGIN_015_DEFAULT_THUMBPRINT "FD C0 7D 8D 47 97 F7 E3 07 05 D3 4E E3 BB 8E 3D C0 EA BE 1C" + # define C015_LOG_PREFIX "BL (ssl): " +# else // ifdef CPLUGIN_015_SSL + #ifdef ESP8266 + # include + #endif + #ifdef ESP32 + # include + #endif + # define CPLUGIN_NAME_015 "Blynk" + # define C015_LOG_PREFIX "BL: " +# endif // ifdef CPLUGIN_015_SSL + + +// Forward declarations: +boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout); +boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings); + + +static unsigned long _C015_LastConnectAttempt[CONTROLLER_MAX] = { 0, 0, 0 }; + +void CPlugin_015_handleInterrupt() { + // This cplugin uses modified blynk library. + // It includes support of calling this during time-wait operations + // like blynk connection process to keep espeasy stability. + backgroundtasks(); +} + +void Blynk_Run_c015() { + // user callbacks processing. Called from run10TimesPerSecond. + if (Blynk.connected()) { + Blynk.run(); + } +} + +bool CPlugin_015(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_015; + proto.usesMQTT = false; + proto.usesAccount = false; + proto.usesPassword = true; + proto.usesExtCreds = true; + proto.defaultPort = 80; + proto.usesID = false; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_015); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c015_delay_queue(event->ControllerIndex); + + // when connected to another server and user has changed settings + if (success && Blynk.connected()) { + addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "disconnect from server")); + Blynk.disconnect(); + } + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c015_delay_queue(); + break; + } + + # ifdef CPLUGIN_015_SSL + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + char thumbprint[60] = {0}; + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); + + if (strlen(thumbprint) != 59) { + strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); + } + addFormTextBox(F("Server thumbprint string"), F("c015_thumbprint"), thumbprint, 60); + success = true; + break; + } + # endif // ifdef CPLUGIN_015_SSL + + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + success = true; + + if (isFormItemChecked(F("controllerenabled"))) { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); + + if (validProtocolIndex(ProtocolIndex)) { + const cpluginID_t number = getCPluginID_from_ProtocolIndex(ProtocolIndex); + if ((i != event->ControllerIndex) && (number == 15) && Settings.ControllerEnabled[i]) { + success = false; + + // FIXME: this will only show a warning message and not uncheck "enabled" in webform. + // Webserver object is not checking result of "success" var :( + addHtmlError(F("Only one enabled instance of blynk controller is supported")); + break; + } + } + } + + // force to connect without delay when webform saved + _C015_LastConnectAttempt[event->ControllerIndex] = 0; + + # ifdef CPLUGIN_015_SSL + char thumbprint[60] = {0}; + String error = F("Specify server thumbprint with exactly 59 symbols string like " CPLUGIN_015_DEFAULT_THUMBPRINT); + + if (!safe_strncpy(thumbprint, webArg("c015_thumbprint"), 60) || (strlen(thumbprint) != 59)) { + addHtmlError(error); + } + SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); + # endif // ifdef CPLUGIN_015_SSL + } + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C015_DelayHandler == nullptr) { + break; + } + if (C015_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + if (!Settings.ControllerEnabled[event->ControllerIndex]) { + break; + } + + // Collect the values at the same run, to make sure all are from the same sample + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + std::unique_ptr element(new (std::nothrow) C015_queue_element(event, valueCount)); + success = C015_DelayHandler->addToQueue(std::move(element)); + + if (success) { + // Element was added. + // Now we try to append to the existing element + // and thus preventing the need to create a long string only to copy it to a queue element. + C015_queue_element& element = static_cast(*(C015_DelayHandler->sendQueue.back())); + + const String taskDeviceName = getTaskDeviceName(event->TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + bool isvalid; + String formattedValue = formatUserVar(event, x, isvalid); + + if (!isvalid) { + // send empty string to Blynk in case of error + formattedValue = String(); + } + + const String valueName = Cache.getTaskDeviceValueName(event->TaskIndex, x); + const String valueFullName = strformat( + F("%s.%s"), + taskDeviceName.c_str(), + valueName.c_str()); + const String vPinNumberStr = valueName.substring(1, 4); + int vPinNumber = vPinNumberStr.toInt(); + + if ((vPinNumber < 0) || (vPinNumber > 255)) { + vPinNumber = -1; + } + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F(C015_LOG_PREFIX); + log += Blynk.connected() ? F("(online): ") : F("(offline): "); + + if ((vPinNumber > 0) && (vPinNumber < 256)) { + log += strformat( + F("send %s = %s to blynk pin v%d"), + valueFullName.c_str(), + formattedValue.c_str(), + vPinNumber); + } else { + log += strformat( + F("error got vPin number for %s, got not valid value: %s"), + valueFullName.c_str(), + vPinNumberStr.c_str()); + } + addLogMove(LOG_LEVEL_INFO, log); + } + element.vPin[x] = vPinNumber; + move_special(element.txt[x], std::move(formattedValue)); + } + } + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE, C015_DelayHandler->getNextScheduleTime()); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Process Queued Blynk request, with data set to NULL +// ******************************************************************************** +// controller_plugin_number = 015 because of C015 + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c015_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C015_queue_element& element = static_cast(element_base); +// *INDENT-ON* + if (!Settings.ControllerEnabled[element._controller_idx]) { + // controller has been disabled. Answer true to flush queue. + return true; + } + + if (!NetworkConnected()) { + return false; + } + + if (!Blynk_keep_connection_c015(element._controller_idx, ControllerSettings)) { + return false; + } + + while (element.vPin[element.valuesSent] == -1) { + // A non valid value, which we are not going to send. + // answer ok and skip real sending + if (element.checkDone(true)) { + return true; + } + } + + bool sendSuccess = Blynk_send_c015( + element.txt[element.valuesSent], + element.vPin[element.valuesSent], + ControllerSettings.ClientTimeout); + + return element.checkDone(sendSuccess); +} + +boolean Blynk_keep_connection_c015(int controllerIndex, ControllerSettingsStruct& ControllerSettings) { + if (!NetworkConnected()) { + return false; + } + + if (!Blynk.connected()) { + String auth = getControllerPass(controllerIndex, ControllerSettings); + boolean connectDefault = false; + + if (timePassedSince(_C015_LastConnectAttempt[controllerIndex]) < CPLUGIN_015_RECONNECT_INTERVAL) { + // "skip connect to blynk server too often. Wait a little..."; + return false; + } + _C015_LastConnectAttempt[controllerIndex] = millis(); + + # ifdef CPLUGIN_015_SSL + char thumbprint[60] = {0}; + LoadCustomControllerSettings(controllerIndex, reinterpret_cast(&thumbprint), sizeof(thumbprint)); + + if (strlen(thumbprint) != 59) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Saved thumprint value is not correct:")); + addLog(LOG_LEVEL_INFO, thumbprint); + } + strcpy(thumbprint, CPLUGIN_015_DEFAULT_THUMBPRINT); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "using default one:")); + addLog(LOG_LEVEL_INFO, thumbprint); + } + } + # endif // ifdef CPLUGIN_015_SSL + + String log = F(C015_LOG_PREFIX); + + if (ControllerSettings.UseDNS) { + String hostName = ControllerSettings.getHost(); + + if (!hostName.isEmpty()) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("Connecting to custom blynk server "); + log += ControllerSettings.getHostPortString(); + } + Blynk.config(auth.c_str(), + CPlugin_015_handleInterrupt, + hostName.c_str(), + ControllerSettings.Port + # ifdef CPLUGIN_015_SSL + , thumbprint + # endif // ifdef CPLUGIN_015_SSL + ); + } + else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("Custom blynk server name not specified. "); + } + connectDefault = true; + } + } + else { + IPAddress ip = ControllerSettings.getIP(); + + if ((ip[0] + ip[1] + ip[2] + ip[3]) > 0) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("Connecting to custom blynk server "); + log += ControllerSettings.getHostPortString(); + } + Blynk.config(auth.c_str(), + CPlugin_015_handleInterrupt, + ip, + ControllerSettings.Port + # ifdef CPLUGIN_015_SSL + , thumbprint + # endif // ifdef CPLUGIN_015_SSL + ); + } + else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("Custom blynk server ip not specified. "); + } + connectDefault = true; + } + } + addLogMove(LOG_LEVEL_INFO, log); + + if (connectDefault) { + addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "Connecting to default server")); + Blynk.config(auth.c_str(), + CPlugin_015_handleInterrupt, + BLYNK_DEFAULT_DOMAIN + # ifdef CPLUGIN_015_SSL + , BLYNK_DEFAULT_PORT_SSL + , thumbprint + # else // ifdef CPLUGIN_015_SSL + , BLYNK_DEFAULT_PORT + # endif // ifdef CPLUGIN_015_SSL + ); + } + + # ifdef CPLUGIN_015_SSL + + if (!Blynk.connect()) { + if (!_blynkWifiClient.verify(thumbprint, BLYNK_DEFAULT_DOMAIN)) { + addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "thumbprint check FAILED! Check thumbprint in device settings and server thumbprint")); + addLog(LOG_LEVEL_INFO, thumbprint); + } + } + # else // ifdef CPLUGIN_015_SSL + Blynk.connect(); + # endif // ifdef CPLUGIN_015_SSL + } + + return Blynk.connected(); +} + +String Command_Blynk_Set_c015(struct EventStruct *event, const char *Line) { + // todo add multicontroller support and chek it is connected and enabled + if (!Blynk.connected()) { + return F("Not connected to blynk server"); + } + + int vPin = event->Par1; + + if ((vPin < 0) || (vPin > 255)) { + return concat(F("Not correct blynk vPin number "), vPin); + } + + String data = parseString(Line, 3); + + if (data.isEmpty()) { + return concat(F("Skip sending empty data to blynk vPin "), vPin); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F(C015_LOG_PREFIX "(online): send blynk pin v%d = %s"), + vPin, + data.c_str())); + } + + Blynk.virtualWrite(vPin, data); + return return_command_success(); +} + +boolean Blynk_send_c015(const String& value, int vPin, unsigned int clientTimeout) +{ + Blynk.virtualWrite(vPin, value); + + unsigned long timer = millis() + clientTimeout; + + while (!timeOutReached(timer)) { + backgroundtasks(); + } + return true; +} + +// This is called for all virtual pins, that don't have BLYNK_WRITE handler +BLYNK_WRITE_DEFAULT() { + const unsigned int vPin = request.pin; + const float pinValue = param.asFloat(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F(C015_LOG_PREFIX "server set v%u to %f"), + vPin, + pinValue)); + } + + if (Settings.UseRules) { + eventQueue.addMove(strformat( + F("blynkv%d=%f"), + vPin, + pinValue)); + } +} + +BLYNK_CONNECTED() { + // Your code here when hardware connects to Blynk Cloud or private server. + // It’s common to call sync functions inside of this function. + // Requests all stored on the server latest values for all widgets. + if (Settings.UseRules) { + eventQueue.add(F("blynk_connected")); + } + + // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "connected handler")); +} + +// This is called when Smartphone App is opened +BLYNK_APP_CONNECTED() { + if (Settings.UseRules) { + eventQueue.add(F("blynk_app_connected")); + } + + // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app connected handler")); +} + +// This is called when Smartphone App is closed +BLYNK_APP_DISCONNECTED() { + if (Settings.UseRules) { + eventQueue.add(F("blynk_app_disconnected")); + } + + // addLog(LOG_LEVEL_INFO, F(C015_LOG_PREFIX "app disconnected handler")); +} + #endif // ifdef USES_C015 \ No newline at end of file diff --git a/src/_C016.cpp b/src/_C016.cpp index 59bfecbb1..625c377b0 100644 --- a/src/_C016.cpp +++ b/src/_C016.cpp @@ -1,183 +1,186 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C016 - -// ####################################################################################################### -// ########################### Controller Plugin 016: Controller - Cache ################################# -// ####################################################################################################### - -/* - This is a cache layer to collect data while not connected to a network. - The data will first be stored in RTC memory, which will survive a crash/reboot and even an OTA update. - If this RTC buffer is full, it will be flushed to whatever is set here as storage. - - Typical sample sets contain: - - UNIX timestamp - - task index delivering the data - - 4 float values - - These are the result of any plugin sending data to this controller. - - The controller can save the samples from RTC memory to several places on the flash: - - Files on FS - - Part reserved for OTA update (TODO) - - Unused flash after the partitioned space (TODO) - - The controller can deliver the data to: - - */ - -# include "src/Globals/C016_ControllerCache.h" -# include "src/Globals/ESPEasy_time.h" - -# define CPLUGIN_016 -# define CPLUGIN_ID_016 16 -# define CPLUGIN_NAME_016 "Cache Controller [Experimental]" - -// #include - -bool C016_allowLocalSystemTime = false; - -bool CPlugin_016(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_016; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesExtCreds = false; - proto.defaultPort = 80; - proto.usesID = false; - proto.usesHost = false; - proto.usesPort = false; - proto.usesQueue = false; - proto.usesCheckReply = false; - proto.usesTimeout = false; - proto.usesSampleSets = false; - proto.needsNetwork = false; - proto.allowsExpire = false; - proto.allowLocalSystemTime = true; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_016); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - { - MakeControllerSettings(ControllerSettings); // -V522 - - if (AllocatedControllerSettings()) { - LoadControllerSettings(event->ControllerIndex, *ControllerSettings); - C016_allowLocalSystemTime = ControllerSettings->useLocalSystemTime(); - } - } - success = init_c016_delay_queue(event->ControllerIndex); - ControllerCache.init(); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c016_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: - { - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - { - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: - { - event->String1 = String(); - event->String2 = String(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - // Collect the values at the same run, to make sure all are from the same sample - uint8_t valueCount = getValueCountForTask(event->TaskIndex); - - if (event->timestamp == 0) { - event->timestamp = C016_allowLocalSystemTime ? node_time.now() : node_time.getUnixTime(); - } - const C016_queue_element element( - event, - valueCount); - - const C016_binary_element binary_element = element.getBinary(); - success = ControllerCache.write(reinterpret_cast(&binary_element), sizeof(C016_binary_element)); - break; - } - - case CPlugin::Function::CPLUGIN_WRITE: - { - if (C016_CacheInitialized()) { - const String command = parseString(string, 1); - - if (equals(command, F("cachecontroller"))) { - const String subcommand = parseString(string, 2); - - if (equals(subcommand, F("flush"))) { - C016_flush(); - success = true; - } - } - } - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - C016_flush(); - delay(0); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - string = F("-"); - break; - } - - default: - break; - } - return success; -} - -// ******************************************************************************** -// Process the data from the cache -// ******************************************************************************** -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c016_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { -// *INDENT-ON* -return true; - -// FIXME TD-er: Hand over data to wherever it needs to be. -// Ideas: -// - Upload bin files to some server (HTTP post?) -// - Provide a sample to any connected controller -// - Do nothing and let some extern host pull the data from the node. -// - JavaScript to process the data inside the browser. -// - Feed it to some plugin (e.g. a display to show a chart) -} - -#endif // ifdef USES_C016 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C016 + +// ####################################################################################################### +// ########################### Controller Plugin 016: Controller - Cache ################################# +// ####################################################################################################### + +/* + This is a cache layer to collect data while not connected to a network. + The data will first be stored in RTC memory, which will survive a crash/reboot and even an OTA update. + If this RTC buffer is full, it will be flushed to whatever is set here as storage. + + Typical sample sets contain: + - UNIX timestamp + - task index delivering the data + - 4 float values + + These are the result of any plugin sending data to this controller. + + The controller can save the samples from RTC memory to several places on the flash: + - Files on FS + - Part reserved for OTA update (TODO) + - Unused flash after the partitioned space (TODO) + + The controller can deliver the data to: + + */ + +# include "src/Globals/C016_ControllerCache.h" +# include "src/Globals/ESPEasy_time.h" + +# define CPLUGIN_016 +# define CPLUGIN_ID_016 16 +# define CPLUGIN_NAME_016 "Cache Controller [Experimental]" + +// #include + +bool C016_allowLocalSystemTime = false; + +bool CPlugin_016(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_016; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesExtCreds = false; + proto.defaultPort = 80; + proto.usesID = false; + proto.usesHost = false; + proto.usesPort = false; + proto.usesQueue = false; + proto.usesCheckReply = false; + proto.usesTimeout = false; + proto.usesSampleSets = false; + proto.needsNetwork = false; + proto.allowsExpire = false; + proto.allowLocalSystemTime = true; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_016); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + { + MakeControllerSettings(ControllerSettings); // -V522 + + if (AllocatedControllerSettings()) { + LoadControllerSettings(event->ControllerIndex, *ControllerSettings); + C016_allowLocalSystemTime = ControllerSettings->useLocalSystemTime(); + } + } + success = init_c016_delay_queue(event->ControllerIndex); + ControllerCache.init(); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c016_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: + { + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: + { + event->String1 = String(); + event->String2 = String(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + // Collect the values at the same run, to make sure all are from the same sample + uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + if (event->timestamp_sec == 0) { + if (C016_allowLocalSystemTime) + event->setLocalTimeTimestamp(); + else + event->setUnixTimeTimestamp(); + } + const C016_queue_element element( + event, + valueCount); + + const C016_binary_element binary_element = element.getBinary(); + success = ControllerCache.write(reinterpret_cast(&binary_element), sizeof(C016_binary_element)); + break; + } + + case CPlugin::Function::CPLUGIN_WRITE: + { + if (C016_CacheInitialized()) { + const String command = parseString(string, 1); + + if (equals(command, F("cachecontroller"))) { + const String subcommand = parseString(string, 2); + + if (equals(subcommand, F("flush"))) { + C016_flush(); + success = true; + } + } + } + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + C016_flush(); + delay(0); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + string = F("-"); + break; + } + + default: + break; + } + return success; +} + +// ******************************************************************************** +// Process the data from the cache +// ******************************************************************************** +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c016_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { +// *INDENT-ON* +return true; + +// FIXME TD-er: Hand over data to wherever it needs to be. +// Ideas: +// - Upload bin files to some server (HTTP post?) +// - Provide a sample to any connected controller +// - Do nothing and let some extern host pull the data from the node. +// - JavaScript to process the data inside the browser. +// - Feed it to some plugin (e.g. a display to show a chart) +} + +#endif // ifdef USES_C016 diff --git a/src/_C017.cpp b/src/_C017.cpp index 874f24af9..fc8f5131d 100644 --- a/src/_C017.cpp +++ b/src/_C017.cpp @@ -1,152 +1,152 @@ -#include "src/Helpers/_CPlugin_Helper.h" -#ifdef USES_C017 - -// ####################################################################################################### -// ########################### Controller Plugin 017: ZABBIX ########################################## -// ####################################################################################################### -// Based on https://www.zabbix.com/documentation/current/manual/appendix/items/trapper -// and https://www.zabbix.com/documentation/4.2/manual/appendix/protocols/header_datalen - -// USAGE: at Zabbix server you go at Configuration -> Hosts -> Create host -// The "Host name" should match exactly the EspEasy name (Config -> Unit Name) -// Add a group (mandatory) and hit add. No need to set up IP address or agent. -// Go to the newly created host ->Items ->Create Item -// Name the item something descriptive -// For Key add the EspEasy task Value name (case sensitive) -// Type of information select "Numeric (float)" and press add. -// Aslo make sure that you enable send to controller (under Data Acquisition in tasks) -// and set an interval because you need to actively send the data to Zabbix - -# define CPLUGIN_017 -# define CPLUGIN_ID_017 17 -# define CPLUGIN_NAME_017 "Zabbix" -# include - -bool CPlugin_017(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_017; - proto.usesMQTT = false; - proto.usesTemplate = false; - proto.usesAccount = false; - proto.usesPassword = false; - proto.usesID = false; - proto.defaultPort = 10051; - break; - } - - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: - { - string = F(CPLUGIN_NAME_017); - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c017_delay_queue(event->ControllerIndex); - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - exit_c017_delay_queue(); - break; - } - - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: - { - if (C017_DelayHandler == nullptr) { - break; - } - if (C017_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - std::unique_ptr element(new C017_queue_element(event)); - success = C017_DelayHandler->addToQueue(std::move(element)); - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE, C017_DelayHandler->getNextScheduleTime()); - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c017_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -// Uncrustify may change this into multi line, which will result in failed builds -// *INDENT-OFF* -bool do_process_c017_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C017_queue_element& element = static_cast(element_base); -// *INDENT-ON* - if (element.valueCount == 0) { - return true; // exit if we don't have anything to send. - } - - if (!NetworkConnected(10)) - { - return false; - } - - WiFiClient client; - - if (!try_connect_host(controller_number, client, ControllerSettings, F("ZBX : "))) - { - return false; - } - - const size_t capacity = JSON_ARRAY_SIZE(VARS_PER_TASK) + JSON_OBJECT_SIZE(2) + VARS_PER_TASK * JSON_OBJECT_SIZE(3) + VARS_PER_TASK * 50; //Size for esp8266 with 4 variables per task: 288+200 - String JSON_packet_content; - { - // Place the JSON document in a separate scope to have it destructed as soon as it is no longer needed. - DynamicJsonDocument root(capacity); - - // Create the schafolding - root[F("request")] = F("sender data"); - JsonArray data = root.createNestedArray(F("data")); - - // Populate JSON with the data - for (uint8_t i = 0; i < element.valueCount; i++) - { - const String taskValueName = getTaskValueName(element._taskIndex, i); - if (taskValueName.isEmpty()) { - continue; // Zabbix will ignore an empty key anyway - } - JsonObject block = data.createNestedObject(); - block[F("host")] = Settings.getName(); // Zabbix hostname, Unit Name for the ESP easy - block[F("key")] = taskValueName; // Zabbix item key // Value Name for the ESP easy - float value = 0.0f; - validFloatFromString(element.txt[i], value); - block[F("value")] = value; // ESPeasy supports only floats - } - serializeJson(root, JSON_packet_content); - } - - // Assemble packet - char packet_header[] = "ZBXD\1"; - - uint64_t payload_len = JSON_packet_content.length(); - - // addLog(LOG_LEVEL_INFO, String(F("ZBX: ")) + JSON_packet_content); - // Send the packet - client.write(packet_header, sizeof(packet_header) - 1); - client.write(reinterpret_cast(&payload_len), sizeof(payload_len)); - client.write(JSON_packet_content.c_str(), payload_len); - - client.stop(); - return true; -} - -#endif // ifdef USES_C017 +#include "src/Helpers/_CPlugin_Helper.h" +#ifdef USES_C017 + +// ####################################################################################################### +// ########################### Controller Plugin 017: ZABBIX ########################################## +// ####################################################################################################### +// Based on https://www.zabbix.com/documentation/current/manual/appendix/items/trapper +// and https://www.zabbix.com/documentation/4.2/manual/appendix/protocols/header_datalen + +// USAGE: at Zabbix server you go at Configuration -> Hosts -> Create host +// The "Host name" should match exactly the EspEasy name (Config -> Unit Name) +// Add a group (mandatory) and hit add. No need to set up IP address or agent. +// Go to the newly created host ->Items ->Create Item +// Name the item something descriptive +// For Key add the EspEasy task Value name (case sensitive) +// Type of information select "Numeric (float)" and press add. +// Aslo make sure that you enable send to controller (under Data Acquisition in tasks) +// and set an interval because you need to actively send the data to Zabbix + +# define CPLUGIN_017 +# define CPLUGIN_ID_017 17 +# define CPLUGIN_NAME_017 "Zabbix" +# include + +bool CPlugin_017(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_017; + proto.usesMQTT = false; + proto.usesTemplate = false; + proto.usesAccount = false; + proto.usesPassword = false; + proto.usesID = false; + proto.defaultPort = 10051; + break; + } + + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: + { + string = F(CPLUGIN_NAME_017); + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c017_delay_queue(event->ControllerIndex); + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + exit_c017_delay_queue(); + break; + } + + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: + { + if (C017_DelayHandler == nullptr) { + break; + } + if (C017_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + std::unique_ptr element(new (std::nothrow) C017_queue_element(event)); + success = C017_DelayHandler->addToQueue(std::move(element)); + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE, C017_DelayHandler->getNextScheduleTime()); + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c017_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +// Uncrustify may change this into multi line, which will result in failed builds +// *INDENT-OFF* +bool do_process_c017_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C017_queue_element& element = static_cast(element_base); +// *INDENT-ON* + if (element.valueCount == 0) { + return true; // exit if we don't have anything to send. + } + + if (!NetworkConnected(10)) + { + return false; + } + + WiFiClient client; + + if (!try_connect_host(cpluginID, client, ControllerSettings, F("ZBX : "))) + { + return false; + } + + const size_t capacity = JSON_ARRAY_SIZE(VARS_PER_TASK) + JSON_OBJECT_SIZE(2) + VARS_PER_TASK * JSON_OBJECT_SIZE(3) + VARS_PER_TASK * 50; //Size for esp8266 with 4 variables per task: 288+200 + String JSON_packet_content; + { + // Place the JSON document in a separate scope to have it destructed as soon as it is no longer needed. + DynamicJsonDocument root(capacity); + + // Create the schafolding + root[F("request")] = F("sender data"); + JsonArray data = root.createNestedArray(F("data")); + + // Populate JSON with the data + for (uint8_t i = 0; i < element.valueCount; i++) + { + const String taskValueName = Cache.getTaskDeviceValueName(element._taskIndex, i); + if (taskValueName.isEmpty()) { + continue; // Zabbix will ignore an empty key anyway + } + JsonObject block = data.createNestedObject(); + block[F("host")] = Settings.getName(); // Zabbix hostname, Unit Name for the ESP easy + block[F("key")] = taskValueName; // Zabbix item key // Value Name for the ESP easy + float value = 0.0f; + validFloatFromString(element.txt[i], value); + block[F("value")] = value; // ESPeasy supports only floats + } + serializeJson(root, JSON_packet_content); + } + + // Assemble packet + char packet_header[] = "ZBXD\1"; + + uint64_t payload_len = JSON_packet_content.length(); + + // addLog(LOG_LEVEL_INFO, concat(F("ZBX: "), JSON_packet_content)); + // Send the packet + client.write(packet_header, sizeof(packet_header) - 1); + client.write(reinterpret_cast(&payload_len), sizeof(payload_len)); + client.write(JSON_packet_content.c_str(), payload_len); + + client.stop(); + return true; +} + +#endif // ifdef USES_C017 diff --git a/src/_C018.cpp b/src/_C018.cpp index a25bb2881..6642609dc 100644 --- a/src/_C018.cpp +++ b/src/_C018.cpp @@ -1,432 +1,433 @@ -#include "src/Helpers/_CPlugin_Helper.h" - -#ifdef USES_C018 - -// ####################################################################################################### -// ########################### Controller Plugin 018: LoRa TTN - RN2483/RN2903 ########################### -// ####################################################################################################### - -# define CPLUGIN_018 -# define CPLUGIN_ID_018 18 -# define CPLUGIN_NAME_018 "LoRa TTN - RN2483/RN2903" - - - -# include - -# include "src/ControllerQueue/C018_queue_element.h" -# include "src/Controller_config/C018_config.h" -# include "src/Controller_struct/C018_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" - - -// Have this define after the includes, so we can set it in Custom.h -# ifndef C018_FORCE_SW_SERIAL -# define C018_FORCE_SW_SERIAL false -# endif // ifndef C018_FORCE_SW_SERIAL - - -// FIXME TD-er: Must add a controller data struct vector, like with plugins. -C018_data_struct *C018_data = nullptr; - - -// Forward declarations -bool C018_init(struct EventStruct *event); -String c018_add_joinChanged_script_element_line(const String& id, - bool forOTAA); - - -bool CPlugin_018(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_018; - 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_018); - break; - } - - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - { - if ((C018_data != nullptr) && C018_data->isInitialized()) { - string = F("Dev addr: "); - string += C018_data->getDevaddr(); - string += C018_data->useOTAA() ? F(" (OTAA)") : F(" (ABP)"); - } else { - string = F("-"); - } - break; - } - - case CPlugin::Function::CPLUGIN_INIT: - { - success = init_c018_delay_queue(event->ControllerIndex); - - if (success) { - C018_init(event); - } - break; - } - - case CPlugin::Function::CPLUGIN_EXIT: - { - if (C018_data != nullptr) { - C018_data->reset(); - delete C018_data; - C018_data = nullptr; - } - exit_c018_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';")); - addHtml(c018_add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, - ControllerSettingsStruct::CONTROLLER_USER), - true)); - addHtml(c018_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)); - - 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)); - addHtml('}'); - html_add_script_end(); - } - - { - // Keep this object in a small scope so we can destruct it as soon as possible again. - std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); - - if (!customConfig) { - break; - } - LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C018_ConfigStruct)); - customConfig->webform_load(C018_data); - } - - break; - } - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: - { - std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); - - if (customConfig) { - customConfig->webform_save(); - SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), - sizeof(C018_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 (C018_DelayHandler == nullptr) { - break; - } - - if (C018_DelayHandler->queueFull(event->ControllerIndex)) { - break; - } - - if (C018_data != nullptr) { - { - std::unique_ptr element(new 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()); - } - - if (!C018_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. - C018_init(event); - } - } - break; - } - - 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); - break; - } - - case CPlugin::Function::CPLUGIN_WRITE: - { - if (C018_data != nullptr) { - if (C018_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 = C018_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 (C018_data != nullptr) { - C018_data->async_loop(); - } - - // FIXME TD-er: Handle reading error state or return values. - break; - } - - case CPlugin::Function::CPLUGIN_FLUSH: - { - process_c018_delay_queue(); - delay(0); - break; - } - - default: - break; - } - return success; -} - -bool C018_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 (C018_data != nullptr) { - C018_data->reset(); - delete C018_data; - C018_data = nullptr; - } - - - C018_data = new (std::nothrow) C018_data_struct; - - if (C018_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); - C018_DelayHandler->cacheControllerSettings(*ControllerSettings); - AppEUI = getControllerUser(event->ControllerIndex, *ControllerSettings); - AppKey = getControllerPass(event->ControllerIndex, *ControllerSettings); - SampleSetInitiator = ControllerSettings->SampleSetInitiator; - Port = ControllerSettings->Port; - } - - std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); - - if (!customConfig) { - return false; - } - LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C018_ConfigStruct)); - customConfig->validate(); - - if (!C018_data->init(customConfig->serialPort, customConfig->rxpin, customConfig->txpin, customConfig->baudrate, - (customConfig->joinmethod == C018_USE_OTAA), - SampleSetInitiator, customConfig->resetpin)) - { - return false; - } - - C018_data->setFrequencyPlan(static_cast(customConfig->frequencyplan), customConfig->rx2_freq); - - if (!C018_data->setSF(customConfig->sf)) { - return false; - } - - if (!C018_data->setAdaptiveDataRate(customConfig->adr != 0)) { - return false; - } - - if (!C018_data->setTTNstack(static_cast(customConfig->stackVersion))) { - return false; - } - - if (customConfig->joinmethod == C018_USE_OTAA) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("OTAA: AppEUI: "); - log += AppEUI; - log += F(" AppKey: "); - log += AppKey; - log += F(" DevEUI: "); - log += customConfig->DeviceEUI; - - addLogMove(LOG_LEVEL_INFO, log); - } - - if (!C018_data->initOTAA(AppEUI, AppKey, customConfig->DeviceEUI)) { - return false; - } - } - else { - if (!C018_data->initABP(customConfig->DeviceAddr, customConfig->AppSessionKey, customConfig->NetworkSessionKey)) { - return false; - } - } - - - if (!C018_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_c018_delay_queue(int controller_number, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { - const C018_queue_element& element = static_cast(element_base); -// *INDENT-ON* -uint8_t pl = (element.packed.length() / 2); -float airtime_ms = C018_data->getLoRaAirTime(pl); -bool mustSetDelay = false; -bool success = false; - -if (!C018_data->command_finished()) { - mustSetDelay = true; -} else { - success = C018_data->txHexBytes(element.packed, ControllerSettings.Port); - - if (success) { - if (airtime_ms > 0.0f) { - ADD_TIMER_STAT(C018_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 = C018_data->getLastError(); // Clear the error string. - -if (error.indexOf(F("no_free_ch")) != -1) { - mustSetDelay = true; -} - -if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("C018 : 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. - C018_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; -} - -String c018_add_joinChanged_script_element_line(const String& id, bool forOTAA) { - String result = F("document.getElementById('tr_"); - - result += id; - result += F("').style.display = style"); - result += forOTAA ? F("OTAA") : F("ABP"); - result += ';'; - return result; -} - -#endif // ifdef USES_C018 +#include "src/Helpers/_CPlugin_Helper.h" + +#ifdef USES_C018 + +// ####################################################################################################### +// ########################### Controller Plugin 018: LoRa TTN - RN2483/RN2903 ########################### +// ####################################################################################################### + +# define CPLUGIN_018 +# define CPLUGIN_ID_018 18 +# define CPLUGIN_NAME_018 "LoRa TTN - RN2483/RN2903" + + +# include + +# include "src/ControllerQueue/C018_queue_element.h" +# include "src/Controller_config/C018_config.h" +# include "src/Controller_struct/C018_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" + + +// Have this define after the includes, so we can set it in Custom.h +# ifndef C018_FORCE_SW_SERIAL +# define C018_FORCE_SW_SERIAL false +# endif // ifndef C018_FORCE_SW_SERIAL + + +// FIXME TD-er: Must add a controller data struct vector, like with plugins. +C018_data_struct *C018_data = nullptr; + + +// Forward declarations +bool C018_init(struct EventStruct *event); +String c018_add_joinChanged_script_element_line(const String& id, + bool forOTAA); + + +bool CPlugin_018(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_018; + 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_018); + break; + } + + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + { + if ((C018_data != nullptr) && C018_data->isInitialized()) { + string = F("Dev addr: "); + string += C018_data->getDevaddr(); + string += C018_data->useOTAA() ? F(" (OTAA)") : F(" (ABP)"); + } else { + string = F("-"); + } + break; + } + + case CPlugin::Function::CPLUGIN_INIT: + { + success = init_c018_delay_queue(event->ControllerIndex); + + if (success) { + C018_init(event); + } + break; + } + + case CPlugin::Function::CPLUGIN_EXIT: + { + if (C018_data != nullptr) { + C018_data->reset(); + delete C018_data; + C018_data = nullptr; + } + exit_c018_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';")); + addHtml(c018_add_joinChanged_script_element_line(getControllerParameterInternalName(ProtocolIndex, + ControllerSettingsStruct::CONTROLLER_USER), + true)); + addHtml(c018_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)); + + 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)); + addHtml('}'); + html_add_script_end(); + } + + { + // Keep this object in a small scope so we can destruct it as soon as possible again. + std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); + + if (!customConfig) { + break; + } + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C018_ConfigStruct)); + customConfig->webform_load(C018_data); + } + + break; + } + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: + { + std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); + + if (customConfig) { + customConfig->webform_save(); + SaveCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), + sizeof(C018_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 (C018_DelayHandler == nullptr) { + break; + } + + if (C018_DelayHandler->queueFull(event->ControllerIndex)) { + break; + } + + if (C018_data != nullptr) { + { + std::unique_ptr element(new (std::nothrow) 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()); + } + + if (!C018_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. + C018_init(event); + } + } + break; + } + + 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); + break; + } + + case CPlugin::Function::CPLUGIN_WRITE: + { + if (C018_data != nullptr) { + if (C018_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 = C018_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 (C018_data != nullptr) { + C018_data->async_loop(); + } + + // FIXME TD-er: Handle reading error state or return values. + break; + } + + case CPlugin::Function::CPLUGIN_FLUSH: + { + process_c018_delay_queue(); + delay(0); + break; + } + + default: + break; + } + return success; +} + +bool C018_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 (C018_data != nullptr) { + C018_data->reset(); + delete C018_data; + C018_data = nullptr; + } + + + C018_data = new (std::nothrow) C018_data_struct; + + if (C018_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); + C018_DelayHandler->cacheControllerSettings(*ControllerSettings); + AppEUI = getControllerUser(event->ControllerIndex, *ControllerSettings); + AppKey = getControllerPass(event->ControllerIndex, *ControllerSettings); + SampleSetInitiator = ControllerSettings->SampleSetInitiator; + Port = ControllerSettings->Port; + } + + std::shared_ptr customConfig(new (std::nothrow) C018_ConfigStruct); + + if (!customConfig) { + return false; + } + LoadCustomControllerSettings(event->ControllerIndex, reinterpret_cast(customConfig.get()), sizeof(C018_ConfigStruct)); + customConfig->validate(); + + if (!C018_data->init(customConfig->serialPort, customConfig->rxpin, customConfig->txpin, customConfig->baudrate, + (customConfig->joinmethod == C018_USE_OTAA), + SampleSetInitiator, customConfig->resetpin)) + { + return false; + } + + C018_data->setFrequencyPlan(static_cast(customConfig->frequencyplan), customConfig->rx2_freq); + + if (!C018_data->setSF(customConfig->sf)) { + return false; + } + + if (!C018_data->setAdaptiveDataRate(customConfig->adr != 0)) { + return false; + } + + if (!C018_data->setTTNstack(static_cast(customConfig->stackVersion))) { + return false; + } + + if (customConfig->joinmethod == C018_USE_OTAA) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("OTAA: AppEUI: "); + log += AppEUI; + log += F(" AppKey: "); + log += AppKey; + log += F(" DevEUI: "); + log += customConfig->DeviceEUI; + + addLogMove(LOG_LEVEL_INFO, log); + } + + if (!C018_data->initOTAA(AppEUI, AppKey, customConfig->DeviceEUI)) { + return false; + } + } + else { + if (!C018_data->initABP(customConfig->DeviceAddr, customConfig->AppSessionKey, customConfig->NetworkSessionKey)) { + return false; + } + } + + + if (!C018_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_c018_delay_queue(cpluginID_t cpluginID, const Queue_element_base& element_base, ControllerSettingsStruct& ControllerSettings) { + const C018_queue_element& element = static_cast(element_base); +// *INDENT-ON* + uint8_t pl = (element.packed.length() / 2); + float airtime_ms = C018_data->getLoRaAirTime(pl); + bool mustSetDelay = false; + bool success = false; + + if (!C018_data->command_finished()) { + mustSetDelay = true; + } else { + success = C018_data->txHexBytes(element.packed, ControllerSettings.Port); + + if (success) { + if (airtime_ms > 0.0f) { + ADD_TIMER_STAT(C018_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 = C018_data->getLastError(); // Clear the error string. + + if (error.indexOf(F("no_free_ch")) != -1) { + mustSetDelay = true; + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("C018 : 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. + C018_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; +} + +String c018_add_joinChanged_script_element_line(const String& id, bool forOTAA) { + String result = F("document.getElementById('tr_"); + + result += id; + result += F("').style.display = style"); + result += forOTAA ? F("OTAA") : F("ABP"); + result += ';'; + return result; +} + +#endif // ifdef USES_C018 diff --git a/src/_N001_Email.cpp b/src/_N001_Email.cpp index 338265355..9736f60ab 100644 --- a/src/_N001_Email.cpp +++ b/src/_N001_Email.cpp @@ -1,322 +1,492 @@ -#include "ESPEasy_common.h" - -#ifdef USES_N001 - -// ####################################################################################################### -// ########################### Notification Plugin 001: Email ############################################ -// ####################################################################################################### - -/** Changelog: - * 2022-12-29 tonhuisman: Add Date: field to email header to reduce spam score, see https://github.com/letscontrolit/ESPEasy/issues/3865 - * 2022-12-29 tonhuisman: Start changelog -*/ - -# define NPLUGIN_001 -# define NPLUGIN_ID_001 1 -# define NPLUGIN_NAME_001 "Email (SMTP)" - -# define NPLUGIN_001_TIMEOUT 5000 - -# include "src/DataStructs/ESPEasy_EventStruct.h" -# include "src/DataStructs/NotificationSettingsStruct.h" -# include "src/ESPEasyCore/ESPEasy_Log.h" -# include "src/ESPEasyCore/ESPEasy_backgroundtasks.h" -# include "src/Globals/NPlugins.h" -# include "src/Globals/Settings.h" -# include "src/Helpers/ESPEasy_Storage.h" -# include "src/Helpers/ESPEasy_time_calc.h" -# include "src/Helpers/Networking.h" -# include "src/Helpers/StringGenerator_System.h" -# include "src/Helpers/StringParser.h" -# include "src/Helpers/_CPlugin_Helper.h" // safeReadStringUntil -# include "src/Helpers/_NPlugin_init.h" - -# include - -// Forward declaration -bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, - const String & aSub, - String & aMesg); -bool NPlugin_001_Auth(WiFiClient & client, - const String& user, - const String& pass); -bool NPlugin_001_MTA(WiFiClient & client, - const String& aStr, - uint16_t aWaitForPattern); -bool getNextMailAddress(const String& data, - String & address, - int index); - - -// The message body is included in event->String1 -bool NPlugin_001(NPlugin::Function function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) { - case NPlugin::Function::NPLUGIN_PROTOCOL_ADD: - { - Notification[++notificationCount].Number = NPLUGIN_ID_001; - Notification[notificationCount].usesMessaging = true; - Notification[notificationCount].usesGPIO = 0; - break; - } - - case NPlugin::Function::NPLUGIN_GET_DEVICENAME: - { - string = F(NPLUGIN_NAME_001); - break; - } - - // Edwin: NPlugin::Function::NPLUGIN_WRITE seems to be not implemented/not used yet? Disabled because its confusing now. - // case NPlugin::Function::NPLUGIN_WRITE: - // { - // String log; - // String command = parseString(string, 1); - // - // if (command == F("email")) - // { - // MakeNotificationSettings(NotificationSettings); - // LoadNotificationSettings(event->NotificationIndex, (uint8_t*)&NotificationSettings, sizeof(NotificationSettingsStruct)); - // NPlugin_001_send(NotificationSettings.Domain, NotificationSettings.Receiver, NotificationSettings.Sender, - // NotificationSettings.Subject, NotificationSettings.Body, NotificationSettings.Server, NotificationSettings.Port); - // success = true; - // } - // break; - // } - - case NPlugin::Function::NPLUGIN_NOTIFY: - { - MakeNotificationSettings(NotificationSettings); - LoadNotificationSettings(event->NotificationIndex, (uint8_t *)&NotificationSettings, sizeof(NotificationSettingsStruct)); - NotificationSettings.validate(); - String subject = NotificationSettings.Subject; - String body; - - if (event->String1.length() > 0) { - body = event->String1; - } - else { - body = NotificationSettings.Body; - } - subject = parseTemplate(subject); - body = parseTemplate(body); - NPlugin_001_send(NotificationSettings, subject, body); - success = true; - break; - } - - default: - break; - } - return success; -} - -bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, const String& aSub, String& aMesg) -{ - // String& aDomain , String aTo, String aFrom, String aSub, String aMesg, String aHost, int aPort) - bool myStatus = false; - - // Use WiFiClient class to create TCP connections - WiFiClient client; - -# ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - client.setTimeout((CONTROLLER_CLIENTTIMEOUT_MAX + 500) / 1000); // in seconds!!!! - Client *pClient = &client; - pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX); -# else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - client.setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX); // in msec as it should be! -# endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - String aHost = notificationsettings.Server; - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, String(F("EMAIL: Connecting to ")) + aHost + notificationsettings.Port); - } -#endif - - if (!connectClient(client, aHost.c_str(), notificationsettings.Port, CONTROLLER_CLIENTTIMEOUT_DFLT)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, String(F("EMAIL: Error connecting to ")) + aHost + notificationsettings.Port); - } - myStatus = false; - } else { - String mailheader = F( - "From: $nodename <$emailfrom>\r\n" - "To: $ato\r\n" - "Subject: $subject\r\n" - "Reply-To: $nodename <$emailfrom>\r\n" - "Date: $date\r\n" - "MIME-VERSION: 1.0\r\n" - "Content-type: text/html; charset=UTF-8\r\n" - "X-Mailer: EspEasy v$espeasyversion\r\n\r\n" - ); - - String email_address = notificationsettings.Sender; - int pos_less = email_address.indexOf('<'); - - if (pos_less == -1) { - // No email address markup - mailheader.replace(F("$nodename"), Settings.getHostname()); - mailheader.replace(F("$emailfrom"), notificationsettings.Sender); - } else { - String senderName = email_address.substring(0, pos_less); - removeChar(senderName, '"'); // Remove quotes - String address = email_address.substring(pos_less + 1); - removeChar(address, '<'); - removeChar(address, '>'); - address.trim(); - senderName.trim(); - mailheader.replace(F("$nodename"), senderName); - mailheader.replace(F("$emailfrom"), address); - } - - mailheader.replace(F("$nodename"), Settings.getHostname()); - mailheader.replace(F("$emailfrom"), notificationsettings.Sender); - mailheader.replace(F("$ato"), notificationsettings.Receiver); - mailheader.replace(F("$subject"), aSub); - String dateFmtHdr = F("%sysweekday_s%, %sysday_0% %sysmonth_s% %sysyear% %systime% %systzoffset%"); - String date = parseTemplate(dateFmtHdr); - mailheader.replace(F("$date"), date); - mailheader.replace(F("$espeasyversion"), getSystemBuildString()); - aMesg.replace(F("\r"), F("
")); // re-write line breaks for Content-type: text/html - - // Wait for Client to Start Sending - // The MTA Exchange - while (true) { - if (!NPlugin_001_MTA(client, EMPTY_STRING, 220)) { break; } - - if (!NPlugin_001_MTA(client, concat(F("EHLO "), String(notificationsettings.Domain)), 250)) { break; } - - if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass)) { break; } - - if (!NPlugin_001_MTA(client, concat(F("MAIL FROM:<"), String(notificationsettings.Sender) + '>') , 250)) { break; } - - bool nextAddressAvailable = true; - int i = 0; - String emailTo; - const String receiver(notificationsettings.Receiver); - if (!getNextMailAddress(receiver, emailTo, i)) { - addLog(LOG_LEVEL_ERROR, F("Email: No recipient given")); - break; - } - - while (nextAddressAvailable) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo)); - } - - if (!NPlugin_001_MTA(client, concat(F("RCPT TO:<"), emailTo + '>'), 250)) { break; } - ++i; - nextAddressAvailable = getNextMailAddress(receiver, emailTo, i); - } - - if (!NPlugin_001_MTA(client, F("DATA"), 354)) { break; } - - if (!NPlugin_001_MTA(client, mailheader + aMesg + F("\r\n.\r\n"), 250)) { break; } - - myStatus = true; - break; - } - - client.flush(); - client.stop(); - - if (myStatus == true) { - addLog(LOG_LEVEL_INFO, F("EMAIL: Connection Closed Successfully")); - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("EMAIL: Connection Closed With Error. Used header: "), mailheader)); - } - } - } - return myStatus; -} - -bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass) -{ - if (user.isEmpty() || pass.isEmpty()) { - // No user/password given. - return true; - } - base64 encoder; - return NPlugin_001_MTA(client, F("AUTH LOGIN"), 334) && - NPlugin_001_MTA(client, encoder.encode(user), 334) && - NPlugin_001_MTA(client, encoder.encode(pass), 235); -} - -bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern) -{ -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, aStr); - } -#endif - - if (aStr.length()) { client.println(aStr); } - - // Wait For Response - unsigned long timer = millis() + NPLUGIN_001_TIMEOUT; - - backgroundtasks(); - - const String aWaitForPattern_str = String(aWaitForPattern) + ' '; - - while (true) { - if (timeOutReached(timer)) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("NPlugin_001_MTA: timeout. "); - log += aStr; - addLogMove(LOG_LEVEL_ERROR, log); - } - return false; - } - - delay(0); - - // String line = client.readStringUntil('\n'); - String line; - safeReadStringUntil(client, line, '\n'); - - const bool patternFound = line.indexOf(aWaitForPattern_str) >= 0; - -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, line); - } -# endif // ifndef BUILD_NO_DEBUG - - if (patternFound) { - return true; - } - } - - return false; -} - -bool getNextMailAddress(const String& data, String& address, int index) -{ - int found = 0; - int strIndex[] = { 0, -1 }; - const int maxIndex = data.length() - 1; - - for (int i = 0; i <= maxIndex && found <= index; i++) { - if ((data.charAt(i) == ',') || (i == maxIndex)) { - found++; - strIndex[0] = strIndex[1] + 1; - strIndex[1] = (i == maxIndex) ? i + 1 : i; - } - } - - if (found > index) { - address = data.substring(strIndex[0], strIndex[1]); - return true; - } - return false; -} - -#endif // ifdef USES_N001 +#include "ESPEasy_common.h" + +#ifdef USES_N001 + +// ####################################################################################################### +// ########################### Notification Plugin 001: Email ############################################ +// ####################################################################################################### + +/** Changelog: + * 2024-07-01 ThomasB : Modified to support new email server protocol used by some ISP hosting providers + * Add support for (also) supplying an alternate email address via the notify command + * Now uses Plugin's new Timeout Setting for SMTP server response. + * Can now use substitute email address(s), provided within Notify command rule. + * 2024-04-06 tonhuisman: Add support for (also) supplying a custom subject when sending an email notification via the notify command + * Log reply from mailserver at DEBUG level + * Code improvements + * 2022-12-29 tonhuisman: Add Date: field to email header to reduce spam score, see https://github.com/letscontrolit/ESPEasy/issues/3865 + * 2022-12-29 tonhuisman: Start changelog + */ + +# define NPLUGIN_001 +# define NPLUGIN_ID_001 1 +# define NPLUGIN_NAME_001 "Email (SMTP)" + +# define NPLUGIN_001_PKT_SZ 256 + +# include "src/DataStructs/ESPEasy_EventStruct.h" +# include "src/DataStructs/NotificationSettingsStruct.h" +# include "src/ESPEasyCore/ESPEasy_Log.h" +# include "src/ESPEasyCore/ESPEasy_backgroundtasks.h" +# include "src/Globals/NPlugins.h" +# include "src/Globals/Settings.h" +# include "src/Helpers/ESPEasy_Storage.h" +# include "src/Helpers/ESPEasy_time_calc.h" +# include "src/Helpers/Networking.h" +# include "src/Helpers/StringGenerator_System.h" +# include "src/Helpers/StringParser.h" +# include "src/Helpers/_CPlugin_Helper.h" // safeReadStringUntil +# include "src/Helpers/_NPlugin_init.h" + +# include + +// Forward declaration +bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, + const String & aSub, + String & aMesg); +bool NPlugin_001_Auth(WiFiClient & client, + const String& user, + const String& pass, + uint16_t timeout); +bool NPlugin_001_MTA(WiFiClient & client, + const String& aStr, + uint16_t aWaitForPattern, + uint16_t timeout); +bool getNextMailAddress(const String& data, + String & address, + int index); + + +// The message body is included in event->String1 +bool NPlugin_001(NPlugin::Function function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) { + case NPlugin::Function::NPLUGIN_PROTOCOL_ADD: + { + Notification[++notificationCount].Number = NPLUGIN_ID_001; + Notification[notificationCount].usesMessaging = true; + Notification[notificationCount].usesGPIO = 0; + break; + } + + case NPlugin::Function::NPLUGIN_GET_DEVICENAME: + { + string = F(NPLUGIN_NAME_001); + break; + } + + // Edwin: NPlugin::Function::NPLUGIN_WRITE seems to be not implemented/not used yet? Disabled because its confusing now. + // case NPlugin::Function::NPLUGIN_WRITE: + // { + // String log; + // String command = parseString(string, 1); + // + // if (command == F("email")) + // { + // MakeNotificationSettings(NotificationSettings); + // LoadNotificationSettings(event->NotificationIndex, (uint8_t*)&NotificationSettings, sizeof(NotificationSettingsStruct)); + // NPlugin_001_send(NotificationSettings.Domain, NotificationSettings.Receiver, NotificationSettings.Sender, + // NotificationSettings.Subject, NotificationSettings.Body, NotificationSettings.Server, NotificationSettings.Port); + // success = true; + // } + // break; + // } + + case NPlugin::Function::NPLUGIN_NOTIFY: + { + MakeNotificationSettings(NotificationSettings); + LoadNotificationSettings(event->NotificationIndex, (uint8_t *)&NotificationSettings, sizeof(NotificationSettingsStruct)); + NotificationSettings.validate(); + String subject = NotificationSettings.Subject; + String body = NotificationSettings.Body; + + if (!event->String1.isEmpty()) { + body = event->String1; + } + + if (!event->String2.isEmpty()) { + subject = event->String2; + } + subject = parseTemplate(subject); + body = parseTemplate(body); + NPlugin_001_send(NotificationSettings, subject, body); + success = true; + break; + } + + default: + break; + } + return success; +} + +bool NPlugin_001_send(const NotificationSettingsStruct& notificationsettings, const String& aSub, String& aMesg) +{ + // String& aDomain , String aTo, String aFrom, String aSub, String aMesg, String aHost, int aPort) + bool myStatus = false; + bool failFlag = false; + + // Use WiFiClient class to create TCP connections + WiFiClient client; + + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + client.setTimeout((CONTROLLER_CLIENTTIMEOUT_MAX + 500) / 1000); // in seconds!!!! + Client *pClient = &client; + pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX); + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + client.setTimeout(CONTROLLER_CLIENTTIMEOUT_MAX); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("Email: Connecting to %s:%d"), + notificationsettings.Server, + notificationsettings.Port)); + } + # endif // ifndef BUILD_NO_DEBUG + + if (!connectClient(client, notificationsettings.Server, notificationsettings.Port, CONTROLLER_CLIENTTIMEOUT_DFLT)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("Email: Error connecting to %s:%d"), + notificationsettings.Server, + notificationsettings.Port)); + } + myStatus = false; + failFlag = true; + } else { + String mailheader = F( + "From: $nodename <$emailfrom>\r\n" + "To: $ato\r\n" + "Subject: $subject\r\n" + "Reply-To: $nodename <$emailfrom>\r\n" + "Date: $date\r\n" + "MIME-VERSION: 1.0\r\n" + "Content-type: text/html; charset=UTF-8\r\n" + "X-Mailer: EspEasy v$espeasyversion\r\n\r\n" + ); + + uint16_t clientTimeout = notificationsettings.Timeout * 1000; // Convert to mS. + if (clientTimeout < NPLUGIN_001_MIN_TM || clientTimeout > NPLUGIN_001_MAX_TM) { + clientTimeout = NPLUGIN_001_DEF_TM; + } + + String email_address(notificationsettings.Sender); + int pos_less = email_address.indexOf('<'); + String senderName = Settings.getHostname(); + + if (pos_less > -1) { + senderName = email_address.substring(0, pos_less); + removeChar(senderName, '"'); // Remove quotes + email_address = email_address.substring(pos_less + 1); + removeChar(email_address, '<'); + removeChar(email_address, '>'); + email_address.trim(); + senderName.trim(); + } + + + // Use Notify Command's destination email address(s) if provided in Command rules. + // Sample Rule: Notify 1, "{email1@domain.com;email2@domain.net}Test email from %sysname%.
How are you?
Have a good day.
" + String subAddr = ""; + String tmp_ato = ""; + int pos_brace1 = aMesg.indexOf('{'); + int pos_amper = aMesg.indexOf('@'); + int pos_brace2 = aMesg.indexOf('}'); + if(pos_brace1 == 0 && pos_amper > pos_brace1 && pos_brace2 > pos_amper) { + subAddr = aMesg.substring(pos_brace1+1, pos_brace2); + subAddr.trim(); + tmp_ato = subAddr; + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Receiver (ato): %s"), subAddr.c_str())); + } + # endif + + String subMsg = aMesg.substring(pos_brace2+1); // Remove substitute email address from subject line. + subMsg.trim(); + if(subMsg.indexOf(',') == 0) { + subMsg = subMsg.substring(1); // Remove leading comma. + subMsg.trim(); + } + if(!subMsg.length()) { + subMsg = "ERROR: ESPEasy Notify Rule missing the message text. Please correct the rule."; + } + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Substitute Message: %s"), subMsg.c_str())); + } + # endif + aMesg = subMsg; + } + else { + tmp_ato = notificationsettings.Receiver; // Use plugin's receiver. + } + + // Clean up receiver address. + tmp_ato.replace(";", ","); + tmp_ato.replace(" ", ""); + + mailheader.replace(F("$nodename"), senderName); + mailheader.replace(F("$emailfrom"), email_address); + mailheader.replace(F("$ato"), tmp_ato); + mailheader.replace(F("$subject"), aSub); + String dateFmtHdr = F("%sysweekday_s%, %sysday_0% %sysmonth_s% %sysyear% %systime% %systzoffset%"); + mailheader.replace(F("$date"), parseTemplate(dateFmtHdr)); + mailheader.replace(F("$espeasyversion"), getSystemBuildString()); + + // Make sure to replace the char '\r' and not the string "\r" + // See: https://github.com/letscontrolit/ESPEasy/issues/4967 + removeChar(aMesg, '\r'); + aMesg.replace(String('\n'), F("
")); // re-write line breaks for Content-type: text/html + + // Wait for Client to Start Sending + // The MTA Exchange + + if (!failFlag) { + addLog(LOG_LEVEL_INFO, F("Email: Initializing ...")); + + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("Email: Max Allowed Timeout is %d secs"), clientTimeout/1000)); + # endif + + while (true) { + if (!NPlugin_001_MTA(client, EMPTY_STRING, 220, clientTimeout)) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: Initialization Fail")); + } + # endif + failFlag = true; + break; + } + + if (!failFlag) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: Sending EHLO domain")); + } + # endif + if (!NPlugin_001_MTA(client, strformat(F("EHLO %s"), notificationsettings.Domain), 250, clientTimeout)) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: EHLO Domain Fail")); + } + # endif + failFlag = true; + } + } + + // Must retrieve SMTP Reply Packet. Data not used, ignored. + if (!failFlag) { + unsigned long timeout = millis(); + String replyStr; + String catStr = ""; + while (client.available()) { + if (millis() > timeout + clientTimeout) { + failFlag = true; + break; + } + safeReadStringUntil(client, replyStr, '\n', NPLUGIN_001_PKT_SZ, clientTimeout); + catStr += replyStr; + } + + if(!catStr.length()) { + catStr = "Empty!"; + } + + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("Email: Packet Rcvd is: > %s <"),catStr.c_str())); + } + # endif + } + + if (!failFlag) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: Sending User/Pass")); + } + # endif + if (!NPlugin_001_Auth(client, notificationsettings.User, notificationsettings.Pass, clientTimeout)) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: User/Pass Fail")); + } + # endif + failFlag = true; + break; + } + } + + if (!failFlag) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Email: Sending email Addr")); + # endif + if (!NPlugin_001_MTA(client, strformat(F("MAIL FROM:<%s>"), email_address.c_str()), 250, clientTimeout)) { + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, F("Email: Addr Fail")); + } + # endif + failFlag = true; + break; + } + } + + if (!failFlag) { + bool nextAddressAvailable = true; + int i = 0; + String emailTo; + const String receiver(tmp_ato); + + addLog(LOG_LEVEL_INFO, strformat(F("Email: Receiver(s): %s"),receiver.c_str())); + + if (!getNextMailAddress(receiver, emailTo, i)) { + addLog(LOG_LEVEL_ERROR, F("Email: Receiver missing!")); + break; + } + + while (nextAddressAvailable) { + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("Email: To "), emailTo)); + } + + if (!NPlugin_001_MTA(client, strformat(F("RCPT TO:<%s>"), emailTo.c_str()), 250, clientTimeout)) { break; } + ++i; + nextAddressAvailable = getNextMailAddress(receiver, emailTo, i); + } + } + + if (!failFlag) { + if (!NPlugin_001_MTA(client, F("DATA"), 354, clientTimeout)) { + failFlag = true; + break; + } + } + + if (!failFlag) { + if (!NPlugin_001_MTA(client, strformat(F("%s%s\r\n.\r\n"), mailheader.c_str(), aMesg.c_str()), 250, clientTimeout)) { + failFlag = true; + break; + } + } + + // Email Sent. Do some final housekeeping, tell server we're leaving. + if (!failFlag) { + myStatus = true; + } + + NPlugin_001_MTA(client, F("QUIT"), 221, clientTimeout); // Sent successfully, close SMTP protocol, ignore failure + break; + } + } + client.flush(); + client.stop(); + + if (myStatus == true) { + addLog(LOG_LEVEL_INFO, F("Email: Connection Closed Successfully")); + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("Email: Connection Closed With Error. Used header: "), mailheader)); + } + } + } + return myStatus; +} + +bool NPlugin_001_Auth(WiFiClient& client, const String& user, const String& pass, uint16_t timeout) +{ + if (user.isEmpty() || pass.isEmpty()) { + // No user/password given. + return true; + } + base64 encoder; + + bool mta1 = NPlugin_001_MTA(client, F("AUTH LOGIN"), 334, timeout); + bool mta2 = NPlugin_001_MTA(client, encoder.encode(user), 334, timeout); + bool mta3 = NPlugin_001_MTA(client, encoder.encode(pass), 235, timeout); + + if (mta1 && mta2 && mta3) { + addLog(LOG_LEVEL_INFO, F("Email: Credentials Accepted")); + } + return (mta1 && mta2 && mta3); + +} + +bool NPlugin_001_MTA(WiFiClient& client, const String& aStr, uint16_t aWaitForPattern, uint16_t timeout) +{ + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, aStr); + } + # endif // ifndef BUILD_NO_DEBUG + + client.flush(); + + if (aStr.length()) { client.println(aStr); } + + // Wait For Response + unsigned long timer = millis() + timeout; + + backgroundtasks(); + + const String aWaitForPattern_str = strformat(F("%d "), aWaitForPattern); + while (true) { + if (timeOutReached(timer)) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, + concat(F("NPlugin_001_MTA: timeout. "), aStr)); + } + break; + } + + delay(0); + + String line; + safeReadStringUntil(client, line, '\n', NPLUGIN_001_PKT_SZ, timeout); + + line.replace("-", " "); // Must Remove optional dash from MTA response code. + + const bool patternFound = line.indexOf(aWaitForPattern_str) >= 0; + + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, line); + } + # endif // ifndef BUILD_NO_DEBUG + + return patternFound; + } + + return false; +} + +bool getNextMailAddress(const String& data, String& address, int index) +{ + int found = 0; + int strIndex[] = { 0, -1 }; + const int maxIndex = data.length() - 1; + + for (int i = 0; i <= maxIndex && found <= index; i++) { + if ((data.charAt(i) == ',') || (i == maxIndex)) { + found++; + strIndex[0] = strIndex[1] + 1; + strIndex[1] = (i == maxIndex) ? i + 1 : i; + } + } + + if (found > index) { + address = data.substring(strIndex[0], strIndex[1]); + return true; + } + return false; +} + +#endif // ifdef USES_N001 diff --git a/src/_P001_Switch.ino b/src/_P001_Switch.ino index 01ca45ab1..b54691f26 100644 --- a/src/_P001_Switch.ino +++ b/src/_P001_Switch.ino @@ -62,8 +62,9 @@ // TD-er: Needed to fix a mistake in earlier fixes. uint8_t P001_getSwitchType(struct EventStruct *event) { const uint8_t choice = PCONFIG(0); - if (choice == 2 || // Old implementation for Dimmer - choice == PLUGIN_001_TYPE_DIMMER) + + if ((choice == 2) || // Old implementation for Dimmer + (choice == PLUGIN_001_TYPE_DIMMER)) { return PLUGIN_001_TYPE_DIMMER; } @@ -132,10 +133,10 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) } { - const __FlashStringHelper *options[2] = { F("Switch"), F("Dimmer") }; - int optionValues[2] = { PLUGIN_001_TYPE_SWITCH, PLUGIN_001_TYPE_DIMMER }; - const uint8_t switchtype = P001_getSwitchType(event); - addFormSelector(F("Switch Type"), F("type"), 2, options, optionValues, switchtype); + const __FlashStringHelper *options[] = { F("Switch"), F("Dimmer") }; + const int optionValues[] = { PLUGIN_001_TYPE_SWITCH, PLUGIN_001_TYPE_DIMMER }; + const uint8_t switchtype = P001_getSwitchType(event); + addFormSelector(F("Switch Type"), F("type"), NR_ELEMENTS(optionValues), options, optionValues, switchtype); if (switchtype == PLUGIN_001_TYPE_DIMMER) { @@ -144,11 +145,10 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) } { - uint8_t choice = PCONFIG(2); - const __FlashStringHelper *buttonOptions[3] = { F("Normal Switch"), F("Push Button Active Low"), F("Push Button Active High") }; - int buttonOptionValues[3] = + const __FlashStringHelper *buttonOptions[] = { F("Normal Switch"), F("Push Button Active Low"), F("Push Button Active High") }; + const int buttonOptionValues[] = { PLUGIN_001_BUTTON_TYPE_NORMAL_SWITCH, PLUGIN_001_BUTTON_TYPE_PUSH_ACTIVE_LOW, PLUGIN_001_BUTTON_TYPE_PUSH_ACTIVE_HIGH }; - addFormSelector(F("Switch Button Type"), F("button"), 3, buttonOptions, buttonOptionValues, choice); + addFormSelector(F("Switch Button Type"), F("button"), NR_ELEMENTS(buttonOptionValues), buttonOptions, buttonOptionValues, PCONFIG(2)); } SwitchWebformLoad( @@ -261,79 +261,6 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) break; } - /* - case PLUGIN_REQUEST: - { - // String device = parseString(string, 1); - // String command = parseString(string, 2); - // String strPar1 = parseString(string, 3); - - // returns pin value using syntax: [plugin#gpio#pinstate#xx] - if ((string.length() >= 13) && string.substring(0, 13).equalsIgnoreCase(F("gpio,pinstate"))) - { - int32_t par1; - - if (validIntFromString(parseString(string, 3), par1)) { - string = digitalRead(par1); - } - success = true; - } - break; - } - */ - /* - case PLUGIN_UNCONDITIONAL_POLL: - { - // port monitoring, generates an event by rule command 'monitor,gpio,port#' - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if ((it->second.monitor || it->second.command || it->second.init) && getPluginFromKey(it->first)==PLUGIN_ID_001) { - const uint16_t port = getPortFromKey(it->first); - uint8_t state = Plugin_001_read_switch_state(port, it->second.mode); - if (it->second.state != state || it->second.forceMonitor) { - if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up - by 10xSEC function - if (it->second.monitor) { - it->second.forceMonitor=0; //reset flag - String eventString = F("GPIO#"); - eventString += port; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - } - } - break; - } - - */ - /* - case PLUGIN_MONITOR: - { - // port monitoring, generates an event by rule command 'monitor,gpio,port#' - const uint32_t key = createKey(PLUGIN_ID_001, event->Par1); - const portStatusStruct currentStatus = globalMapPortStatus[key]; - - // if (currentStatus.monitor || currentStatus.command || currentStatus.init) { - uint8_t state = GPIO_Read_Switch_State(event->Par1, currentStatus.mode); - - if ((currentStatus.state != state) || (currentStatus.forceMonitor && currentStatus.monitor)) { - if (!currentStatus.task) globalMapPortStatus[key].state = state; //do not update state if task flag=1 otherwise it will not be picked up by 10xSEC function - if (currentStatus.monitor) { - String eventString = F("GPIO#"); - eventString += event->Par1; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - globalMapPortStatus[key].forceMonitor = 0; // reset flag - - // } - - break; - } - */ case PLUGIN_TEN_PER_SECOND: { /**************************************************************************\ @@ -387,7 +314,7 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) // reset timer for long press PCONFIG_LONG(2) = millis(); - PCONFIG(6) = 0; + PCONFIG(6) = 0; const unsigned long debounceTime = timePassedSince(PCONFIG_LONG(0)); @@ -466,15 +393,17 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) } UserVar.setFloat(event->TaskIndex, 0, output_value); - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, - concat(F("SW : GPIO="), static_cast(CONFIG_PIN1)) + - concat(F(" State="), state ? '1' : '0') + - concat(output_value == 3 ? F(" Doubleclick=") : F(" Output value="), static_cast(output_value))); + strformat(F("SW : GPIO=%d State=%d%s"), + CONFIG_PIN1, + state ? 1 : 0, + concat(output_value == 3 ? F(" Doubleclick=") : F(" Output value="), + static_cast(output_value)).c_str())); } - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG // send task event sendData(event); @@ -526,7 +455,7 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) if (deltaLP >= (unsigned long)lround(P001_LP_MIN_INT)) { uint8_t output_value; - bool needToSendEvent = false; + bool needToSendEvent = false; PCONFIG(6) = 1; @@ -563,10 +492,11 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, - concat(F("SW : LongPress: GPIO= "), static_cast(CONFIG_PIN1)) + - concat(F(" State="), state ? '1' : '0') + - concat(F(" Output value="), static_cast(output_value))); + addLogMove(LOG_LEVEL_INFO, + strformat(F("SW : LongPress: GPIO= %d State=%d Output value=%d"), + CONFIG_PIN1, + state ? 1 : 0, + output_value)); } # endif // ifndef BUILD_NO_DEBUG @@ -595,9 +525,8 @@ boolean Plugin_001(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, - concat(F("SW : SafeButton: false positive detected. GPIO= "), CONFIG_PIN1) + - concat(F(" State="), tempUserVar)); + addLogMove(LOG_LEVEL_INFO, + strformat(F("SW : SafeButton: false positive detected. GPIO= %d State=%d"), CONFIG_PIN1, tempUserVar)); } # endif // ifndef BUILD_NO_DEBUG diff --git a/src/_P002_ADC.ino b/src/_P002_ADC.ino index b24c84b63..2b5d810a6 100644 --- a/src/_P002_ADC.ino +++ b/src/_P002_ADC.ino @@ -1,173 +1,173 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P002 - - -# include "src/Helpers/Hardware.h" -# include "src/PluginStructs/P002_data_struct.h" - -// ####################################################################################################### -// #################################### Plugin 002: Analog ############################################### -// ####################################################################################################### - -# define PLUGIN_002 -# define PLUGIN_ID_002 2 -# define PLUGIN_NAME_002 "Analog input - internal" -# define PLUGIN_VALUENAME1_002 "Analog" - - -boolean Plugin_002(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_002; - Device[deviceCount].Type = DEVICE_TYPE_ANALOG; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - Device[deviceCount].TaskLogsOwnPeaks = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_002); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_002)); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P002_data) { - P002_data->webformLoad(event); - success = true; - } else { - P002_data = new (std::nothrow) P002_data_struct(); - - if (nullptr != P002_data) { - P002_data->init(event); - P002_data->webformLoad(event); - success = true; - delete P002_data; - } - } - break; - } - -# if FEATURE_PLUGIN_STATS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: - { - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P002_data) { - success = P002_data->webformLoad_show_stats(event); - } - break; - } -# endif // if FEATURE_PLUGIN_STATS - - case PLUGIN_WEBFORM_SAVE: - { - addHtmlError(P002_data_struct::webformSave(event)); - - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P002_data_struct()); - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P002_data) { - success = true; - P002_data->init(event); - } - break; - } - case PLUGIN_TEN_PER_SECOND: - { - if (P002_OVERSAMPLING != P002_USE_CURENT_SAMPLE) // Use multiple samples - { - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P002_data) { - P002_data->takeSample(); - } - } - success = true; - break; - } - - case PLUGIN_READ: - { - int raw_value = 0; - float res_value = 0.0f; - - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((P002_data != nullptr) && P002_data->getValue(res_value, raw_value)) { - UserVar.setFloat(event->TaskIndex, 0, res_value); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat( - F("ADC : Analog value: %d = %s"), - raw_value, - formatUserVarNoCheck(event->TaskIndex, 0).c_str()); - - if (P002_OVERSAMPLING == P002_USE_OVERSAMPLING) { - log += strformat(F(" (%u samples)"), P002_data->getOversamplingCount()); - } - addLogMove(LOG_LEVEL_INFO, log); - } - P002_data->reset(); - success = true; - } else { - addLog(LOG_LEVEL_ERROR, F("ADC : No value received ")); - success = false; - } - - break; - } - - case PLUGIN_SET_CONFIG: - { - P002_data_struct *P002_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (P002_data != nullptr) { - success = P002_data->plugin_set_config(event, string); - - if (success) { - P002_data->init(event); - } - } - break; - } - } - return success; -} - -#endif // USES_P002 +#include "_Plugin_Helper.h" +#ifdef USES_P002 + + +# include "src/Helpers/Hardware.h" +# include "src/PluginStructs/P002_data_struct.h" + +// ####################################################################################################### +// #################################### Plugin 002: Analog ############################################### +// ####################################################################################################### + +# define PLUGIN_002 +# define PLUGIN_ID_002 2 +# define PLUGIN_NAME_002 "Analog input - internal" +# define PLUGIN_VALUENAME1_002 "Analog" + + +boolean Plugin_002(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_002; + Device[deviceCount].Type = DEVICE_TYPE_ANALOG; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].TaskLogsOwnPeaks = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_002); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_002)); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P002_data) { + P002_data->webformLoad(event); + success = true; + } else { + P002_data = new (std::nothrow) P002_data_struct(); + + if (nullptr != P002_data) { + P002_data->init(event); + P002_data->webformLoad(event); + success = true; + delete P002_data; + } + } + break; + } + +# if FEATURE_PLUGIN_STATS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: + { + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P002_data) { + success = P002_data->webformLoad_show_stats(event); + } + break; + } +# endif // if FEATURE_PLUGIN_STATS + + case PLUGIN_WEBFORM_SAVE: + { + addHtmlError(P002_data_struct::webformSave(event)); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P002_data_struct()); + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P002_data) { + success = true; + P002_data->init(event); + } + break; + } + case PLUGIN_TEN_PER_SECOND: + { + if (P002_OVERSAMPLING != P002_USE_CURENT_SAMPLE) // Use multiple samples + { + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P002_data) { + P002_data->takeSample(); + } + } + success = true; + break; + } + + case PLUGIN_READ: + { + int raw_value = 0; + float res_value = 0.0f; + + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((P002_data != nullptr) && P002_data->getValue(res_value, raw_value)) { + UserVar.setFloat(event->TaskIndex, 0, res_value); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat( + F("ADC : Analog value: %d = %s"), + raw_value, + formatUserVarNoCheck(event, 0).c_str()); + + if (P002_OVERSAMPLING == P002_USE_OVERSAMPLING) { + log += strformat(F(" (%u samples)"), P002_data->getOversamplingCount()); + } + addLogMove(LOG_LEVEL_INFO, log); + } + P002_data->reset(); + success = true; + } else { + addLog(LOG_LEVEL_ERROR, F("ADC : No value received ")); + success = false; + } + + break; + } + + case PLUGIN_SET_CONFIG: + { + P002_data_struct *P002_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (P002_data != nullptr) { + success = P002_data->plugin_set_config(event, string); + + if (success) { + P002_data->init(event); + } + } + break; + } + } + return success; +} + +#endif // USES_P002 diff --git a/src/_P004_Dallas.ino b/src/_P004_Dallas.ino index 6754f1ace..f5c2853e0 100644 --- a/src/_P004_Dallas.ino +++ b/src/_P004_Dallas.ino @@ -1,354 +1,405 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P004 - -// ####################################################################################################### -// #################################### Plugin 004: TempSensor Dallas DS18B20 ########################### -// ####################################################################################################### - -// Maxim Integrated (ex Dallas) DS18B20 datasheet : https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf - -/** Changelog: - * 2023-04-18 tonhuisman: Add warning on statistics section for Parasite Powered sensors, as these are unsupported. - * 2023-04-17 tonhuisman: Use actual sensor resolution, even when using multiple sensors with different resolutions - * 2023-04-16 tonhuisman: Rename from DS18b20 to 1-Wire Temperature, as it supports several 1-Wire temperature sensors - * Add support for fixed-resolution sensors like MAX31826 - * 2023-04-16 tonhuisman: Start using changelog - */ -# include "src/PluginStructs/P004_data_struct.h" -# include "src/Helpers/Dallas1WireHelper.h" - - -# define PLUGIN_004 -# define PLUGIN_ID_004 4 -# define PLUGIN_NAME_004 "Environment - 1-Wire Temperature" -# define PLUGIN_VALUENAME1_004 "Temperature" - -# define P004_ERROR_NAN 0 -# define P004_ERROR_MIN_RANGE 1 -# define P004_ERROR_ZERO 2 -# define P004_ERROR_MAX_RANGE 3 -# define P004_ERROR_IGNORE 4 - -// place sensor type selector right after the output value settings -# define P004_ERROR_STATE_OUTPUT PCONFIG(0) -# define P004_RESOLUTION PCONFIG(1) -# define P004_SENSOR_TYPE_INDEX 2 -# define P004_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P004_SENSOR_TYPE_INDEX))) - -// Used to easily replace a sensor, without configuring. -// Can only be used for a single instance of this plugin and a single sensor. -# define P004_SCAN_ON_INIT PCONFIG(3) - - -boolean Plugin_004(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_004; - Device[deviceCount].Type = DEVICE_TYPE_DUAL; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_004); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - ExtraTaskSettings.populateDeviceValueNamesSeq(F("Temperature"), P004_NR_OUTPUT_VALUES, 2, false); - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P004_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P004_SENSOR_TYPE_INDEX)); - event->idx = P004_SENSOR_TYPE_INDEX; - success = true; - break; - } - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(P004_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - - success = true; - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_RX(false); - event->String2 = formatGpioName_TX(true); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. - int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; - int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; - - if (Plugin_004_DallasPin_TX == -1) { - Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; - } - - const int valueCount = P004_NR_OUTPUT_VALUES; - - if (validGpio(Plugin_004_DallasPin_RX) && validGpio(Plugin_004_DallasPin_TX)) { - addFormCheckBox(F("Auto Select Sensor"), F("autoselect"), P004_SCAN_ON_INIT, valueCount > 1); - addFormNote(F("Auto Select can only be used for 1 Dallas sensor per GPIO pin.")); - Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, valueCount); - - { - // Device Resolution select - int activeRes = P004_RESOLUTION; - - uint8_t savedAddress[8]; - Dallas_plugin_get_addr(savedAddress, event->TaskIndex); - - if (savedAddress[0] != 0) { - activeRes = Dallas_getResolution(savedAddress, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX); - } - - int resolutionChoice = P004_RESOLUTION; - - if ((resolutionChoice < 9) || (resolutionChoice > 12)) { resolutionChoice = activeRes; } - const __FlashStringHelper *resultsOptions[4] = { F("9"), F("10"), F("11"), F("12") }; - int resultsOptionValues[4] = { 9, 10, 11, 12 }; - addFormSelector(F("Device Resolution"), F("res"), 4, resultsOptions, resultsOptionValues, resolutionChoice); - addHtml(F(" Bit")); - } - - { - // Value in case of Error - const __FlashStringHelper *resultsOptions[5] = { F("NaN"), F("-127"), F("0"), F("125"), F("Ignore") }; - int resultsOptionValues[5] = - { P004_ERROR_NAN, P004_ERROR_MIN_RANGE, P004_ERROR_ZERO, P004_ERROR_MAX_RANGE, P004_ERROR_IGNORE }; - addFormSelector(F("Error State Value"), F("err"), 5, resultsOptions, resultsOptionValues, P004_ERROR_STATE_OUTPUT); - } - addFormNote(F("External pull up resistor is needed, see docs!")); - - { - P004_data_struct *P004_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P004_data) { - for (uint8_t i = 0; i < valueCount; ++i) { - if (i == 0) { - addFormSubHeader(F("Statistics")); - } else { - addFormSeparator(2); - } - Dallas_show_sensor_stats_webform_load(P004_data->get_sensor_data(i)); - } - } - } - } - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; - int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; - - if (Plugin_004_DallasPin_TX == -1) { - Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; - } - - if (validGpio(Plugin_004_DallasPin_RX) && validGpio(Plugin_004_DallasPin_TX)) { - // save the address for selected device and store into extra tasksettings - Dallas_addr_selector_webform_save(event->TaskIndex, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, P004_NR_OUTPUT_VALUES); - - uint8_t res = getFormItemInt(F("res")); - - if ((res < 9) || (res > 12)) { res = 12; } - P004_RESOLUTION = res; - - uint8_t savedAddress[8]; - Dallas_plugin_get_addr(savedAddress, event->TaskIndex); - Dallas_setResolution(savedAddress, res, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX); - } - P004_SCAN_ON_INIT = isFormItemChecked(F("autoselect")); - P004_ERROR_STATE_OUTPUT = getFormItemInt(F("err")); - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - P004_data_struct *P004_data = - static_cast(getPluginTaskData(event->TaskIndex)); - int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; - int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; - const int valueCount = P004_NR_OUTPUT_VALUES; - - if (Plugin_004_DallasPin_TX == -1) { - Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; - } - - - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < valueCount) { - if (i != 0) { - string += F("
"); - } - - if (nullptr != P004_data) { - // Show the actively used IDs - // For "Auto Select Sensor" no value is stored - string += P004_data->get_formatted_address(i); - } else { - // Read the data from the settings. - uint8_t addr[8]{}; - bool hasFixedResolution = false; // FIXME tonhuisman: Not sure if I _want_ to read the resolution here... - Dallas_plugin_get_addr(addr, event->TaskIndex, i); - Dallas_getResolution(addr, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, hasFixedResolution); - - string += Dallas_format_address(addr, hasFixedResolution); - } - } - } - success = true; - break; - } - - case PLUGIN_INIT: - { - int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; - int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; - const uint8_t res = P004_RESOLUTION; - const int valueCount = P004_NR_OUTPUT_VALUES; - - if (Plugin_004_DallasPin_TX == -1) { - Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; - } - - { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - initPluginTaskData(event->TaskIndex, new (std::nothrow) P004_data_struct( - event->TaskIndex, - Plugin_004_DallasPin_RX, - Plugin_004_DallasPin_TX, - res, - valueCount == 1 && P004_SCAN_ON_INIT)); - } - P004_data_struct *P004_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P004_data) { - for (uint8_t i = 0; i < valueCount; ++i) { - uint8_t addr[8] = { 0 }; - Dallas_plugin_get_addr(addr, event->TaskIndex, i); - P004_data->add_addr(addr, i); - } - P004_data->init(); - success = true; - } - - break; - } - - case PLUGIN_READ: - { - P004_data_struct *P004_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P004_data) { - const int valueCount = P004_NR_OUTPUT_VALUES; - if ((valueCount == 1) && P004_SCAN_ON_INIT) { - if (!P004_data->sensorAddressSet()) { - P004_data->init(); - } - } - - if (!timeOutReached(P004_data->get_timer())) { - Scheduler.schedule_task_device_timer(event->TaskIndex, P004_data->get_timer()); - } else { - if (!P004_data->measurement_active()) { - if (P004_data->initiate_read()) { - Scheduler.schedule_task_device_timer(event->TaskIndex, P004_data->get_timer()); - } - } else { - // Try to get in sync with the existing interval again. - Scheduler.reschedule_task_device_timer(event->TaskIndex, P004_data->get_measurement_start()); - - P004_data->collect_values(); - - for (uint8_t i = 0; i < valueCount; ++i) { - float value = 0.0f; - - if (P004_data->read_temp(value, i)) - { - UserVar.setFloat(event->TaskIndex, i, value); - success = true; - } - else - { - if (P004_ERROR_STATE_OUTPUT != P004_ERROR_IGNORE) { - float errorValue = NAN; - - switch (P004_ERROR_STATE_OUTPUT) { - case P004_ERROR_MIN_RANGE: errorValue = -127.0f; break; - case P004_ERROR_ZERO: errorValue = 0.0f; break; - case P004_ERROR_MAX_RANGE: errorValue = 125.0f; break; - default: - break; - } - UserVar.setFloat(event->TaskIndex, i, errorValue); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DS : Temperature: "); - - if (success) { - log += formatUserVarNoCheck(event, i); - } else { - log += F("Error!"); - } - log += F(" ("); - log += P004_data->get_formatted_address(i); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - } - } - P004_data->set_measurement_inactive(); - } - } - } - break; - } - } - return success; -} - -#endif // USES_P004 +#include "_Plugin_Helper.h" +#ifdef USES_P004 + +// ####################################################################################################### +// #################################### Plugin 004: TempSensor Dallas DS18B20 ########################### +// ####################################################################################################### + +// Maxim Integrated (ex Dallas) DS18B20 datasheet : https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf + +/** Changelog: + * 2024-05-11 tonhuisman: Add Get Config Value support for sensor statistics: Read success, Read retry, Read failed, + * Read init failed, Resolution and Address (formatted) + * [#sensorstats,,success|retry|failed|initfailed|resolution|address] + * 2023-04-18 tonhuisman: Add warning on statistics section for Parasite Powered sensors, as these are unsupported. + * 2023-04-17 tonhuisman: Use actual sensor resolution, even when using multiple sensors with different resolutions + * 2023-04-16 tonhuisman: Rename from DS18b20 to 1-Wire Temperature, as it supports several 1-Wire temperature sensors + * Add support for fixed-resolution sensors like MAX31826 + * 2023-04-16 tonhuisman: Start using changelog + */ +# include "src/PluginStructs/P004_data_struct.h" +# include "src/Helpers/Dallas1WireHelper.h" + + +# define PLUGIN_004 +# define PLUGIN_ID_004 4 +# define PLUGIN_NAME_004 "Environment - 1-Wire Temperature" +# define PLUGIN_VALUENAME1_004 "Temperature" + +# define P004_ERROR_NAN 0 +# define P004_ERROR_MIN_RANGE 1 +# define P004_ERROR_ZERO 2 +# define P004_ERROR_MAX_RANGE 3 +# define P004_ERROR_IGNORE 4 + +// place sensor type selector right after the output value settings +# define P004_ERROR_STATE_OUTPUT PCONFIG(0) +# define P004_RESOLUTION PCONFIG(1) +# define P004_SENSOR_TYPE_INDEX 2 +# define P004_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P004_SENSOR_TYPE_INDEX))) + +// Used to easily replace a sensor, without configuring. +// Can only be used for a single instance of this plugin and a single sensor. +# define P004_SCAN_ON_INIT PCONFIG(3) + + +boolean Plugin_004(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_004; + Device[deviceCount].Type = DEVICE_TYPE_DUAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_004); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + ExtraTaskSettings.populateDeviceValueNamesSeq(F("Temperature"), P004_NR_OUTPUT_VALUES, 2, false); + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P004_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P004_SENSOR_TYPE_INDEX)); + event->idx = P004_SENSOR_TYPE_INDEX; + success = true; + break; + } + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(P004_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + + success = true; + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_RX(false); + event->String2 = formatGpioName_TX(true); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. + int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; + int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; + + if (Plugin_004_DallasPin_TX == -1) { + Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; + } + + const int valueCount = P004_NR_OUTPUT_VALUES; + + if (validGpio(Plugin_004_DallasPin_RX) && validGpio(Plugin_004_DallasPin_TX)) { + addFormCheckBox(F("Auto Select Sensor"), F("autoselect"), P004_SCAN_ON_INIT, valueCount > 1); + addFormNote(F("Auto Select can only be used for 1 Dallas sensor per GPIO pin.")); + Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, valueCount); + + { + // Device Resolution select + int activeRes = P004_RESOLUTION; + + uint8_t savedAddress[8]; + Dallas_plugin_get_addr(savedAddress, event->TaskIndex); + + if (savedAddress[0] != 0) { + activeRes = Dallas_getResolution(savedAddress, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX); + } + + int resolutionChoice = P004_RESOLUTION; + + if ((resolutionChoice < 9) || (resolutionChoice > 12)) { resolutionChoice = activeRes; } + const __FlashStringHelper *resultsOptions[4] = { F("9"), F("10"), F("11"), F("12") }; + int resultsOptionValues[4] = { 9, 10, 11, 12 }; + addFormSelector(F("Device Resolution"), F("res"), 4, resultsOptions, resultsOptionValues, resolutionChoice); + addHtml(F(" Bit")); + } + + { + // Value in case of Error + const __FlashStringHelper *resultsOptions[5] = { F("NaN"), F("-127"), F("0"), F("125"), F("Ignore") }; + int resultsOptionValues[5] = + { P004_ERROR_NAN, P004_ERROR_MIN_RANGE, P004_ERROR_ZERO, P004_ERROR_MAX_RANGE, P004_ERROR_IGNORE }; + addFormSelector(F("Error State Value"), F("err"), 5, resultsOptions, resultsOptionValues, P004_ERROR_STATE_OUTPUT); + } + addFormNote(F("External pull up resistor is needed, see docs!")); + + { + P004_data_struct *P004_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P004_data) { + for (uint8_t i = 0; i < valueCount; ++i) { + if (i == 0) { + addFormSubHeader(F("Statistics")); + } else { + addFormSeparator(2); + } + Dallas_show_sensor_stats_webform_load(P004_data->get_sensor_data(i)); + } + } + } + } + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; + int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; + + if (Plugin_004_DallasPin_TX == -1) { + Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; + } + + if (validGpio(Plugin_004_DallasPin_RX) && validGpio(Plugin_004_DallasPin_TX)) { + // save the address for selected device and store into extra tasksettings + Dallas_addr_selector_webform_save(event->TaskIndex, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, P004_NR_OUTPUT_VALUES); + + uint8_t res = getFormItemInt(F("res")); + + if ((res < 9) || (res > 12)) { res = 12; } + P004_RESOLUTION = res; + + uint8_t savedAddress[8]; + Dallas_plugin_get_addr(savedAddress, event->TaskIndex); + Dallas_setResolution(savedAddress, res, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX); + } + P004_SCAN_ON_INIT = isFormItemChecked(F("autoselect")); + P004_ERROR_STATE_OUTPUT = getFormItemInt(F("err")); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + P004_data_struct *P004_data = + static_cast(getPluginTaskData(event->TaskIndex)); + int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; + int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; + const int valueCount = P004_NR_OUTPUT_VALUES; + + if (Plugin_004_DallasPin_TX == -1) { + Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; + } + + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < valueCount) { + if (i != 0) { + string += F("
"); + } + + if (nullptr != P004_data) { + // Show the actively used IDs + // For "Auto Select Sensor" no value is stored + string += P004_data->get_formatted_address(i); + } else { + // Read the data from the settings. + uint8_t addr[8]{}; + bool hasFixedResolution = false; // FIXME tonhuisman: Not sure if I _want_ to read the resolution here... + Dallas_plugin_get_addr(addr, event->TaskIndex, i); + Dallas_getResolution(addr, Plugin_004_DallasPin_RX, Plugin_004_DallasPin_TX, hasFixedResolution); + + string += Dallas_format_address(addr, hasFixedResolution); + } + } + } + success = true; + break; + } + + case PLUGIN_INIT: + { + int8_t Plugin_004_DallasPin_RX = CONFIG_PIN1; + int8_t Plugin_004_DallasPin_TX = CONFIG_PIN2; + const uint8_t res = P004_RESOLUTION; + const int valueCount = P004_NR_OUTPUT_VALUES; + + if (Plugin_004_DallasPin_TX == -1) { + Plugin_004_DallasPin_TX = Plugin_004_DallasPin_RX; + } + + { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + initPluginTaskData(event->TaskIndex, new (std::nothrow) P004_data_struct( + event->TaskIndex, + Plugin_004_DallasPin_RX, + Plugin_004_DallasPin_TX, + res, + valueCount == 1 && P004_SCAN_ON_INIT)); + } + P004_data_struct *P004_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P004_data) { + for (uint8_t i = 0; i < valueCount; ++i) { + uint8_t addr[8] = { 0 }; + Dallas_plugin_get_addr(addr, event->TaskIndex, i); + P004_data->add_addr(addr, i); + } + P004_data->init(); + success = true; + } + + break; + } + + case PLUGIN_READ: + { + P004_data_struct *P004_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P004_data) { + const int valueCount = P004_NR_OUTPUT_VALUES; + + if ((valueCount == 1) && P004_SCAN_ON_INIT) { + if (!P004_data->sensorAddressSet()) { + P004_data->init(); + } + } + + if (!timeOutReached(P004_data->get_timer())) { + Scheduler.schedule_task_device_timer(event->TaskIndex, P004_data->get_timer()); + } else { + if (!P004_data->measurement_active()) { + if (P004_data->initiate_read()) { + Scheduler.schedule_task_device_timer(event->TaskIndex, P004_data->get_timer()); + } + } else { + // Try to get in sync with the existing interval again. + Scheduler.reschedule_task_device_timer(event->TaskIndex, P004_data->get_measurement_start()); + + P004_data->collect_values(); + + for (uint8_t i = 0; i < valueCount; ++i) { + float value = 0.0f; + + if (P004_data->read_temp(value, i)) + { + UserVar.setFloat(event->TaskIndex, i, value); + success = true; + } + else + { + if (P004_ERROR_STATE_OUTPUT != P004_ERROR_IGNORE) { + float errorValue = NAN; + + switch (P004_ERROR_STATE_OUTPUT) { + case P004_ERROR_MIN_RANGE: errorValue = -127.0f; break; + case P004_ERROR_ZERO: errorValue = 0.0f; break; + case P004_ERROR_MAX_RANGE: errorValue = 125.0f; break; + default: + break; + } + UserVar.setFloat(event->TaskIndex, i, errorValue); + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("DS : Temperature: "); + + if (success) { + log += formatUserVarNoCheck(event, i); + } else { + log += F("Error!"); + } + log += F(" ("); + log += P004_data->get_formatted_address(i); + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + } + } + P004_data->set_measurement_inactive(); + } + } + } + break; + } + + # if P004_FEATURE_GET_CONFIG_VALUE + case PLUGIN_GET_CONFIG_VALUE: + { + P004_data_struct *P004_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P004_data) { + const String cmd = parseString(string, 1, '.'); + + if (equals(cmd, F("sensorstats"))) { // To distinguish from 'DeviceStats' + const String par1 = parseString(string, 2, '.'); + int32_t nPar1; + + if (validIntFromString(par1, nPar1) && (nPar1 > 0) && (nPar1 <= P004_NR_OUTPUT_VALUES)) { + nPar1--; // From DeviceNr to array index + const String subcmd = parseString(string, 3, '.'); + Dallas_SensorData sensorData = P004_data->get_sensor_data(nPar1); + success = true; + + if (equals(subcmd, F("success"))) { + string = sensorData.read_success; + } else + if (equals(subcmd, F("retry"))) { + string = sensorData.read_retry; + } else + if (equals(subcmd, F("failed"))) { + string = sensorData.read_failed; + } else + if (equals(subcmd, F("initfailed"))) { + string = sensorData.start_read_failed; + } else + if (equals(subcmd, F("resolution"))) { + string = sensorData.actual_res; + } else + if (equals(subcmd, F("address"))) { + string = sensorData.get_formatted_address(); + } else + { // Unsupported stat + success = false; + } + } + } + } + break; + } + # endif // if P004_FEATURE_GET_CONFIG_VALUE + } + return success; +} + +#endif // USES_P004 diff --git a/src/_P006_BMP085.ino b/src/_P006_BMP085.ino index 7c115ea63..059c3bbdd 100644 --- a/src/_P006_BMP085.ino +++ b/src/_P006_BMP085.ino @@ -1,128 +1,124 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P006 - -// ####################################################################################################### -// ######################## Plugin 006 BMP085/180 I2C Barometric Pressure Sensor ######################## -// ####################################################################################################### - - -# include "src/PluginStructs/P006_data_struct.h" - -# define PLUGIN_006 -# define PLUGIN_ID_006 6 -# define PLUGIN_NAME_006 "Environment - BMP085/180" -# define PLUGIN_VALUENAME1_006 "Temperature" -# define PLUGIN_VALUENAME2_006 "Pressure" - - -boolean Plugin_006(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_006; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_006); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_006)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_006)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x77); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x77; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Altitude [m]"), F("elev"), PCONFIG(1)); - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(1) = getFormItemInt(F("elev")); - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P006_data_struct()); - P006_data_struct *P006_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - success = (nullptr != P006_data); - break; - } - - case PLUGIN_READ: - { - P006_data_struct *P006_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P006_data) { - if (P006_data->begin()) - { - UserVar.setFloat(event->TaskIndex, 0, P006_data->readTemperature()); - int elev = PCONFIG(1); - float pressure = static_cast(P006_data->readPressure()) / 100.0f; - - if (elev != 0) - { - pressure = pressureElevation(pressure, elev); - } - UserVar.setFloat(event->TaskIndex, 1, pressure); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("BMP : Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("BMP : Barometric Pressure: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - } - } - break; - } - } - return success; -} - -#endif // USES_P006 +#include "_Plugin_Helper.h" +#ifdef USES_P006 + +// ####################################################################################################### +// ######################## Plugin 006 BMP085/180 I2C Barometric Pressure Sensor ######################## +// ####################################################################################################### + + +# include "src/PluginStructs/P006_data_struct.h" + +# define PLUGIN_006 +# define PLUGIN_ID_006 6 +# define PLUGIN_NAME_006 "Environment - BMP085/180" +# define PLUGIN_VALUENAME1_006 "Temperature" +# define PLUGIN_VALUENAME2_006 "Pressure" + + +boolean Plugin_006(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_006; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_006); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_006)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_006)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x77); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x77; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("Altitude [m]"), F("elev"), PCONFIG(1)); + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(1) = getFormItemInt(F("elev")); + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P006_data_struct()); + P006_data_struct *P006_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P006_data); + break; + } + + case PLUGIN_READ: + { + P006_data_struct *P006_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P006_data) { + if (P006_data->begin()) + { + UserVar.setFloat(event->TaskIndex, 0, P006_data->readTemperature()); + int elev = PCONFIG(1); + float pressure = static_cast(P006_data->readPressure()) / 100.0f; + + if (elev != 0) + { + pressure = pressureElevation(pressure, elev); + } + UserVar.setFloat(event->TaskIndex, 1, pressure); + + 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))); + } + success = true; + } + } + break; + } + } + return success; +} + +#endif // USES_P006 diff --git a/src/_P007_PCF8591.ino b/src/_P007_PCF8591.ino index 6526c3ce6..517633057 100644 --- a/src/_P007_PCF8591.ino +++ b/src/_P007_PCF8591.ino @@ -1,247 +1,247 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P007 - -// ####################################################################################################### -// #################################### Plugin 007: ExtWiredAnalog ####################################### -// ####################################################################################################### - -/** Changelog: - * 2023-11-24 tonhuisman: Add Device flag for I2CMax100kHz as this sensor won't work at 400 kHz - * 2022-05-08 tonhuisman: Use ESPEasy core I2C functions where possible - * Add support for use of the Analog output pin and 'analogout,' command - * Add configuration of all possible analog input modes - * 2022-05-08 tonhuisman: Started changelog, older changes not recorded - ********************************************************************************************************/ - -// commands: -// analogout, : If the Analog output is enabled, the value range is 0..255, and linear to Vref - -# define PLUGIN_007 -# define PLUGIN_ID_007 7 -# define PLUGIN_NAME_007 "Analog input - PCF8591" -# define PLUGIN_VALUENAME1_007 "Analog" - -# define P007_SENSOR_TYPE_INDEX 2 -# define P007_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P007_SENSOR_TYPE_INDEX))) -# define P007_INPUT_MODE PCONFIG_LONG(0) -# define P007_OUTPUT_MODE PCONFIG_LONG(1) -# define P007_OUTPUT_ENABLED (0b01000000) - - -boolean Plugin_007(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_007; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; - Device[deviceCount].I2CMax100kHz = true; // Max 100 kHz allowed/supported - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_007); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - ExtraTaskSettings.populateDeviceValueNamesSeq(F(PLUGIN_VALUENAME1_007), P007_NR_OUTPUT_VALUES, 2, true); - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - if (PCONFIG(P007_SENSOR_TYPE_INDEX) == 0) { - PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - } - event->Par1 = P007_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P007_SENSOR_TYPE_INDEX)); - event->idx = P007_SENSOR_TYPE_INDEX; - success = true; - break; - } - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - - success = true; - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - String portNames[4]; - int portValues[4]; - const uint8_t unit = (CONFIG_PORT - 1) / 4; - const uint8_t port = CONFIG_PORT - (unit * 4); - const uint8_t address = 0x48 + unit; - - for (uint8_t x = 0; x < 4; x++) { - portValues[x] = x + 1; - portNames[x] = 'A'; - portNames[x] += x; - } - addFormSelectorI2C(F("pi2c"), 8, i2cAddressValues, address); - addFormSelector(F("Port"), F("pport"), 4, portNames, portValues, port); - addFormNote(F("Selected Port value will be stored in first 'Values' field and consecutively for 'Number Output Values' > Single.")); - } else { - success = intArrayContains(8, i2cAddressValues, event->Par1); - } - - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - const uint8_t unit = (CONFIG_PORT - 1) / 4; - event->Par1 = 0x48 + unit; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("Hardware configuration")); - - const __FlashStringHelper *inputModeOptions[] = { - F("4 single-ended inputs"), - F("3 differential inputs, A0/A1/A2 differential with AIN3"), - F("2 single-ended, A0, A1, AIN2/AIN3 differential -> A2"), - F("AIN0/AIN1 differential -> A0, AIN2/AIN3 differential -> A1"), - }; - const int inputModeValues[] = { - 0b00000000, - 0b00010000, - 0b00100000, - 0b00110000, - }; - addFormSelector(F("Input mode"), F("input_mode"), 4, inputModeOptions, inputModeValues, P007_INPUT_MODE); - - addFormCheckBox(F("Enable Analog output (AOUT)"), F("output_mode"), P007_OUTPUT_MODE == P007_OUTPUT_ENABLED); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - if (PCONFIG(P007_SENSOR_TYPE_INDEX) == 0) { - PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - } - uint8_t i2c = getFormItemInt(F("pi2c")); - uint8_t port = getFormItemInt(F("pport")); - CONFIG_PORT = (((i2c - 0x48) << 2) + port); - - P007_INPUT_MODE = getFormItemInt(F("input_mode")); - P007_OUTPUT_MODE = isFormItemChecked(F("output_mode")) ? P007_OUTPUT_ENABLED : 0; - - success = true; - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - const uint8_t unit = (CONFIG_PORT - 1) / 4; - uint8_t port = CONFIG_PORT - (unit * 4); - const uint8_t address = 0x48 + unit; - - uint8_t var = 0; - const uint8_t valueCount = P007_NR_OUTPUT_VALUES; - - for (; var < valueCount; ++port, ++var) { - if (port <= 4) { // Only read available ports, hardwired limited to 4 - // Setup all required bits to the config register - uint8_t configRegister = port - 1; - configRegister |= P007_INPUT_MODE; - configRegister |= P007_OUTPUT_MODE; - - // get the current pin value - I2C_write8(address, configRegister); - - Wire.requestFrom(address, (uint8_t)0x2); // No fitting I2C standard function available - - if (Wire.available()) - { - 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 - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat( - F("PCF : Analog port: A%d value %d: %s"), - port - 1, - var + 1, - formatUserVarNoCheck(event->TaskIndex, var).c_str())); - } - success = true; - } - } else { - UserVar.setFloat(event->TaskIndex, var, 0); - } - } - - for (; var < VARS_PER_TASK; ++var) { - UserVar.setFloat(event->TaskIndex, var, 0); - } - break; - } - - case PLUGIN_WRITE: - { - String command = parseString(string, 1); - - if ((P007_OUTPUT_MODE == P007_OUTPUT_ENABLED) && - equals(command, F("analogout")) && - (event->Par1 >= 0) && (event->Par1 <= 255)) { - uint8_t unit = (CONFIG_PORT - 1) / 4; - uint8_t address = 0x48 + unit; - - // Setup all required bits to the config register - uint8_t configRegister = 0; - configRegister |= P007_INPUT_MODE; - configRegister |= P007_OUTPUT_MODE; - - I2C_write8_reg(address, configRegister, static_cast(event->Par1)); - - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P007 +#include "_Plugin_Helper.h" +#ifdef USES_P007 + +// ####################################################################################################### +// #################################### Plugin 007: ExtWiredAnalog ####################################### +// ####################################################################################################### + +/** Changelog: + * 2023-11-24 tonhuisman: Add Device flag for I2CMax100kHz as this sensor won't work at 400 kHz + * 2022-05-08 tonhuisman: Use ESPEasy core I2C functions where possible + * Add support for use of the Analog output pin and 'analogout,' command + * Add configuration of all possible analog input modes + * 2022-05-08 tonhuisman: Started changelog, older changes not recorded + ********************************************************************************************************/ + +// commands: +// analogout, : If the Analog output is enabled, the value range is 0..255, and linear to Vref + +# define PLUGIN_007 +# define PLUGIN_ID_007 7 +# define PLUGIN_NAME_007 "Analog input - PCF8591" +# define PLUGIN_VALUENAME1_007 "Analog" + +# define P007_SENSOR_TYPE_INDEX 2 +# define P007_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P007_SENSOR_TYPE_INDEX))) +# define P007_INPUT_MODE PCONFIG_LONG(0) +# define P007_OUTPUT_MODE PCONFIG_LONG(1) +# define P007_OUTPUT_ENABLED (0b01000000) + + +boolean Plugin_007(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_007; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + Device[deviceCount].I2CMax100kHz = true; // Max 100 kHz allowed/supported + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_007); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + ExtraTaskSettings.populateDeviceValueNamesSeq(F(PLUGIN_VALUENAME1_007), P007_NR_OUTPUT_VALUES, 2, true); + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + if (PCONFIG(P007_SENSOR_TYPE_INDEX) == 0) { + PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + } + event->Par1 = P007_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P007_SENSOR_TYPE_INDEX)); + event->idx = P007_SENSOR_TYPE_INDEX; + success = true; + break; + } + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + + success = true; + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + String portNames[4]; + int portValues[4]; + const uint8_t unit = (CONFIG_PORT - 1) / 4; + const uint8_t port = CONFIG_PORT - (unit * 4); + const uint8_t address = 0x48 + unit; + + for (uint8_t x = 0; x < 4; x++) { + portValues[x] = x + 1; + portNames[x] = 'A'; + portNames[x] += x; + } + addFormSelectorI2C(F("pi2c"), 8, i2cAddressValues, address); + addFormSelector(F("Port"), F("pport"), 4, portNames, portValues, port); + addFormNote(F("Selected Port value will be stored in first 'Values' field and consecutively for 'Number Output Values' > Single.")); + } else { + success = intArrayContains(8, i2cAddressValues, event->Par1); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + const uint8_t unit = (CONFIG_PORT - 1) / 4; + event->Par1 = 0x48 + unit; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + addFormSubHeader(F("Hardware configuration")); + + const __FlashStringHelper *inputModeOptions[] = { + F("4 single-ended inputs"), + F("3 differential inputs, A0/A1/A2 differential with AIN3"), + F("2 single-ended, A0, A1, AIN2/AIN3 differential -> A2"), + F("AIN0/AIN1 differential -> A0, AIN2/AIN3 differential -> A1"), + }; + const int inputModeValues[] = { + 0b00000000, + 0b00010000, + 0b00100000, + 0b00110000, + }; + addFormSelector(F("Input mode"), F("input_mode"), 4, inputModeOptions, inputModeValues, P007_INPUT_MODE); + + addFormCheckBox(F("Enable Analog output (AOUT)"), F("output_mode"), P007_OUTPUT_MODE == P007_OUTPUT_ENABLED); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + if (PCONFIG(P007_SENSOR_TYPE_INDEX) == 0) { + PCONFIG(P007_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + } + uint8_t i2c = getFormItemInt(F("pi2c")); + uint8_t port = getFormItemInt(F("pport")); + CONFIG_PORT = (((i2c - 0x48) << 2) + port); + + P007_INPUT_MODE = getFormItemInt(F("input_mode")); + P007_OUTPUT_MODE = isFormItemChecked(F("output_mode")) ? P007_OUTPUT_ENABLED : 0; + + success = true; + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + const uint8_t unit = (CONFIG_PORT - 1) / 4; + uint8_t port = CONFIG_PORT - (unit * 4); + const uint8_t address = 0x48 + unit; + + uint8_t var = 0; + const uint8_t valueCount = P007_NR_OUTPUT_VALUES; + + for (; var < valueCount; ++port, ++var) { + if (port <= 4) { // Only read available ports, hardwired limited to 4 + // Setup all required bits to the config register + uint8_t configRegister = port - 1; + configRegister |= P007_INPUT_MODE; + configRegister |= P007_OUTPUT_MODE; + + // get the current pin value + I2C_write8(address, configRegister); + + Wire.requestFrom(address, (uint8_t)0x2); // No fitting I2C standard function available + + if (Wire.available()) + { + 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 + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("PCF : Analog port: A%d value %d: %s"), + port - 1, + var + 1, + formatUserVarNoCheck(event, var).c_str())); + } + success = true; + } + } else { + UserVar.setFloat(event->TaskIndex, var, 0); + } + } + + for (; var < VARS_PER_TASK; ++var) { + UserVar.setFloat(event->TaskIndex, var, 0); + } + break; + } + + case PLUGIN_WRITE: + { + String command = parseString(string, 1); + + if ((P007_OUTPUT_MODE == P007_OUTPUT_ENABLED) && + equals(command, F("analogout")) && + (event->Par1 >= 0) && (event->Par1 <= 255)) { + const uint8_t unit = (CONFIG_PORT - 1) / 4; + const uint8_t address = 0x48 + unit; + + // Setup all required bits to the config register + uint8_t configRegister = 0; + configRegister |= P007_INPUT_MODE; + configRegister |= P007_OUTPUT_MODE; + + I2C_write8_reg(address, configRegister, static_cast(event->Par1)); + + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P007 diff --git a/src/_P009_MCP.ino b/src/_P009_MCP.ino index 220f3bc29..a88f588b9 100644 --- a/src/_P009_MCP.ino +++ b/src/_P009_MCP.ino @@ -99,7 +99,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) const uint8_t port = CONFIG_PORT - (unit * 16); const uint8_t address = 0x20 + unit; - for (uint8_t x = 0; x < 16; x++) { + for (uint8_t x = 0; x < 16; ++x) { portValues[x] = x + 1; portNames[x] = 'P'; portNames[x] += (x < 8 ? 'A' : 'B'); @@ -187,9 +187,8 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) newStatus.state = GPIO_MCP_Read(CONFIG_PORT); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP INIT="); - log += newStatus.state; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + concat(F("MCP INIT="), newStatus.state)); } newStatus.output = newStatus.state; newStatus.mode = (newStatus.state == -1) ? PIN_MODE_OFFLINE : PIN_MODE_INPUT_PULLUP; @@ -247,7 +246,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) const uint8_t address = 0x20 + unit; if (!I2C_deviceCheck(address, event->TaskIndex, 10, PLUGIN_I2C_GET_ADDRESS)) { // Generate stats - break; // Will return the default false for success + break; // Will return the default false for success } # endif // if FEATURE_I2C_DEVICE_CHECK const int8_t state = GPIO_MCP_Read(CONFIG_PORT); @@ -343,10 +342,7 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, output_value); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state; + String log = strformat(F("MCP : Port=%d State=%d"), CONFIG_PORT, state); log += output_value == 3 ? F(" Doubleclick=") : F(" Output value="); log += output_value; addLogMove(LOG_LEVEL_INFO, log); @@ -410,13 +406,8 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, output_value); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : LongPress: Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state ? '1' : '0'; - log += F(" Output value="); - log += output_value; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("MCP : LongPress: Port=%d State=%d Output value=%d"), CONFIG_PORT, state ? 1 : 0, output_value)); } // send task event @@ -438,11 +429,8 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, 4); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : SafeButton: false positive detected. GPIO= "); - log += CONFIG_PORT; - log += F(" State="); - log += tempUserVar; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("MCP : SafeButton: false positive detected. GPIO= %d State=%d"), CONFIG_PORT, tempUserVar)); } // send task event: DO NOT SEND TASK EVENT @@ -457,14 +445,12 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) } else if ((state != currentStatus.state) && (state == -1)) { // set UserVar and switchState = -1 and send EVENT to notify user UserVar.setFloat(event->TaskIndex, 0, state); - currentStatus.mode = PIN_MODE_OFFLINE; + currentStatus.mode = PIN_MODE_OFFLINE; // switchstate[event->TaskIndex] = state; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); - log += CONFIG_PORT; - log += F(" is offline (EVENT= -1)"); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("MCP : Port=%d is offline (EVENT= -1)"), CONFIG_PORT)); } // send task event @@ -491,11 +477,8 @@ 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 if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MCP : Port="); - log += CONFIG_PORT; - log += F(" State="); - log += UserVar[event->BaseVarIndex]; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("MCP : Port=%d State=%d"), CONFIG_PORT, UserVar[event->BaseVarIndex])); } success = true; break; @@ -520,14 +503,6 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_WRITE: - { - // String log; - // String command = parseString(string, 1); - - break; - } - case PLUGIN_TASKTIMER_IN: case PLUGIN_DEVICETIMER_IN: { @@ -551,150 +526,4 @@ boolean Plugin_009(uint8_t function, struct EventStruct *event, String& string) return success; } -// ******************************************************************************** -// MCP23017 read -// ******************************************************************************** - -/* - int8_t Plugin_009_Read(uint8_t Par1) - { - int8_t state = -1; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankValueReg = 0x12; - - if (port > 8) - { - port = port - 8; - IOBankValueReg++; - } - - // get the current pin status - Wire.beginTransmission(address); - Wire.write(IOBankValueReg); // IO data register - Wire.endTransmission(); - Wire.requestFrom(address, (uint8_t)0x1); - - if (Wire.available()) - { - state = ((Wire.read() & _BV(port - 1)) >> (port - 1)); - } - return state; - } - */ - -// ******************************************************************************** -// MCP23017 write -// ******************************************************************************** - -/* - boolean Plugin_009_Write(uint8_t Par1, uint8_t Par2) - { - boolean success = false; - uint8_t portvalue = 0; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankConfigReg = 0; - uint8_t IOBankValueReg = 0x12; - - if (port > 8) - { - port = port - 8; - IOBankConfigReg++; - IOBankValueReg++; - } - - // turn this port into output, first read current config - Wire.beginTransmission(address); - Wire.write(IOBankConfigReg); // IO config register - Wire.endTransmission(); - Wire.requestFrom(address, (uint8_t)0x1); - - if (Wire.available()) - { - portvalue = Wire.read(); - portvalue &= ~(1 << (port - 1)); // change pin from (default) input to output - - // write new IO config - Wire.beginTransmission(address); - Wire.write(IOBankConfigReg); // IO config register - Wire.write(portvalue); - Wire.endTransmission(); - } - - // get the current pin status - Wire.beginTransmission(address); - Wire.write(IOBankValueReg); // IO data register - Wire.endTransmission(); - Wire.requestFrom(address, (uint8_t)0x1); - - if (Wire.available()) - { - portvalue = Wire.read(); - - if (Par2 == 1) { - portvalue |= (1 << (port - 1)); - } - else { - portvalue &= ~(1 << (port - 1)); - } - - // write back new data - Wire.beginTransmission(address); - Wire.write(IOBankValueReg); - Wire.write(portvalue); - Wire.endTransmission(); - success = true; - } - return success; - } - */ - -// ******************************************************************************** -// MCP23017 config -// ******************************************************************************** - -/* - void Plugin_009_Config(uint8_t Par1, uint8_t Par2) - { - // boolean success = false; - uint8_t portvalue = 0; - uint8_t unit = (Par1 - 1) / 16; - uint8_t port = Par1 - (unit * 16); - uint8_t address = 0x20 + unit; - uint8_t IOBankConfigReg = 0xC; - - if (port > 8) - { - port = port - 8; - IOBankConfigReg++; - } - - // turn this port pullup on - Wire.beginTransmission(address); - Wire.write(IOBankConfigReg); - Wire.endTransmission(); - Wire.requestFrom(address, (uint8_t)0x1); - - if (Wire.available()) - { - portvalue = Wire.read(); - - if (Par2 == 1) { - portvalue |= (1 << (port - 1)); - } - else { - portvalue &= ~(1 << (port - 1)); - } - - // write new IO config - Wire.beginTransmission(address); - Wire.write(IOBankConfigReg); // IO config register - Wire.write(portvalue); - Wire.endTransmission(); - } - } - */ #endif // USES_P009 diff --git a/src/_P010_BH1750.ino b/src/_P010_BH1750.ino index a5bb18b2e..3f092ffbd 100644 --- a/src/_P010_BH1750.ino +++ b/src/_P010_BH1750.ino @@ -1,153 +1,150 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P010 - -// ####################################################################################################### -// #################################### Plugin-010: LuxRead ############################################ -// ####################################################################################################### - - -# include - -# define PLUGIN_010 -# define PLUGIN_ID_010 10 -# define PLUGIN_NAME_010 "Light/Lux - BH1750" -# define PLUGIN_VALUENAME1_010 "Lux" - - -boolean Plugin_010(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_010; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_010); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_010)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { BH1750_DEFAULT_I2CADDR, BH1750_SECOND_I2CADDR }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); - addFormNote(F("ADDR Low=0x23, High=0x5c")); - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - const __FlashStringHelper *optionsMode[] = { - F("RESOLUTION_LOW"), - F("RESOLUTION_NORMAL"), - F("RESOLUTION_HIGH"), - F("RESOLUTION_AUTO_HIGH"), - }; - const int optionValuesMode[] = { - RESOLUTION_LOW, - RESOLUTION_NORMAL, - RESOLUTION_HIGH, - RESOLUTION_AUTO_HIGH, - }; - addFormSelector(F("Measurement mode"), F("pmode"), 4, optionsMode, optionValuesMode, PCONFIG(1)); - - addFormCheckBox(F("Send sensor to sleep"), F("psleep"), PCONFIG(2)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("pmode")); - PCONFIG(2) = isFormItemChecked(F("psleep")); - success = true; - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - AS_BH1750 sensor = AS_BH1750(PCONFIG(0)); - - // replaced the 8 lines below to optimize code - sensors_resolution_t mode = static_cast(PCONFIG(1)); - - // if (PCONFIG(1)==RESOLUTION_LOW) - // mode = RESOLUTION_LOW; - // if (PCONFIG(1)==RESOLUTION_NORMAL) - // mode = RESOLUTION_NORMAL; - // if (PCONFIG(1)==RESOLUTION_HIGH) - // mode = RESOLUTION_HIGH; - // if (PCONFIG(1)==RESOLUTION_AUTO_HIGH) - // mode = RESOLUTION_AUTO_HIGH; - - sensor.begin(mode, PCONFIG(2) == 1); - - float lux = sensor.readLightLevel(); - - if (lux != -1) { - UserVar.setFloat(event->TaskIndex, 0, lux); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("BH1750 Address: "); - log += formatToHex(PCONFIG(0), 2); - log += F(" Mode: "); - log += formatToHex(PCONFIG(1), 2); - log += F(" : Light intensity: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P010 +#include "_Plugin_Helper.h" + +#ifdef USES_P010 + +// ####################################################################################################### +// #################################### Plugin-010: LuxRead ############################################ +// ####################################################################################################### + + +# include + +# define PLUGIN_010 +# define PLUGIN_ID_010 10 +# define PLUGIN_NAME_010 "Light/Lux - BH1750" +# define PLUGIN_VALUENAME1_010 "Lux" + + +boolean Plugin_010(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_010; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_010); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_010)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { BH1750_DEFAULT_I2CADDR, BH1750_SECOND_I2CADDR }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); + addFormNote(F("ADDR Low=0x23, High=0x5c")); + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + const __FlashStringHelper *optionsMode[] = { + F("RESOLUTION_LOW"), + F("RESOLUTION_NORMAL"), + F("RESOLUTION_HIGH"), + F("RESOLUTION_AUTO_HIGH"), + }; + const int optionValuesMode[] = { + RESOLUTION_LOW, + RESOLUTION_NORMAL, + RESOLUTION_HIGH, + RESOLUTION_AUTO_HIGH, + }; + addFormSelector(F("Measurement mode"), F("pmode"), 4, optionsMode, optionValuesMode, PCONFIG(1)); + + addFormCheckBox(F("Send sensor to sleep"), F("psleep"), PCONFIG(2)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + PCONFIG(1) = getFormItemInt(F("pmode")); + PCONFIG(2) = isFormItemChecked(F("psleep")); + success = true; + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + AS_BH1750 sensor = AS_BH1750(PCONFIG(0)); + + // replaced the 8 lines below to optimize code + sensors_resolution_t mode = static_cast(PCONFIG(1)); + + // if (PCONFIG(1)==RESOLUTION_LOW) + // mode = RESOLUTION_LOW; + // if (PCONFIG(1)==RESOLUTION_NORMAL) + // mode = RESOLUTION_NORMAL; + // if (PCONFIG(1)==RESOLUTION_HIGH) + // mode = RESOLUTION_HIGH; + // if (PCONFIG(1)==RESOLUTION_AUTO_HIGH) + // mode = RESOLUTION_AUTO_HIGH; + + sensor.begin(mode, PCONFIG(2) == 1); + + const float lux = sensor.readLightLevel(); + + if (lux != -1.0f) { + UserVar.setFloat(event->TaskIndex, 0, lux); + + 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())); + } + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P010 diff --git a/src/_P011_PME.ino b/src/_P011_PME.ino index dec17e063..5e66e21d7 100644 --- a/src/_P011_PME.ino +++ b/src/_P011_PME.ino @@ -1,334 +1,361 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P011 - - -// ####################################################################################################### -// #################################### Plugin 011: Pro Mini Extender #################################### -// ####################################################################################################### - - -#define PLUGIN_011 -#define PLUGIN_ID_011 11 -#define PLUGIN_NAME_011 "Extra IO - ProMini Extender" -#define PLUGIN_VALUENAME1_011 "Value" - -#define PLUGIN_011_I2C_ADDRESS 0x7f - -constexpr pluginID_t P011_PLUGIN_ID{PLUGIN_ID_011}; - -boolean Plugin_011(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_011; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].Ports = 14; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_011); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_011)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x7f); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x7f; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - const __FlashStringHelper * options[2] = { F("Digital"), F("Analog") }; - addFormSelector(F("Port Type"), F("p011"), 2, options, nullptr, PCONFIG(0)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("p011")); - success = true; - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - UserVar.setFloat(event->TaskIndex, 0, Plugin_011_Read(PCONFIG(0), CONFIG_PORT)); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("PME : PortValue: "), formatUserVarNoCheck(event->TaskIndex, 0))); - } - success = true; - break; - } - - case PLUGIN_WRITE: - { - String log; - String command = parseString(string, 1); - - if (equals(command, F("extgpio"))) - { - success = true; - portStatusStruct tempStatus; - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); - - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page - savePortStatus(key, tempStatus); - - Plugin_011_Write(event->Par1, event->Par2); - - // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = F("PME : GPIO "); - log += event->Par1; - log += F(" Set to "); - log += event->Par2; - addLog(LOG_LEVEL_INFO, log); - - // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); - } - - if (equals(command, F("extpwm"))) - { - success = true; - uint8_t address = PLUGIN_011_I2C_ADDRESS; - Wire.beginTransmission(address); - Wire.write(3); - Wire.write(event->Par1); - Wire.write(event->Par2 & 0xff); - Wire.write((event->Par2 >> 8)); - Wire.endTransmission(); - - portStatusStruct tempStatus; - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); - - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode = PIN_MODE_PWM; - tempStatus.state = event->Par2; - tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page - savePortStatus(key, tempStatus); - - // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_PWM, event->Par2); - log = F("PME : GPIO "); - log += event->Par1; - log += F(" Set PWM to "); - log += event->Par2; - addLog(LOG_LEVEL_INFO, log); - - // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); - } - - if (equals(command, F("extpulse"))) - { - success = true; - - if ((event->Par1 >= 0) && (event->Par1 <= 13)) - { - Plugin_011_Write(event->Par1, event->Par2); - delay(event->Par3); - Plugin_011_Write(event->Par1, !event->Par2); - - portStatusStruct tempStatus; - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); - - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page - savePortStatus(key, tempStatus); - - // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = F("PME : GPIO "); - log += event->Par1; - log += F(" Pulsed for "); - log += event->Par3; - log += F(" mS"); - addLog(LOG_LEVEL_INFO, log); - - // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); - } - } - - if (equals(command, F("extlongpulse"))) - { - success = true; - - if ((event->Par1 >= 0) && (event->Par1 <= 13)) - { - Plugin_011_Write(event->Par1, event->Par2); - Scheduler.setPluginTaskTimer(event->Par3 * 1000, event->TaskIndex, event->Par1, !event->Par2); - - portStatusStruct tempStatus; - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); - - // WARNING: operator [] creates an entry in the map if key does not exist - // So the next command should be part of each command: - tempStatus = globalMapPortStatus[key]; - tempStatus.mode = PIN_MODE_OUTPUT; - tempStatus.state = event->Par2; - tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page - savePortStatus(key, tempStatus); - - // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - log = F("PME : GPIO "); - log += event->Par1; - log += F(" Pulse set for "); - log += event->Par3; - log += F(" S"); - addLog(LOG_LEVEL_INFO, log); - - // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); - SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); - } - } - - if (equals(command, F("status"))) { - if (equals(parseString(string, 2), F("ext"))) - { - success = true; - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 - String dummyString; - - if (!existPortStatus(key)) { // tempStatus.mode == PIN_MODE_OUTPUT) // has been set as output - SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, dummyString, 0); - } - else - { - uint8_t port = event->Par2; // port 0-13 is digital, ports 20-27 are mapped to A0-A7 - uint8_t type = 0; // digital - - if (port > 13) - { - type = 1; - port -= 20; - } - int state = Plugin_011_Read(type, port); // report as input (todo: analog reading) - - if (state != -1) { - SendStatusOnlyIfNeeded(event, NO_SEARCH_PIN_STATE, key, dummyString, state); - } - - // status = getPinStateJSON(NO_SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par2, dummyString, state); - } - } - } - break; - } - - case PLUGIN_TASKTIMER_IN: - { - Plugin_011_Write(event->Par1, event->Par2); - portStatusStruct tempStatus; - - // WARNING: operator [] creates an entry in the map if key does not exist - const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); - tempStatus = globalMapPortStatus[key]; - - tempStatus.state = event->Par2; - tempStatus.mode = PIN_MODE_OUTPUT; - savePortStatus(key, tempStatus); - - // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); - break; - } - } - return success; -} - -// ******************************************************************************** -// PME read -// ******************************************************************************** -int Plugin_011_Read(uint8_t Par1, uint8_t Par2) -{ - int value = -1; - uint8_t address = PLUGIN_011_I2C_ADDRESS; - - Wire.beginTransmission(address); - - if (Par1 == 0) { - Wire.write(2); // Digital Read - } - else { - Wire.write(4); // Analog Read - } - Wire.write(Par2); - Wire.write(0); - Wire.write(0); - Wire.endTransmission(); - delay(1); // remote unit needs some time for conversion... - Wire.requestFrom(address, (uint8_t)0x4); - uint8_t buffer[4]; - - if (Wire.available() == 4) - { - for (uint8_t x = 0; x < 4; x++) { - buffer[x] = Wire.read(); - } - value = buffer[0] + 256 * buffer[1]; - } - return value; -} - -// ******************************************************************************** -// PME write -// ******************************************************************************** -void Plugin_011_Write(uint8_t Par1, uint8_t Par2) -{ - uint8_t address = 0x7f; - - Wire.beginTransmission(address); - Wire.write(1); - Wire.write(Par1); - Wire.write(Par2 & 0xff); - Wire.write((Par2 >> 8)); - Wire.endTransmission(); -} - -#endif // USES_P011 +#include "_Plugin_Helper.h" +#ifdef USES_P011 + + +// ####################################################################################################### +// #################################### Plugin 011: Pro Mini Extender #################################### +// ####################################################################################################### + +/** Changelog: + * 2024-04-14 tonhuisman: Add support for Get Config Values, to obtain a port state/value without instantiating a task for each pin. + * Only a single, enabled, task is required to handle the Get Config Values. + * Variables: [#D] and [#A,] + * 2024-03-29 tonhuisman: Add support for Input (switch) behavior, that only generates an event if the input pin changes state + * De-duplicate (merge) extpulse and extlongpulse code, and make extpulse only blocking for durations up to 10 msec + * 2024-03-28 tonhuisman: Start changelog. + */ + +# define PLUGIN_011 +# define PLUGIN_ID_011 11 +# define PLUGIN_NAME_011 "Extra IO - ProMini Extender" +# define PLUGIN_VALUENAME1_011 "Value" + +# define PLUGIN_011_I2C_ADDRESS 0x7f + +# define PLUGIN_011_PORTS 14 +# define PLUGIN_011_A_PORTS 8 + +# define P011_PORT_TYPE PCONFIG(0) +# define P011_TYPE_DIGITAL 0 +# define P011_TYPE_ANALOG 1 +# define P011_TYPE_SWITCH 2 + +constexpr pluginID_t P011_PLUGIN_ID{ PLUGIN_ID_011 }; + +boolean Plugin_011(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_011; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].Ports = PLUGIN_011_PORTS; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_011); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_011)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == PLUGIN_011_I2C_ADDRESS); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PLUGIN_011_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + const __FlashStringHelper *options[] = { F("Digital"), F("Analog"), F("Input (switch)") }; + const int optionValues[] = { P011_TYPE_DIGITAL, P011_TYPE_ANALOG, P011_TYPE_SWITCH }; + addFormSelector(F("Port Type"), F("p011"), NR_ELEMENTS(options), options, optionValues, P011_PORT_TYPE); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P011_PORT_TYPE = getFormItemInt(F("p011")); + success = true; + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + if (P011_TYPE_SWITCH != P011_PORT_TYPE) { // Not for Switch type + UserVar.setFloat(event->TaskIndex, 0, Plugin_011_Read(P011_PORT_TYPE, CONFIG_PORT)); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("PME : PortValue: "), formatUserVarNoCheck(event, 0))); + } + success = true; + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: + { + if (P011_TYPE_SWITCH == P011_PORT_TYPE) { // Only for Switch type + const int oldValue = static_cast(UserVar.getFloat(event->TaskIndex, 0)); + const int newValue = Plugin_011_Read(P011_TYPE_DIGITAL, CONFIG_PORT); + + if (oldValue != newValue) { // Changed? + UserVar.setFloat(event->TaskIndex, 0, newValue); + sendData(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("PME : Switch state: %d"), newValue)); + } + success = true; + } + } + break; + } + + case PLUGIN_WRITE: + { + String log; + const String command = parseString(string, 1); + + if (equals(command, F("extgpio"))) + { + success = true; + portStatusStruct tempStatus; + const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + Plugin_011_Write(event->Par1, event->Par2); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + log = strformat(F("PME : GPIO %d Set to %d"), event->Par1, event->Par2); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); + } + else + if (equals(command, F("extpwm"))) + { + success = true; + uint8_t address = PLUGIN_011_I2C_ADDRESS; + Wire.beginTransmission(address); + Wire.write(3); + Wire.write(event->Par1); + Wire.write(event->Par2 & 0xff); + Wire.write(event->Par2 >> 8); + Wire.endTransmission(); + + portStatusStruct tempStatus; + const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + tempStatus.mode = PIN_MODE_PWM; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_PWM, event->Par2); + log = strformat(F("PME : GPIO %d Set PWM to %d"), event->Par1, event->Par2); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); + } + else + if (equals(command, F("extpulse")) || equals(command, F("extlongpulse"))) // De-duplicated + { + if ((event->Par1 >= 0) && (event->Par1 < PLUGIN_011_PORTS)) + { + const int factor = equals(command, F("extlongpulse")) ? 1000 : 1; + const int duration = event->Par3 * factor; + success = true; + Plugin_011_Write(event->Par1, event->Par2); + + if (duration <= 10) { // Short pulses (<= 10 msec) only use direct delay + delay(event->Par3); + Plugin_011_Write(event->Par1, !event->Par2); + log = strformat(F("PME : GPIO %d Pulsed for %d mS"), event->Par1, duration); + } else { + Scheduler.setPluginTaskTimer(duration, event->TaskIndex, event->Par1, !event->Par2); + log = strformat(F("PME : GPIO %d Pulse set for %d %cS"), event->Par1, duration, factor == 1 ? 'm' : ' '); + } + + portStatusStruct tempStatus; + const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); + + // WARNING: operator [] creates an entry in the map if key does not exist + // So the next command should be part of each command: + tempStatus = globalMapPortStatus[key]; + tempStatus.mode = PIN_MODE_OUTPUT; + tempStatus.state = event->Par2; + tempStatus.command = 1; // set to 1 in order to display the status in the PinStatus page + savePortStatus(key, tempStatus); + + // setPinState(PLUGIN_ID_011, event->Par1, PIN_MODE_OUTPUT, event->Par2); + addLog(LOG_LEVEL_INFO, log); + + // SendStatus(event, getPinStateJSON(SEARCH_PIN_STATE, PLUGIN_ID_011, event->Par1, log, 0)); + SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); + } + } + else + if (equals(command, F("status"))) { + if (equals(parseString(string, 2), F("ext"))) + { + success = true; + const uint32_t key = createKey(P011_PLUGIN_ID, event->Par2); // WARNING: 'status' uses Par2 instead of Par1 + String dummyString; + + if (!existPortStatus(key)) { // tempStatus.mode == PIN_MODE_OUTPUT) // has been set as output + SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, dummyString, 0); + } + else + { + uint8_t port = event->Par2; // port 0-13 is digital, ports 20-27 are mapped to A0-A7 + uint8_t type = 0; // digital + + if (port >= PLUGIN_011_PORTS) + { + type = 1; + port -= 20; + } + int state = Plugin_011_Read(type, port); // report as input (todo: analog reading) + + if (state != -1) { + SendStatusOnlyIfNeeded(event, NO_SEARCH_PIN_STATE, key, dummyString, state); + } + } + } + } + break; + } + + case PLUGIN_TASKTIMER_IN: + { + Plugin_011_Write(event->Par1, event->Par2); + portStatusStruct tempStatus; + + // WARNING: operator [] creates an entry in the map if key does not exist + const uint32_t key = createKey(P011_PLUGIN_ID, event->Par1); + tempStatus = globalMapPortStatus[key]; + + tempStatus.state = event->Par2; + tempStatus.mode = PIN_MODE_OUTPUT; + savePortStatus(key, tempStatus); + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + char sep = '.'; + + if ((-1 == string.indexOf(sep)) && (string.indexOf(',') >= 0)) { + sep = ','; + } + const String typ = parseString(string, 1, sep); + const String port = parseString(string, 2, sep); + int32_t portnr = -1; + validIntFromString(port, portnr); + + // [#D.] : Read Digital value from + if (equals(typ, F("d")) && !port.isEmpty() && (portnr >= 0) && (portnr < PLUGIN_011_PORTS)) { + string = Plugin_011_Read(0, portnr); + success = true; + } else + + // [#A.] : Read Analog value from + if (equals(typ, F("a")) && !port.isEmpty() && (portnr >= 0) && (portnr < PLUGIN_011_A_PORTS)) { + string = Plugin_011_Read(1, portnr); + success = true; + } + + break; + } + } + return success; +} + +// ******************************************************************************** +// PME read +// ******************************************************************************** +int Plugin_011_Read(uint8_t Par1, uint8_t Par2) +{ + int value = -1; + uint8_t address = PLUGIN_011_I2C_ADDRESS; + + Wire.beginTransmission(address); + + if (Par1 == P011_TYPE_DIGITAL) { + Wire.write(2); // Digital Read + } + else { + Wire.write(4); // Analog Read + } + Wire.write(Par2); + Wire.write(0); + Wire.write(0); + Wire.endTransmission(); + delay(1); // remote unit needs some time for conversion... + Wire.requestFrom(address, (uint8_t)0x4); + uint8_t buffer[4]; + + if (Wire.available() == 4) + { + for (uint8_t x = 0; x < 4; ++x) { + buffer[x] = Wire.read(); + } + value = buffer[0] + 256 * buffer[1]; + } + return value; +} + +// ******************************************************************************** +// PME write +// ******************************************************************************** +void Plugin_011_Write(uint8_t Par1, uint8_t Par2) +{ + uint8_t address = PLUGIN_011_I2C_ADDRESS; + + Wire.beginTransmission(address); + Wire.write(1); + Wire.write(Par1); + Wire.write(Par2 & 0xff); + Wire.write(Par2 >> 8); + Wire.endTransmission(); +} + +#endif // USES_P011 diff --git a/src/_P012_LCD.ino b/src/_P012_LCD.ino index 5ad557ddc..bd1841451 100644 --- a/src/_P012_LCD.ino +++ b/src/_P012_LCD.ino @@ -1,274 +1,289 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P012 - -# include "src/Helpers/StringParser.h" -# include "src/PluginStructs/P012_data_struct.h" - -// ####################################################################################################### -// #################################### Plugin 012: LCD ################################################## -// ####################################################################################################### - -/** Changelog: - * 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces - * 2023-03: First changelog added, older changes not logged - */ - -// Sample templates -// Temp: [DHT11#Temperature] Hum:[DHT11#humidity] -// DS Temp:[Dallas1#Temperature#R] -// Lux:[Lux#Lux#R] -// Baro:[Baro#Pressure#R] -// Pump:[Pump#on#O] -> ON/OFF - - -# define PLUGIN_012 -# define PLUGIN_ID_012 12 -# define PLUGIN_NAME_012 "Display - LCD2004" -# define PLUGIN_VALUENAME1_012 "LCD" - -# define P12_Nlines 4 // The number of different lines which can be displayed -# define P12_Nchars 80 - -# define P012_I2C_ADDR PCONFIG(0) -# define P012_SIZE PCONFIG(1) -# define P012_TIMER PCONFIG(2) -# define P012_MODE PCONFIG(3) -# define P012_INVERSE_BTN PCONFIG(4) - -boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_012; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_012); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_012)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 16, i2cAddressValues, P012_I2C_ADDR); - } else { - success = intArrayContains(16, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P012_I2C_ADDR; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - { - const __FlashStringHelper *options2[] = { - F("2 x 16"), - F("4 x 20"), - }; - const int optionValues2[2] = { 1, 2 }; - addFormSelector(F("Display Size"), F("psize"), 2, options2, optionValues2, P012_SIZE); - } - - { - String strings[P12_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P12_Nlines, P12_Nchars); - - for (int varNr = 0; varNr < P12_Nlines; varNr++) - { - addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P12_Nchars); - } - } - - addRowLabel(F("Display button")); - addPinSelect(PinSelectPurpose::Generic_input, F("taskdevicepin3"), CONFIG_PIN3); - - addFormCheckBox(F("Inversed logic"), F("pinv_btn"), P012_INVERSE_BTN == 1, false); - - addFormNumericBox(F("Display Timeout"), F("ptimer"), P012_TIMER); - - { - const __FlashStringHelper *options3[] { - F("Continue to next line (as in v1.4)"), - F("Truncate exceeding message"), - F("Clear then truncate exceeding message"), - }; - const int optionValues3[] = { 0, 1, 2 }; - addFormSelector(F("LCD command Mode"), F("pmode"), 3, options3, optionValues3, P012_MODE); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P012_I2C_ADDR = getFormItemInt(F("i2c_addr")); - P012_SIZE = getFormItemInt(F("psize")); - P012_TIMER = getFormItemInt(F("ptimer")); - P012_MODE = getFormItemInt(F("pmode")); - P012_INVERSE_BTN = isFormItemChecked(F("pinv_btn")) ? 1 : 0; - - // FIXME TD-er: This is a huge stack allocated object. - char deviceTemplate[P12_Nlines][P12_Nchars] = {}; - String error; - - for (uint8_t varNr = 0; varNr < P12_Nlines; varNr++) - { - if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P12_Nchars)) { - error += getCustomTaskSettingsError(varNr); - } - } - - if (error.length() > 0) { - addHtmlError(error); - } - SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P012_data_struct(P012_I2C_ADDR, P012_SIZE, P012_MODE, P012_TIMER)); - P012_data_struct *P012_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P012_data) { - P012_data->init(); - - if (validGpio(CONFIG_PIN3)) { - pinMode(CONFIG_PIN3, INPUT_PULLUP); - } - success = true; - } - - break; - } - - case PLUGIN_TEN_PER_SECOND: - { - if (validGpio(CONFIG_PIN3)) - { - if (digitalRead(CONFIG_PIN3) == P012_INVERSE_BTN) - { - P012_data_struct *P012_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P012_data) { - P012_data->setBacklightTimer(P012_TIMER); - } - } - } - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - P012_data_struct *P012_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P012_data) { - P012_data->checkTimer(); - } - break; - } - - case PLUGIN_READ: - { - P012_data_struct *P012_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P012_data) { - // FIXME TD-er: This is a huge stack allocated object. - char deviceTemplate[P12_Nlines][P12_Nchars]; - LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); - - for (uint8_t x = 0; x < P012_data->Plugin_012_rows; x++) - { - String tmpString = deviceTemplate[x]; - - if (tmpString.length()) - { - String newString = P012_data->P012_parseTemplate(tmpString, P012_data->Plugin_012_cols); - P012_data->lcdWrite(newString, 0, x); - } - } - success = false; - } - break; - } - - case PLUGIN_WRITE: - { - P012_data_struct *P012_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P012_data) { - String cmd = parseString(string, 1); - - if (cmd.equalsIgnoreCase(F("LCDCMD"))) - { - success = true; - String arg1 = parseString(string, 2); - - if (arg1.equalsIgnoreCase(F("Off"))) { - P012_data->lcd.noBacklight(); - } - else if (arg1.equalsIgnoreCase(F("On"))) { - P012_data->lcd.backlight(); - } - else if (arg1.equalsIgnoreCase(F("Clear"))) { - P012_data->lcd.clear(); - } - } - else if (cmd.equalsIgnoreCase(F("LCD"))) - { - success = true; - int colPos = event->Par2 - 1; - int rowPos = event->Par1 - 1; - String text = parseStringKeepCaseNoTrim(string, 4); - text = P012_data->P012_parseTemplate(text, P012_data->Plugin_012_cols); - - P012_data->lcdWrite(text, colPos, rowPos); - } - break; - } - } - } - return success; -} - -#endif // USES_P012 +#include "_Plugin_Helper.h" + +#ifdef USES_P012 + +# include "src/Helpers/StringParser.h" +# include "src/PluginStructs/P012_data_struct.h" + +// ####################################################################################################### +// #################################### Plugin 012: LCD ################################################## +// ####################################################################################################### + +/** Changelog: + * 2023-12-26 tonhuisman: Clear the splash from the display after 5 seconds if not already overwritten + * 2023-03-07 tonhuisman: Parse text to display without trimming off leading and trailing spaces + * 2023-03: First changelog added, older changes not logged + */ + +// Sample templates +// Temp: [DHT11#Temperature] Hum:[DHT11#humidity] +// DS Temp:[Dallas1#Temperature#R] +// Lux:[Lux#Lux#R] +// Baro:[Baro#Pressure#R] +// Pump:[Pump#on#O] -> ON/OFF + + +# define PLUGIN_012 +# define PLUGIN_ID_012 12 +# define PLUGIN_NAME_012 "Display - LCD2004" +# define PLUGIN_VALUENAME1_012 "LCD" + +# define P12_Nlines 4 // The number of different lines which can be displayed +# define P12_Nchars 80 + +# define P012_I2C_ADDR PCONFIG(0) +# define P012_SIZE PCONFIG(1) +# define P012_TIMER PCONFIG(2) +# define P012_MODE PCONFIG(3) +# define P012_INVERSE_BTN PCONFIG(4) + +boolean Plugin_012(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_012; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_012); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_012)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 16, i2cAddressValues, P012_I2C_ADDR); + } else { + success = intArrayContains(16, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P012_I2C_ADDR; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *options2[] = { + F("2 x 16"), + F("4 x 20"), + }; + const int optionValues2[2] = { 1, 2 }; + addFormSelector(F("Display Size"), F("psize"), 2, options2, optionValues2, P012_SIZE); + } + + { + String strings[P12_Nlines]; + LoadCustomTaskSettings(event->TaskIndex, strings, P12_Nlines, P12_Nchars); + + for (int varNr = 0; varNr < P12_Nlines; varNr++) + { + addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P12_Nchars); + } + } + + addRowLabel(F("Display button")); + addPinSelect(PinSelectPurpose::Generic_input, F("taskdevicepin3"), CONFIG_PIN3); + + addFormCheckBox(F("Inversed logic"), F("pinv_btn"), P012_INVERSE_BTN == 1, false); + + addFormNumericBox(F("Display Timeout"), F("ptimer"), P012_TIMER); + + { + const __FlashStringHelper *options3[] { + F("Continue to next line (as in v1.4)"), + F("Truncate exceeding message"), + F("Clear then truncate exceeding message"), + }; + const int optionValues3[] = { 0, 1, 2 }; + addFormSelector(F("LCD command Mode"), F("pmode"), 3, options3, optionValues3, P012_MODE); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P012_I2C_ADDR = getFormItemInt(F("i2c_addr")); + P012_SIZE = getFormItemInt(F("psize")); + P012_TIMER = getFormItemInt(F("ptimer")); + P012_MODE = getFormItemInt(F("pmode")); + P012_INVERSE_BTN = isFormItemChecked(F("pinv_btn")) ? 1 : 0; + + // FIXME TD-er: This is a huge stack allocated object. + char deviceTemplate[P12_Nlines][P12_Nchars] = {}; + String error; + + for (uint8_t varNr = 0; varNr < P12_Nlines; varNr++) + { + if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P12_Nchars)) { + error += getCustomTaskSettingsError(varNr); + } + } + + if (error.length() > 0) { + addHtmlError(error); + } + SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P012_data_struct(P012_I2C_ADDR, P012_SIZE, P012_MODE, P012_TIMER)); + P012_data_struct *P012_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P012_data) { + P012_data->init(); + + if (validGpio(CONFIG_PIN3)) { + pinMode(CONFIG_PIN3, INPUT_PULLUP); + } + success = true; + } + + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + if (validGpio(CONFIG_PIN3)) + { + if (digitalRead(CONFIG_PIN3) == P012_INVERSE_BTN) + { + P012_data_struct *P012_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P012_data) { + P012_data->setBacklightTimer(P012_TIMER); + } + } + } + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P012_data_struct *P012_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P012_data) { + P012_data->checkTimer(); + } + break; + } + + case PLUGIN_READ: + { + P012_data_struct *P012_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P012_data) { + // FIXME TD-er: This is a huge stack allocated object. + char deviceTemplate[P12_Nlines][P12_Nchars]; + LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); + + switch (P012_data->splashState) { + case P012_splashState_e::SplashCleared: + // Most common route + break; + case P012_splashState_e::SplashInitial: + P012_data->splashState = P012_splashState_e::SplashTimerRunning; + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 5000); + break; + case P012_splashState_e::SplashTimerRunning: + P012_data->lcdWrite(F(" "), 0, 0); // Wipe 'ESP Easy' splash text, will reset splashState + break; + } + + for (uint8_t x = 0; x < P012_data->Plugin_012_rows; x++) + { + String tmpString = deviceTemplate[x]; + + if (tmpString.length()) + { + String newString = P012_data->P012_parseTemplate(tmpString, P012_data->Plugin_012_cols); + P012_data->lcdWrite(newString, 0, x); + } + } + success = false; + } + break; + } + + case PLUGIN_WRITE: + { + P012_data_struct *P012_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P012_data) { + String cmd = parseString(string, 1); + + if (cmd.equalsIgnoreCase(F("LCDCMD"))) + { + success = true; + String arg1 = parseString(string, 2); + + if (arg1.equalsIgnoreCase(F("Off"))) { + P012_data->lcd.noBacklight(); + } + else if (arg1.equalsIgnoreCase(F("On"))) { + P012_data->lcd.backlight(); + } + else if (arg1.equalsIgnoreCase(F("Clear"))) { + P012_data->lcd.clear(); + P012_data->splashState = P012_splashState_e::SplashCleared; + } + } + else if (cmd.equalsIgnoreCase(F("LCD"))) + { + success = true; + int colPos = event->Par2 - 1; + int rowPos = event->Par1 - 1; + String text = parseStringKeepCaseNoTrim(string, 4); + text = P012_data->P012_parseTemplate(text, P012_data->Plugin_012_cols); + + P012_data->lcdWrite(text, colPos, rowPos); + } + break; + } + } + } + return success; +} + +#endif // USES_P012 diff --git a/src/_P013_HCSR04.ino b/src/_P013_HCSR04.ino index c70513a4d..ebe60be6c 100644 --- a/src/_P013_HCSR04.ino +++ b/src/_P013_HCSR04.ino @@ -1,467 +1,467 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P013 - -// ####################################################################################################### -// ############################### Plugin 013: HC-SR04, RCW-0001, etc. ################################### -// ####################################################################################################### - -/** Changelog: - * 2023-02-25 tonhuisman: Make Interval optional, and also disable the added feature P013_FEATURE_INTERVALEVENT, as setting Interval - * to 0 is effectively the same. (Small code reduction) - * Changed second value label for Combined mode to State - * 2023-02-19 tonhuisman: Suggested modification with variable trigger width was only partly accepted by NewPing library, and as - * we already have a modified library using the DIRECT_Gpio functions, I'm not merging back that change - * (compile-time setting, default now: 12 usec) but keep the proposed changes for a runtime configurable setting. - * 2023-01-28 tonhuisman: Add Combined mode, as started in https://github.com/letscontrolit/ESPEasy/pull/3157 - * 2023-01-20 tonhuisman: Limit trigger-range to 10-20 usec. (20 already seems to be on the high side) - * Reduce build-size by disabling new features on 1M builds and leaving out some non-essential messages - * Move #define stuff and #includes to src/PluginStructs/P013_data_struct.h - * 2022-12-31 tonhuisman: Code improvements, change start-trigger range to 10-50 usec. - * Optionally not send regular Interval events when using State mode - * 2022-12-29 tonhuisman: Add start-trigger setting, range 10-30 usec. See https://github.com/letscontrolit/ESPEasy/issues/3857 - * 2022-12-29 tonhuisman: Add changelog - */ - -# define PLUGIN_013 -# define PLUGIN_ID_013 13 -# define PLUGIN_NAME_013 "Position - HC-SR04, RCW-0001, etc." -# define PLUGIN_VALUENAME1_013 "Distance" -# define PLUGIN_VALUENAME2_013 "State" - -# include "src/PluginStructs/P013_data_struct.h" - -// map of sensors -std::map > P_013_sensordefs; - -// Forward declarations -float Plugin_013_read(struct EventStruct *event); -const __FlashStringHelper* Plugin_013_getErrorStatusString(struct EventStruct *event); - -boolean Plugin_013(uint8_t function, struct EventStruct *event, String& string) -{ - static uint8_t switchstate[TASKS_MAX]; - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_013; - Device[deviceCount].Type = DEVICE_TYPE_DUAL; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_013); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_013)); - # if P013_FEATURE_COMBINED_MODE - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_013)); - # endif // if P013_FEATURE_COMBINED_MODE - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_output(F("Trigger")); - event->String2 = formatGpioName_input(F("Echo, 5V")); - break; - } - - # if P013_FEATURE_COMBINED_MODE - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1); - event->idx = P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1; - success = true; - break; - } - # endif // if P013_FEATURE_COMBINED_MODE - - case PLUGIN_SET_DEFAULTS: - { - P013_FILTER_SIZE = P013_DEFAULT_FILTER_SIZE; - # if P013_FEATURE_TRIGGERWIDTH - P013_TRIGGER_WIDTH = P013_DEFAULT_TRIGGER_WIDTH; - # endif // if P013_FEATURE_TRIGGERWIDTH - - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - const __FlashStringHelper *strUnit = (P013_MEASURINGUNIT == UNIT_CM) ? F("cm") : F("inch"); - - { - const int optionValuesOpMode[] = { - OPMODE_VALUE, - OPMODE_STATE, - # if P013_FEATURE_COMBINED_MODE - OPMODE_COMBINED, - # endif // if P013_FEATURE_COMBINED_MODE - }; - const __FlashStringHelper *optionsOpMode[] { - F("Value"), - F("State"), - # if P013_FEATURE_COMBINED_MODE - F("Combined"), - # endif // if P013_FEATURE_COMBINED_MODE - }; - addFormSelector(F("Mode"), F("pmode"), - # if P013_FEATURE_COMBINED_MODE - 3 - # else // if P013_FEATURE_COMBINED_MODE - 2 - # endif // if P013_FEATURE_COMBINED_MODE - , optionsOpMode, optionValuesOpMode, P013_OPERATINGMODE); - } - - if ((P013_OPERATINGMODE == OPMODE_STATE) - # if P013_FEATURE_COMBINED_MODE - || (P013_OPERATINGMODE == OPMODE_COMBINED) - # endif // if P013_FEATURE_COMBINED_MODE - ) { - # if P013_FEATURE_INTERVALEVENT - addFormCheckBox(F("State event (also) on Interval"), F("pevent"), P013_SEND_STATE_VALUE == 0); - # endif // if P013_FEATURE_INTERVALEVENT - addFormNumericBox(F("Threshold"), F("thres"), P013_THRESHOLD); - addUnit(strUnit); - } - addFormNumericBox(F("Max Distance"), F("max_d"), P013_MAX_DISTANCE, 0, 500); - addUnit(strUnit); - - { - const int optionValuesUnit[2] = { UNIT_CM, UNIT_INCH }; - const __FlashStringHelper *optionsUnit[] { - F("Metric"), - F("Imperial"), - }; - addFormSelector(F("Unit"), F("pUnit"), 2, optionsUnit, optionValuesUnit, P013_MEASURINGUNIT); - } - - { - const int optionValuesFilter[2] = { FILTER_NONE, FILTER_MEDIAN }; - const __FlashStringHelper *optionsFilter[] { - F("None"), - F("Median"), - }; - addFormSelector(F("Filter"), F("fltr"), 2, optionsFilter, optionValuesFilter, P013_FILTERTYPE); - } - - // enable filtersize option if filter is used, - if (P013_FILTERTYPE != FILTER_NONE) { - addFormNumericBox(F("Number of Pings"), F("size"), P013_FILTER_SIZE, 2, 20); - # if P013_EXTENDED_LOG - addUnit(F("2..20")); - # endif // if P013_EXTENDED_LOG - } - - # if P013_FEATURE_TRIGGERWIDTH - addFormNumericBox(F("Trigger width"), F("wdth"), P013_TRIGGER_WIDTH, 10, 20); - addUnit(F("10..20 µsec")); - # endif // if P013_FEATURE_TRIGGERWIDTH - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - int16_t prevOperatingMode = P013_OPERATINGMODE; - int16_t prevFilterType = P013_FILTERTYPE; - - P013_OPERATINGMODE = getFormItemInt(F("pmode")); - - if ((prevOperatingMode == OPMODE_STATE) - # if P013_FEATURE_COMBINED_MODE - || (prevOperatingMode == OPMODE_COMBINED) - # endif // if P013_FEATURE_COMBINED_MODE - ) { - # if P013_FEATURE_INTERVALEVENT - P013_SEND_STATE_VALUE = isFormItemChecked(F("pevent")) ? 0 : 1; // Inverted state - # endif // if P013_FEATURE_INTERVALEVENT - P013_THRESHOLD = getFormItemInt(F("thres")); - } - # if P013_FEATURE_COMBINED_MODE - - if ((P013_OPERATINGMODE == OPMODE_COMBINED) && (ExtraTaskSettings.TaskDeviceValueNames[1][0] == '\0')) { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_013)); - } - # endif // if P013_FEATURE_COMBINED_MODE - P013_MAX_DISTANCE = getFormItemInt(F("max_d")); - - P013_MEASURINGUNIT = getFormItemInt(F("pUnit")); - P013_FILTERTYPE = getFormItemInt(F("fltr")); - - if (prevFilterType != FILTER_NONE) { - P013_FILTER_SIZE = getFormItemInt(F("size")); - } - # if P013_FEATURE_TRIGGERWIDTH - P013_TRIGGER_WIDTH = getFormItemInt(F("wdth")); - # endif // if P013_FEATURE_TRIGGERWIDTH - - success = true; - break; - } - - case PLUGIN_INIT: - { - if (P013_FILTER_SIZE == 0) { P013_FILTER_SIZE = P013_DEFAULT_FILTER_SIZE; } - - # if P013_FEATURE_TRIGGERWIDTH - - if (P013_TRIGGER_WIDTH == 0) { P013_TRIGGER_WIDTH = P013_DEFAULT_TRIGGER_WIDTH; } - # endif // if P013_FEATURE_TRIGGERWIDTH - - int16_t max_distance_cm = (P013_MEASURINGUNIT == UNIT_CM) ? P013_MAX_DISTANCE : static_cast(P013_MAX_DISTANCE) * 2.54f; - - // 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, - P013_ECHO_PIN, - max_distance_cm, - P013_TRIGGER_WIDTH)); - success = true; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex + 1; - log += F(" TrigPin: "); - log += P013_TRIGGER_PIN; - log += F(" IRQ_Pin: "); - log += P013_ECHO_PIN; - - if (nullptr != P_013_sensordefs[event->TaskIndex]) { // Initialization successful - # if P013_EXTENDED_LOG - log += F(" width [usec]: "); - log += P013_TRIGGER_WIDTH; - log += F(" max dist "); - log += (P013_MEASURINGUNIT == UNIT_CM) ? F("[cm]: ") : F("[inch]: "); - log += P013_MAX_DISTANCE; - - log += F(" max echo: "); - log += P_013_sensordefs[event->TaskIndex]->getMaxEchoTime(); - log += F(" Filter: "); - - if (P013_FILTERTYPE == FILTER_NONE) { - log += F("none"); - } - else if (P013_FILTERTYPE == FILTER_MEDIAN) { - log += F("Median size: "); - log += P013_FILTER_SIZE; - } else { - log += F("invalid!"); - } - - log += F(" nr_tasks: "); - log += P_013_sensordefs.size(); - # endif // if P013_EXTENDED_LOG - } else { - log += F(" CONSTRUCTOR FAILED!"); - success = false; // Initialization failed - } - addLogMove(LOG_LEVEL_INFO, log); - } - - break; - } - - case PLUGIN_EXIT: - { - P_013_sensordefs.erase(event->TaskIndex); - break; - } - - case PLUGIN_READ: // If we select value mode, read and send the value based on global timer - { - if ((P013_OPERATINGMODE == OPMODE_VALUE) - # if P013_FEATURE_COMBINED_MODE - || (P013_OPERATINGMODE == OPMODE_COMBINED) - # endif // if P013_FEATURE_COMBINED_MODE - ) { - const float value = Plugin_013_read(event); - UserVar.setFloat(event->TaskIndex, 0, value); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex + 1; - # if P013_EXTENDED_LOG - log += F(" Distance: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - log += ' '; - log += (P013_MEASURINGUNIT == UNIT_CM) ? F("cm") : F("inch"); - # endif // if P013_EXTENDED_LOG - - if (essentiallyEqual(value, NO_ECHO)) { - log += F(" Error: "); - log += Plugin_013_getErrorStatusString(event); - } - - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; // Only send out when actually using Value mode - } else { - # if P013_FEATURE_INTERVALEVENT - - if (P013_SEND_STATE_VALUE == 0) { // Also send on Interval when using State mode - success = true; - } - # else // if P013_FEATURE_INTERVALEVENT - success = true; - # endif // if P013_FEATURE_INTERVALEVENT - } - break; - } - - case PLUGIN_TEN_PER_SECOND: // If we select state mode, do more frequent checks and send only state changes - { - if ((P013_OPERATINGMODE == OPMODE_STATE) - # if P013_FEATURE_COMBINED_MODE - || (P013_OPERATINGMODE == OPMODE_COMBINED) - # endif // if P013_FEATURE_COMBINED_MODE - ) { - uint8_t state = 0; - const float value = Plugin_013_read(event); - - if (!essentiallyEqual(value, NO_ECHO) && definitelyLessThan(value, P013_THRESHOLD)) { - state = 1; - } - - if (state != switchstate[event->TaskIndex]) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ULTRASONIC : TaskNr: "); - log += event->TaskIndex + 1; - - if (value != NO_ECHO) { - log += F(" state: "); - log += state; - } else { - log += F(" Error: "); - log += Plugin_013_getErrorStatusString(event); - } - addLogMove(LOG_LEVEL_INFO, log); - } - switchstate[event->TaskIndex] = state; - UserVar.setFloat(event->TaskIndex, 0, state); - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; - sendData(event); - } - } - success = true; - - break; - } - } - return success; -} - -/*********************************************************************/ -float Plugin_013_read(struct EventStruct *event) - -/*********************************************************************/ -{ - if (P_013_sensordefs.count(event->TaskIndex) == 0u) { - return 0.0f; - } - - int16_t max_distance_cm = (P013_MEASURINGUNIT == UNIT_CM) ? P013_MAX_DISTANCE : static_cast(P013_MAX_DISTANCE) * 2.54f; - - unsigned int echoTime = 0; - - switch (P013_FILTERTYPE) { - case FILTER_NONE: - echoTime = (P_013_sensordefs[event->TaskIndex])->ping(); - break; - case FILTER_MEDIAN: - echoTime = (P_013_sensordefs[event->TaskIndex])->ping_median(P013_FILTER_SIZE, max_distance_cm); - break; - # if P013_EXTENDED_LOG - default: - addLog(LOG_LEVEL_ERROR, F("invalid Filter Type setting!")); // Should not be possible... - # endif // if P013_EXTENDED_LOG - } - - if (P013_MEASURINGUNIT == UNIT_CM) { - return NewPing::convert_cm_F(echoTime); - } - else { - return NewPing::convert_in_F(echoTime); - } -} - -/*********************************************************************/ -const __FlashStringHelper* Plugin_013_getErrorStatusString(struct EventStruct *event) - -/*********************************************************************/ -{ - if (P_013_sensordefs.count(event->TaskIndex) == 0) { - return F("invalid taskindex"); - } - - switch ((P_013_sensordefs[event->TaskIndex])->getErrorState()) { - case NewPing::STATUS_SENSOR_READY: { // 0 - # if P013_EXTENDED_LOG - return F("Sensor ready"); - # endif // if P013_EXTENDED_LOG - } - - case NewPing::STATUS_MEASUREMENT_VALID: { // 1 - # if P013_EXTENDED_LOG - return F("no error, measurement valid"); - # endif // if P013_EXTENDED_LOG - } - - case NewPing::STATUS_ECHO_TRIGGERED: { // 2 - # if P013_EXTENDED_LOG - return F("Echo triggered, waiting for Echo end"); - # else // if P013_EXTENDED_LOG - return F("Ok"); - # endif // if P013_EXTENDED_LOG - } - - case NewPing::STATUS_ECHO_STATE_ERROR: { // 6 - return F("Error, Echopin not low on trigger"); - } - - case NewPing::STATUS_ECHO_START_TIMEOUT_50ms: { // 4 - return F("Error, no echo start whithin 50 ms"); - } - - case NewPing::STATUS_ECHO_START_TIMEOUT_DISTANCE: { // 5 - return F("Error, no echo start whithin time for max. distance"); - } - - case NewPing::STATUS_MAX_DISTANCE_EXCEEDED: { // 3 - return F("Echo too late, maximum distance exceeded"); - } - - default: { - return F("unknown error"); - } - } -} - -#endif // USES_P013 +#include "_Plugin_Helper.h" + +#ifdef USES_P013 + +// ####################################################################################################### +// ############################### Plugin 013: HC-SR04, RCW-0001, etc. ################################### +// ####################################################################################################### + +/** Changelog: + * 2023-02-25 tonhuisman: Make Interval optional, and also disable the added feature P013_FEATURE_INTERVALEVENT, as setting Interval + * to 0 is effectively the same. (Small code reduction) + * Changed second value label for Combined mode to State + * 2023-02-19 tonhuisman: Suggested modification with variable trigger width was only partly accepted by NewPing library, and as + * we already have a modified library using the DIRECT_Gpio functions, I'm not merging back that change + * (compile-time setting, default now: 12 usec) but keep the proposed changes for a runtime configurable setting. + * 2023-01-28 tonhuisman: Add Combined mode, as started in https://github.com/letscontrolit/ESPEasy/pull/3157 + * 2023-01-20 tonhuisman: Limit trigger-range to 10-20 usec. (20 already seems to be on the high side) + * Reduce build-size by disabling new features on 1M builds and leaving out some non-essential messages + * Move #define stuff and #includes to src/PluginStructs/P013_data_struct.h + * 2022-12-31 tonhuisman: Code improvements, change start-trigger range to 10-50 usec. + * Optionally not send regular Interval events when using State mode + * 2022-12-29 tonhuisman: Add start-trigger setting, range 10-30 usec. See https://github.com/letscontrolit/ESPEasy/issues/3857 + * 2022-12-29 tonhuisman: Add changelog + */ + +# define PLUGIN_013 +# define PLUGIN_ID_013 13 +# define PLUGIN_NAME_013 "Position - HC-SR04, RCW-0001, etc." +# define PLUGIN_VALUENAME1_013 "Distance" +# define PLUGIN_VALUENAME2_013 "State" + +# include "src/PluginStructs/P013_data_struct.h" + +// map of sensors +std::map > P_013_sensordefs; + +// Forward declarations +float Plugin_013_read(struct EventStruct *event); +const __FlashStringHelper* Plugin_013_getErrorStatusString(struct EventStruct *event); + +boolean Plugin_013(uint8_t function, struct EventStruct *event, String& string) +{ + static uint8_t switchstate[TASKS_MAX]; + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_013; + Device[deviceCount].Type = DEVICE_TYPE_DUAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_013); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_013)); + # if P013_FEATURE_COMBINED_MODE + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_013)); + # endif // if P013_FEATURE_COMBINED_MODE + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_output(F("Trigger")); + event->String2 = formatGpioName_input(F("Echo, 5V")); + break; + } + + # if P013_FEATURE_COMBINED_MODE + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1); + event->idx = P013_OPERATINGMODE == OPMODE_COMBINED ? 2 : 1; + success = true; + break; + } + # endif // if P013_FEATURE_COMBINED_MODE + + case PLUGIN_SET_DEFAULTS: + { + P013_FILTER_SIZE = P013_DEFAULT_FILTER_SIZE; + # if P013_FEATURE_TRIGGERWIDTH + P013_TRIGGER_WIDTH = P013_DEFAULT_TRIGGER_WIDTH; + # endif // if P013_FEATURE_TRIGGERWIDTH + + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + const __FlashStringHelper *strUnit = (P013_MEASURINGUNIT == UNIT_CM) ? F("cm") : F("inch"); + + { + const int optionValuesOpMode[] = { + OPMODE_VALUE, + OPMODE_STATE, + # if P013_FEATURE_COMBINED_MODE + OPMODE_COMBINED, + # endif // if P013_FEATURE_COMBINED_MODE + }; + const __FlashStringHelper *optionsOpMode[] { + F("Value"), + F("State"), + # if P013_FEATURE_COMBINED_MODE + F("Combined"), + # endif // if P013_FEATURE_COMBINED_MODE + }; + addFormSelector(F("Mode"), F("pmode"), + # if P013_FEATURE_COMBINED_MODE + 3 + # else // if P013_FEATURE_COMBINED_MODE + 2 + # endif // if P013_FEATURE_COMBINED_MODE + , optionsOpMode, optionValuesOpMode, P013_OPERATINGMODE); + } + + if ((P013_OPERATINGMODE == OPMODE_STATE) + # if P013_FEATURE_COMBINED_MODE + || (P013_OPERATINGMODE == OPMODE_COMBINED) + # endif // if P013_FEATURE_COMBINED_MODE + ) { + # if P013_FEATURE_INTERVALEVENT + addFormCheckBox(F("State event (also) on Interval"), F("pevent"), P013_SEND_STATE_VALUE == 0); + # endif // if P013_FEATURE_INTERVALEVENT + addFormNumericBox(F("Threshold"), F("thres"), P013_THRESHOLD); + addUnit(strUnit); + } + addFormNumericBox(F("Max Distance"), F("max_d"), P013_MAX_DISTANCE, 0, 500); + addUnit(strUnit); + + { + const int optionValuesUnit[2] = { UNIT_CM, UNIT_INCH }; + const __FlashStringHelper *optionsUnit[] { + F("Metric"), + F("Imperial"), + }; + addFormSelector(F("Unit"), F("pUnit"), 2, optionsUnit, optionValuesUnit, P013_MEASURINGUNIT); + } + + { + const int optionValuesFilter[2] = { FILTER_NONE, FILTER_MEDIAN }; + const __FlashStringHelper *optionsFilter[] { + F("None"), + F("Median"), + }; + addFormSelector(F("Filter"), F("fltr"), 2, optionsFilter, optionValuesFilter, P013_FILTERTYPE); + } + + // enable filtersize option if filter is used, + if (P013_FILTERTYPE != FILTER_NONE) { + addFormNumericBox(F("Number of Pings"), F("size"), P013_FILTER_SIZE, 2, 20); + # if P013_EXTENDED_LOG + addUnit(F("2..20")); + # endif // if P013_EXTENDED_LOG + } + + # if P013_FEATURE_TRIGGERWIDTH + addFormNumericBox(F("Trigger width"), F("wdth"), P013_TRIGGER_WIDTH, 10, 20); + addUnit(F("10..20 µsec")); + # endif // if P013_FEATURE_TRIGGERWIDTH + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + int16_t prevOperatingMode = P013_OPERATINGMODE; + int16_t prevFilterType = P013_FILTERTYPE; + + P013_OPERATINGMODE = getFormItemInt(F("pmode")); + + if ((prevOperatingMode == OPMODE_STATE) + # if P013_FEATURE_COMBINED_MODE + || (prevOperatingMode == OPMODE_COMBINED) + # endif // if P013_FEATURE_COMBINED_MODE + ) { + # if P013_FEATURE_INTERVALEVENT + P013_SEND_STATE_VALUE = isFormItemChecked(F("pevent")) ? 0 : 1; // Inverted state + # endif // if P013_FEATURE_INTERVALEVENT + P013_THRESHOLD = getFormItemInt(F("thres")); + } + # if P013_FEATURE_COMBINED_MODE + + if ((P013_OPERATINGMODE == OPMODE_COMBINED) && (ExtraTaskSettings.TaskDeviceValueNames[1][0] == '\0')) { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_013)); + } + # endif // if P013_FEATURE_COMBINED_MODE + P013_MAX_DISTANCE = getFormItemInt(F("max_d")); + + P013_MEASURINGUNIT = getFormItemInt(F("pUnit")); + P013_FILTERTYPE = getFormItemInt(F("fltr")); + + if (prevFilterType != FILTER_NONE) { + P013_FILTER_SIZE = getFormItemInt(F("size")); + } + # if P013_FEATURE_TRIGGERWIDTH + P013_TRIGGER_WIDTH = getFormItemInt(F("wdth")); + # endif // if P013_FEATURE_TRIGGERWIDTH + + success = true; + break; + } + + case PLUGIN_INIT: + { + if (P013_FILTER_SIZE == 0) { P013_FILTER_SIZE = P013_DEFAULT_FILTER_SIZE; } + + # if P013_FEATURE_TRIGGERWIDTH + + if (P013_TRIGGER_WIDTH == 0) { P013_TRIGGER_WIDTH = P013_DEFAULT_TRIGGER_WIDTH; } + # endif // if P013_FEATURE_TRIGGERWIDTH + + int16_t max_distance_cm = (P013_MEASURINGUNIT == UNIT_CM) ? P013_MAX_DISTANCE : static_cast(P013_MAX_DISTANCE) * 2.54f; + + // 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, + P013_ECHO_PIN, + max_distance_cm, + P013_TRIGGER_WIDTH)); + success = true; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ULTRASONIC : TaskNr: "); + log += event->TaskIndex + 1; + log += F(" TrigPin: "); + log += P013_TRIGGER_PIN; + log += F(" IRQ_Pin: "); + log += P013_ECHO_PIN; + + if (nullptr != P_013_sensordefs[event->TaskIndex]) { // Initialization successful + # if P013_EXTENDED_LOG + log += F(" width [usec]: "); + log += P013_TRIGGER_WIDTH; + log += F(" max dist "); + log += (P013_MEASURINGUNIT == UNIT_CM) ? F("[cm]: ") : F("[inch]: "); + log += P013_MAX_DISTANCE; + + log += F(" max echo: "); + log += P_013_sensordefs[event->TaskIndex]->getMaxEchoTime(); + log += F(" Filter: "); + + if (P013_FILTERTYPE == FILTER_NONE) { + log += F("none"); + } + else if (P013_FILTERTYPE == FILTER_MEDIAN) { + log += F("Median size: "); + log += P013_FILTER_SIZE; + } else { + log += F("invalid!"); + } + + log += F(" nr_tasks: "); + log += P_013_sensordefs.size(); + # endif // if P013_EXTENDED_LOG + } else { + log += F(" CONSTRUCTOR FAILED!"); + success = false; // Initialization failed + } + addLogMove(LOG_LEVEL_INFO, log); + } + + break; + } + + case PLUGIN_EXIT: + { + P_013_sensordefs.erase(event->TaskIndex); + break; + } + + case PLUGIN_READ: // If we select value mode, read and send the value based on global timer + { + if ((P013_OPERATINGMODE == OPMODE_VALUE) + # if P013_FEATURE_COMBINED_MODE + || (P013_OPERATINGMODE == OPMODE_COMBINED) + # endif // if P013_FEATURE_COMBINED_MODE + ) { + const float value = Plugin_013_read(event); + UserVar.setFloat(event->TaskIndex, 0, value); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ULTRASONIC : TaskNr: "); + log += event->TaskIndex + 1; + # if P013_EXTENDED_LOG + log += F(" Distance: "); + log += formatUserVarNoCheck(event, 0); + log += ' '; + log += (P013_MEASURINGUNIT == UNIT_CM) ? F("cm") : F("inch"); + # endif // if P013_EXTENDED_LOG + + if (essentiallyEqual(value, NO_ECHO)) { + log += F(" Error: "); + log += Plugin_013_getErrorStatusString(event); + } + + addLogMove(LOG_LEVEL_INFO, log); + } + success = true; // Only send out when actually using Value mode + } else { + # if P013_FEATURE_INTERVALEVENT + + if (P013_SEND_STATE_VALUE == 0) { // Also send on Interval when using State mode + success = true; + } + # else // if P013_FEATURE_INTERVALEVENT + success = true; + # endif // if P013_FEATURE_INTERVALEVENT + } + break; + } + + case PLUGIN_TEN_PER_SECOND: // If we select state mode, do more frequent checks and send only state changes + { + if ((P013_OPERATINGMODE == OPMODE_STATE) + # if P013_FEATURE_COMBINED_MODE + || (P013_OPERATINGMODE == OPMODE_COMBINED) + # endif // if P013_FEATURE_COMBINED_MODE + ) { + uint8_t state = 0; + const float value = Plugin_013_read(event); + + if (!essentiallyEqual(value, NO_ECHO) && definitelyLessThan(value, P013_THRESHOLD)) { + state = 1; + } + + if (state != switchstate[event->TaskIndex]) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ULTRASONIC : TaskNr: "); + log += event->TaskIndex + 1; + + if (value != NO_ECHO) { + log += F(" state: "); + log += state; + } else { + log += F(" Error: "); + log += Plugin_013_getErrorStatusString(event); + } + addLogMove(LOG_LEVEL_INFO, log); + } + switchstate[event->TaskIndex] = state; + UserVar.setFloat(event->TaskIndex, 0, state); + event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + sendData(event); + } + } + success = true; + + break; + } + } + return success; +} + +/*********************************************************************/ +float Plugin_013_read(struct EventStruct *event) + +/*********************************************************************/ +{ + if (P_013_sensordefs.count(event->TaskIndex) == 0u) { + return 0.0f; + } + + int16_t max_distance_cm = (P013_MEASURINGUNIT == UNIT_CM) ? P013_MAX_DISTANCE : static_cast(P013_MAX_DISTANCE) * 2.54f; + + unsigned int echoTime = 0; + + switch (P013_FILTERTYPE) { + case FILTER_NONE: + echoTime = (P_013_sensordefs[event->TaskIndex])->ping(); + break; + case FILTER_MEDIAN: + echoTime = (P_013_sensordefs[event->TaskIndex])->ping_median(P013_FILTER_SIZE, max_distance_cm); + break; + # if P013_EXTENDED_LOG + default: + addLog(LOG_LEVEL_ERROR, F("invalid Filter Type setting!")); // Should not be possible... + # endif // if P013_EXTENDED_LOG + } + + if (P013_MEASURINGUNIT == UNIT_CM) { + return NewPing::convert_cm_F(echoTime); + } + else { + return NewPing::convert_in_F(echoTime); + } +} + +/*********************************************************************/ +const __FlashStringHelper* Plugin_013_getErrorStatusString(struct EventStruct *event) + +/*********************************************************************/ +{ + if (P_013_sensordefs.count(event->TaskIndex) == 0) { + return F("invalid taskindex"); + } + + switch ((P_013_sensordefs[event->TaskIndex])->getErrorState()) { + case NewPing::STATUS_SENSOR_READY: { // 0 + # if P013_EXTENDED_LOG + return F("Sensor ready"); + # endif // if P013_EXTENDED_LOG + } + + case NewPing::STATUS_MEASUREMENT_VALID: { // 1 + # if P013_EXTENDED_LOG + return F("no error, measurement valid"); + # endif // if P013_EXTENDED_LOG + } + + case NewPing::STATUS_ECHO_TRIGGERED: { // 2 + # if P013_EXTENDED_LOG + return F("Echo triggered, waiting for Echo end"); + # else // if P013_EXTENDED_LOG + return F("Ok"); + # endif // if P013_EXTENDED_LOG + } + + case NewPing::STATUS_ECHO_STATE_ERROR: { // 6 + return F("Error, Echopin not low on trigger"); + } + + case NewPing::STATUS_ECHO_START_TIMEOUT_50ms: { // 4 + return F("Error, no echo start whithin 50 ms"); + } + + case NewPing::STATUS_ECHO_START_TIMEOUT_DISTANCE: { // 5 + return F("Error, no echo start whithin time for max. distance"); + } + + case NewPing::STATUS_MAX_DISTANCE_EXCEEDED: { // 3 + return F("Echo too late, maximum distance exceeded"); + } + + default: { + return F("unknown error"); + } + } +} + +#endif // USES_P013 diff --git a/src/_P014_SI70xx.ino b/src/_P014_SI70xx.ino index e67ba9fa5..51c9805d0 100644 --- a/src/_P014_SI70xx.ino +++ b/src/_P014_SI70xx.ino @@ -209,14 +209,12 @@ boolean Plugin_014(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("P014: Temperature: "); - log += UserVar[event->BaseVarIndex + 0]; - log += F(" Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; + String log = strformat(F("P014: Temperature: %.2f Humidity: %.2f"), + UserVar[event->BaseVarIndex + 0], + UserVar[event->BaseVarIndex + 1]); if (P014_data->chip_id == CHIP_ID_SI7013) { - log += F(" ADC: "); - log += UserVar[event->BaseVarIndex + 2]; + log += strformat(F(" ADC: %.2f"), UserVar[event->BaseVarIndex + 2]); } addLog(LOG_LEVEL_INFO, log); } diff --git a/src/_P015_TSL2561.ino b/src/_P015_TSL2561.ino index 640847075..49b06d36b 100644 --- a/src/_P015_TSL2561.ino +++ b/src/_P015_TSL2561.ino @@ -69,10 +69,10 @@ boolean Plugin_015(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { TSL2561_ADDR, TSL2561_ADDR_1, TSL2561_ADDR_0 }; + const uint8_t i2cAddressValues[] = { TSL2561_ADDR_0, TSL2561_ADDR, TSL2561_ADDR_1 }; if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 3, i2cAddressValues, P015_I2C_ADDR); + addFormSelectorI2C(F("i2c_addr"), 3, i2cAddressValues, P015_I2C_ADDR, TSL2561_ADDR); } else { success = intArrayContains(3, i2cAddressValues, event->Par1); } @@ -88,6 +88,14 @@ boolean Plugin_015(uint8_t function, struct EventStruct *event, String& string) } # endif // if FEATURE_I2C_GET_ADDRESS + case PLUGIN_SET_DEFAULTS: + { + P015_I2C_ADDR = TSL2561_ADDR; // Default address + + success = true; + break; + } + case PLUGIN_WEBFORM_LOAD: { { diff --git a/src/_P016_IR.ino b/src/_P016_IR.ino index 97322c33e..8b897bd70 100644 --- a/src/_P016_IR.ino +++ b/src/_P016_IR.ino @@ -23,6 +23,17 @@ // IRSENDAC,'{"protocol":"COOLIX","power":"on","mode":"dry","fanspeed":"auto","temp":22,"swingv":"max","swingh":"off"}' /** Changelog: + * 2024-01-26 uwekaditz: Decode type UNKNOWN was not added to the web settings + * Decode types UNKNOWN and UNUSED were mixed up + * Workaround for decode type UNKNOWN is not necessary + * Initalisation of the variables with decode type UNUSED + * 2024-01-23 uwekaditz: Use the new property addToQueue in ExecuteCommand_all() due to the lack of resources + * Using strformat() for the debug messages + * Heap and memory can be reported (P016_CHECK_HEAP) + * 2023-12-11 uwekaditz: Add protocol RAW to UI if 'Accept DecodeType UNKNOWN' is set + * Note: for decoding a RAW message DECODE_HASH must be set + * uint64ToString() in debug message in PLUGIN_TEN_PER_SECOND can not handle decode_type_t::UNKNOWN (-1), + * changed to ll2String() * 2022-08-08 tonhuisman: Optionally (compile-time) disable command handling by setting #define P016_FEATURE_COMMAND_HDNLING 0 * Make reserved buffer size for receiver configurable 100..1024 uint16_t = 200-2048 bytes * Change UI to show buffer size in bytes instead of 'units' to avoid confusion. @@ -42,6 +53,9 @@ # ifdef P016_P035_Extended_AC # include # endif // ifdef P016_P035_Extended_AC +# ifdef P016_CHECK_HEAP +# include "src/Helpers/Memory.h" +# endif // ifdef P016_CHECK_HEAP # define PLUGIN_016 # define PLUGIN_ID_016 16 @@ -56,6 +70,14 @@ # endif // ifndef P016_SEND_IR_TO_CONTROLLER // History +// @uwekaditz: 2024-01-23 +// CHG: Use the new property addToQueue in ExecuteCommand_all() due to the lack of resources +// NEW: Heap and memory can be reported (P016_CHECK_HEAP) +// MSG: Using strformat() for the debug messages +// @uwekaditz: 2023-12-11 +// NEW: Add protocol RAW to UI if 'Accept DecodeType UNKNOWN' is set +// MSG: for decoding a RAW message DECODE_HASH must be set +// FIX: uint64ToString() in debug message in PLUGIN_TEN_PER_SECOND can not handle decode_type_t::UNKNOWN (-1), changed to ll2String() // @tonhuisman: 2022-08-08 // FIX: Resolve high memory use bu having the default buffer size reduced from 1024 to 100, and make that a setting // @tonhuisman: 2021-08-05 @@ -145,6 +167,12 @@ const uint16_t kMinUnknownSize = 12; IRrecv *irReceiver = nullptr; bool bEnableIRcodeAdding = false; + +# ifdef P016_CHECK_HEAP +uint32_t fMem = 0; +uint32_t fFreeStack = 0; +# endif // ifdef P016_CHECK_HEAP + # ifdef P016_P035_USE_RAW_RAW2 /* *INDENT-OFF* */ @@ -155,17 +183,9 @@ boolean displayRawToReadableB32Hex(String& outputStr, decode_results results); # ifdef PLUGIN_016_DEBUG void P016_infoLogMemory(const __FlashStringHelper *text) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(40 + strlen_P((PGM_P)text))) { - log += F("P016: Free memory "); - log += text; - log += F(": "); - log += FreeMem(); - log += F(" stack: "); - log += getCurrentFreeStack(); - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat( + F("P016: %s FreeMem: %d FreeStack:%d"), + text, FreeMem(), getCurrentFreeStack())); } } @@ -181,11 +201,11 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) { Device[++deviceCount].Number = PLUGIN_ID_016; Device[deviceCount].Type = DEVICE_TYPE_SINGLE; -#if P016_SEND_IR_TO_CONTROLLER - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; -#else - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_ULONG; -#endif + # if P016_SEND_IR_TO_CONTROLLER + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; + # else // if P016_SEND_IR_TO_CONTROLLER + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_ULONG; + # endif // if P016_SEND_IR_TO_CONTROLLER Device[deviceCount].Ports = 0; Device[deviceCount].PullUpOption = true; Device[deviceCount].InverseLogicOption = true; @@ -357,8 +377,12 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) int protocolCount = 0; - for (int i = 0; i < size; i++) { - const String protocol = typeToString(static_cast(i), false); + for (int i = static_cast(decode_type_t::UNKNOWN); i < size; i++) { + const String protocol = typeToString(static_cast(i), false); + + if ((!bAcceptUnknownType) && (static_cast(i) == UNKNOWN)) { + continue; + } if (protocol.length() > 1) { decodeTypeOptions.push_back(i); @@ -371,13 +395,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; // Log this always - - if (log.reserve(30)) { - log += F("IR: available decodetypes: "); - log += protocolCount; - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat(F("IR: available decodetypes: %d"), protocolCount)); } const String P016_HEX_INPUT_PATTERN = F("(0x)?[0-9a-fA-F]{0,16}"); // 16 nibbles = 64 bit, 0x prefix is allowed but not added by @@ -391,7 +409,7 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) html_table_header(F("Alt. Decode type")); html_table_header(F("Repeat")); html_table_header(F("Alt. Code [Hex]")); - html_TR(); //added to make "tworow" work + html_TR(); // added to make "tworow" work int rowCnt = 0; @@ -597,8 +615,14 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) { decode_results results; + # ifdef P016_CHECK_HEAP + fMem = FreeMem(); + fFreeStack = getCurrentFreeStack(); + # endif // ifdef P016_CHECK_HEAP + if (irReceiver->decode(&results)) { + success = true; yield(); // Feed the WDT after a time expensive decoding procedure if (results.overflow) @@ -612,34 +636,29 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) if ((results.decode_type != decode_type_t::UNKNOWN) || (bitRead(PCONFIG_LONG(0), P016_BitAcceptUnknownType))) { { - String output; - output.reserve(100); // Length of expected string, needed for strings > 11 chars // String output = String(F("IRSEND,")) + typeToString(results.decode_type, results.repeat) + ',' + // resultToHexidecimal(&results) // + ',' + uint64ToString(results.bits); // addLog(LOG_LEVEL_INFO, output); //Show the appropriate command to the user, so he can replay the message via P035 // Old // style // command - output += F("{\"protocol\":\""); - output += typeToString(results.decode_type, results.repeat); - output += F("\",\"data\":\""); - output += resultToHexidecimal(&results); - output += F("\",\"bits\":"); - output += uint64ToString(results.bits); - output += '}'; + event->String2 = strformat( + F("{\"protocol\":\"%s\",\"data\":\"%s\",\"bits\":%s}"), + typeToString(results.decode_type, results.repeat).c_str(), + resultToHexidecimal(&results).c_str(), + uint64ToString(results.bits).c_str()); if (loglevelActiveFor(LOG_LEVEL_INFO)) { String Log; - if (Log.reserve(output.length() + 22)) { + if (Log.reserve(event->String2.length() + 22)) { Log += F("IRSEND,\'"); - Log += output; - Log += F("\' type: 0x"); - Log += uint64ToString(results.decode_type); + Log += event->String2; + Log += F("\' type as int: "); + Log += ll2String(results.decode_type); addLogMove(LOG_LEVEL_INFO, Log); // JSON representation of the command } } - event->String2 = std::move(output); } # if P016_FEATURE_COMMAND_HANDLING @@ -659,17 +678,22 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) if (strCode.length() <= P16_Cchars) { iCode += hexToULL(strCode); - if (iCodeDecodeType == decode_type_t::UNKNOWN) { - // set iCodeDecodeType UNKNOWN to RAW, otherwise AddCode() or ExecuteCode() will fail - iCodeDecodeType = decode_type_t::RAW; - } - if (bitRead(PCONFIG_LONG(0), P016_BitAddNewCode) && bEnableIRcodeAdding) { P016_data->AddCode(iCode, iCodeDecodeType, iCodeFlags); // add code if not saved so far } + # ifdef P016_CHECK_HEAP + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("Before decode: FreeMem: %d FreeStack:%d / After: FreeMem: %d FreeStack:%d"), + fMem, fFreeStack, + FreeMem(), getCurrentFreeStack())); + } + # endif // ifdef P016_CHECK_HEAP + if (bitRead(PCONFIG_LONG(0), P016_BitExecuteCmd)) { - P016_data->ExecuteCode(iCode, iCodeDecodeType, iCodeFlags); // execute command for code if available + success = P016_data->ExecuteCode(iCode, iCodeDecodeType, iCodeFlags); // execute command for code if available } } } @@ -725,18 +749,12 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) state.sleep = -1; state.clock = -1; - String description = IRAcUtils::resultAcToString(&results); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String description = IRAcUtils::resultAcToString(&results); - if (!description.isEmpty()) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (!description.isEmpty()) { // If we got a human-readable description of the message, display it. - String log; - - if (log.reserve(10 + description.length())) { - log += F("AC State: "); - log += description; - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat(F("AC State: %s"), description.c_str())); } } @@ -824,16 +842,15 @@ boolean Plugin_016(uint8_t function, struct EventStruct *event, String& string) } # endif // P016_P035_Extended_AC -#if !P016_SEND_IR_TO_CONTROLLER + # if !P016_SEND_IR_TO_CONTROLLER { unsigned long IRcode = results.value; UserVar.setSensorTypeLong(event->TaskIndex, IRcode); } -#endif + # endif // if !P016_SEND_IR_TO_CONTROLLER sendData(event); + break; } - success = true; - break; } } return success; @@ -1041,7 +1058,7 @@ unsigned int storeB32Hex(char out[], unsigned int iOut, unsigned int val) void enableIR_RX(boolean enable) { -#ifdef PLUGIN_016 + #ifdef PLUGIN_016 if (irReceiver == 0) { return; } @@ -1050,5 +1067,5 @@ void enableIR_RX(boolean enable) } else { irReceiver->disableIRIn(); // Stop the receiver } -#endif // PLUGIN_016 + #endif // PLUGIN_016 } diff --git a/src/_P017_PN532.ino b/src/_P017_PN532.ino index 22da48630..5db94bbd4 100644 --- a/src/_P017_PN532.ino +++ b/src/_P017_PN532.ino @@ -56,8 +56,8 @@ // DEBUG code using logic analyzer for timings -//# define P017_DEBUG_LOGIC_ANALYZER_PIN 25 -//# define P017_DEBUG_LOGIC_ANALYZER_PIN_INIT 33 +// # define P017_DEBUG_LOGIC_ANALYZER_PIN 25 +// # define P017_DEBUG_LOGIC_ANALYZER_PIN_INIT 33 # include @@ -76,7 +76,7 @@ int16_t Plugin_017_readResponse(uint8_t command, uint8_t buf[], uint8_t len); -boolean Plugin_017(uint8_t function, struct EventStruct *event, String& string) +boolean Plugin_017(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; @@ -141,15 +141,17 @@ boolean Plugin_017(uint8_t function, struct EventStruct *event, String& string) const bool autoTagRemoval = P017_AUTO_TAG_REMOVAL == 0; // Inverted state! addFormCheckBox(F("Automatic Tag removal"), F("tagremove"), autoTagRemoval); - if (P017_REMOVAL_TIMEOUT == 0) { + if (P017_REMOVAL_TIMEOUT == 0) { P017_REMOVAL_TIMEOUT = 500; // Defaulty 500 mSec (was hardcoded value) } + // 0.25 to 60 seconds - addFormNumericBox(F("Automatic Tag removal after"), F("removetime"), P017_REMOVAL_TIMEOUT, 250, 60000); + addFormNumericBox(F("Automatic Tag removal after"), F("removetime"), P017_REMOVAL_TIMEOUT, 250, 60000); addUnit(F("mSec.")); - - addFormNumericBox(F("Value to set on Tag removal"), F("removevalue"), P017_NO_TAG_DETECTED_VALUE, 0, 2147483647); + + addFormNumericBox(F("Value to set on Tag removal"), F("removevalue"), P017_NO_TAG_DETECTED_VALUE, 0, 2147483647); + // Max allowed is int // = // 0x7FFFFFFF ... @@ -191,7 +193,7 @@ boolean Plugin_017(uint8_t function, struct EventStruct *event, String& string) # endif // ifdef P017_DEBUG_LOGIC_ANALYZER_PIN_INIT - for (uint8_t x = 0; x < 3; x++) + for (uint8_t x = 0; x < 3; ++x) { if (Plugin_017_Init(CONFIG_PIN3)) { success = true; @@ -284,7 +286,7 @@ bool P017_handle_timer_in(struct EventStruct *event) uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; uint8_t uidLength; - uint8_t error = Plugin_017_readPassiveTargetID(uid, &uidLength); + const uint8_t error = Plugin_017_readPassiveTargetID(uid, &uidLength); # ifdef P017_DEBUG_LOGIC_ANALYZER_PIN @@ -298,9 +300,8 @@ bool P017_handle_timer_in(struct EventStruct *event) errorCount++; if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("PN532: Read error: "); - log += errorCount; - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, + concat(F("PN532: Read error: "), errorCount)); } } else { @@ -323,12 +324,12 @@ bool P017_handle_timer_in(struct EventStruct *event) unsigned long key = uid[0]; - for (uint8_t i = 1; i < 4; i++) { + for (uint8_t i = 1; i < 4; ++i) { key <<= 8; key += uid[i]; } - unsigned long old_key = UserVar.getSensorTypeLong(event->TaskIndex); - bool new_key = false; + const unsigned long old_key = UserVar.getSensorTypeLong(event->TaskIndex); + bool new_key = false; if (old_key != key) { UserVar.setSensorTypeLong(event->TaskIndex, key); @@ -385,9 +386,8 @@ boolean Plugin_017_Init(int8_t resetPin) if (validGpio(resetPin)) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PN532: Reset on pin: "); - log += resetPin; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + concat(F("PN532: Reset on pin: "), resetPin)); } pinMode(resetPin, OUTPUT); digitalWrite(resetPin, LOW); @@ -404,13 +404,11 @@ boolean Plugin_017_Init(int8_t resetPin) if (versiondata) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PN532: Found chip PN5"); - log += formatToHex_no_prefix((versiondata >> 24) & 0xFF, 2); - log += F(" FW: "); - log += formatToHex_no_prefix((versiondata >> 16) & 0xFF, 2); - log += '.'; - log += formatToHex_no_prefix((versiondata >> 8) & 0xFF, 2); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("PN532: Found chip PN5%s FW: %s.%s"), + formatToHex_no_prefix((versiondata >> 24) & 0xFF, 2).c_str(), + formatToHex_no_prefix((versiondata >> 16) & 0xFF, 2).c_str(), + formatToHex_no_prefix((versiondata >> 8) & 0xFF, 2).c_str())); } } else { @@ -468,8 +466,8 @@ uint32_t getFirmwareVersion(void) // read data packet int16_t status = Plugin_017_readResponse( - PN532_COMMAND_GETFIRMWAREVERSION, - Plugin_017_pn532_packetbuffer, + PN532_COMMAND_GETFIRMWAREVERSION, + Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)); if (0 > status) { @@ -501,7 +499,7 @@ void Plugin_017_powerDown(void) // read and ignore response Plugin_017_readResponse( PN532_COMMAND_POWERDOWN, - Plugin_017_pn532_packetbuffer, + Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)); } @@ -529,7 +527,7 @@ uint8_t Plugin_017_readPassiveTargetID(uint8_t *uid, uint8_t *uidLength) // read data packet const int16_t read_code = Plugin_017_readResponse( PN532_COMMAND_INLISTPASSIVETARGET, - Plugin_017_pn532_packetbuffer, + Plugin_017_pn532_packetbuffer, sizeof(Plugin_017_pn532_packetbuffer)); if (read_code < 0) { @@ -554,7 +552,7 @@ uint8_t Plugin_017_readPassiveTargetID(uint8_t *uid, uint8_t *uidLength) /* Card appears to be Mifare Classic */ *uidLength = Plugin_017_pn532_packetbuffer[5]; - for (uint8_t i = 0; i < Plugin_017_pn532_packetbuffer[5]; i++) { + for (uint8_t i = 0; i < Plugin_017_pn532_packetbuffer[5]; ++i) { uid[i] = Plugin_017_pn532_packetbuffer[6 + i]; } @@ -584,7 +582,7 @@ int8_t Plugin_017_writeCommand(const uint8_t *header, uint8_t hlen) Wire.write(PN532_HOSTTOPN532); uint8_t sum = PN532_HOSTTOPN532; // sum of TFI + DATA - for (uint8_t i = 0; i < hlen; i++) { + for (uint8_t i = 0; i < hlen; ++i) { if (Wire.write(header[i])) { sum += header[i]; } else { @@ -634,7 +632,7 @@ int16_t Plugin_017_readResponse(uint8_t command, uint8_t buf[], uint8_t len) return PN532_INVALID_FRAME; } - uint8_t cmd = command + 1; // response command + const uint8_t cmd = command + 1; // response command if ((PN532_PN532TOHOST != Wire.read()) || ((cmd) != Wire.read())) { return PN532_INVALID_FRAME; @@ -648,7 +646,7 @@ int16_t Plugin_017_readResponse(uint8_t command, uint8_t buf[], uint8_t len) uint8_t sum = PN532_PN532TOHOST + cmd; - for (uint8_t i = 0; i < length; i++) { + for (uint8_t i = 0; i < length; ++i) { buf[i] = Wire.read(); sum += buf[i]; } @@ -691,7 +689,7 @@ int8_t Plugin_017_readAckFrame() } while (1); - for (uint8_t i = 0; i < sizeof(PN532_ACK); i++) { + for (uint8_t i = 0; i < sizeof(PN532_ACK); ++i) { ackBuf[i] = Wire.read(); } diff --git a/src/_P019_PCF8574.ino b/src/_P019_PCF8574.ino index 84dc24f95..f689da3e6 100644 --- a/src/_P019_PCF8574.ino +++ b/src/_P019_PCF8574.ino @@ -98,7 +98,7 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) if (unit > 7) { address += 0x10; } - for (uint8_t x = 0; x < 8; x++) { + for (uint8_t x = 0; x < 8; ++x) { portValues[x] = x + 1; portNames[x] = 'P'; portNames[x] += x; @@ -235,68 +235,6 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) break; } - /* - case PLUGIN_UNCONDITIONAL_POLL: - { - // port monitoring, generates an event by rule command 'monitor,pcf,port#' - for (std::map::iterator it=globalMapPortStatus.begin(); it!=globalMapPortStatus.end(); ++it) { - if (getPluginFromKey(it->first)==PLUGIN_ID_019 && (it->second.monitor || it->second.command || it->second.init)) { - const uint16_t port = getPortFromKey(it->first); - int8_t state = Plugin_019_Read(port); - if (it->second.state != state || it->second.forceMonitor) { - if (it->second.mode == PIN_MODE_OFFLINE) it->second.mode=PIN_MODE_UNDEFINED; //changed from offline to online - if (state == -1) it->second.mode=PIN_MODE_OFFLINE; //changed from online to offline - if (!it->second.task) it->second.state = state; //do not update state if task flag=1 otherwise it will not be picked up - by 10xSEC function - if (it->second.monitor) { - it->second.forceMonitor=0; //reset flag - String eventString = F("PCF#"); - eventString += port; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - } - } - break; - } - } - break; - } - */ - /* - case PLUGIN_MONITOR: - { - // port monitoring, generates an event by rule command 'monitor,gpio,port#' - const uint32_t key = createKey(PLUGIN_PCF, event->Par1); - const portStatusStruct currentStatus = globalMapPortStatus[key]; - - // if (currentStatus.monitor || currentStatus.command || currentStatus.init) { - const int8_t state = Plugin_019_Read(event->Par1); - - if ((currentStatus.state != state) || currentStatus.forceMonitor) { - if (!currentStatus.task) { globalMapPortStatus[key].state = state; // do not update state if task flag=1 otherwise it will not - be - // picked up by 10xSEC function - } - - - if (currentStatus.monitor) { - globalMapPortStatus[key].forceMonitor = 0; // reset flag - String eventString = F("PCF#"); - eventString += event->Par1; - eventString += '='; - eventString += state; - rulesProcessing(eventString); - } - } - - // } - - break; - } - */ case PLUGIN_TEN_PER_SECOND: { # if FEATURE_I2C_DEVICE_CHECK @@ -407,10 +345,7 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, output_value); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port="); - log += CONFIG_PORT; - log += F(" State="); - log += state; + String log = strformat(F("PCF : Port=%d State=%d"), CONFIG_PORT, state); log += output_value == 3 ? F(" Doubleclick=") : F(" Output value="); log += output_value; addLogMove(LOG_LEVEL_INFO, log); @@ -474,13 +409,8 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, output_value); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : LongPress: Port= "); - log += CONFIG_PORT; - log += F(" State="); - log += state ? '1' : '0'; - log += F(" Output value="); - log += output_value; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("PCF : LongPress: Port= %d State=%d Output value=%d"), CONFIG_PORT, state, output_value)); } // send task event @@ -504,11 +434,8 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, 4); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : SafeButton: false positive detected. GPIO= "); - log += CONFIG_PORT; - log += F(" State="); - log += tempUserVar; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("PCF : SafeButton: false positive detected. GPIO= %d State=%d"), CONFIG_PORT, tempUserVar)); } // send task event: DO NOT SEND TASK EVENT @@ -529,10 +456,8 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) currentStatus.mode = PIN_MODE_OFFLINE; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port="); - log += CONFIG_PORT; - log += F(" is offline (EVENT= -1)"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("PCF : Port=%d is offline (EVENT= -1)"), CONFIG_PORT)); } // send task event @@ -559,44 +484,13 @@ 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 if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("PCF : Port= "); - log += CONFIG_PORT; - log += F(" State="); - log += UserVar[event->BaseVarIndex]; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("PCF : Port= %d State=%d"), CONFIG_PORT, UserVar[event->BaseVarIndex])); } success = true; break; } - /* - case PLUGIN_REQUEST: - { - // parseString(string, 1) = device - // parseString(string, 2) = command - // parseString(string, 3) = gpio number - - // returns pin value using syntax: [plugin#pcfgpio#pinstate#xx] - if ((string.length() >= 16) && string.substring(0, 16).equalsIgnoreCase(F("pcfgpio,pinstate"))) - { - int32_t par1; - - if (validIntFromString(parseString(string, 3), par1)) { - string = GPIO_PCF_Read(par1); - } - success = true; - } - break; - } - */ - case PLUGIN_WRITE: - { - // String log; - // String command = parseString(string, 1); - - break; - } - case PLUGIN_TASKTIMER_IN: case PLUGIN_DEVICETIMER_IN: { @@ -637,16 +531,17 @@ boolean Plugin_019(uint8_t function, struct EventStruct *event, String& string) // @giig1967g-20181023: changed to int8_t int8_t Plugin_019_Read(uint8_t Par1) { - int8_t state = -1; - uint8_t unit = (Par1 - 1) / 8; - uint8_t port = Par1 - (unit * 8); - uint8_t address = 0x20 + unit; + int8_t state = -1; + const uint8_t unit = (Par1 - 1) / 8; + const uint8_t port = Par1 - (unit * 8); + uint8_t address = 0x20 + unit; if (unit > 7) { address += 0x10; } // get the current pin status - bool is_ok = false; + bool is_ok = false; const uint8_t rawState = I2C_read8(address, &is_ok); + if (is_ok) { state = ((rawState & _BV(port - 1)) >> (port - 1)); @@ -656,8 +551,9 @@ int8_t Plugin_019_Read(uint8_t Par1) uint8_t Plugin_019_ReadAllPins(uint8_t address) { - bool is_ok = false; + bool is_ok = false; const uint8_t rawState = I2C_read8(address, &is_ok); + return is_ok ? rawState : 0u; } @@ -666,9 +562,9 @@ uint8_t Plugin_019_ReadAllPins(uint8_t address) // ******************************************************************************** boolean Plugin_019_Write(uint8_t Par1, uint8_t Par2) { - uint8_t unit = (Par1 - 1) / 8; - uint8_t port = Par1 - (unit * 8); - uint8_t address = 0x20 + unit; + uint8_t unit = (Par1 - 1) / 8; + const uint8_t port = Par1 - (unit * 8); + uint8_t address = 0x20 + unit; if (unit > 7) { address += 0x10; } @@ -680,7 +576,7 @@ boolean Plugin_019_Write(uint8_t Par1, uint8_t Par2) uint32_t key; - for (i = 0; i < 8; i++) { + for (i = 0; i < 8; ++i) { key = createKey(PLUGIN_PCF, unit + i); auto it = globalMapPortStatus.find(key); diff --git a/src/_P020_Ser2Net.ino b/src/_P020_Ser2Net.ino index 7c91ab184..02c247360 100644 --- a/src/_P020_Ser2Net.ino +++ b/src/_P020_Ser2Net.ino @@ -1,345 +1,508 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P020 - -// ####################################################################################################### -// #################################### Plugin 020: Ser2Net ############################################## -// ####################################################################################################### - -/************ - * Changelog: - * 2022-05-28 tonhuisman: Add option to generate events for all lines of a multi-line message - * 2022-05-26 tonhuisman: Add option to allow processing without webclient connected. - * No older changelog available. - ***************************************************************/ - -# include "src/Helpers/_Plugin_Helper_serial.h" -# include "src/PluginStructs/P020_data_struct.h" -# include - - -# define PLUGIN_020 -# define PLUGIN_ID_020 20 -# define PLUGIN_NAME_020 "Communication - Serial Server" -# define PLUGIN_VALUENAME1_020 "Ser2Net" - - -# define P020_SET_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -# define P020_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] - -# define P020_GET_SERVER_PORT Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0) -# define P020_GET_BAUDRATE Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) - -// #define P020_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] -# define P020_RX_WAIT PCONFIG(4) -# define P020_SERIAL_CONFIG PCONFIG(1) -# define P020_SERIAL_PROCESSING PCONFIG(5) -# define P020_RESET_TARGET_PIN PCONFIG(6) -# define P020_RX_BUFFER PCONFIG(7) - -# define P020_FLAGS PCONFIG_LONG(0) -# define P020_FLAG_IGNORE_CLIENT 0 -# define P020_FLAG_MULTI_LINE 1 -# define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) -# define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) - - -# define P020_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. -# define P020_NR_OUTPUT_OPTIONS 1 -# define P020_QUERY1_CONFIG_POS 3 - -# define P020_DEFAULT_SERVER_PORT 1234 -# define P020_DEFAULT_BAUDRATE 115200 -# define P020_DEFAULT_RESET_TARGET_PIN -1 -# define P020_DEFAULT_RX_BUFFER 256 - - -boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_020; - Device[deviceCount].Type = DEVICE_TYPE_SERIAL; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 0; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = false; - Device[deviceCount].GlobalSyncOption = false; - break; - } - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_020); - break; - } - - - case PLUGIN_SET_DEFAULTS: - { - P020_SET_BAUDRATE = P020_DEFAULT_BAUDRATE; - P020_SET_SERVER_PORT = P020_DEFAULT_SERVER_PORT; - P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; - P020_RX_BUFFER = P020_DEFAULT_RX_BUFFER; - success = true; - break; - } - - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - serialHelper_getGpioNames(event); - break; - } - - case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: - { - string = F("RST: "); - string += formatGpioLabel(P020_RESET_TARGET_PIN, false); - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("TCP Port"), F("p020_port"), P020_GET_SERVER_PORT, 0); - addFormNumericBox(F("Baud Rate"), F("p020_baud"), P020_GET_BAUDRATE, 0); - uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P020_SERIAL_CONFIG); - serialHelper_serialconfig_webformLoad(event, serialConfChoice); - { - const __FlashStringHelper *options[3] = { - F("None"), - F("Generic"), - F("RFLink") - }; - addFormSelector(F("Event processing"), F("p020_events"), 3, options, nullptr, P020_SERIAL_PROCESSING); - addFormCheckBox(F("Process events without client"), F("p020_ignoreclient"), P020_IGNORE_CLIENT_CONNECTED); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("When enabled, will process serial data without a network client connected.")); - # endif // ifndef LIMIT_BUILD_SIZE - addFormCheckBox(F("Multiple lines processing"), F("p020_multiline"), P020_HANDLE_MULTI_LINE); - } - addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p020_rxwait"), P020_RX_WAIT, 0, 20); - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after init"), F("p020_resetpin"), P020_RESET_TARGET_PIN); - - addFormNumericBox(F("RX buffer size (bytes)"), F("p020_rx_buffer"), P020_RX_BUFFER, 256, 1024); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Standard RX buffer 256B; higher values could be unstable; energy meters could require 1024B")); - # endif // ifndef LIMIT_BUILD_SIZE - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P020_SET_SERVER_PORT = getFormItemInt(F("p020_port")); - P020_SET_BAUDRATE = getFormItemInt(F("p020_baud")); - P020_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); - P020_SERIAL_PROCESSING = getFormItemInt(F("p020_events")); - P020_RX_WAIT = getFormItemInt(F("p020_rxwait")); - P020_RESET_TARGET_PIN = getFormItemInt(F("p020_resetpin")); - P020_RX_BUFFER = getFormItemInt(F("p020_rx_buffer")); - - bitWrite(P020_FLAGS, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("p020_ignoreclient"))); - bitWrite(P020_FLAGS, P020_FLAG_MULTI_LINE, isFormItemChecked(F("p020_multiline"))); - - success = true; - break; - } - - case PLUGIN_INIT: - { - if ((P020_GET_SERVER_PORT == 0) || (P020_GET_BAUDRATE == 0)) { - clearPluginTaskData(event->TaskIndex); - break; - } - - // try to reuse to keep webserver running - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != task) && task->isInit()) { - // It was already created and initialzed - // So don't recreate to keep the webserver running. - } else { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P020_Task(event->TaskIndex)); - task = static_cast(getPluginTaskData(event->TaskIndex)); - } - - if (nullptr == task) { - break; - } - task->handleMultiLine = P020_HANDLE_MULTI_LINE; - - // int rxPin =-1; - // int txPin =-1; - int rxPin = CONFIG_PIN1; - int txPin = CONFIG_PIN2; - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - - // const ESPEasySerialPort port= ESPEasySerialPort::serial0; - if ((rxPin < 0) && (txPin < 0)) { - ESPeasySerialType::getSerialTypePins(port, rxPin, txPin); - CONFIG_PIN1 = rxPin; - CONFIG_PIN2 = txPin; - } - - # ifndef LIMIT_BUILD_SIZE - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Ser2net: TaskIndex="); - log += event->TaskIndex; - log += F(" port="); - log += CONFIG_PORT; - log += F(" rxPin="); - log += rxPin; - log += F(" txPin="); - log += txPin; - log += F(" BAUDRATE="); - log += P020_GET_BAUDRATE; - log += F(" SERVER_PORT="); - log += P020_GET_SERVER_PORT; - log += F(" SERIAL_PROCESSING="); - log += P020_SERIAL_PROCESSING; - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // ifndef LIMIT_BUILD_SIZE - - // serial0 on esp32 is Ser2net: port=2 rxPin=3 txPin=1; serial1 on esp32 is Ser2net: port=4 rxPin=13 txPin=15; Serial2 on esp32 is - // Ser2net: port=4 rxPin=16 txPin=17 - uint8_t serialconfig = serialHelper_convertOldSerialConfig(P020_SERIAL_CONFIG); - task->serialBegin(port, rxPin, txPin, P020_GET_BAUDRATE, serialconfig); - task->startServer(P020_GET_SERVER_PORT); - - if (!task->isInit()) { - clearPluginTaskData(event->TaskIndex); - break; - } - - if (validGpio(P020_RESET_TARGET_PIN)) { - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat( - F("Ser2net : P020_RESET_TARGET_PIN : %d"), - P020_RESET_TARGET_PIN)); - } - # endif // ifndef BUILD_NO_DEBUG - pinMode(P020_RESET_TARGET_PIN, OUTPUT); - digitalWrite(P020_RESET_TARGET_PIN, LOW); - delay(500); - digitalWrite(P020_RESET_TARGET_PIN, HIGH); - pinMode(P020_RESET_TARGET_PIN, INPUT_PULLUP); - } - - task->serial_processing = P020_SERIAL_PROCESSING; - success = true; - break; - } - - case PLUGIN_EXIT: - { - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != task) { - task->stopServer(); - task->serialEnd(); - } - success = true; - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == task) { - break; - } - task->checkServer(); - success = true; - break; - } - - case PLUGIN_FIFTY_PER_SECOND: - { - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == task) { - break; - } - - bool hasClient = task->hasClientConnected(); - - if (P020_IGNORE_CLIENT_CONNECTED || hasClient) { - if (hasClient) { - task->handleClientIn(event); - } - task->handleSerialIn(event); // in case of second serial connected, PLUGIN_SERIAL_IN is not called anymore - } - success = true; - break; - } - - case PLUGIN_SERIAL_IN: - { - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == task) { - break; - } - - if (P020_IGNORE_CLIENT_CONNECTED || task->hasClientConnected()) { - task->handleSerialIn(event); - } else { - task->discardSerialIn(); - } - success = true; - break; - } - - case PLUGIN_WRITE: - { - String command = parseString(string, 1); - P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == task) { - break; - } - - if (equals(command, F("serialsend"))) { - task->ser2netSerial->write(string.substring(11).c_str()); - task->ser2netSerial->flush(); - success = true; - } else if (equals(command, F("serialsendmix"))) { - std::vector argument = parseHexTextData(string); - if (argument.size() > 0) { - task->ser2netSerial->write(&argument[0], argument.size()); - task->ser2netSerial->flush(); - } - success = true; - } else if ((equals(command, F("ser2netclientsend"))) && (task->hasClientConnected())) { - task->ser2netClient.print(string.substring(18)); - task->ser2netClient.flush(); - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P020 +#include "_Plugin_Helper.h" +#if defined(USES_P020) || defined(USES_P044) + +// ####################################################################################################### +// #################################### Plugin 020: Ser2Net ############################################## +// #################################### Plugin 044: P1WifiGateway ######################################## +// ####################################################################################################### + +/************ + * Changelog: + * 2023-08-26 tonhuisman: P044 mode: Set RX time-out default to 50 msec for better receive pace of P1 data + * 2023-08-17 tonhuisman: P1 data: Allow some extra reading timeout between the data and the checksum, as some meters need more time to + * calculate the CRC. Add CR/LF before sending P1 data. + * 2023-08-12 tonhuisman: Strip off occasional 8th bit from received data, to avoid unexpected failures on P1 data reception + * 2023-06-24 tonhuisman: Fix initialization with non-GPIO serial ports like CDC/HW-CDC + * Add option: append the task number to the event-name (Generic and RFLink event options) + * 2023-06-23 tonhuisman: Add option: use Serial Port name (serialxxx -> xxx= 0/1/2/0swap/i2c/sw/hwcdc/cdc) as event-name for Generic events + * 2023-06-02 tonhuisman: Allow buffer up to 2kB. Use ESPEasySerial buffering feature + * 2023-03-25 tonhuisman: Change serialsendmix to handle 0x00 also, by implementing parseHexTextData() + * 2022-12-12 tonhuisman: Add character conversion for the received serial data, act on Space and/or Newline + * 2022-10-11 tonhuisman: Add option for including the message in P1 #data event + * 2022-10-09 tonhuisman: Check P044 migration on PLUGIN_INIT too, still needs a manual save (from UI or by save command) + * 2022-10-08 tonhuisman: Merged code from P044 into this plugin, and use a global flag to emulate P044 with P020 + * When USES_P044 is enabled, also USES_P020 will be enabled! + * Add Led settings, similar to P044 + * 2022-05-28 tonhuisman: Add option to generate events for all lines of a multi-line message + * 2022-05-26 tonhuisman: Add option to allow processing without webclient connected. + * No older changelog available. + ***************************************************************/ + +# include "src/Helpers/_Plugin_Helper_serial.h" +# include "src/PluginStructs/P020_data_struct.h" +# include + + +# define PLUGIN_020 +# define PLUGIN_ID_020 20 +# define PLUGIN_NAME_020 "Communication - Serial Server" +# define PLUGIN_VALUENAME1_020 "Ser2Net" + +# define PLUGIN_ID_020_044 44 +# define PLUGIN_NAME_020_044 "Communication - P1 Wifi Gateway" + +bool P020_Emulate_P044 = false; // Global flag +# if defined(USES_P044) && !defined(USES_P044_ORG) + +// Emulate P044 using P020 with a global flag +boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) { + P020_Emulate_P044 = true; + + boolean result = Plugin_020(function, event, string); + + P020_Emulate_P044 = false; + return result; +} + +bool P020_ConvertP044Settings(struct EventStruct *event) { + if (P020_Emulate_P044 && !P020_GET_P044_MODE_SAVED) { + // Convert existing P044 settings to P020 settings + P020_RX_WAIT = PCONFIG(0); // No conflict + // P020_SERIAL_CONFIG = PCONFIG(1); // No need to convert + P020_RESET_TARGET_PIN = CONFIG_PIN1; + + // 'Conflicting' stuff, set defaults to: Serial0, RX=gpio-3 and TX=gpio-1 + CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port + CONFIG_PIN1 = 3; // P044 RX pin + CONFIG_PIN2 = 1; // P044 TX pin + + // Former P044 defaults + P020_FLAGS = 0u; // Reset + bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); // Led enabled... + P020_LED_PIN = P020_STATUS_LED; // ...and connected to GPIO-12 + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing + return true; + } + return false; +} + +# endif // if defined(USES_P044) && !defined(USES_P044_ORG) + +boolean Plugin_020(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + if (P020_Emulate_P044) { + Device[++deviceCount].Number = PLUGIN_ID_020_044; + Device[deviceCount].SendDataOption = false; + } else { + Device[++deviceCount].Number = PLUGIN_ID_020; + Device[deviceCount].SendDataOption = true; + } + Device[deviceCount].Type = DEVICE_TYPE_SERIAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 0; + Device[deviceCount].TimerOption = false; + Device[deviceCount].GlobalSyncOption = false; + break; + } + case PLUGIN_GET_DEVICENAME: + { + string = P020_Emulate_P044 ? F(PLUGIN_NAME_020_044) : F(PLUGIN_NAME_020); + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + if (P020_Emulate_P044) { + CONFIG_PORT = static_cast(ESPEasySerialPort::serial0); // P044 Serial port + CONFIG_PIN1 = 3; // P044 RX pin + CONFIG_PIN2 = 1; // P044 TX pin + P020_SET_BAUDRATE = P020_DEFAULT_P044_BAUDRATE; + P020_SET_SERVER_PORT = P020_DEFAULT_P044_SERVER_PORT; + P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Enable P1 WiFi Gateway processing (only) + P020_LED_PIN = P020_STATUS_LED; + P020_RX_WAIT = 50; // 50 msec for proper P1 packet receive mode + P020_REPLACE_SPACE = 0; // Force empty + P020_REPLACE_NEWLINE = 0; + P020_FLAGS = 0u; // Reset + bitSet(P020_FLAGS, P020_FLAG_LED_ENABLED); + bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Inital config, no conversion needed + } else { + P020_SET_BAUDRATE = P020_DEFAULT_BAUDRATE; + P020_SET_SERVER_PORT = P020_DEFAULT_SERVER_PORT; + P020_RESET_TARGET_PIN = P020_DEFAULT_RESET_TARGET_PIN; + P020_RX_BUFFER = P020_DEFAULT_RX_BUFFER; + P020_LED_PIN = -1; + } + success = true; + break; + } + + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + serialHelper_getGpioNames(event); + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = F("RST: "); + string += formatGpioLabel(P020_RESET_TARGET_PIN, false); + string += event->String1; + string += F("LED: "); + string += formatGpioLabel(P020_GET_LED_ENABLED ? P020_LED_PIN : -1, false); + + if ((P020_GET_LED_INVERTED == 1) && (P020_GET_LED_ENABLED)) { + string += F(" (inv)"); + } + success = true; + break; + } + + # ifdef USES_P044 + case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: + { + // P044 Settings to convert? + if (P020_Emulate_P044 && P020_ConvertP044Settings(event)) { + addFormNote(F("Settings migrated from previous plugin version.")); + } + break; + } + # endif // ifdef USES_P044 + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("TCP Port"), F("pport"), P020_GET_SERVER_PORT, 0); + # ifndef LIMIT_BUILD_SIZE + addUnit(F("0..65535")); + # endif // ifndef LIMIT_BUILD_SIZE + + addFormNumericBox(F("Baud Rate"), F("pbaud"), P020_GET_BAUDRATE, 0); + uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P020_SERIAL_CONFIG); + serialHelper_serialconfig_webformLoad(event, serialConfChoice); + { + if (!P020_Emulate_P044) { + const __FlashStringHelper *options[] = { + F("None"), + F("Generic"), + F("RFLink"), + F("P1 WiFi Gateway") + }; + const int optionValues[] = { + static_cast(P020_Events::None), + static_cast(P020_Events::Generic), + static_cast(P020_Events::RFLink), + static_cast(P020_Events::P1WiFiGateway), + }; + constexpr int optionCount = NR_ELEMENTS(optionValues); + addFormSelector(F("Event processing"), F("pevents"), + optionCount, options, optionValues, + P020_SERIAL_PROCESSING); + } + addFormCheckBox(F("P1 #data event with message"), F("pp1event"), P020_GET_P1_EVENT_DATA); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("When enabled, passes the entire message in the event. Warning: can cause memory overflow issues!")); + # endif // ifndef LIMIT_BUILD_SIZE + + if (P020_Events::Generic == static_cast(P020_SERIAL_PROCESSING)) { + addFormCheckBox(F("Use Serial Port as eventname"), F("pevtname"), P020_GET_EVENT_SERIAL_ID); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("(Event processing: Generic only!)")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + if (P020_Events::P1WiFiGateway != static_cast(P020_SERIAL_PROCESSING)) { + addFormCheckBox(F("Append Task Number to eventname"), F("papptask"), P020_GET_APPEND_TASK_ID); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("(Event processing: Generic and RFLink only!)")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + if (!P020_Emulate_P044) { // Not appropriate for P1 WiFi Gateway + addFormSeparatorCharInput(F("Replace spaces in event by"), F("replspace"), + P020_REPLACE_SPACE, F(P020_REPLACE_CHAR_SET), F("")); + + addFormSeparatorCharInput(F("Replace newlines in event by"), F("replcrlf"), + P020_REPLACE_NEWLINE, F(P020_REPLACE_CHAR_SET), F("")); + } + + addFormCheckBox(F("Process events without client"), F("pignoreclient"), P020_IGNORE_CLIENT_CONNECTED); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("When enabled, will process serial data without a network client connected.")); + # endif // ifndef LIMIT_BUILD_SIZE + + if (!P020_Emulate_P044) { // Not appropriate for P1 WiFi Gateway + addFormCheckBox(F("Multiple lines processing"), F("pmultiline"), P020_HANDLE_MULTI_LINE); + } + } + { + addFormNumericBox(F("RX Receive Timeout (mSec)"), F("prxwait"), P020_RX_WAIT, 0, 200); + addFormPinSelect(PinSelectPurpose::Generic_output, F("Reset target after init"), F("presetpin"), P020_RESET_TARGET_PIN); + + if (!P020_Emulate_P044) { + addFormNumericBox(F("RX buffer size (bytes)"), F("prx_buffer"), P020_RX_BUFFER, 256, 2048); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Standard RX buffer 256B; higher values could be unstable; energy meters could require 1024B")); + # endif // ifndef LIMIT_BUILD_SIZE + } + } + { // Led settings + addFormSubHeader(F("Led")); + + addFormCheckBox(F("Led enabled"), F("pled"), P020_GET_LED_ENABLED); + addFormPinSelect(PinSelectPurpose::Generic_output, F("Led pin"), F("pledpin"), P020_LED_PIN); + addFormCheckBox(F("Led inverted"), F("pledinv"), P020_GET_LED_INVERTED == 1); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P020_SET_SERVER_PORT = getFormItemInt(F("pport")); + P020_SET_BAUDRATE = getFormItemInt(F("pbaud")); + P020_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); + P020_RX_WAIT = getFormItemInt(F("prxwait")); + P020_RESET_TARGET_PIN = getFormItemInt(F("presetpin")); + + if (P020_Emulate_P044) { + P020_SERIAL_PROCESSING = static_cast(P020_Events::P1WiFiGateway); // Force P1 WiFi Gateway processing + } else { + P020_SERIAL_PROCESSING = getFormItemInt(F("pevents")); + P020_RX_BUFFER = getFormItemInt(F("prx_buffer")); + P020_REPLACE_SPACE = getFormItemInt(F("replspace")); + P020_REPLACE_NEWLINE = getFormItemInt(F("replcrlf")); + } + P020_LED_PIN = getFormItemInt(F("pledpin")); + + uint32_t lSettings = 0u; + bitWrite(lSettings, P020_FLAG_IGNORE_CLIENT, isFormItemChecked(F("pignoreclient"))); + bitWrite(lSettings, P020_FLAG_LED_ENABLED, isFormItemChecked(F("pled"))); + bitWrite(lSettings, P020_FLAG_LED_INVERTED, isFormItemChecked(F("pledinv"))); + bitWrite(lSettings, P020_FLAG_P1_EVENT_DATA, isFormItemChecked(F("pp1event"))); + + if (P020_Events::Generic == static_cast(P020_SERIAL_PROCESSING)) { + bitWrite(lSettings, P020_FLAG_EVENT_SERIAL_ID, isFormItemChecked(F("pevtname"))); + } + + if (P020_Events::P1WiFiGateway != static_cast(P020_SERIAL_PROCESSING)) { + bitWrite(lSettings, P020_FLAG_APPEND_TASK_ID, isFormItemChecked(F("papptask"))); + } + + if (P020_Emulate_P044) { + bitSet(lSettings, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on every save + } else { + bitWrite(lSettings, P020_FLAG_MULTI_LINE, isFormItemChecked(F("pmultiline"))); + } + + P020_FLAGS = lSettings; + + success = true; + break; + } + + case PLUGIN_INIT: + { + # ifdef USES_P044 + + // P044 Settings to convert? + if (P020_Emulate_P044 && P020_ConvertP044Settings(event)) { + addLog(LOG_LEVEL_INFO, F("P1 : Automatic settings conversion, please save settings manually.")); + bitSet(P020_FLAGS, P020_FLAG_P044_MODE_SAVED); // Set to P044 configuration done on next save + } + # endif // ifdef USES_P044 + + if (P020_GET_LED_ENABLED && validGpio(P020_LED_PIN)) { + pinMode(P020_LED_PIN, OUTPUT); + digitalWrite(P020_LED_PIN, P020_GET_LED_INVERTED ? 1 : 0); + } + + if ((P020_GET_SERVER_PORT == 0) || (P020_GET_BAUDRATE == 0)) { + clearPluginTaskData(event->TaskIndex); + break; + } + + // try to reuse to keep webserver running + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != task) && task->isInit()) { + // It was already created and initialzed + // So don't recreate to keep the webserver running. + } else { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P020_Task(event)); + task = static_cast(getPluginTaskData(event->TaskIndex)); + } + + if (nullptr == task) { + break; + } + task->handleMultiLine = P020_HANDLE_MULTI_LINE && static_cast(P020_SERIAL_PROCESSING) != P020_Events::P1WiFiGateway; + + int rxPin = CONFIG_PIN1; + int txPin = CONFIG_PIN2; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + + if ((rxPin < 0) && (txPin < 0)) { + ESPeasySerialType::getSerialTypePins(port, rxPin, txPin); + CONFIG_PIN1 = rxPin; + CONFIG_PIN2 = txPin; + } + + # ifndef LIMIT_BUILD_SIZE + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: TaskIndex=%d port=%d rxPin=%d txPin=%d BAUDRATE=%d SERVER_PORT=%d SERIAL_PROCESSING=%d"), + event->TaskIndex + 1, + CONFIG_PORT, + rxPin, + txPin, + P020_GET_BAUDRATE, + P020_GET_SERVER_PORT, + P020_SERIAL_PROCESSING)); + } + # endif // ifndef LIMIT_BUILD_SIZE + + // serial0 on esp32 is Ser2net: port=2 rxPin=3 txPin=1; serial1 on esp32 is Ser2net: port=4 rxPin=13 txPin=15; Serial2 on esp32 is + // Ser2net: port=4 rxPin=16 txPin=17 + uint8_t serialconfig = serialHelper_convertOldSerialConfig(P020_SERIAL_CONFIG); + task->serialBegin(port, rxPin, txPin, P020_GET_BAUDRATE, serialconfig); + task->startServer(P020_GET_SERVER_PORT); + + if (!task->isInit()) { + clearPluginTaskData(event->TaskIndex); + break; + } + + if (validGpio(P020_RESET_TARGET_PIN)) { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat( + F("Ser2net : P020_RESET_TARGET_PIN : %d"), + P020_RESET_TARGET_PIN)); + } + # endif // ifndef BUILD_NO_DEBUG + pinMode(P020_RESET_TARGET_PIN, OUTPUT); + digitalWrite(P020_RESET_TARGET_PIN, LOW); + delay(500); + digitalWrite(P020_RESET_TARGET_PIN, HIGH); + pinMode(P020_RESET_TARGET_PIN, INPUT_PULLUP); + } + + task->serial_processing = static_cast(P020_SERIAL_PROCESSING); + task->_P1EventData = P020_GET_P1_EVENT_DATA; + + task->blinkLED(); + + if (task->serial_processing == P020_Events::P1WiFiGateway) { + task->_CRCcheck = P020_GET_BAUDRATE == 115200; + # ifndef BUILD_NO_DEBUG + + if (task->_CRCcheck) { + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 5 meter, CRC on")); + } else { + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); + } + # endif // ifndef BUILD_NO_DEBUG + } + + success = true; + break; + } + + case PLUGIN_EXIT: + { + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { + task->stopServer(); + task->serialEnd(); + } + success = true; + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { + task->checkServer(); + success = true; + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: + { + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { + bool hasClient = task->hasClientConnected(); + + if (P020_IGNORE_CLIENT_CONNECTED || hasClient) { + if (hasClient) { + task->handleClientIn(event); + } + task->handleSerialIn(event); // in case of second serial connected, PLUGIN_SERIAL_IN is not called anymore + } + task->checkBlinkLED(); + success = true; + } + break; + } + + case PLUGIN_SERIAL_IN: + { + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { + if (P020_IGNORE_CLIENT_CONNECTED || task->hasClientConnected()) { + task->handleSerialIn(event); + } else { + task->discardSerialIn(); + } + success = true; + } + break; + } + + case PLUGIN_WRITE: + { + P020_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { + String command = parseString(string, 1); + + if (equals(command, F("serialsend"))) { + task->ser2netSerial->write(string.substring(11).c_str()); + task->ser2netSerial->flush(); + success = true; + } else if (equals(command, F("serialsendmix"))) { + std::vector argument = parseHexTextData(string); + task->ser2netSerial->write(&argument[0], argument.size()); + task->ser2netSerial->flush(); + success = true; + } else if ((equals(command, F("ser2netclientsend"))) && (task->hasClientConnected())) { + task->ser2netClient.print(string.substring(18)); + task->ser2netClient.flush(); + success = true; + } + break; + } + } + } + return success; +} + +#endif // if defined(USES_P020) || defined(USES_P044) diff --git a/src/_P021_Level.ino b/src/_P021_Level.ino index 19acb9055..92801bc6f 100644 --- a/src/_P021_Level.ino +++ b/src/_P021_Level.ino @@ -231,9 +231,7 @@ boolean Plugin_021(uint8_t function, struct EventStruct *event, String& string) if (state != switchstate[event->TaskIndex]) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("LEVEL: State "); - log += state; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("LEVEL: State "), state)); } switchstate[event->TaskIndex] = state; diff --git a/src/_P022_PCA9685.ino b/src/_P022_PCA9685.ino index 2dd3dfe70..a967139d0 100644 --- a/src/_P022_PCA9685.ino +++ b/src/_P022_PCA9685.ino @@ -18,7 +18,7 @@ # define PLUGIN_NAME_022 "Extra IO - PCA9685" # define PLUGIN_VALUENAME1_022 "PWM" -constexpr pluginID_t P022_PLUGIN_ID{PLUGIN_ID_022}; +constexpr pluginID_t P022_PLUGIN_ID{ PLUGIN_ID_022 }; // FIXME TD-er: This plugin uses a lot of calls to the P022_data_struct, which could be combined in single functions. @@ -84,7 +84,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) { uint8_t optionValues[PCA9685_NUMS_ADDRESS]; - for (uint8_t i = 0; i < PCA9685_NUMS_ADDRESS; i++) + for (uint8_t i = 0; i < PCA9685_NUMS_ADDRESS; ++i) { optionValues[i] = PCA9685_ADDRESS + i; } @@ -115,7 +115,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) String m2Options[PCA9685_MODE2_VALUES]; int m2Values[PCA9685_MODE2_VALUES]; - for (int i = 0; i < PCA9685_MODE2_VALUES; i++) + for (int i = 0; i < PCA9685_MODE2_VALUES; ++i) { m2Values[i] = i; m2Options[i] = formatToHex_decimal(i); @@ -127,10 +127,10 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) addFormSelector(F("MODE2"), F("pmode2"), PCA9685_MODE2_VALUES, m2Options, m2Values, mode2); } addFormNumericBox( - strformat(F("Frequency (%d-%d)"), PCA9685_MIN_FREQUENCY, PCA9685_MAX_FREQUENCY), - F("pfreq"), - freq, - PCA9685_MIN_FREQUENCY, + strformat(F("Frequency (%d-%d)"), PCA9685_MIN_FREQUENCY, PCA9685_MAX_FREQUENCY), + F("pfreq"), + freq, + PCA9685_MIN_FREQUENCY, PCA9685_MAX_FREQUENCY); addFormNote(concat(F("default "), PCA9685_MAX_FREQUENCY)); addFormNumericBox(F("Range (1-10000)"), F("prange"), range, 1, 10000); @@ -320,11 +320,12 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) SendStatusOnlyIfNeeded(event, SEARCH_PIN_STATE, key, log, 0); } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { addLog(LOG_LEVEL_ERROR, - P022_data_struct::P022_logPrefix(address) + - strformat(F("frequency %d out of range."), - event->Par1)); + strformat(F("%sfrequency %d out of range."), + P022_data_struct::P022_logPrefix(address).c_str(), + event->Par1)); + } } } @@ -341,15 +342,15 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) } P022_data->Plugin_022_writeRegister(address, PCA9685_MODE2, event->Par1); - addLog(LOG_LEVEL_INFO, concat( - P022_data_struct::P022_logPrefix(address, F("MODE2 0x")), - formatToHex(event->Par1, 2))); + 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())); } else { addLog(LOG_LEVEL_ERROR, strformat(F("%s%s is out of range"), - P022_data_struct::P022_logPrefix(address, F("MODE2 0x")).c_str(), - formatToHex(event->Par1, 2).c_str())); + P022_data_struct::P022_logPrefix(address, F("MODE2 0x")).c_str(), + formatToHex(event->Par1, 2).c_str())); } } @@ -369,7 +370,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) if (instanceCommand && (equals(command, F("gpio")))) { success = true; - log = P022_data_struct::P022_logPrefix(address, F("GPIO ")); + log = P022_data_struct::P022_logPrefix(address, F("GPIO ")); const bool allPins = equals(parseString(string, 2), F("all")); if (((event->Par1 >= 0) && (event->Par1 <= PCA9685_MAX_PINS)) || @@ -422,8 +423,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) if (instanceCommand && (equals(command, F("pulse")))) { success = true; - log = P022_data_struct::P022_logPrefix(address, F("GPIO ")); - log += event->Par1; + log = concat(P022_data_struct::P022_logPrefix(address, F("GPIO ")), event->Par1); if ((event->Par1 >= 0) && (event->Par1 <= PCA9685_MAX_PINS)) { @@ -455,8 +455,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) if (autoreset > 0) { - log += F(" for "); - log += autoreset; + log += concat(F(" for "), autoreset); } } } @@ -515,8 +514,7 @@ boolean Plugin_022(uint8_t function, struct EventStruct *event, String& string) { if (autoreset > -1) { - log += F(" Pulse auto restart for "); - log += autoreset; + log += concat(F(" Pulse auto restart for "), autoreset); autoreset--; } Scheduler.setPluginTaskTimer(event->Par3 diff --git a/src/_P023_OLED.ino b/src/_P023_OLED.ino index e0b6bbd4e..416231a5f 100644 --- a/src/_P023_OLED.ino +++ b/src/_P023_OLED.ino @@ -84,8 +84,7 @@ boolean Plugin_023(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = F("Btn: "); - string += formatGpioLabel(CONFIG_PIN3, false); + string = concat(F("Btn: "), formatGpioLabel(CONFIG_PIN3, false)); success = true; break; } @@ -98,13 +97,13 @@ boolean Plugin_023(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { - const int controllerValues[2] = { 0, 1 }; + const int controllerValues[] = { 0, 1 }; OLedFormController(F("use_sh1106"), controllerValues, PCONFIG(5)); OLedFormRotation(F("rotate"), PCONFIG(1)); { - const int optionValues3[3] = { 1, 3, 2 }; + const int optionValues3[] = { 1, 3, 2 }; OLedFormSizes(F("size"), optionValues3, PCONFIG(3)); } { @@ -116,7 +115,7 @@ boolean Plugin_023(uint8_t function, struct EventStruct *event, String& string) String strings[P23_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P23_Nlines, P23_Nchars); - for (int varNr = 0; varNr < 8; varNr++) + for (int varNr = 0; varNr < 8; ++varNr) { addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], 64); } @@ -145,7 +144,7 @@ boolean Plugin_023(uint8_t function, struct EventStruct *event, String& string) char deviceTemplate[P23_Nlines][P23_Nchars] = {}; String error; - for (uint8_t varNr = 0; varNr < P23_Nlines; varNr++) { + for (uint8_t varNr = 0; varNr < P23_Nlines; ++varNr) { if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P23_Nchars)) { error += getCustomTaskSettingsError(varNr); } diff --git a/src/_P024_MLX90614.ino b/src/_P024_MLX90614.ino index ba2b70383..2f47affcb 100644 --- a/src/_P024_MLX90614.ino +++ b/src/_P024_MLX90614.ino @@ -1,131 +1,129 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P024 - -// ####################################################################################################### -// #################################### Plugin 024: MLX90614 IR temperature I2C 0x5A) ############################################### -// ####################################################################################################### - -/** Changelog: - * 2023-11-23 tonhuisman: Add Device flag for I2CMax100kHz as this sensor won't work at 400 kHz - * 2023-11-23 tonhuisman: Add Changelog -*/ - -# include "src/PluginStructs/P024_data_struct.h" - -// MyMessage *msgTemp024; // Mysensors - -# define PLUGIN_024 -# define PLUGIN_ID_024 24 -# define PLUGIN_NAME_024 "Environment - MLX90614" -# define PLUGIN_VALUENAME1_024 "Temperature" - -boolean Plugin_024(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - // static uint8_t portValue = 0; - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_024; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 16; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - Device[deviceCount].I2CMax100kHz = true; // Max 100 kHz allowed/supported - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_024); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_024)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x5a); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x5a; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - # define MLX90614_OPTION 2 - - const __FlashStringHelper *options[MLX90614_OPTION] = { - F("IR object temperature"), - F("Ambient temperature") - }; - const int optionValues[MLX90614_OPTION] = { - (0x07), - (0x06) - }; - addFormSelector(F("Option"), F("option"), MLX90614_OPTION, options, optionValues, PCONFIG(0)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("option")); - success = true; - break; - } - - case PLUGIN_INIT: - { - const uint8_t unit = CONFIG_PORT; - const uint8_t address = 0x5A + unit; - - success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P024_data_struct(address)); - break; - } - - case PLUGIN_READ: - { - P024_data_struct *P024_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P024_data) { - UserVar.setFloat(event->TaskIndex, 0, P024_data->readTemperature(PCONFIG(0))); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MLX90614 : Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - } - - // send(msgObjTemp024->set(UserVar[event->BaseVarIndex], 1)); // Mysensors - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P024 +#include "_Plugin_Helper.h" +#ifdef USES_P024 + +// ####################################################################################################### +// #################################### Plugin 024: MLX90614 IR temperature I2C 0x5A) ############################################### +// ####################################################################################################### + +/** Changelog: + * 2023-11-23 tonhuisman: Add Device flag for I2CMax100kHz as this sensor won't work at 400 kHz + * 2023-11-23 tonhuisman: Add Changelog +*/ + +# include "src/PluginStructs/P024_data_struct.h" + +// MyMessage *msgTemp024; // Mysensors + +# define PLUGIN_024 +# define PLUGIN_ID_024 24 +# define PLUGIN_NAME_024 "Environment - MLX90614" +# define PLUGIN_VALUENAME1_024 "Temperature" + +boolean Plugin_024(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + // static uint8_t portValue = 0; + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_024; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 16; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].I2CMax100kHz = true; // Max 100 kHz allowed/supported + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_024); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_024)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x5a); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x5a; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + # define MLX90614_OPTION 2 + + const __FlashStringHelper *options[MLX90614_OPTION] = { + F("IR object temperature"), + F("Ambient temperature") + }; + const int optionValues[MLX90614_OPTION] = { + (0x07), + (0x06) + }; + addFormSelector(F("Option"), F("option"), MLX90614_OPTION, options, optionValues, PCONFIG(0)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("option")); + success = true; + break; + } + + case PLUGIN_INIT: + { + const uint8_t unit = CONFIG_PORT; + const uint8_t address = 0x5A + unit; + + success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P024_data_struct(address)); + break; + } + + case PLUGIN_READ: + { + P024_data_struct *P024_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P024_data) { + UserVar.setFloat(event->TaskIndex, 0, P024_data->readTemperature(PCONFIG(0))); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("MLX90614 : Temperature: "), formatUserVarNoCheck(event, 0))); + } + + // send(msgObjTemp024->set(UserVar[event->BaseVarIndex], 1)); // Mysensors + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P024 diff --git a/src/_P025_ADS1115.ino b/src/_P025_ADS1115.ino index 40886d7d8..aa7cd3864 100644 --- a/src/_P025_ADS1115.ino +++ b/src/_P025_ADS1115.ino @@ -1,223 +1,220 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P025 - -// ####################################################################################################### -// #################################### Plugin 025: ADS1x15 I2C 0x48) ############################################### -// ####################################################################################################### - - -# include "src/PluginStructs/P025_data_struct.h" - -# define PLUGIN_025 -# define PLUGIN_ID_025 25 -# define PLUGIN_NAME_025 "Analog input - ADS1x15" -# define PLUGIN_VALUENAME1_025 "Analog" - - -boolean Plugin_025(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - // static uint8_t portValue = 0; - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_025; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_025); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - const int valueCount = P025_NR_OUTPUT_VALUES; - - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < valueCount) { - const uint8_t pconfigIndex = P025_PCONFIG_INDEX(i); - ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_025_valuename(PCONFIG(pconfigIndex), false)); - } else { - ExtraTaskSettings.clearTaskDeviceValueName(i); - } - } - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P025_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P025_SENSOR_TYPE_INDEX)); - event->idx = P025_SENSOR_TYPE_INDEX; - success = true; - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4A, 0x4B }; - constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P025_I2C_ADDR); - } else { - success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P025_I2C_ADDR; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(P025_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - success = P025_data_struct::webformLoad(event); - break; - } - - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - { - const __FlashStringHelper *valOptions[] = { - Plugin_025_valuename(0, true), - Plugin_025_valuename(1, true), - Plugin_025_valuename(2, true), - Plugin_025_valuename(3, true), - Plugin_025_valuename(4, true), - Plugin_025_valuename(5, true), - Plugin_025_valuename(6, true), - Plugin_025_valuename(7, true) - }; - constexpr int nrOptions = NR_ELEMENTS(valOptions); - - for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; i++) { - sensorTypeHelper_loadOutputSelector(event, - P025_PCONFIG_INDEX(i), - i, - nrOptions, - valOptions); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - success = P025_data_struct::webformSave(event); - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - success = P025_data_struct::webform_showConfig(event); - break; - } - - case PLUGIN_INIT: - { - // int value = 0; - // uint8_t unit = (CONFIG_PORT - 1) / 4; - // uint8_t port = CONFIG_PORT - (unit * 4); - // uint8_t address = 0x48 + unit; - - success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P025_data_struct(event)); - break; - } - - case PLUGIN_READ: - { - const P025_data_struct *P025_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P025_data) { - for (taskVarIndex_t i = 0; i < P025_NR_OUTPUT_VALUES; ++i) { - float value{}; - - if (P025_data->read(value, i)) { - success = true; - - # ifndef BUILD_NO_DEBUG - String log; - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log = F("ADS1x15 : Analog value: "); - log += value; - log += F(" / Channel: "); - log += P025_MUX(i); - } - # endif // ifndef BUILD_NO_DEBUG - - UserVar.setFloat(event->TaskIndex, i, value); - - const P025_VARIOUS_BITS_t p025_variousBits(P025_VARIOUS_BITS); - - if (p025_variousBits.cal) { // Calibration? - const int adc1 = P025_CAL_ADC1; - const int adc2 = P025_CAL_ADC2; - const float out1 = P025_CAL_OUT1; - const float out2 = P025_CAL_OUT2; - - if (adc1 != adc2) - { - const float normalized = static_cast(value - adc1) / static_cast(adc2 - adc1); - UserVar.setFloat(event->TaskIndex, i, normalized * (out2 - out1) + out1); - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log += ' '; - log += formatUserVarNoCheck(event->TaskIndex, i); - } - # endif // ifndef BUILD_NO_DEBUG - } - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG - } - } - } - break; - } - } - return success; -} - -#endif // USES_P025 +#include "_Plugin_Helper.h" +#ifdef USES_P025 + +// ####################################################################################################### +// #################################### Plugin 025: ADS1x15 I2C 0x48) ############################################### +// ####################################################################################################### + + +# include "src/PluginStructs/P025_data_struct.h" + +# define PLUGIN_025 +# define PLUGIN_ID_025 25 +# define PLUGIN_NAME_025 "Analog input - ADS1x15" +# define PLUGIN_VALUENAME1_025 "Analog" + + +boolean Plugin_025(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + // static uint8_t portValue = 0; + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_025; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_025); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + const int valueCount = P025_NR_OUTPUT_VALUES; + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < valueCount) { + const uint8_t pconfigIndex = P025_PCONFIG_INDEX(i); + ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_025_valuename(PCONFIG(pconfigIndex), false)); + } else { + ExtraTaskSettings.clearTaskDeviceValueName(i); + } + } + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P025_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P025_SENSOR_TYPE_INDEX)); + event->idx = P025_SENSOR_TYPE_INDEX; + success = true; + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4A, 0x4B }; + constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P025_I2C_ADDR); + } else { + success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P025_I2C_ADDR; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(P025_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_SINGLE); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + success = P025_data_struct::webformLoad(event); + break; + } + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + const __FlashStringHelper *valOptions[] = { + Plugin_025_valuename(0, true), + Plugin_025_valuename(1, true), + Plugin_025_valuename(2, true), + Plugin_025_valuename(3, true), + Plugin_025_valuename(4, true), + Plugin_025_valuename(5, true), + Plugin_025_valuename(6, true), + Plugin_025_valuename(7, true) + }; + constexpr int nrOptions = NR_ELEMENTS(valOptions); + + for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; i++) { + sensorTypeHelper_loadOutputSelector(event, + P025_PCONFIG_INDEX(i), + i, + nrOptions, + valOptions); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P025_data_struct::webformSave(event); + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + success = P025_data_struct::webform_showConfig(event); + break; + } + + case PLUGIN_INIT: + { + // int value = 0; + // uint8_t unit = (CONFIG_PORT - 1) / 4; + // uint8_t port = CONFIG_PORT - (unit * 4); + // uint8_t address = 0x48 + unit; + + success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P025_data_struct(event)); + break; + } + + case PLUGIN_READ: + { + const P025_data_struct *P025_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P025_data) { + for (taskVarIndex_t i = 0; i < P025_NR_OUTPUT_VALUES; ++i) { + float value{}; + + if (P025_data->read(value, i)) { + success = true; + + # ifndef BUILD_NO_DEBUG + String log; + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log = strformat(F("ADS1x15 : Analog value: %.2f / Channel: %d"), value, P025_MUX(i)); + } + # endif // ifndef BUILD_NO_DEBUG + + UserVar.setFloat(event->TaskIndex, i, value); + + const P025_VARIOUS_BITS_t p025_variousBits(P025_VARIOUS_BITS); + + if (p025_variousBits.cal) { // Calibration? + const int adc1 = P025_CAL_ADC1; + const int adc2 = P025_CAL_ADC2; + const float out1 = P025_CAL_OUT1; + const float out2 = P025_CAL_OUT2; + + if (adc1 != adc2) + { + const float normalized = static_cast(value - adc1) / static_cast(adc2 - adc1); + UserVar.setFloat(event->TaskIndex, i, normalized * (out2 - out1) + out1); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += ' '; + log += formatUserVarNoCheck(event, i); + } + # endif // ifndef BUILD_NO_DEBUG + } + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + } + } + } + break; + } + } + return success; +} + +#endif // USES_P025 diff --git a/src/_P026_Sysinfo.ino b/src/_P026_Sysinfo.ino index c96dcea77..ff7858767 100644 --- a/src/_P026_Sysinfo.ino +++ b/src/_P026_Sysinfo.ino @@ -1,133 +1,133 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P026 - -// ####################################################################################################### -// #################################### Plugin 026: System Info ########################################## -// ####################################################################################################### - -/** Changelog: - * 2023-09-24 tonhuisman: Add support for getting all values via Get Config option [#] where is the default - * name as set for an output value. None is ignored. Not available in MINIMAL_OTA builds. - * Move all includes to P026_data_struct.h - * 2023-09-23 tonhuisman: Add Internal temperature option for ESP32 - * Format source using Uncrustify - * Move #if check to P026_data_struct.h as Arduino compiler doesn't support that :( - * Move other defines to P026_data_struct.h - * 2023-09-23 tonhuisman: Start changelog - */ - -# define PLUGIN_026 -# define PLUGIN_ID_026 26 -# define PLUGIN_NAME_026 "Generic - System Info" - -# include "src/PluginStructs/P026_data_struct.h" // Arduino doesn't do #if in .ino sources :( - - - -boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_026; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].ValueCount = 4; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_026); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - success = P026_data_struct::GetDeviceValueNames(event); - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = P026_NR_OUTPUT_VALUES; - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(P026_SENSOR_TYPE_INDEX)); - event->idx = P026_SENSOR_TYPE_INDEX; - success = true; - break; - } - - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(0) = 0; // "Uptime" - - for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { - PCONFIG(i) = 11; // "None" - } - PCONFIG(P026_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - { - success = P026_data_struct::WebformLoadOutputSelector(event); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - success = P026_data_struct::WebformSave(event); - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - success = P026_data_struct::Plugin_Read(event); - break; - } - # ifndef PLUGIN_BUILD_MINIMAL_OTA - case PLUGIN_GET_CONFIG_VALUE: - { - success = P026_data_struct::Plugin_GetConfigValue(event, string); - break; - } - # endif // ifndef PLUGIN_BUILD_MINIMAL_OTA -# if FEATURE_PACKED_RAW_DATA - case PLUGIN_GET_PACKED_RAW_DATA: - { - success = P026_data_struct::Plugin_GetPackedRawData(event, string); - break; - } -# endif // if FEATURE_PACKED_RAW_DATA - } - return success; -} - - -#endif // USES_P026 +#include "_Plugin_Helper.h" +#ifdef USES_P026 + +// ####################################################################################################### +// #################################### Plugin 026: System Info ########################################## +// ####################################################################################################### + +/** Changelog: + * 2023-09-24 tonhuisman: Add support for getting all values via Get Config option [#] where is the default + * name as set for an output value. None is ignored. Not available in MINIMAL_OTA builds. + * Move all includes to P026_data_struct.h + * 2023-09-23 tonhuisman: Add Internal temperature option for ESP32 + * Format source using Uncrustify + * Move #if check to P026_data_struct.h as Arduino compiler doesn't support that :( + * Move other defines to P026_data_struct.h + * 2023-09-23 tonhuisman: Start changelog + */ + +# define PLUGIN_026 +# define PLUGIN_ID_026 26 +# define PLUGIN_NAME_026 "Generic - System Info" + +# include "src/PluginStructs/P026_data_struct.h" // Arduino doesn't do #if in .ino sources :( + + + +boolean Plugin_026(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_026; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_026); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + success = P026_data_struct::GetDeviceValueNames(event); + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P026_NR_OUTPUT_VALUES; + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(P026_SENSOR_TYPE_INDEX)); + event->idx = P026_SENSOR_TYPE_INDEX; + success = true; + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(0) = 0; // "Uptime" + + for (uint8_t i = 1; i < VARS_PER_TASK; ++i) { + PCONFIG(i) = 11; // "None" + } + PCONFIG(P026_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + success = P026_data_struct::WebformLoadOutputSelector(event); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P026_data_struct::WebformSave(event); + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + success = P026_data_struct::Plugin_Read(event); + break; + } + # ifndef PLUGIN_BUILD_MINIMAL_OTA + case PLUGIN_GET_CONFIG_VALUE: + { + success = P026_data_struct::Plugin_GetConfigValue(event, string); + break; + } + # endif // ifndef PLUGIN_BUILD_MINIMAL_OTA +# if FEATURE_PACKED_RAW_DATA + case PLUGIN_GET_PACKED_RAW_DATA: + { + success = P026_data_struct::Plugin_GetPackedRawData(event, string); + break; + } +# endif // if FEATURE_PACKED_RAW_DATA + } + return success; +} + + +#endif // USES_P026 diff --git a/src/_P027_INA219.ino b/src/_P027_INA219.ino index 1af9ab7aa..b6789ed45 100644 --- a/src/_P027_INA219.ino +++ b/src/_P027_INA219.ino @@ -105,9 +105,9 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { { - const __FlashStringHelper *optionsMode[] = { F("32V, 2A"), F("32V, 1A"), F("16V, 0.4A") }; - const int optionValuesMode[] = { 0, 1, 2 }; - addFormSelector(F("Measure range"), F("range"), 3, optionsMode, optionValuesMode, PCONFIG(0)); + const __FlashStringHelper *optionsMode[] = { F("32V, 2A"), F("32V, 1A"), F("16V, 0.4A"), F("26V, 8A") }; + const int optionValuesMode[] = { 0, 1, 2, 3 }; + addFormSelector(F("Measure range"), F("range"), 4, optionsMode, optionValuesMode, PCONFIG(0)); } { const __FlashStringHelper *options[] = { F("Voltage"), F("Current"), F("Power"), F("Voltage/Current/Power") }; @@ -169,6 +169,14 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) P027_data->setCalibration_16V_400mA(); break; } + case 3: + { + if (mustLog) { + log += F("26V, 8A"); + } + P027_data->setCalibration_26V_8A(); + break; + } } if (mustLog) { @@ -206,10 +214,11 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) // for backward compability we allow the user to select if only one measurement should be returned // or all 3 measurements at once + event->sensorType = Sensor_VType::SENSOR_TYPE_SINGLE; + switch (PCONFIG(2)) { case 0: { - event->sensorType = Sensor_VType::SENSOR_TYPE_SINGLE; UserVar.setFloat(event->TaskIndex, 0, voltage); if (mustLog) { @@ -220,7 +229,6 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) } case 1: { - event->sensorType = Sensor_VType::SENSOR_TYPE_SINGLE; UserVar.setFloat(event->TaskIndex, 0, current); if (mustLog) { @@ -231,7 +239,6 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) } case 2: { - event->sensorType = Sensor_VType::SENSOR_TYPE_SINGLE; UserVar.setFloat(event->TaskIndex, 0, power); if (mustLog) { @@ -242,7 +249,7 @@ boolean Plugin_027(uint8_t function, struct EventStruct *event, String& string) } case 3: { - event->sensorType = Sensor_VType::SENSOR_TYPE_TRIPLE; + event->sensorType = Sensor_VType::SENSOR_TYPE_TRIPLE; UserVar.setFloat(event->TaskIndex, 0, voltage); UserVar.setFloat(event->TaskIndex, 1, current); UserVar.setFloat(event->TaskIndex, 2, power); diff --git a/src/_P028_BME280.ino b/src/_P028_BME280.ino index 1cd45a6a6..58113e26b 100644 --- a/src/_P028_BME280.ino +++ b/src/_P028_BME280.ino @@ -1,375 +1,375 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P028 - -// ####################################################################################################### -// #################### Plugin 028 BME280 I2C Temp/Hum/Barometric Pressure Sensor ####################### -// ####################################################################################################### - -/** Changelog: - * 2023-07-27 tonhuisman: Revert most below changes and implement PLUGIN_GET_DEVICEVTYPE so the P2P controller validates against the correct - * setting. Setting is only available if a remote data-feed is active, and offers BME280 and BMP280 options only. - * 2023-07-26 tonhuisman: Ignore all humidity data (and log messages) if BMP280 Sensor model is selected - * 2023-07-25 tonhuisman: Add setting to enable forcing the plugin into either BME280 or BMP280 mode, default is Auto-detect - * Add changelog - */ - -# include "src/PluginStructs/P028_data_struct.h" - -// #include - -# define PLUGIN_028 -# define PLUGIN_ID_028 28 -# define PLUGIN_NAME_028 "Environment - BMx280" -# define PLUGIN_VALUENAME1_028 "Temperature" -# define PLUGIN_VALUENAME2_028 "Humidity" -# define PLUGIN_VALUENAME3_028 "Pressure" - - -boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_028; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].ErrorStateValues = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_028); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_028)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_028)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_028)); - break; - } - - case PLUGIN_INIT_VALUE_RANGES: - { - // Min/Max values obtained from the BMP280/BME280 datasheets (both have equal ranges) - ExtraTaskSettings.setAllowedRange(0, -40.0f, 85.0f); // Temperature min/max - 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 - case P028_ERROR_IGNORE: - ExtraTaskSettings.setIgnoreRangeCheck(0); - break; - case P028_ERROR_MIN_RANGE: - ExtraTaskSettings.TaskDeviceErrorValue[0] = ExtraTaskSettings.TaskDeviceMinValue[0] - 1.0f; - break; - case P028_ERROR_ZERO: - ExtraTaskSettings.TaskDeviceErrorValue[0] = 0.0f; - break; - case P028_ERROR_MAX_RANGE: - ExtraTaskSettings.TaskDeviceErrorValue[0] = ExtraTaskSettings.TaskDeviceMaxValue[0] + 1.0f; - break; - case P028_ERROR_NAN: - ExtraTaskSettings.TaskDeviceErrorValue[0] = NAN; - break; - # ifndef LIMIT_BUILD_SIZE - case P028_ERROR_MIN_K: - ExtraTaskSettings.TaskDeviceErrorValue[0] = -274.0f; - break; - # endif // ifndef LIMIT_BUILD_SIZE - default: - break; - } - - ExtraTaskSettings.TaskDeviceErrorValue[1] = -1.0f; // Humidity error - ExtraTaskSettings.TaskDeviceErrorValue[2] = -1.0f; // Pressure error - - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - const P028_data_struct::BMx_DetectMode detectMode = static_cast(P028_DETECTION_MODE); - - // We want to configure this only when a remote data-feed is used - if ((Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) && (P028_data_struct::BMx_DetectMode::BMP280 == detectMode)) { - // Patch the sensor type to output only the measured values, and/or match with a P2P remote sensor - event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; - event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); - } - - success = true; - break; - } - - case PLUGIN_INIT: - { - const float tempOffset = P028_TEMPERATURE_OFFSET / 10.0f; - success = initPluginTaskData( - event->TaskIndex, - new (std::nothrow) P028_data_struct(P028_I2C_ADDRESS, tempOffset)); - - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x76, 0x77 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P028_I2C_ADDRESS); - addFormNote(F("SDO Low=0x76, High=0x77")); - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P028_I2C_ADDRESS; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - bool wire_status = false; - const uint8_t chip_id = I2C_read8_reg(P028_I2C_ADDRESS, BMx280_REGISTER_CHIPID, &wire_status); - - if (wire_status) { - addRowLabel(F("Detected Sensor Type")); - addHtml(P028_data_struct::getDeviceName(static_cast(chip_id))); - } - - addFormNumericBox(F("Altitude"), F("elev"), P028_ALTITUDE); - addUnit('m'); - - addFormNumericBox(F("Temperature offset"), F("tempoffset"), P028_TEMPERATURE_OFFSET); - addUnit(F("x 0.1C")); - String offsetNote = F("Offset in units of 0.1 degree Celsius"); - - P028_data_struct *P028_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P028_data) { - if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && - P028_data->hasHumidity()) { - offsetNote += F(" (also correct humidity)"); - } - } - addFormNote(offsetNote); - - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD_ALWAYS: - { - if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this *only* when a remote data-feed is used - const __FlashStringHelper *detectOptionList[] = { - P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BME280_DEVICE), - P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BMP280_DEVICE), - }; - const int detectOptions[] = { - static_cast(P028_data_struct::BMx_DetectMode::BME280), - static_cast(P028_data_struct::BMx_DetectMode::BMP280), - }; - addFormSelector(F("Output values mode"), F("det"), 2, detectOptionList, detectOptions, P028_DETECTION_MODE); - - success = true; - } - break; - } - - -# if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: - { - P028_data_struct *P028_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P028_data) { - if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && - P028_data->hasHumidity()) - { - P028_data->plot_ChartJS_scatter( - 0, - 1, - F("temphumscatter"), - { F("Temp/Humidity Scatter Plot") }, - { F("temp/hum"), F("rgb(255, 99, 132)") }, - 500, - 500); - } - } - // Do not set success = true, since we're not actually adding stats, but just plotting a scatter plot - break; - } -#endif - - - case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: - { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("BMx280: SHOW_ERRORSTATE_OPT")); - # endif // ifndef BUILD_NO_DEBUG - - // Value in case of Error - const __FlashStringHelper *resultsOptions[] = { - F("Ignore"), - F("Min -1 (-41°C)"), - F("0"), - F("Max +1 (+86°C)"), - F("NaN"), - # ifndef LIMIT_BUILD_SIZE - F("-1°K (-274°C)") - # endif // ifndef LIMIT_BUILD_SIZE - }; - const int resultsOptionValues[] = { - P028_ERROR_IGNORE, - P028_ERROR_MIN_RANGE, - P028_ERROR_ZERO, - P028_ERROR_MAX_RANGE, - P028_ERROR_NAN, - # ifndef LIMIT_BUILD_SIZE - P028_ERROR_MIN_K - # endif // ifndef LIMIT_BUILD_SIZE - }; - constexpr int P028_ERROR_STATE_COUNT = NR_ELEMENTS(resultsOptions); - addFormSelector(F("Temperature Error Value"), - F("err"), - P028_ERROR_STATE_COUNT, - resultsOptions, - resultsOptionValues, - P028_ERROR_STATE_OUTPUT); - - break; - } - - case PLUGIN_READ_ERROR_OCCURED: - { - // Called if PLUGIN_READ returns false - // Function returns "true" when last measurement was an error. - P028_data_struct *P028_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P028_data) { - if (P028_data->lastMeasurementError) { - success = true; // "success" may be a confusing name here - string = F("Sensor Not Found"); - } - } - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P028_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); - P028_ALTITUDE = getFormItemInt(F("elev")); - P028_TEMPERATURE_OFFSET = getFormItemInt(F("tempoffset")); - P028_ERROR_STATE_OUTPUT = getFormItemInt(F("err")); - - if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this only when a remote data-feed is used - P028_DETECTION_MODE = getFormItemInt(F("det")); - } - success = true; - break; - } - case PLUGIN_ONCE_A_SECOND: - { - P028_data_struct *P028_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P028_data) { - if (P028_data->updateMeasurements(event->TaskIndex)) { - // Update was succesfull, schedule a read. - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - } - } - break; - } - - case PLUGIN_READ: - { - P028_data_struct *P028_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P028_data) { - // PLUGIN_READ is called from `TaskRun` or on the set interval or it has re-scheduled itself to output read samples. - // So if there aren't any new values, it must have been called to get a new sample. - if (P028_data->state != P028_data_struct::BMx_New_values) { - P028_data->startMeasurement(); - - if (P028_ERROR_STATE_OUTPUT != P028_ERROR_IGNORE) { - if (P028_data->lastMeasurementError) { - success = true; // "success" may be a confusing name here - - for (uint8_t i = 0; i < 3; i++) { - UserVar.setFloat(event->TaskIndex, i, ExtraTaskSettings.TaskDeviceErrorValue[i]); - } - } - } - } else { - P028_data->state = P028_data_struct::BMx_Values_read; - - if (!P028_data->hasHumidity()) { - // Patch the sensor type to output only the measured values. - event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; - event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); - } - UserVar.setFloat(event->TaskIndex, 0, ExtraTaskSettings.checkAllowedRange(0, P028_data->last_temp_val)); - UserVar.setFloat(event->TaskIndex, 1, P028_data->last_hum_val); - const int elev = P028_ALTITUDE; - - if (elev != 0) { - UserVar.setFloat(event->TaskIndex, 2, pressureElevation(P028_data->last_press_val, elev)); - } else { - UserVar.setFloat(event->TaskIndex, 2, P028_data->last_press_val); - } - - # ifndef LIMIT_BUILD_SIZE - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String hum; - if (P028_data->hasHumidity()) { - hum = formatUserVarNoCheck(event->TaskIndex, 1); - } - addLogMove(LOG_LEVEL_INFO, concat( - P028_data_struct::getDeviceName(P028_data->sensorID), - strformat( - F(": Addr: %s T: %s H: %s P: %s"), - formatToHex(P028_I2C_ADDRESS, 2).c_str(), - formatUserVarNoCheck(event->TaskIndex, 0).c_str(), - hum.c_str(), - formatUserVarNoCheck(event->TaskIndex, 2).c_str()))); - } - # endif // ifndef LIMIT_BUILD_SIZE - success = true; - } - } - break; - } - } - return success; -} - -#endif // USES_P028 +#include "_Plugin_Helper.h" +#ifdef USES_P028 + +// ####################################################################################################### +// #################### Plugin 028 BME280 I2C Temp/Hum/Barometric Pressure Sensor ####################### +// ####################################################################################################### + +/** Changelog: + * 2023-07-27 tonhuisman: Revert most below changes and implement PLUGIN_GET_DEVICEVTYPE so the P2P controller validates against the correct + * setting. Setting is only available if a remote data-feed is active, and offers BME280 and BMP280 options only. + * 2023-07-26 tonhuisman: Ignore all humidity data (and log messages) if BMP280 Sensor model is selected + * 2023-07-25 tonhuisman: Add setting to enable forcing the plugin into either BME280 or BMP280 mode, default is Auto-detect + * Add changelog + */ + +# include "src/PluginStructs/P028_data_struct.h" + +// #include + +# define PLUGIN_028 +# define PLUGIN_ID_028 28 +# define PLUGIN_NAME_028 "Environment - BMx280" +# define PLUGIN_VALUENAME1_028 "Temperature" +# define PLUGIN_VALUENAME2_028 "Humidity" +# define PLUGIN_VALUENAME3_028 "Pressure" + + +boolean Plugin_028(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_028; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].ErrorStateValues = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_028); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_028)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_028)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_028)); + break; + } + + case PLUGIN_INIT_VALUE_RANGES: + { + // Min/Max values obtained from the BMP280/BME280 datasheets (both have equal ranges) + ExtraTaskSettings.setAllowedRange(0, -40.0f, 85.0f); // Temperature min/max + 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 + case P028_ERROR_IGNORE: + ExtraTaskSettings.setIgnoreRangeCheck(0); + break; + case P028_ERROR_MIN_RANGE: + ExtraTaskSettings.TaskDeviceErrorValue[0] = ExtraTaskSettings.TaskDeviceMinValue[0] - 1.0f; + break; + case P028_ERROR_ZERO: + ExtraTaskSettings.TaskDeviceErrorValue[0] = 0.0f; + break; + case P028_ERROR_MAX_RANGE: + ExtraTaskSettings.TaskDeviceErrorValue[0] = ExtraTaskSettings.TaskDeviceMaxValue[0] + 1.0f; + break; + case P028_ERROR_NAN: + ExtraTaskSettings.TaskDeviceErrorValue[0] = NAN; + break; + # ifndef LIMIT_BUILD_SIZE + case P028_ERROR_MIN_K: + ExtraTaskSettings.TaskDeviceErrorValue[0] = -274.0f; + break; + # endif // ifndef LIMIT_BUILD_SIZE + default: + break; + } + + ExtraTaskSettings.TaskDeviceErrorValue[1] = -1.0f; // Humidity error + ExtraTaskSettings.TaskDeviceErrorValue[2] = -1.0f; // Pressure error + + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + const P028_data_struct::BMx_DetectMode detectMode = static_cast(P028_DETECTION_MODE); + + // We want to configure this only when a remote data-feed is used + if ((Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) && (P028_data_struct::BMx_DetectMode::BMP280 == detectMode)) { + // Patch the sensor type to output only the measured values, and/or match with a P2P remote sensor + event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; + event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); + } + + success = true; + break; + } + + case PLUGIN_INIT: + { + const float tempOffset = P028_TEMPERATURE_OFFSET / 10.0f; + success = initPluginTaskData( + event->TaskIndex, + new (std::nothrow) P028_data_struct(P028_I2C_ADDRESS, tempOffset)); + + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x76, 0x77 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P028_I2C_ADDRESS); + addFormNote(F("SDO Low=0x76, High=0x77")); + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P028_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + bool wire_status = false; + const uint8_t chip_id = I2C_read8_reg(P028_I2C_ADDRESS, BMx280_REGISTER_CHIPID, &wire_status); + + if (wire_status) { + addRowLabel(F("Detected Sensor Type")); + addHtml(P028_data_struct::getDeviceName(static_cast(chip_id))); + } + + addFormNumericBox(F("Altitude"), F("elev"), P028_ALTITUDE); + addUnit('m'); + + addFormNumericBox(F("Temperature offset"), F("tempoffset"), P028_TEMPERATURE_OFFSET); + addUnit(F("x 0.1C")); + String offsetNote = F("Offset in units of 0.1 degree Celsius"); + + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && + P028_data->hasHumidity()) { + offsetNote += F(" (also correct humidity)"); + } + } + addFormNote(offsetNote); + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD_ALWAYS: + { + if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this *only* when a remote data-feed is used + const __FlashStringHelper *detectOptionList[] = { + P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BME280_DEVICE), + P028_data_struct::getDeviceName(P028_data_struct::BMx_ChipId::BMP280_DEVICE), + }; + const int detectOptions[] = { + static_cast(P028_data_struct::BMx_DetectMode::BME280), + static_cast(P028_data_struct::BMx_DetectMode::BMP280), + }; + addFormSelector(F("Output values mode"), F("det"), 2, detectOptionList, detectOptions, P028_DETECTION_MODE); + + success = true; + } + break; + } + + +# if FEATURE_PLUGIN_STATS && FEATURE_CHART_JS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: + { + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if ((P028_data_struct::BMx_DetectMode::BMP280 != static_cast(P028_DETECTION_MODE)) && + P028_data->hasHumidity()) + { + P028_data->plot_ChartJS_scatter( + 0, + 1, + F("temphumscatter"), + { F("Temp/Humidity Scatter Plot") }, + { F("temp/hum"), F("rgb(255, 99, 132)") }, + 500, + 500); + } + } + // Do not set success = true, since we're not actually adding stats, but just plotting a scatter plot + break; + } +#endif + + + case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: + { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("BMx280: SHOW_ERRORSTATE_OPT")); + # endif // ifndef BUILD_NO_DEBUG + + // Value in case of Error + const __FlashStringHelper *resultsOptions[] = { + F("Ignore"), + F("Min -1 (-41°C)"), + F("0"), + F("Max +1 (+86°C)"), + F("NaN"), + # ifndef LIMIT_BUILD_SIZE + F("-1°K (-274°C)") + # endif // ifndef LIMIT_BUILD_SIZE + }; + const int resultsOptionValues[] = { + P028_ERROR_IGNORE, + P028_ERROR_MIN_RANGE, + P028_ERROR_ZERO, + P028_ERROR_MAX_RANGE, + P028_ERROR_NAN, + # ifndef LIMIT_BUILD_SIZE + P028_ERROR_MIN_K + # endif // ifndef LIMIT_BUILD_SIZE + }; + constexpr int P028_ERROR_STATE_COUNT = NR_ELEMENTS(resultsOptions); + addFormSelector(F("Temperature Error Value"), + F("err"), + P028_ERROR_STATE_COUNT, + resultsOptions, + resultsOptionValues, + P028_ERROR_STATE_OUTPUT); + + break; + } + + case PLUGIN_READ_ERROR_OCCURED: + { + // Called if PLUGIN_READ returns false + // Function returns "true" when last measurement was an error. + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if (P028_data->lastMeasurementError) { + success = true; // "success" may be a confusing name here + string = F("Sensor Not Found"); + } + } + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P028_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + P028_ALTITUDE = getFormItemInt(F("elev")); + P028_TEMPERATURE_OFFSET = getFormItemInt(F("tempoffset")); + P028_ERROR_STATE_OUTPUT = getFormItemInt(F("err")); + + if (Settings.TaskDeviceDataFeed[event->TaskIndex] != 0) { // We want to configure this only when a remote data-feed is used + P028_DETECTION_MODE = getFormItemInt(F("det")); + } + success = true; + break; + } + case PLUGIN_ONCE_A_SECOND: + { + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + if (P028_data->updateMeasurements(event->TaskIndex)) { + // Update was succesfull, schedule a read. + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + } + } + break; + } + + case PLUGIN_READ: + { + P028_data_struct *P028_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P028_data) { + // PLUGIN_READ is called from `TaskRun` or on the set interval or it has re-scheduled itself to output read samples. + // So if there aren't any new values, it must have been called to get a new sample. + if (P028_data->state != P028_data_struct::BMx_New_values) { + P028_data->startMeasurement(); + + if (P028_ERROR_STATE_OUTPUT != P028_ERROR_IGNORE) { + if (P028_data->lastMeasurementError) { + success = true; // "success" may be a confusing name here + + for (uint8_t i = 0; i < 3; i++) { + UserVar.setFloat(event->TaskIndex, i, ExtraTaskSettings.TaskDeviceErrorValue[i]); + } + } + } + } else { + P028_data->state = P028_data_struct::BMx_Values_read; + + if (!P028_data->hasHumidity()) { + // Patch the sensor type to output only the measured values. + event->sensorType = Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO; + event->idx = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_TEMP_EMPTY_BARO); + } + UserVar.setFloat(event->TaskIndex, 0, ExtraTaskSettings.checkAllowedRange(0, P028_data->last_temp_val)); + UserVar.setFloat(event->TaskIndex, 1, P028_data->last_hum_val); + const int elev = P028_ALTITUDE; + + if (elev != 0) { + UserVar.setFloat(event->TaskIndex, 2, pressureElevation(P028_data->last_press_val, elev)); + } else { + UserVar.setFloat(event->TaskIndex, 2, P028_data->last_press_val); + } + + # ifndef LIMIT_BUILD_SIZE + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String hum; + if (P028_data->hasHumidity()) { + hum = formatUserVarNoCheck(event, 1); + } + addLogMove(LOG_LEVEL_INFO, concat( + P028_data_struct::getDeviceName(P028_data->sensorID), + strformat( + F(": Addr: %s T: %s H: %s P: %s"), + formatToHex(P028_I2C_ADDRESS, 2).c_str(), + formatUserVarNoCheck(event, 0).c_str(), + hum.c_str(), + formatUserVarNoCheck(event, 2).c_str()))); + } + # endif // ifndef LIMIT_BUILD_SIZE + success = true; + } + } + break; + } + } + return success; +} + +#endif // USES_P028 diff --git a/src/_P029_Output.ino b/src/_P029_Output.ino index 9f737f33b..1acbbc552 100644 --- a/src/_P029_Output.ino +++ b/src/_P029_Output.ino @@ -1,74 +1,93 @@ #include "_Plugin_Helper.h" #ifdef USES_P029 -//####################################################################################################### -//#################################### Plugin 029: Output ############################################### -//####################################################################################################### +// ####################################################################################################### +// #################################### Plugin 029: Output ############################################### +// ####################################################################################################### + +/** Changelog: + * 2024-03-24 tonhuisman: Reformat source (Uncrustify), add option 'Invert On/Off value' + * 2024-03-24 tonhuisman: Start Changelog (newest on top) + */ + +# define PLUGIN_029 +# define PLUGIN_ID_029 29 +# define PLUGIN_NAME_029 "Output - Domoticz MQTT Helper" +# define PLUGIN_VALUENAME1_029 "Output" + +# define P029_INVERTED PCONFIG(0) -#define PLUGIN_029 -#define PLUGIN_ID_029 29 -#define PLUGIN_NAME_029 "Output - Domoticz MQTT Helper" -#define PLUGIN_VALUENAME1_029 "Output" boolean Plugin_029(uint8_t function, struct EventStruct *event, String& string) { boolean success = false; switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_029; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; // FIXME TD-er: Does this need a pin? Seems not to be used - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SWITCH; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = false; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_029; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SWITCH; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = false; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_029); - break; - } + { + string = F(PLUGIN_NAME_029); + break; + } case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_029)); - ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; - break; - } + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_029)); + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; + break; + } case PLUGIN_WEBFORM_LOAD: - { - // We need the index of the controller we are: 0-CONTROLLER_MAX - uint8_t controllerNr = 0; - for (controllerIndex_t i=0; i < CONTROLLER_MAX; i++) - { -// if (Settings.Protocol[i] == CPLUGIN_ID_002) { controllerNr = i; } -> error: 'CPLUGIN_ID_002' was not declared in this scope - if (Settings.Protocol[i] == 2) { controllerNr = i; } - } + { + // We need the index of the controller we are: 0-CONTROLLER_MAX + uint8_t controllerNr = 0; - addRowLabel(F("IDX")); - addNumericBox( - concat(F("TDID"), controllerNr + 1), //="taskdeviceid" - Settings.TaskDeviceID[controllerNr][event->TaskIndex], - 0, - DOMOTICZ_MAX_IDX); - success = true; - break; + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; i++) + { + // if (Settings.Protocol[i] == CPLUGIN_ID_002) { controllerNr = i; } -> error: 'CPLUGIN_ID_002' was not declared in + // this scope + if (Settings.Protocol[i] == 2) { controllerNr = i; } } + addRowLabel(F("IDX")); + addNumericBox( + concat(F("TDID"), controllerNr + 1), // ="taskdeviceid" + Settings.TaskDeviceID[controllerNr][event->TaskIndex], + 0, + DOMOTICZ_MAX_IDX); + + addFormCheckBox(F("Invert On/Off value"), F("inverted"), P029_INVERTED == 1); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P029_INVERTED = isFormItemChecked(F("inverted")) ? 1 : 0; + success = true; + break; + } case PLUGIN_INIT: - { - success = true; - break; - } + { + success = true; + break; + } } return success; } + #endif // USES_P029 diff --git a/src/_P031_SHT1X.ino b/src/_P031_SHT1X.ino index 0b57dc8f2..b8cfdf7f3 100644 --- a/src/_P031_SHT1X.ino +++ b/src/_P031_SHT1X.ino @@ -131,8 +131,8 @@ boolean Plugin_031(uint8_t function, struct EventStruct *event, String& string) if (P031_data->measurementReady()) { UserVar.setFloat(event->TaskIndex, 0, P031_data->tempC); UserVar.setFloat(event->TaskIndex, 1, P031_data->rhTrue); - success = true; - P031_data->state = P031_IDLE; + success = true; + P031_data->state = P031_IDLE; } else if (P031_data->state == P031_IDLE) { P031_data->startMeasurement(); } else if (P031_data->hasError()) { diff --git a/src/_P032_MS5611.ino b/src/_P032_MS5611.ino index e92e2fc02..372cecd79 100644 --- a/src/_P032_MS5611.ino +++ b/src/_P032_MS5611.ino @@ -1,138 +1,144 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P032 - -// ####################################################################################################### -// ################ Plugin 032 MS5611 (GY-63) I2C Temp/Barometric Pressure Sensor ####################### -// ####################################################################################################### -// This sketch is based on https://github.com/Schm1tz1/arduino-ms5xxx - - -# include "src/PluginStructs/P032_data_struct.h" - -# define PLUGIN_032 -# define PLUGIN_ID_032 32 -# define PLUGIN_NAME_032 "Environment - MS5611 (GY-63)" -# define PLUGIN_VALUENAME1_032 "Temperature" -# define PLUGIN_VALUENAME2_032 "Pressure" - -boolean Plugin_032(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_032; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_BARO; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_032); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_032)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_032)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x77, 0x76 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Altitude [m]"), F("elev"), PCONFIG(1)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("elev")); - success = true; - break; - } - - case PLUGIN_INIT: - { - success = initPluginTaskData( - event->TaskIndex, - new (std::nothrow) P032_data_struct(PCONFIG(0))); - break; - } - - case PLUGIN_READ: - { - P032_data_struct *P032_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P032_data) { - if (P032_data->begin()) { - P032_data->read_prom(); - P032_data->readout(); - - UserVar.setFloat(event->TaskIndex, 0, P032_data->ms5611_temperature / 100); - - const int elev = PCONFIG(1); - - if (elev != 0) - { - UserVar.setFloat(event->TaskIndex, 1, pressureElevation(P032_data->ms5611_pressure, elev)); - } else { - UserVar.setFloat(event->TaskIndex, 1, P032_data->ms5611_pressure); - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MS5611 : Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("MS5611 : Barometric Pressure: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - } - } - break; - } - } - return success; -} - -#endif // USES_P032 +#include "_Plugin_Helper.h" +#ifdef USES_P032 + +// ####################################################################################################### +// ################ Plugin 032 MS5611 (GY-63) I2C Temp/Barometric Pressure Sensor ####################### +// ####################################################################################################### +// This sketch is based on https://github.com/Schm1tz1/arduino-ms5xxx + + +# include "src/PluginStructs/P032_data_struct.h" + +# define PLUGIN_032 +# define PLUGIN_ID_032 32 +# define PLUGIN_NAME_032 "Environment - MS5611 (GY-63)" +# define PLUGIN_VALUENAME1_032 "Temperature" +# define PLUGIN_VALUENAME2_032 "Pressure" + +boolean Plugin_032(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_032; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_032); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_032)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_032)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x76, 0x77 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0), 0x77); + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(0) = 0x77; // Default address + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("Altitude [m]"), F("elev"), PCONFIG(1)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + PCONFIG(1) = getFormItemInt(F("elev")); + success = true; + break; + } + + case PLUGIN_INIT: + { + success = initPluginTaskData( + event->TaskIndex, + new (std::nothrow) P032_data_struct(PCONFIG(0))); + break; + } + + case PLUGIN_READ: + { + P032_data_struct *P032_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P032_data) { + if (P032_data->begin()) { + P032_data->read_prom(); + P032_data->readout(); + + UserVar.setFloat(event->TaskIndex, 0, P032_data->ms5611_temperature / 100); + + const int elev = PCONFIG(1); + + if (elev != 0) + { + UserVar.setFloat(event->TaskIndex, 1, pressureElevation(P032_data->ms5611_pressure, elev)); + } else { + UserVar.setFloat(event->TaskIndex, 1, P032_data->ms5611_pressure); + } + + 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))); + } + success = true; + } + } + break; + } + } + return success; +} + +#endif // USES_P032 diff --git a/src/_P033_Dummy.ino b/src/_P033_Dummy.ino index 709f63098..a5f7562b7 100644 --- a/src/_P033_Dummy.ino +++ b/src/_P033_Dummy.ino @@ -1,104 +1,102 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P033 - -// ####################################################################################################### -// #################################### Plugin 033: Dummy ################################################ -// ####################################################################################################### - -# define PLUGIN_033 -# 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; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_033; - Device[deviceCount].Type = DEVICE_TYPE_DUMMY; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = true; - Device[deviceCount].ValueCount = 4; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::All; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_033); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - // FIXME TD-er: Copy names as done in P026_Sysinfo.ino. - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_033)); - const Sensor_VType sensorType = static_cast(PCONFIG(0)); - if (isIntegerOutputDataType(sensorType)) { - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - ExtraTaskSettings.TaskDeviceValueDecimals[i] = 0; - } - } - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = getValueCountFromSensorType(static_cast(PCONFIG(0))); - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast(PCONFIG(0)); - event->idx = 0; - success = true; - break; - } - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - event->sensorType = static_cast(PCONFIG(0)); - #ifndef LIMIT_BUILD_SIZE - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const uint8_t valueCount = - getValueCountFromSensorType(static_cast(PCONFIG(0))); - for (uint8_t x = 0; x < valueCount; x++) - { - String log = F("Dummy: value "); - log += x + 1; - log += F(": "); - log += formatUserVarNoCheck(event->TaskIndex, x); - addLogMove(LOG_LEVEL_INFO, log); - } - } - #endif - success = true; - break; - } - - } - return success; -} - -#endif // USES_P033 +#include "_Plugin_Helper.h" +#ifdef USES_P033 + +// ####################################################################################################### +// #################################### Plugin 033: Dummy ################################################ +// ####################################################################################################### + +# define PLUGIN_033 +# 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; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_033; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::All; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_033); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + // FIXME TD-er: Copy names as done in P026_Sysinfo.ino. + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_033)); + const Sensor_VType sensorType = static_cast(PCONFIG(0)); + + if (isIntegerOutputDataType(sensorType)) { + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + ExtraTaskSettings.TaskDeviceValueDecimals[i] = 0; + } + } + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = getValueCountFromSensorType(static_cast(PCONFIG(0))); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast(PCONFIG(0)); + event->idx = 0; + success = true; + break; + } + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + event->sensorType = static_cast(PCONFIG(0)); + # ifndef LIMIT_BUILD_SIZE + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const uint8_t valueCount = + getValueCountFromSensorType(static_cast(PCONFIG(0))); + + for (uint8_t x = 0; x < valueCount; ++x) + { + addLog(LOG_LEVEL_INFO, + strformat(F("Dummy: value %d: %s"), x + 1, formatUserVarNoCheck(event, x).c_str())); + } + } + # endif // ifndef LIMIT_BUILD_SIZE + success = true; + break; + } + } + return success; +} + +#endif // USES_P033 diff --git a/src/_P034_DHT12.ino b/src/_P034_DHT12.ino index 7a86da27a..35c0a57e1 100644 --- a/src/_P034_DHT12.ino +++ b/src/_P034_DHT12.ino @@ -1,145 +1,140 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P034 - -// ####################################################################################################### -// ######################## Plugin 034: Temperature and Humidity sensor DHT 12 (I2C) ##################### -// ####################################################################################################### - - - -#define PLUGIN_034 -#define PLUGIN_ID_034 34 -#define PLUGIN_NAME_034 "Environment - DHT12 (I2C)" -#define PLUGIN_VALUENAME1_034 "Temperature" -#define PLUGIN_VALUENAME2_034 "Humidity" - -#define DHT12_I2C_ADDRESS 0x5C // I2C address for the sensor - -boolean Plugin_034(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_034; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_034); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_034)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_034)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == DHT12_I2C_ADDRESS); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = DHT12_I2C_ADDRESS; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - uint8_t dht_dat[5]; - - // uint8_t dht_in; - uint8_t i; - - // uint8_t Retry = 0; - boolean error = false; - - Wire.beginTransmission(DHT12_I2C_ADDRESS); // start transmission to device - Wire.write(0); // sends register address to read from - Wire.endTransmission(); // end transmission - - if (Wire.requestFrom(DHT12_I2C_ADDRESS, 5) == 5) { // send data n-bytes read - for (i = 0; i < 5; i++) - { - dht_dat[i] = Wire.read(); // receive DATA - } - } else { - error = true; - } - - if (!error) - { - // Checksum calculation is a Rollover Checksum by design! - uint8_t dht_check_sum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]; // check check_sum - - if (dht_dat[4] == dht_check_sum) - { - float temperature = float(dht_dat[2] * 10 + (dht_dat[3] & 0x7f)) / 10.0f; // Temperature - - if (dht_dat[3] & 0x80) { temperature = -temperature; } - float humidity = float(dht_dat[0] * 10 + dht_dat[1]) / 10.0f; // Humidity - - UserVar.setFloat(event->TaskIndex, 0, temperature); - UserVar.setFloat(event->TaskIndex, 1, humidity); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DHT12: Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("DHT12: Humidity: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - - /* - log = F("DHT12: Data: "); - for (int i=0; i < 5; i++) - { - log += dht_dat[i]; - log += ", "; - } - addLog(LOG_LEVEL_INFO, log); - */ - } - success = true; - } // checksum - } // error - - if (!success) - { - addLog(LOG_LEVEL_INFO, F("DHT12: No reading!")); - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - } - break; - } - } - return success; -} - -#endif // USES_P034 +#include "_Plugin_Helper.h" +#ifdef USES_P034 + +// ####################################################################################################### +// ######################## Plugin 034: Temperature and Humidity sensor DHT 12 (I2C) ##################### +// ####################################################################################################### + + +# define PLUGIN_034 +# define PLUGIN_ID_034 34 +# define PLUGIN_NAME_034 "Environment - DHT12 (I2C)" +# define PLUGIN_VALUENAME1_034 "Temperature" +# define PLUGIN_VALUENAME2_034 "Humidity" + +# define DHT12_I2C_ADDRESS 0x5C // I2C address for the sensor + +boolean Plugin_034(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_034; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_034); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_034)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_034)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == DHT12_I2C_ADDRESS); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = DHT12_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + uint8_t dht_dat[5]; + bool error = false; + + Wire.beginTransmission(DHT12_I2C_ADDRESS); // start transmission to device + Wire.write(0); // sends register address to read from + Wire.endTransmission(); // end transmission + + if (Wire.requestFrom(DHT12_I2C_ADDRESS, 5) == 5) { // send data n-bytes read + for (uint8_t i = 0; i < 5; ++i) + { + dht_dat[i] = Wire.read(); // receive DATA + } + } else { + error = true; + } + + if (!error) + { + // Checksum calculation is a Rollover Checksum by design! + const uint8_t dht_check_sum = dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]; // check check_sum + + if (dht_dat[4] == dht_check_sum) + { + const float temperature = static_cast( + (dht_dat[2] * 10 + (dht_dat[3] & 0x7f)) * (dht_dat[3] & 0x80 ? -1 : 1)) / 10.0f; // Temperature + + const float humidity = float(dht_dat[0] * 10 + dht_dat[1]) / 10.0f; // Humidity + + UserVar.setFloat(event->TaskIndex, 0, temperature); + UserVar.setFloat(event->TaskIndex, 1, humidity); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, + concat(F("DHT12: Temperature: "), + formatUserVarNoCheck(event, 0))); + addLogMove(LOG_LEVEL_INFO, + concat(F("DHT12: Humidity: "), + formatUserVarNoCheck(event, 1))); + + /* + log = F("DHT12: Data: "); + for (int i=0; i < 5; i++) + { + log += dht_dat[i]; + log += ", "; + } + addLog(LOG_LEVEL_INFO, log); + */ + } + success = true; + } // checksum + } // error + + if (!success) + { + addLog(LOG_LEVEL_INFO, F("DHT12: No reading!")); + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + } + break; + } + } + return success; +} + +#endif // USES_P034 diff --git a/src/_P036_FrameOLED.ino b/src/_P036_FrameOLED.ino index cb856223e..d04a4ba39 100644 --- a/src/_P036_FrameOLED.ino +++ b/src/_P036_FrameOLED.ino @@ -409,9 +409,9 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) { const __FlashStringHelper *options9[] = - { F("SSID"), F("SysName"), F("IP"), F("MAC"), F("RSSI"), - F("BSSID"), F("WiFi channel"), F("Unit"), F("SysLoad"), F("SysHeap"), - F("SysStack"), F("Date"), F("Time"), F("PageNumbers"), + { F("SSID"), F("SysName"), F("IP"), F("MAC"), F("RSSI"), + F("BSSID"), F("WiFi channel"), F("Unit"), F("SysLoad"), F("SysHeap"), + F("SysStack"), F("Date"), F("Time"), F("PageNumbers"), # if P036_USERDEF_HEADERS F("User defined 1"), F("User defined 2"), @@ -512,7 +512,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) html_table_header(F("Modify font")); html_table_header(F("Alignment")); - for (int varNr = 0; varNr < P36_Nlines; varNr++) + for (int varNr = 0; varNr < P36_Nlines; ++varNr) { html_TR_TD(); // All columns use max. width available addHtml(F(" ")); @@ -634,7 +634,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) P036_CheckHeap(F("_SAVE: After (*P036_lines = new)")); # endif // P036_CHECK_HEAP - for (uint8_t varNr = 0; varNr < P36_Nlines; varNr++) + for (uint8_t varNr = 0; varNr < P36_Nlines; ++varNr) { P036_lines.DisplayLinesV1[varNr].Content = webArg(getPluginCustomArgName(varNr)); P036_lines.DisplayLinesV1[varNr].FontType = 0xff; @@ -1034,9 +1034,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P036_data) { - if (P036_data->isInitialized()) { - success = P036_data->plugin_write(event, string); - } + success = P036_data->plugin_write(event, string); } break; @@ -1045,20 +1043,13 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) return success; } - # ifdef P036_CHECK_HEAP void P036_CheckHeap(String dbgtxt) { - String log; - - log.reserve(80); - log += dbgtxt; - log += F(" FreeHeap:"); - log += ESP.getFreeHeap(); - log += F(" FreeStack:"); - log += getCurrentFreeStack(); - addLog(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("%s FreeHeap:%d FreeStack:%d"), + dbgtxt.c_str(), ESP.getFreeHeap(), getCurrentFreeStack())); } # endif // ifdef P036_CHECK_HEAP -#endif // USES_P036 +#endif // USES_P036 \ No newline at end of file diff --git a/src/_P037_MQTTImport.ino b/src/_P037_MQTTImport.ino index 99e0d79c9..69ca16807 100644 --- a/src/_P037_MQTTImport.ino +++ b/src/_P037_MQTTImport.ino @@ -13,6 +13,7 @@ /** * 2023-06-17, tonhuisman: Replace Device[].FormulaOption by Device[].DecimalsOnly option, as no (successful) PLUGIN_READ is done * 2023-03-06, tonhuisman: Fix PLUGIN_INIT behavior to now always return success = true + * 2022-12-13, tonhuisman: Implement separator character input selector * 2022-11-14, tonhuisman: Add support for selecting JSON sub-attributes, using the . notation, like main.sub (1 level only) * 2022-11-02, tonhuisman: Enable plugin to generate events initially, like the plugin did before the mapping, filtering and json parsing * features were added @@ -196,11 +197,8 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) } # if P037_REPLACE_BY_COMMA_SUPPORT { - String character = F(" "); - character[0] = (P037_REPLACE_BY_COMMA == 0 ? 0x20 : static_cast(P037_REPLACE_BY_COMMA)); - addRowLabel(F("To replace by comma in event")); - addTextBox(F("preplch"), character, 1, false, false, F("[!@$%^ &*;:.|/\\]"), F("widenumber")); - addUnit(F("Single character only, limited to: ! @ $ % ^ & * ; : . | / \\ is replaced by: , ")); + addFormSeparatorCharInput(F("To replace by comma in event"), F("preplch"), + P037_REPLACE_BY_COMMA, F(P037_REPLACE_CHAR_SET), F("")); } # endif // if P037_REPLACE_BY_COMMA_SUPPORT @@ -253,13 +251,9 @@ boolean Plugin_037(uint8_t function, struct EventStruct *event, String& string) P037_SEND_EVENTS = isFormItemChecked(F("p037_send_events")) ? 1 : 0; P037_DEDUPLICATE_EVENTS = isFormItemChecked(F("pdedupe")) ? 1 : 0; P037_QUEUEDEPTH_EVENTS = getFormItemInt(F("pquedepth")); - # if P037_REPLACE_BY_COMMA_SUPPORT - String character = webArg(F("preplch")); - P037_REPLACE_BY_COMMA = character[0]; - if (P037_REPLACE_BY_COMMA == 0x20) { // Space -> 0 - P037_REPLACE_BY_COMMA = 0x0; - } + # if P037_REPLACE_BY_COMMA_SUPPORT + P037_REPLACE_BY_COMMA = getFormItemInt(F("preplch")); # endif // if P037_REPLACE_BY_COMMA_SUPPORT success = P037_data->webform_save( diff --git a/src/_P039_Thermosensors.ino b/src/_P039_Thermosensors.ino index 281da5c64..096d14108 100644 --- a/src/_P039_Thermosensors.ino +++ b/src/_P039_Thermosensors.ino @@ -1,2131 +1,2139 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P039 - -// ####################################################################################################### -// ######################## Plugin 039: Thermocouple (MAX6675 / MAX31855) ################################ -// ####################################################################################################### - -// Original work by Dominik - -// Plugin Description -// This Plugin reads the data from Thermocouples. You have to use an Adapter Board with a -// MAX6675 or MAX31855 in order to read the values. Take a look at ebay 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. -// But nevertheless you need at least 3 Pins to use SPI. So using an very simple ESP-01 is no option - Sorry. -// The Wiring is straight forward ... -// -// If you like to send suggestions feel free to send me an email : dominik@logview.info -// Have fun ... Dominik - -/** Changelog: - * 2023-01-08 tonhuisman: Add Low temperature threshold setting (default 0 K/-273.15 C) to ignore temperatures below that value - * 2023-01-02 tonhuisman: Cleanup and uncrustify source - * 2022-10-22 tonhuisman: Correct CS pin check to allow GPIO0 - * 2022-10: Older changelog not recorded - */ - -// Wiring -// https://de.wikipedia.org/wiki/Serial_Peripheral_Interface -// You need an ESP8266 device with accessible SPI Pins. These are: -// Name Description GPIO NodeMCU Notes -// MOSI Master Output GPIO13 D7 Not used (No Data sending to MAX) -// MISO Master Input GPIO12 D6 Hardware SPI -// SCK Clock Output GPIO14 D5 Hardware SPI -// CS Chip Select GPIO15 D8 Hardware SPI (CS is configurable through the web interface) - -// Thermocouple Infos -// http://www.bristolwatch.com/ele2/therc.htm - -// Resistor Temperature Detector Infos -// https://en.wikipedia.org/wiki/Resistance_thermometer - -// Chips -// MAX6675 - Cold-Junction-Compensated K-Thermocouple-to-Digital Converter ( 0°C to +1024°C) -// https://cdn-shop.adafruit.com/datasheets/MAX6675.pdf (only -// MAX31855 - Cold-Junction Compensated Thermocouple-to-Digital Converter (-270°C to +1800°C) -// https://cdn-shop.adafruit.com/datasheets/MAX31855.pdf -// MAX31856 - Precision Thermocouple to Digital Converter with Linearization (-210°C to +1800°C) -// https://datasheets.maximintegrated.com/en/ds/MAX31856.pdf -// MAX31865 - Precision Resistor Temperature Detector to Digital Converter with Linearization (PT100 / PT1000) -// https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf -// TI Digital Temperature sensors with SPI interface -// https://www.ti.com/sensors/temperature-sensors/digital/products.html#p1918=SPI,%20Microwire -// TI LM7x - Digital temperature sensor with SPI interface -// https://www.ti.com/lit/gpn/LM70 -// https://www.ti.com/lit/gpn/LM71 -// https://www.ti.com/lit/gpn/LM70 -// https://www.ti.com/lit/gpn/LM74 -// TI TMP12x Digital temperature sensor with SPI interface -// https://www.ti.com/lit/gpn/TMP121 -// https://www.ti.com/lit/gpn/TMP122 -// https://www.ti.com/lit/gpn/TMP123 -// https://www.ti.com/lit/gpn/TMP124 - -# include - -// #include -# include "src/PluginStructs/P039_data_struct.h" - - -// // plugin-local quick activation of debug messages -// #ifdef BUILD_NO_DEBUG -// #undef BUILD_NO_DEBUG -// #endif - - -# define MAX31865_RD_ADDRESS(n) (MAX31865_READ_ADDR_BASE + (n)) -# define MAX31865_WR_ADDRESS(n) (MAX31865_WRITE_ADDR_BASE + (n)) - -# define PLUGIN_039 -# define PLUGIN_ID_039 39 -# define PLUGIN_NAME_039 "Environment - Thermosensors" -# define PLUGIN_VALUENAME1_039 "Temperature" - -# define P039_SET true -# define P039_RESET false - -// typically 500ns of wating on positive/negative edge of CS should be enough ( -> datasheet); to make sure we cover a lot of devices we -// spend 1ms -// FIX 2021-05-05: review of all covered device datasheets showed 2µs is more than enough; review with every newly added device -# define P039_CS_Delay() delayMicroseconds(2u) - -# define P039_MAX_TYPE PCONFIG(0) -# define P039_TC_TYPE PCONFIG(1) -# define P039_FAM_TYPE PCONFIG(2) -# define P039_RTD_TYPE PCONFIG(3) -# define P039_CONFIG_4 PCONFIG(4) -# define P039_RTD_FILT_TYPE PCONFIG(5) -# define P039_RTD_LM_TYPE PCONFIG(6) -# define P039_RTD_LM_SHTDWN PCONFIG(7) -# define P039_RTD_RES PCONFIG_LONG(0) -# define P039_FLAGS PCONFIG_ULONG(3) -# define P039_TEMP_THRESHOLD_FLAG 0 -# define P039_RTD_OFFSET PCONFIG_FLOAT(0) -# define P039_TEMP_THRESHOLD PCONFIG_FLOAT(1) - -# define P039_TEMP_THRESHOLD_DEFAULT (-273.15f) // Default and minimum value -# define P039_TEMP_THRESHOLD_MIN P039_TEMP_THRESHOLD_DEFAULT -# define P039_TEMP_THRESHOLD_MAX (1000.0f) // Max value -# define P039_TC 0u -# define P039_RTD 1u - -# define P039_MAX6675 1 -# define P039_MAX31855 2 -# define P039_MAX31856 3 -# define P039_MAX31865 4 -# define P039_LM7x 5 - -// MAX 6675 related defines - -// bit masks to identify failures for MAX 6675 -# define MAX6675_TC_DEVID 0x0002u -# define MAX6675_TC_OC 0x0004u - -// MAX 31855 related defines - -// bit masks to identify failures for MAX 31855 -# define MAX31855_TC_OC 0x00000001u -# define MAX31855_TC_SC 0x00000002u -# define MAX31855_TC_SCVCC 0x00000004u -# define MAX31855_TC_GENFLT 0x00010000u - - -// MAX 31856 related defines - -// base address for read/write acces to MAX 31856 -# define MAX31856_READ_ADDR_BASE 0x00u -# define MAX31856_WRITE_ADDR_BASE 0x80u - -// register offset values for MAX 31856 -# define MAX31856_CR0 0u -# define MAX31856_CR1 1u -# define MAX31856_MASK 2u -# define MAX31856_CJHF 3u -# define MAX31856_CJLF 4u -# define MAX31856_LTHFTH 5u -# define MAX31856_LTHFTL 6u -# define MAX31856_LTLFTH 7u -# define MAX31856_LTLFTL 8u -# define MAX31856_CJTO 9u -# define MAX31856_CJTH 10u -# define MAX31856_CJTL 11u -# define MAX31856_LTCBH 12u -# define MAX31856_LTCBM 13u -# define MAX31856_LTCBL 14u -# define MAX31856_SR 15u - -# define MAX31856_NO_REG 16u - -// bit masks to identify failures for MAX 31856 -# define MAX31856_TC_OC 0x01u -# define MAX31856_TC_OVUV 0x02u -# define MAX31856_TC_TCLOW 0x04u -# define MAX31856_TC_TCLHIGH 0x08u -# define MAX31856_TC_CJLOW 0x10u -# define MAX31856_TC_CJHIGH 0x20u -# define MAX31856_TC_TCRANGE 0x40u -# define MAX31856_TC_CJRANGE 0x80u - -// bit masks for access of configuration bits -# define MAX31856_SET_50HZ 0x01u -# define MAX31856_CLEAR_FAULTS 0x02u -# define MAX31856_FLT_ISR_MODE 0x04u -# define MAX31856_CJ_SENS_DISABLE 0x08u -# define MAX31856_FAULT_CTRL_MASK 0x30u -# define MAX31856_SET_ONE_SHOT 0x40u -# define MAX31856_SET_CONV_AUTO 0x80u - - -// RTD related defines - -// MAX 31865 related defines - -// waiting time until "in sequence" conversion is ready (-> used in case device is set to shutdown in between call cycles) -// typically 70ms should be fine, according to datasheet maximum -> 66ms - give a little adder to "be sure" conversion is done -// alternatively ONE SHOT bit could be polled (system/SPI bus load !) -# define MAX31865_CONVERSION_TIME 70ul -# define MAX31865_BIAS_WAIT_TIME 10ul - -// MAX 31865 Main States -# define MAX31865_INIT_STATE 0u -# define MAX31865_BIAS_ON_STATE 1u -# define MAX31865_RD_STATE 2u -# define MAX31865_RDY_STATE 3u - -// sensor type -# define MAX31865_PT100 0u -# define MAX31865_PT1000 1u - -// base address for read/write acces to MAX 31865 -# define MAX31865_READ_ADDR_BASE 0x00u -# define MAX31865_WRITE_ADDR_BASE 0x80u - -// register offset values for MAX 31865 -# define MAX31865_CONFIG 0u -# define MAX31865_RTD_MSB 1u -# define MAX31865_RTD_LSB 2u -# define MAX31865_HFT_MSB 3u -# define MAX31865_HFT_LSB 4u -# define MAX31865_LFT_MSB 5u -# define MAX31865_LFT_LSB 6u -# define MAX31865_FAULT 7u - -// total number of registers in MAX 31865 -# define MAX31865_NO_REG 8u - -// bit masks to identify failures for MAX 31865 -# define MAX31865_FAULT_HIGHTHRESH 0x80u -# define MAX31865_FAULT_LOWTHRESH 0x40u -# define MAX31865_FAULT_REFINLOW 0x20u -# define MAX31865_FAULT_REFINHIGH 0x10u -# define MAX31865_FAULT_RTDINLOW 0x08u -# define MAX31865_FAULT_OVUV 0x04u - -// bit masks for access of configuration bits -# define MAX31865_SET_50HZ 0x01u -# define MAX31865_CLEAR_FAULTS 0x02u -# define MAX31865_FAULT_CTRL_MASK 0x0Cu -# define MAX31865_SET_3WIRE 0x10u -# define MAX31865_SET_ONE_SHOT 0x20u -# define MAX31865_SET_CONV_AUTO 0x40u -# define MAX31865_SET_VBIAS_ON 0x80u - -// LM7x related defines - -// LM7x subtype defines -# define LM7x_SD70 0x00u -# define LM7x_SD71 0x01u -# define LM7x_SD74 0x04u -# define LM7x_SD121 0x05u -# define LM7x_SD122 0x06u -# define LM7x_SD123 0x07u -# define LM7x_SD124 0x08u -# define LM7x_SD125 0x09u - -// bit masks for access of configuration bits -# define LM7x_CONV_RDY 0x02u - - -void P039_AddMainsFrequencyFilterSelection(struct EventStruct *event); - -boolean Plugin_039(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_039; - Device[deviceCount].Type = DEVICE_TYPE_SPI; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_039); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_039)); - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_output(F("CS")); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K - bitSet(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG); - break; - } - - case PLUGIN_INIT: - { - if (!bitRead(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG)) { - P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K - } - - if (P039_MAX_TYPE < P039_MAX6675 || P039_MAX_TYPE > P039_LM7x) { - break; - } - - - - initPluginTaskData(event->TaskIndex, new (std::nothrow) P039_data_struct()); - P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - // set the slaveSelectPin as an output: - init_SPI_CS_Pin(CS_pin_no); - - // initialize SPI: - SPI.setHwCs(false); - SPI.begin(); - - // ensure MODE3 access to SPI device - SPI.setDataMode(SPI_MODE3); - -/* - if (P039_MAX_TYPE == P039_MAX6675) { - - // SPI.setBitOrder(MSBFIRST); - } -*/ - if (P039_MAX_TYPE == P039_MAX31855) { - // SPI.setBitOrder(MSBFIRST); - - if (nullptr != P039_data) { - // FIXED: c.k.i. : moved static fault flag to instance data structure - P039_data->sensorFault = false; - } - } - - - if (P039_MAX_TYPE == P039_MAX31856) { - // init string - content accoring to inital implementation of P039 - MAX31856 read function - // write to Adress 0x80 - // activate 50Hz filter in CR0, choose averaging and TC type from configuration in CR1, activate OV/UV/OC faults, write defaults to - // CJHF, CJLF, LTHFTH, LTHFTL, LTLFTH, LTLFTL, CJTO - uint8_t sendBuffer[11] = - { 0x80, static_cast(P039_RTD_FILT_TYPE), static_cast((P039_CONFIG_4 << 4) | P039_TC_TYPE), 0xFC, 0x7F, 0xC0, 0x7F, - 0xFF, 0x80, 0x00, 0x00 }; - - transfer_n_ByteSPI(CS_pin_no, 11, &sendBuffer[0]); - - if (nullptr != P039_data) { - // FIXED: c.k.i. : moved static fault flag to instance data structure - P039_data->sensorFault = false; - } - - // start on shot conversion for upcoming read cycle - change8BitRegister(CS_pin_no, - (MAX31856_READ_ADDR_BASE + MAX31856_CR0), - (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), - MAX31856_SET_ONE_SHOT, - P039_SET); - } - - - if (P039_MAX_TYPE == P039_MAX31865) { - // two step initialization buffer - uint8_t initSendBufferHFTH[3] = { (MAX31865_WRITE_ADDR_BASE + MAX31865_HFT_MSB), 0xFF, 0xFF }; - uint8_t initSendBufferLFTH[3] = { (MAX31865_WRITE_ADDR_BASE + MAX31865_HFT_MSB), 0xFF, 0xFF }; - - // write intially 0x00 to CONFIG register - write8BitRegister(CS_pin_no, (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), 0x00u); - - // activate 50Hz filter, clear all faults, no auto conversion, no conversion started - change8BitRegister(CS_pin_no, - MAX31865_RD_ADDRESS(MAX31865_CONFIG), - MAX31865_WR_ADDRESS(MAX31865_CONFIG), - MAX31865_SET_50HZ, - static_cast(P039_RTD_FILT_TYPE)); - - // configure 2/4-wire sensor connection as default - MAX31865_setConType(CS_pin_no, P039_CONFIG_4); - - // set HighFault Threshold - transfer_n_ByteSPI(CS_pin_no, 3, &initSendBufferHFTH[0]); - - // set LowFault Threshold - transfer_n_ByteSPI(CS_pin_no, 3, &initSendBufferLFTH[0]); - - // clear all faults - MAX31865_clearFaults(CS_pin_no); - - // activate BIAS short before read, to reduce power consumption - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_VBIAS_ON, - P039_SET); - - if (nullptr != P039_data) { - // save current timer for next calculation - P039_data->timer = millis(); - - // start time to follow up on BIAS activation before starting the conversion - // and start conversion sequence via TIMER API - - Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); - } - } - -/* - if (P039_MAX_TYPE == P039_LM7x) - { - // TODO: c.k.i.: more detailed inits depending on the sub devices expected , e.g. TMP 122/124 - } -*/ -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("P039 : %s : SPI Init - DONE"), getTaskDeviceName(event->TaskIndex).c_str())); - } -#endif - - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("Sensor Family Selection")); - - const uint8_t family = P039_FAM_TYPE; - { - const __FlashStringHelper *Foptions[2] = { F("Thermocouple"), F("RTD") }; - const int FoptionValues[2] = { P039_TC, P039_RTD }; - addFormSelector(F("Sensor Family Type"), F("famtype"), 2, Foptions, FoptionValues, family, true); // auto reload activated - } - - const uint8_t choice = P039_MAX_TYPE; - - addFormSubHeader(F("Device Type Settings")); - if (family == P039_TC) { - { - const __FlashStringHelper *options[3] = { F("MAX 6675"), F("MAX 31855"), F("MAX 31856") }; - const int optionValues[3] = { P039_MAX6675, P039_MAX31855, P039_MAX31856 }; - addFormSelector(F("Adapter IC"), F("maxtype"), 3, options, optionValues, choice, true); // auto reload activated - } - - if (choice == P039_MAX31856) { - addFormSubHeader(F("Device Settings")); - { - const __FlashStringHelper *Toptions[10] = { F("B"), F("E"), F("J"), F("K"), F("N"), F("R"), F("S"), F("T"), F("VM8"), F("VM32") }; - - // 2021-05-17: c.k.i.: values are directly written to device register for configuration, therefore no linear values are used - // here - // MAX 31856 datasheet (page 20): - // Thermocouple Type - // 0000 = B Type - // 0001 = E Type - // 0010 = J Type - // 0011 = K Type (default) - // 0100 = N Type - // 0101 = R Type - // 0110 = S Type - // 0111 = T Type - // 10xx = Voltage Mode, Gain = 8. Code = 8 x 1.6 x 217 x VIN - // 11xx = Voltage Mode, Gain = 32. Code = 32 x 1.6 x 217 x VIN - // Where Code is 19 bit signed number from TC registers and VIN is thermocouple input voltage - - const int ToptionValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12 }; - addFormSelector(F("Thermocouple type"), F("tctype"), 10, Toptions, ToptionValues, P039_TC_TYPE); - } - { - const __FlashStringHelper *Coptions[5] = { F("1"), F("2"), F("4"), F("8"), F("16") }; - const int CoptionValues[5] = { 0, 1, 2, 3, 4 }; - addFormSelector(F("Averaging"), F("contype"), 5, Coptions, CoptionValues, P039_CONFIG_4); - addUnit(F("sample(s)")); - } - P039_AddMainsFrequencyFilterSelection(event); - } - } - else { - { - const __FlashStringHelper *TPoptions[2] = { F("MAX 31865"), F("LM7x") }; - const int TPoptionValues[2] = { P039_MAX31865, P039_LM7x }; - addFormSelector(F("Adapter IC"), F("maxtype"), 2, TPoptions, TPoptionValues, choice, true); // auto reload activated - addFormNote(F("LM7x support is experimental.")); - } - - - if (choice == P039_MAX31865) - { - { - addFormSubHeader(F("Device Settings")); - } - { - const __FlashStringHelper *PToptions[2] = { F("PT100"), F("PT1000") }; - const int PToptionValues[2] = { MAX31865_PT100, MAX31865_PT1000 }; - addFormSelector(F("Resistor Type"), F("rtdtype"), 2, PToptions, PToptionValues, P039_RTD_TYPE); - } - { - const __FlashStringHelper *Coptions[2] = { F("2-/4"), F("3") }; - const int CoptionValues[2] = { 0, 1 }; - addFormSelector(F("Connection Type"), F("contype"), 2, Coptions, CoptionValues, P039_CONFIG_4); - addUnit(F("wire")); - } - - P039_AddMainsFrequencyFilterSelection(event); - - { - addFormNumericBox(F("Reference Resistor"), F("res"), P039_RTD_RES, 0); - addUnit(F("Ohm")); - addFormNote(F("PT100: typically 430 [OHM]; PT1000: typically 4300 [OHM]")); - } - { - addFormFloatNumberBox(F("Temperature Offset"), F("offset"), P039_RTD_OFFSET, -50.0f, 50.0f, 2, 0.01f); - addUnit('K'); - # ifndef BUILD_NO_DEBUG - addFormNote(F("Valid values: [-50.0...50.0 K], min. stepsize: [0.01]")); - #endif - } - } - - if (choice == P039_LM7x) - { - { - addFormSubHeader(F("Device Settings")); - } - - { - const __FlashStringHelper *PToptions[8] = - { F("LM70"), F("LM71"), F("LM74"), F("TMP121"), F("TMP122"), F("TMP123"), F("TMP124"), F("TMP125") }; - const int PToptionValues[8] = { LM7x_SD70, LM7x_SD71, LM7x_SD74, LM7x_SD121, LM7x_SD122, LM7x_SD123, LM7x_SD124, LM7x_SD125 }; - addFormSelector(F("LM7x device details"), F("rtd_lm_type"), 8, PToptions, PToptionValues, P039_RTD_LM_TYPE); - addFormNote(F("TMP122/124 Limited support -> fixed 12 Bit res, no advanced options")); - } - { - addFormCheckBox(F("Enable Shutdown Mode"), F("rtd_lm_shtdwn"), P039_RTD_LM_SHTDWN); - # ifndef BUILD_NO_DEBUG - addFormNote(F("Device is set to shutdown between sample cycles. Useful for very long call cycles, to save power.\nWithout LM7x device conversion happens in between call cycles. Call Cylces should therefore not become lower than 350ms.")); - #endif - } - } - } - - addFormSubHeader(F("Value validation")); - - if (!bitRead(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG)) { - P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K - } - addFormFloatNumberBox(F("Low temperature threshold"), - F("temp_thres"), - P039_TEMP_THRESHOLD, - P039_TEMP_THRESHOLD_MIN, - P039_TEMP_THRESHOLD_MAX, - 2u); - addUnit(F("°C")); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P039_FAM_TYPE = getFormItemInt(F("famtype")); - P039_MAX_TYPE = getFormItemInt(F("maxtype")); - P039_TC_TYPE = getFormItemInt(F("tctype")); - P039_RTD_TYPE = getFormItemInt(F("rtdtype")); - P039_CONFIG_4 = getFormItemInt(F("contype")); - P039_RTD_FILT_TYPE = getFormItemInt(F("filttype")); - P039_RTD_RES = getFormItemInt(F("res")); - P039_RTD_OFFSET = getFormItemFloat(F("offset")); - P039_RTD_LM_TYPE = getFormItemInt(F("rtd_lm_type")); - P039_RTD_LM_SHTDWN = isFormItemChecked(F("rtd_lm_shtdwn")); - P039_TEMP_THRESHOLD = getFormItemFloat(F("temp_thres")); - bitSet(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG); // We've set a value, don't replace by default - - success = true; - break; - } - - case PLUGIN_READ: - { - // Get the MAX Type (6675 / 31855 / 31856) - uint8_t MaxType = P039_MAX_TYPE; - - float Plugin_039_Celsius = NAN; - - switch (MaxType) { - case P039_MAX6675: - Plugin_039_Celsius = readMax6675(event); - break; - case P039_MAX31855: - Plugin_039_Celsius = readMax31855(event); - break; - case P039_MAX31856: - Plugin_039_Celsius = readMax31856(event); - break; - case P039_MAX31865: - Plugin_039_Celsius = readMax31865(event); - break; - case P039_LM7x: - Plugin_039_Celsius = readLM7x(event); - break; - } - - if (isValidFloat(Plugin_039_Celsius)) - { - UserVar.setFloat(event->TaskIndex, 0, Plugin_039_Celsius); - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = strformat(F("P039 : %s :"), getTaskDeviceName(event->TaskIndex).c_str()); - for (uint8_t i = 0; i < getValueCountForTask(event->TaskIndex); i++) - { - log += strformat( - F(" %s: %s"), - getTaskValueName(event->TaskIndex, i).c_str(), - formatUserVarNoCheck(event->TaskIndex, i).c_str()); - } - addLogMove(LOG_LEVEL_INFO, log); - } -#endif - - if (definitelyGreaterThan(Plugin_039_Celsius, P039_TEMP_THRESHOLD)) { - success = true; - } - } - else - { - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, strformat(F("P039 : %s : No Sensor attached!"), getTaskDeviceName(event->TaskIndex).c_str())); - } - success = false; - } - - break; - } - - case PLUGIN_TASKTIMER_IN: - { - P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - // Get the MAX Type (6675 / 31855 / 31856) - uint8_t MaxType = P039_MAX_TYPE; - - switch (MaxType) - { - case P039_MAX31865: - { - if ((nullptr != P039_data)) { - switch (event->Par1) - { - case MAX31865_BIAS_ON_STATE: - { - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat( - F("P039 : %s : current state: MAX31865_BIAS_ON_STATE; delta: %d ms"), - getTaskDeviceName(event->TaskIndex).c_str(), - timePassedSince(P039_data->timer))); // calc delta since last call - } - # endif // ifndef BUILD_NO_DEBUG - - // save current timer for next calculation - P039_data->timer = millis(); - - // activate one shot conversion - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_ONE_SHOT, - P039_SET); - - // set next state in sequence -> READ STATE - // start time to follow up on conversion and read the conversion result - P039_data->convReady = false; - Scheduler.setPluginTaskTimer(MAX31865_CONVERSION_TIME, event->TaskIndex, MAX31865_RD_STATE); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLog(LOG_LEVEL_DEBUG, strformat( - F("P039 : %s : Next State: %d"), - getTaskDeviceName(event->TaskIndex).c_str(), - event->Par1)); - } - # endif // ifndef BUILD_NO_DEBUG - - break; - } - case MAX31865_RD_STATE: - { - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat( - F("P039 : %s : current state: MAX31865_RD_STATE; delta: %d ms"), - getTaskDeviceName(event->TaskIndex).c_str(), - timePassedSince(P039_data->timer))); // calc delta since last call - } - # endif // ifndef BUILD_NO_DEBUG - - // save current timer for next calculation - P039_data->timer = millis(); - - // read conversion result - P039_data->conversionResult = read16BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_RTD_MSB)); - - // deactivate BIAS short after read, to reduce power consumption - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_VBIAS_ON, - P039_RESET); - - // read fault register to get a full picture - P039_data->deviceFaults = read8BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_FAULT)); - - // mark conversion as ready - P039_data->convReady = true; - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - - if ((log.reserve(170u))) { // reserve value derived from example log file - log = F("P039 : "); // 7 char - log += getTaskDeviceName(event->TaskIndex); // 41 char ( max length of task device name + 1) - log += F(" : conversionResult: "); // 21 char - log += formatToHex_decimal(P039_data->conversionResult); // 11 char - log += F("; deviceFaults: "); // 16 char - log += formatToHex_decimal(P039_data->deviceFaults); // 9 char - log += F("; Next State: "); // 13 char - log += event->Par1; // 4 char - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - # endif // ifndef BUILD_NO_DEBUG - - - break; - } - case MAX31865_INIT_STATE: - default: - { - // clear all faults - MAX31865_clearFaults(CS_pin_no); - - // activate BIAS short before read, to reduce power consumption - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_VBIAS_ON, - P039_SET); - - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - - if ((log.reserve(140u))) { // reserve value derived from example log file - log = F("P039 : "); // 7 char - log += getTaskDeviceName(event->TaskIndex); // 41 char - log += F(" : "); // 3 char - log += F("current state: MAX31865_INIT_STATE, default;"); // many char - 44 - log += F(" next state: MAX31865_BIAS_ON_STATE"); // a little less char - 35 - addLogMove(LOG_LEVEL_DEBUG, log); - } - - // save current timer for next calculation - P039_data->timer = millis(); - } - # endif // ifndef BUILD_NO_DEBUG - - // start time to follow up on BIAS activation before starting the conversion - // and start conversion sequence via TIMER API - // set next state in sequence -> BIAS ON STATE - - Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); - - - break; - } - } - } - break; - } - default: - { - break; - } - } - - success = true; - break; - } - } - return success; -} - -void P039_AddMainsFrequencyFilterSelection(struct EventStruct *event) -{ - const __FlashStringHelper *FToptions[2] = { F("60"), F("50") }; - const int FToptionValues[2] = { 0, 1 }; - addFormSelector(F("Supply Frequency Filter"), F("filttype"), 2, FToptions, FToptionValues, P039_RTD_FILT_TYPE); - addUnit(F("Hz")); - addFormNote(F("Filter power net frequency (50/60 Hz)")); -} - -float readMax6675(struct EventStruct *event) -{ - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - uint8_t messageBuffer[2] = { 0 }; - uint16_t rawvalue = 0u; - - - // "transfer" 2 bytes to SPI to get 16 Bit return value - transfer_n_ByteSPI(CS_pin_no, 2, &messageBuffer[0]); - - // merge 16Bit return value from messageBuffer - rawvalue = ((messageBuffer[0] << 8) | messageBuffer[1]); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(130u))) { // reserve value derived from example log file - log = F("P039 : MAX6675 : RAW - BIN: "); // 27 char - log += String(rawvalue, BIN); // 18 char - log += F(" HEX: "); // 5 char - log += formatToHex(rawvalue); // 4 char - log += F(" DEC: "); // 5 char - log += String(rawvalue); // 5 char - log += F(" MSB: "); // 5 char - log += formatToHex_decimal(messageBuffer[0]); // 9 char - log += F(" LSB: "); // 5 char - log += formatToHex_decimal(messageBuffer[1]); // 9 char - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - // Open Thermocouple - // Bit D2 is normally low and goes high if the thermocouple input is open. In order to allow the operation of the - // open thermocouple detector, T- must be grounded. Make the ground connection as close to the GND pin - // as possible. - // 2021-05-11: FIXED: c.k.i.: OC Flag already checked; migrated to #define for improved maintenance - const bool Plugin_039_SensorAttached = !(rawvalue & MAX6675_TC_OC); - - if (Plugin_039_SensorAttached) - { - // shift RAW value 3 Bits to the right to get the data - rawvalue >>= 3; - - // calculate Celsius with device resolution 0.25 K/bit - return rawvalue * 0.25f; - } - else - { - return NAN; - } -} - -float readMax31855(struct EventStruct *event) -{ - P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); - - uint8_t messageBuffer[4] = { 0 }; - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - // "transfer" 0x0 and read the 32 Bit conversion register from the Chip - transfer_n_ByteSPI(CS_pin_no, 4, &messageBuffer[0]); - - // merge rawvalue from 4 bytes of messageBuffer - uint32_t rawvalue = - ((static_cast(messageBuffer[0]) << - 24) | - (static_cast(messageBuffer[1]) << - 16) | (static_cast(messageBuffer[2]) << 8) | static_cast(messageBuffer[3])); - - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(200u))) { // reserve value derived from example log file - log = F("P039 : MAX31855 : RAW - BIN: "); // 35 char - log += String(rawvalue, BIN); // 16 char - log += F(" rawvalue,HEX: "); // 15 char - log += formatToHex(rawvalue); // 4 char - log += F(" rawvalue,DEC: "); // 15 char - log += rawvalue; // 5 char - log += F(" messageBuffer[],HEX:"); // 21 char - - for (size_t i = 0u; i < 4; i++) - { - log += ' '; // 1 char - log += formatToHex_decimal(messageBuffer[i]); // 9 char - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - if (nullptr != P039_data) { - // FIXED: c.k.i. : moved static fault flag to instance data structure - - // check for fault flags in LSB of 32 Bit messageBuffer - if (P039_data->sensorFault != ((rawvalue & (MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)) == 0)) { - // Fault code changed, log them - P039_data->sensorFault = ((rawvalue & (MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)) == 0); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(120u))) { // reserve value derived from example log file - log = F("P039 : MAX31855 : "); - - if ((P039_data->sensorFault)) { - log += F("Fault resolved"); - } else { - log += F("Fault code :"); - - if (rawvalue & MAX31855_TC_OC) { - log += F(" Open (no connection)"); - } - - if (rawvalue & MAX31855_TC_SC) { - log += F(" Short-circuit to GND"); - } - - if (rawvalue & MAX31855_TC_SCVCC) { - log += F(" Short-circuit to Vcc"); - } - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - # endif // ifndef BUILD_NO_DEBUG - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(120u))) { // reserve value derived from example log file - log = F("P039 : MAX31855 : "); - log += F("rawvalue: "); - log += formatToHex_decimal(rawvalue); - log += F(" P039_data->sensorFault: "); - log += formatToHex_decimal(P039_data->sensorFault); - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - } - - // D16 - This bit reads at 1 when any of the SCV, SCG, or OC faults are active. Default value is 0. - // 2020-05-11: FIXED: c.k.i.: migrated plain flag mask to #defines to enhance maintainability; added all fault flags for safety reasons - const bool Plugin_039_SensorAttached = !(rawvalue & (MAX31855_TC_GENFLT | MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)); - - if (Plugin_039_SensorAttached) - { - // Data is D[31:18] - // Shift RAW value 18 Bits to the right to get the data - rawvalue >>= 18; - - // Check for negative Values - // +25.00 0000 0001 1001 00 - // 0.00 0000 0000 0000 00 - // -0.25 1111 1111 1111 11 - // -1.00 1111 1111 1111 00 - // -250.00 1111 0000 0110 00 - // We're left with (32 - 18 =) 14 bits - int temperature = Plugin_039_convert_two_complement(rawvalue, 14); - - // Calculate Celsius - return temperature * 0.25f; - } - else - { - // Fault state, thus output no value. - return NAN; - } -} - -float readMax31856(struct EventStruct *event) -{ - P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - - uint8_t registers[MAX31856_NO_REG] = { 0 }; - uint8_t messageBuffer[MAX31856_NO_REG + 1] = { 0 }; - - messageBuffer[0] = MAX31856_READ_ADDR_BASE; - - // "transfer" 0x0 starting at address 0x00 and read the all registers from the Chip - transfer_n_ByteSPI(CS_pin_no, (MAX31856_NO_REG + 1), &messageBuffer[0]); - - // transfer data from messageBuffer and get rid of initial address uint8_t - for (uint8_t i = 0u; i < MAX31856_NO_REG; ++i) { - registers[i] = messageBuffer[i + 1]; - } - - // configure device for next conversion - // activate frequency filter according to configuration - change8BitRegister(CS_pin_no, - (MAX31856_READ_ADDR_BASE + MAX31856_CR0), - (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), - MAX31856_SET_50HZ, - static_cast(P039_RTD_FILT_TYPE)); - - // set averaging and TC type - write8BitRegister(CS_pin_no, (MAX31856_WRITE_ADDR_BASE + MAX31856_CR1), static_cast((P039_CONFIG_4 << 4) | P039_TC_TYPE)); - - - // start on shot conversion for next read cycle - change8BitRegister(CS_pin_no, - (MAX31856_READ_ADDR_BASE + MAX31856_CR0), - (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), - MAX31856_SET_ONE_SHOT, - P039_SET); - - - // now derive raw value from respective registers - uint32_t rawvalue = static_cast(registers[MAX31856_LTCBH]); - - rawvalue = (rawvalue << 8) | static_cast(registers[MAX31856_LTCBM]); - rawvalue = (rawvalue << 8) | static_cast(registers[MAX31856_LTCBL]); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(210u))) { // reserve value derived from example log file - log = F("P039 : MAX31856 :"); - - for (uint8_t i = 0; i < MAX31856_NO_REG; ++i) { - log += ' '; - log += formatToHex_decimal(registers[i]); - } - log += F(" rawvalue: "); - log += formatToHex_decimal(rawvalue); - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - - // ignore TC Range Bit in case Voltage Modes are used - // datasheet: - // Thermocouple Out-of-Range fault. - // 0 = The Thermocouple Hot Junction temperature is within the normal operating range (see Table 1). - // 1 = The Thermocouple Hot Junction temperature is outside of the normal operating range. - // Note: The TC Range bit should be ignored in voltage mode. - uint8_t sr = registers[MAX31856_SR]; - - if ((8u == P039_TC_TYPE) || (12u == P039_TC_TYPE)) { - sr &= ~MAX31856_TC_TCRANGE; - } - - - // FIXED: c.k.i. : moved static fault flag to instance data structure - if ((nullptr != P039_data)) { - // P039_data->sensorFault = false; - - P039_data->sensorFault = (sr != 0); // Set new state - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - // FIXME TD-er: Part of expression is always false (sr == 0) - const bool faultResolved = (P039_data->sensorFault) && (sr == 0); - - if ((P039_data->sensorFault) || faultResolved) { - String log; - - if ((log.reserve(140u))) { // reserve value derived from example log file - log = F("P039 : MAX31856 : "); - - if ((P039_data->sensorFault) == 0) { - log += F("Fault resolved"); - } else { - log += F("Fault :"); - - if (sr & MAX31856_TC_OC) { - log += F(" Open (no connection)"); - } - - if (sr & MAX31856_TC_OVUV) { - log += F(" Over/Under Voltage"); - } - - if (sr & MAX31856_TC_TCLOW) { - log += F(" TC Low"); - } - - if (sr & MAX31856_TC_TCLHIGH) { - log += F(" TC High"); - } - - if (sr & MAX31856_TC_CJLOW) { - log += F(" CJ Low"); - } - - if (sr & MAX31856_TC_CJHIGH) { - log += F(" CJ High"); - } - - if (sr & MAX31856_TC_TCRANGE) { - log += F(" TC Range"); - } - - if (sr & MAX31856_TC_CJRANGE) { - log += F(" CJ Range"); - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - } - } - # endif // ifndef BUILD_NO_DEBUG - } - - - const bool Plugin_039_SensorAttached = (sr == 0); - - if (Plugin_039_SensorAttached) - { - rawvalue >>= 5; // bottom 5 bits are unused - // We're left with (24 - 5 =) 19 bits - - { - float temperature = 0; - - switch (P039_TC_TYPE) - { - case 8: - { - temperature = rawvalue / 1677721.6f; // datasheet: rawvalue = 8 x 1.6 x 2^17 x VIN -> VIN = rawvalue / (8 x 1.6 x 2^17) - break; - } - case 12: - { - temperature = rawvalue / 6710886.4f; // datasheet: rawvalue = 32 x 1.6 x 2^17 x VIN -> VIN = rawvalue / (32 x 1.6 x 2^17) - break; - } - default: - { - temperature = Plugin_039_convert_two_complement(rawvalue, 19); - - // Calculate Celsius - temperature /= 128.0f; - break; - } - } - - return temperature; - } - } - else - { - // Fault state, thus output no value. - return NAN; - } -} - -float readMax31865(struct EventStruct *event) -{ - P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (P039_data == nullptr) { - return NAN; - } - - uint8_t registers[MAX31865_NO_REG] = { 0 }; - uint16_t rawValue = 0u; - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(80u))) { // reserve value derived from example log file - log = F("P039 : MAX31865 :"); - log += F(" P039_data->convReady: "); - log += boolToString(P039_data->convReady); - - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - - // read conversion result and faults from plugin data structure - // if pointer exists and conversion has been finished - if (P039_data->convReady) { - rawValue = P039_data->conversionResult; - registers[MAX31865_FAULT] = P039_data->deviceFaults; - } - - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(160u))) { // reserve value derived from example log file - for (uint8_t i = 0u; i < MAX31865_NO_REG; ++i) - { - registers[i] = read8BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + i)); - } - - log = F("P039 : MAX31865 :"); - - for (uint8_t i = 0u; i < MAX31865_NO_REG; ++i) - { - log += ' '; - log += formatToHex_decimal(registers[i]); - } - - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - // Prepare and start next conversion, before handling faults and rawValue - // clear all faults - MAX31865_clearFaults(CS_pin_no); - - // set frequency filter - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_50HZ, - static_cast(P039_RTD_FILT_TYPE)); - - - // configure read access with configuration from web interface - MAX31865_setConType(CS_pin_no, P039_CONFIG_4); - - // activate BIAS short before read, to reduce power consumption - change8BitRegister(CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_VBIAS_ON, - P039_SET); - - // start time to follow up on BIAS activation before starting the conversion - // and start conversion sequence via TIMER API - // save current timer for next calculation - P039_data->timer = millis(); - - // set next state to MAX31865_BIAS_ON_STATE - - Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - if (registers[MAX31865_FAULT]) - { - String log; - - if ((log.reserve(210u))) { // reserve value derived from example log file - log = F("P039 : MAX31865 : "); - - log += F("Fault : "); - log += formatToHex_decimal(registers[MAX31865_FAULT]); - log += F(" :"); - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_OVUV) - { - log += F(" Under/Over voltage"); - } - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_RTDINLOW) - { - log += F(" RTDIN- < 0.85 x Bias - FORCE- open"); - } - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_REFINHIGH) - { - log += F(" REFIN- < 0.85 x Bias - FORCE- open"); - } - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_REFINLOW) - { - log += F(" REFIN- > 0.85 x Bias"); - } - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_LOWTHRESH) - { - log += F(" RTD Low Threshold"); - } - - if (registers[MAX31865_FAULT] & MAX31865_FAULT_HIGHTHRESH) - { - log += F(" RTD High Threshold"); - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - } - # endif // ifndef BUILD_NO_DEBUG - - - bool ValueValid = false; - - if (registers[MAX31865_FAULT] == 0x00u) { - ValueValid = true; - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(85u))) { // reserve value derived from example log file - log = F("P039 : Temperature :"); // 20 char - log += F(" registers[MAX31865_FAULT]: "); // 33 char - log += formatToHex_decimal(registers[MAX31865_FAULT]); // 7 char - log += F(" ValueValid: "); // 13 char - log += boolToString(ValueValid); // 5 char - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - if (ValueValid) - { - rawValue >>= 1; // bottom fault bits is unused - - float temperature = Plugin_039_convert_to_temperature(rawValue, getNomResistor(P039_RTD_TYPE), P039_RTD_RES); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(110u))) { // reserve value derived from example log file - log = F("P039 : Temperature :"); // 20 char - log += F(" rawValue: "); // 11 char - log += formatToHex_decimal(rawValue); // 9 char - log += F(" temperature: "); // 14 char - log += temperature; // 11 char - log += F(" P039_RTD_TYPE: "); // 16 char - log += P039_RTD_TYPE; // 1 char - log += F(" P039_RTD_RES: "); // 15 char - log += P039_RTD_RES; // 4 char - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - // add offset handling from configuration webpage - temperature += P039_RTD_OFFSET; - - // Calculate Celsius - return temperature; - } - else - { - // Fault state, thus output no value. - return NAN; - } -} - -void MAX31865_clearFaults(int8_t l_CS_pin_no) -{ - uint8_t l_reg = 0u; - - // read in config register - l_reg = read8BitRegister(l_CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG)); - - - // clear all faults ( write "0" to D2, D3, D5; write "1" to D2) - l_reg &= ~(MAX31865_SET_ONE_SHOT | MAX31865_FAULT_CTRL_MASK); - l_reg |= MAX31865_CLEAR_FAULTS; - - // write configuration - write8BitRegister(l_CS_pin_no, (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), l_reg); -} - -void MAX31865_setConType(int8_t l_CS_pin_no, uint8_t l_conType) -{ - bool l_set_reset = false; - - // configure if 3 WIRE bit will be set/reset - switch (l_conType) - { - case 0: - l_set_reset = P039_RESET; - break; - case 1: - l_set_reset = P039_SET; - break; - default: - l_set_reset = P039_RESET; - break; - } - - // change to configuration register - change8BitRegister(l_CS_pin_no, - (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), - (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), - MAX31865_SET_3WIRE, - l_set_reset); -} - -/**************************************************************************/ - -/*! - @brief Read the temperature in C from the RTD through calculation of the - resistance. Uses - http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf - technique - @param RTDnominal The 'nominal' resistance of the RTD sensor, usually 100 - or 1000 - @param refResistor The value of the matching reference resistor, usually - 430 or 4300 - @returns Temperature in C - */ - -/**************************************************************************/ -float Plugin_039_convert_to_temperature(uint32_t l_rawvalue, float RTDnominal, float refResistor) -{ - # define RTD_A 3.9083e-3f - # define RTD_B -5.775e-7f - - float Z1, Z2, Z3, Z4, Rt, temp; - - Rt = l_rawvalue; - Rt /= 32768u; - Rt *= refResistor; - - Z1 = -RTD_A; - Z2 = RTD_A * RTD_A - (4 * RTD_B); - Z3 = (4 * RTD_B) / RTDnominal; - Z4 = 2 * RTD_B; - - temp = Z2 + (Z3 * Rt); - temp = (sqrtf(temp) + Z1) / Z4; - - if (temp >= 0) { - return temp; - } - - Rt /= RTDnominal; - Rt *= 100; // normalize to 100 ohm - - float rpoly = Rt; - - temp = -242.02f; - temp += 2.2228f * rpoly; - rpoly *= Rt; // square - temp += 2.5859e-3f * rpoly; - rpoly *= Rt; // ^3 - temp -= 4.8260e-6f * rpoly; - rpoly *= Rt; // ^4 - temp -= 2.8183e-8f * rpoly; - rpoly *= Rt; // ^5 - temp += 1.5243e-10f * rpoly; - - return temp; -} - -uint16_t getNomResistor(uint8_t l_RType) -{ - uint16_t l_returnValue = 100u; - - switch (l_RType) - { - case MAX31865_PT100: - l_returnValue = 100u; - break; - case MAX31865_PT1000: - l_returnValue = 1000u; - break; - default: - l_returnValue = 100u; - break; - } - return l_returnValue; -} - -int Plugin_039_convert_two_complement(uint32_t value, int nr_bits) { - const bool negative = (value & (1 << (nr_bits - 1))) != 0; - int nativeInt; - - if (negative) { - // Add zeroes to the left to create the proper negative native-sized integer. - nativeInt = value | ~((1 << nr_bits) - 1); - } else { - nativeInt = value; - } - return nativeInt; -} - -float readLM7x(struct EventStruct *event) -{ - float temperature = 0.0f; - uint16_t device_id = 0u; - uint16_t rawValue = 0u; - - int8_t CS_pin_no = get_SPI_CS_Pin(event); - - // operate LM7x devices in polling mode, assuming conversion is ready with every call of this read function ( >=210ms call cycle) - // this allows usage of multiples generations of LM7x devices, that doe not provde conversion ready information in temperature register - - rawValue = readLM7xRegisters(CS_pin_no, P039_RTD_LM_TYPE, P039_RTD_LM_SHTDWN, &device_id); - - temperature = convertLM7xTemp(rawValue, P039_RTD_LM_TYPE); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) - { - String log; - - if ((log.reserve(130u))) { // reserve value derived from example log file - log = F("P039 : LM7x : readLM7x : "); - log += F(" rawValue: "); - log += formatToHex_decimal(rawValue); - log += F(" device_id: "); - log += formatToHex(device_id); - log += F(" temperature: "); - log += temperature; - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - return temperature; -} - -float convertLM7xTemp(uint16_t l_rawValue, uint16_t l_LM7xsubtype) -{ - float l_returnValue = 0.0f; - float l_lsbvalue = 0.0f; - uint8_t l_noBits = 0u; - int l_intTemperature = 0; - - switch (l_LM7xsubtype) - { - case LM7x_SD70: - l_rawValue >>= 5; - l_lsbvalue = 0.25f; - l_noBits = 11u; - break; - case LM7x_SD71: - l_rawValue >>= 2; - l_lsbvalue = 0.03125f; - l_noBits = 14u; - break; - case LM7x_SD74: - l_rawValue >>= 3; - l_lsbvalue = 0.0625f; - l_noBits = 13u; - break; - case LM7x_SD121: - case LM7x_SD122: - case LM7x_SD123: - case LM7x_SD124: - l_rawValue >>= 4; - l_lsbvalue = 0.0625f; - l_noBits = 12u; - break; - case LM7x_SD125: - l_rawValue >>= 5; - l_lsbvalue = 0.25f; - l_noBits = 10u; - break; - default: // use lowest resolution as fallback if no device has been configured - l_rawValue >>= 5; - l_lsbvalue = 0.25f; - l_noBits = 11u; - break; - } - - l_intTemperature = Plugin_039_convert_two_complement(l_rawValue, l_noBits); - - l_returnValue = l_intTemperature * l_lsbvalue; - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(185u))) { // reserve value derived from example log file - log = F("P039 : LM7x : convertLM7xTemp : "); - log += F(" l_returnValue: "); - log += formatToHex_decimal(l_returnValue); - log += F(" l_LM7xsubtype: "); - log += formatToHex_decimal(l_LM7xsubtype); - log += F(" l_rawValue: "); - log += formatToHex_decimal(l_rawValue); - log += F(" l_noBits: "); - log += l_noBits; - log += F(" l_lsbvalue: "); - log += l_lsbvalue; - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - return l_returnValue; -} - -uint16_t readLM7xRegisters(int8_t l_CS_pin_no, uint8_t l_LM7xsubType, uint8_t l_runMode, uint16_t *l_device_id) -{ - uint16_t l_returnValue = 0u; - uint16_t l_mswaitTime = 0u; - - - switch (l_LM7xsubType) - { - case LM7x_SD70: - case LM7x_SD71: - case LM7x_SD74: - l_mswaitTime = 300; - break; - case LM7x_SD121: - case LM7x_SD122: - case LM7x_SD123: - case LM7x_SD124: - l_mswaitTime = 320; - break; - case LM7x_SD125: - l_mswaitTime = 100; - break; - default: - l_mswaitTime = 500; - break; - } - - // // activate communication -> CS low - // handle_SPI_CS_Pin(l_CS_pin_no, LOW); - - if (l_runMode) - { - // shutdown mode active -> conversion when called - uint8_t messageBuffer[12] = { 0xFF, 0xFF, 0xFF, 0X00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF }; - - // send inital 4 bytes to wake the device and start the conversion - transfer_n_ByteSPI(l_CS_pin_no, 4, &messageBuffer[0]); - - // wait specific ms for conversion to be ready (TI datasheet per devices) - delay(l_mswaitTime); - - // send remaining 8 bytes to read the device ID and shutdown the device - transfer_n_ByteSPI(l_CS_pin_no, 8, &messageBuffer[4]); - - // read temperature value (16 Bit) - l_returnValue = ((messageBuffer[4] << 8) | messageBuffer[5]); - - // read Manufatures/Device ID (16 Bit) - *(l_device_id) = ((messageBuffer[8] << 8) | messageBuffer[9]); - - // // wakeup device and start conversion - // // initial read of conversion result is obsolete - // SPI.transfer16(0xFFFF); - - // // (wakeup device with "all zero2 message in the last 8 bits - // SPI.transfer16(0xFF00); - - // //wait specific ms for conversion to be ready (TI datasheet per devices) - // delay(l_mswaitTime); - - // //read temperature value (16 Bit) - // l_returnValue = SPI.transfer16(0x0000); - // // l_returnValue <<= 8; - // // l_returnValue = SPI.transfer(0x00); - - // // set device to shutdown with "all one" message in the last 8 bits - // SPI.transfer16(0xFFFF); - - // // read Manufatures/Device ID (16 Bit) - // *(l_device_id) = SPI.transfer16(0x0000); - // // *(l_device_id) <<= 8; - // // *(l_device_id) = SPI.transfer(0x00); - - // // set device to shutdown with "all one" message in the last 8 bits ( maybe redundant, check with test) - // SPI.transfer16(0xFFFF); - } - else - { - // shutdown mode inactive -> normal background conversion during call cycle - uint8_t messageBuffer[8] = { 0x00, 0x00, 0xFF, 0XFF, 0x00, 0x00, 0x00, 0x00 }; - - transfer_n_ByteSPI(l_CS_pin_no, 8, &messageBuffer[0]); - - // read temperature value (16 Bit) - l_returnValue = ((messageBuffer[0] << 8) | messageBuffer[1]); - - // read Manufatures/Device ID (16 Bit) - *(l_device_id) = ((messageBuffer[4] << 8) | messageBuffer[5]); - - - // l_returnValue = SPI.transfer16(0x0000); //read temperature value (16 Bit) - // // l_returnValue <<= 8; - // // l_returnValue = SPI.transfer(0x00); - - // // set device to shutdown - // SPI.transfer16(0xFFFF); - - // // read Manufatures/Device ID (16 Bit) - // *(l_device_id) = SPI.transfer16(0x0000); - // // *(l_device_id) <<= 8; - // // *(l_device_id) = SPI.transfer(0x00); - - // // start conversion until next read (8 Bit sufficient) - // // 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F allowed - else device goes to test mode (not desirable here) - // SPI.transfer(0x00); - // // SPI.transfer16(0x0000); - } - - // // stop communication -> CS high - // handle_SPI_CS_Pin(l_CS_pin_no, HIGH); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(115u))) { // reserve value derived from example log file - log = F("P039 : LM7x : readLM7xRegisters : "); - log += F(" l_returnValue: "); - log += formatToHex_decimal(l_returnValue); - log += F(" l_device_id: "); - log += formatToHex(*(l_device_id)); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - return l_returnValue; -} - -// POSSIBLE START OF GENERIC SPI HIGH LEVEL FUNCTIONS WITH POTENTIAL OF SYSTEM WIDE RE-USE - -/**************************************************************************/ - -/*! - @brief generic high level library to access SPI interface from plugins - with GPIO pin handled as CS - chri.kai.in 2021 - - Initial Revision - chri.kai.in 2021 - - TODO: c.k.i.: make it generic and carve out to generic _SPI_helper.c library - - - /**************************************************************************/ - - -/**************************************************************************/ - -/*! - - @brief Identifying the CS pin from the event basic data structure - @param event pointer to the event structure; default GPIO is chosen as GPIO 15 - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -int get_SPI_CS_Pin(struct EventStruct *event) { // If no Pin is in Config we use 15 as default -> Hardware Chip Select on ESP8266 - if (CONFIG_PIN1 != -1) { - return CONFIG_PIN1; - } - return 15; // D8 -} - -/**************************************************************************/ - -/*! - @brief Initializing GPIO as OUTPUT for CS for SPI communication - @param l_CS_pin_no the GPIO pin number used as CS - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void init_SPI_CS_Pin(int8_t l_CS_pin_no) { - // set the slaveSelectPin as an output: - pinMode(l_CS_pin_no, OUTPUT); -} - -/**************************************************************************/ - -/*! - @brief Handling GPIO as CS for SPI communication - @param l_CS_pin_no the GPIO pin number used as CS - @param l_state the state of the CS pin: "HIGH/LOW" reflecting the physical level - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void handle_SPI_CS_Pin(int8_t l_CS_pin_no, bool l_state) { - P039_CS_Delay(); // tCWH (min) >= x00ns - digitalWrite(l_CS_pin_no, l_state); - P039_CS_Delay(); // tCC (min) >= x00ns -} - -/**************************************************************************/ - -/*! - @brief write 8 bits to adress l_address on the SPI interface, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_address the register addess of the connected SPI device - @param value the unsigned 8 Bit message to be transferred - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void write8BitRegister(int8_t l_CS_pin_no, uint8_t l_address, uint8_t value) -{ - uint8_t l_messageBuffer[2] = { l_address, value }; - - transfer_n_ByteSPI(l_CS_pin_no, 2, l_messageBuffer); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(100u))) { // reserve value derived from example log file - log = F("P039 : SPI : write8BitRegister : "); - log += F("l_address: "); - log += formatToHex(l_address); - log += F(" value: "); - log += formatToHex_decimal(value); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG -} - -/**************************************************************************/ - -/*! - @brief write 16 bits to adress l_address on the SPI interface, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_address the register addess of the connected SPI device - @param value the unsigned 16 Bit message to be transferred - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void write16BitRegister(int8_t l_CS_pin_no, uint8_t l_address, uint16_t value) -{ - uint8_t l_messageBuffer[3] = { l_address, static_cast((value >> 8) & 0xFF), static_cast(value & 0xFF) }; - - transfer_n_ByteSPI(l_CS_pin_no, 3, l_messageBuffer); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(110u))) { // reserve value derived from example log file - log = F("P039 : SPI : write16BitRegister : "); - log += F("l_address: "); - log += formatToHex(l_address); - log += F(" value: "); - log += formatToHex_decimal(value); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG -} - -/**************************************************************************/ - -/*! - @brief read 8 bits from adress l_address on the SPI interface, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_address the register addess of the connected SPI device - - @returns the unsigned 8 Bit message read from l_address - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -uint8_t read8BitRegister(int8_t l_CS_pin_no, uint8_t l_address) -{ - uint8_t l_messageBuffer[2] = { l_address, 0x00 }; - - transfer_n_ByteSPI(l_CS_pin_no, 2, l_messageBuffer); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(100u))) { // reserve value derived from example log file - log = F("P039 : SPI : read8BitRegister : "); - log += F("l_address: "); - log += formatToHex(l_address); - log += F(" returnvalue: "); - log += formatToHex_decimal(l_messageBuffer[1]); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - return l_messageBuffer[1]; -} - -/**************************************************************************/ - -/*! - @brief write 16 bits to adress l_address on the SPI interface, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_address the register addess of the connected SPI device - - @returns the unsigned 16 Bit message read from l_address - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -uint16_t read16BitRegister(int8_t l_CS_pin_no, uint8_t l_address) -{ - uint8_t l_messageBuffer[3] = { l_address, 0x00, 0x00 }; - uint16_t l_returnValue; - - transfer_n_ByteSPI(l_CS_pin_no, 3, l_messageBuffer); - l_returnValue = ((l_messageBuffer[1] << 8) | l_messageBuffer[2]); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(110u))) { // reserve value derived from example log file - log = F("P039 : SPI : read16BitRegister : "); - log += F("l_address: "); - log += formatToHex(l_address); - log += F(" l_returnValue: "); - log += formatToHex_decimal(l_returnValue); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG - - return l_returnValue; -} - -/**************************************************************************/ - -/*! - @brief read from/write to dedicated number of bytes from/to SPI, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_noBytesToSend number of bytes to read/write from/to SPI - @param l_inoutMessageBuffer pointer to the messsage buffer to provide bytes to send - and provide read bytes from the SPI bus after the call - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void transfer_n_ByteSPI(int8_t l_CS_pin_no, uint8_t l_noBytesToSend, uint8_t *l_inoutMessageBuffer) -{ - // activate communication -> CS low - handle_SPI_CS_Pin(l_CS_pin_no, LOW); - - for (size_t i = 0u; i < l_noBytesToSend; i++) - { - l_inoutMessageBuffer[i] = SPI.transfer(l_inoutMessageBuffer[i]); - } - - // stop communication -> CS high - handle_SPI_CS_Pin(l_CS_pin_no, HIGH); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) - { - String log; - - if ((log.reserve(120u))) { // reserve value derived from example log file - log = F("P039 : SPI : transfer_n_ByteSPI : "); // 34 char - - for (uint8_t i = 0; i < l_noBytesToSend; ++i) - { - log += ' '; // 1 char - log += formatToHex_decimal(l_inoutMessageBuffer[i]); // 9 char - } - addLogMove(LOG_LEVEL_DEBUG_MORE, log); - } - } - - # endif // ifndef BUILD_NO_DEBUG -} - -/**************************************************************************/ - -/*! - @brief read a 16Bit register and change a flag, writing it back, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_readaddress SPI read address of the device register - @param l_writeaddress SPI write address of the device register - @param l_flagmask mask set to apply on the read register - @param l_set_reset controls if flag mask will be set (-> true) or reset ( -> false) - - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void change16BitRegister(int8_t l_CS_pin_no, uint8_t l_readaddress, uint8_t l_writeaddress, uint16_t l_flagmask, bool l_set_reset) -{ - uint16_t l_reg = 0u; - - // read in config register - l_reg = read16BitRegister(l_CS_pin_no, l_readaddress); - - if (l_set_reset) { - l_reg |= l_flagmask; - } - else - { - l_reg &= ~(l_flagmask); - } - - // write to configuration register - write16BitRegister(l_CS_pin_no, l_writeaddress, l_reg); -} - -/**************************************************************************/ - -/*! - @brief read a 8 Bit register and change a flag, writing it back, handling a GPIO CS - @param l_CS_pin_no the GPIO pin number used as CS - @param l_readaddress SPI read address of the device register - @param l_writeaddress SPI write address of the device register - @param l_flagmask mask set to apply on the read register - @param l_set_reset controls if flag mask will be set (-> true) or reset ( -> false) - - - @returns - - Initial Revision - chri.kai.in 2021 - - /**************************************************************************/ -void change8BitRegister(int8_t l_CS_pin_no, uint8_t l_readaddress, uint8_t l_writeaddress, uint8_t l_flagmask, bool l_set_reset) -{ - uint8_t l_reg = 0u; - - // read in config register - l_reg = read8BitRegister(l_CS_pin_no, l_readaddress); - - - // TODO: c.k.i.: analyze opportunity to use arduino bitSet/Clear macros instead - if (l_set_reset) { - l_reg |= l_flagmask; - } - else - { - l_reg &= ~(l_flagmask); - } - - // write to configuration register - write8BitRegister(l_CS_pin_no, l_writeaddress, l_reg); -} - -#endif // USES_P039 +#include "_Plugin_Helper.h" +#ifdef USES_P039 + +// ####################################################################################################### +// ######################## Plugin 039: Thermocouple (MAX6675 / MAX31855) ################################ +// ####################################################################################################### + +// Original work by Dominik + +// Plugin Description +// This Plugin reads the data from Thermocouples. You have to use an Adapter Board with a +// MAX6675 or MAX31855 in order to read the values. Take a look at ebay 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. +// But nevertheless you need at least 3 Pins to use SPI. So using an very simple ESP-01 is no option - Sorry. +// The Wiring is straight forward ... +// +// If you like to send suggestions feel free to send me an email : dominik@logview.info +// Have fun ... Dominik + +/** Changelog: + * 2024-01-04 tonhuisman: Minor corrections, formatted source using Uncrustify + * 2023-01-08 tonhuisman: Add Low temperature threshold setting (default 0 K/-273.15 C) to ignore temperatures below that value + * 2023-01-02 tonhuisman: Cleanup and uncrustify source + * 2022-10-22 tonhuisman: Correct CS pin check to allow GPIO0 + * 2022-10: Older changelog not recorded + */ + +// Wiring +// https://de.wikipedia.org/wiki/Serial_Peripheral_Interface +// You need an ESP8266 device with accessible SPI Pins. These are: +// Name Description GPIO NodeMCU Notes +// MOSI Master Output GPIO13 D7 Not used (No Data sending to MAX) +// MISO Master Input GPIO12 D6 Hardware SPI +// SCK Clock Output GPIO14 D5 Hardware SPI +// CS Chip Select GPIO15 D8 Hardware SPI (CS is configurable through the web interface) + +// Thermocouple Infos +// http://www.bristolwatch.com/ele2/therc.htm + +// Resistor Temperature Detector Infos +// https://en.wikipedia.org/wiki/Resistance_thermometer + +// Chips +// MAX6675 - Cold-Junction-Compensated K-Thermocouple-to-Digital Converter ( 0°C to +1024°C) +// https://cdn-shop.adafruit.com/datasheets/MAX6675.pdf (only +// MAX31855 - Cold-Junction Compensated Thermocouple-to-Digital Converter (-270°C to +1800°C) +// https://cdn-shop.adafruit.com/datasheets/MAX31855.pdf +// MAX31856 - Precision Thermocouple to Digital Converter with Linearization (-210°C to +1800°C) +// https://datasheets.maximintegrated.com/en/ds/MAX31856.pdf +// MAX31865 - Precision Resistor Temperature Detector to Digital Converter with Linearization (PT100 / PT1000) +// https://datasheets.maximintegrated.com/en/ds/MAX31865.pdf +// TI Digital Temperature sensors with SPI interface +// https://www.ti.com/sensors/temperature-sensors/digital/products.html#p1918=SPI,%20Microwire +// TI LM7x - Digital temperature sensor with SPI interface +// https://www.ti.com/lit/gpn/LM70 +// https://www.ti.com/lit/gpn/LM71 +// https://www.ti.com/lit/gpn/LM70 +// https://www.ti.com/lit/gpn/LM74 +// TI TMP12x Digital temperature sensor with SPI interface +// https://www.ti.com/lit/gpn/TMP121 +// https://www.ti.com/lit/gpn/TMP122 +// https://www.ti.com/lit/gpn/TMP123 +// https://www.ti.com/lit/gpn/TMP124 + +# include + +// #include +# include "src/PluginStructs/P039_data_struct.h" + + +// // plugin-local quick activation of debug messages +// #ifdef BUILD_NO_DEBUG +// #undef BUILD_NO_DEBUG +// #endif + + +# define MAX31865_RD_ADDRESS(n) (MAX31865_READ_ADDR_BASE + (n)) +# define MAX31865_WR_ADDRESS(n) (MAX31865_WRITE_ADDR_BASE + (n)) + +# define PLUGIN_039 +# define PLUGIN_ID_039 39 +# define PLUGIN_NAME_039 "Environment - Thermosensors" +# define PLUGIN_VALUENAME1_039 "Temperature" + +# define P039_SET true +# define P039_RESET false + +// typically 500ns of wating on positive/negative edge of CS should be enough ( -> datasheet); to make sure we cover a lot of devices we +// spend 1ms +// FIX 2021-05-05: review of all covered device datasheets showed 2µs is more than enough; review with every newly added device +# define P039_CS_Delay() delayMicroseconds(2u) + +# define P039_MAX_TYPE PCONFIG(0) +# define P039_TC_TYPE PCONFIG(1) +# define P039_FAM_TYPE PCONFIG(2) +# define P039_RTD_TYPE PCONFIG(3) +# define P039_CONFIG_4 PCONFIG(4) +# define P039_RTD_FILT_TYPE PCONFIG(5) +# define P039_RTD_LM_TYPE PCONFIG(6) +# define P039_RTD_LM_SHTDWN PCONFIG(7) +# define P039_RTD_RES PCONFIG_LONG(0) +# define P039_FLAGS PCONFIG_ULONG(3) +# define P039_TEMP_THRESHOLD_FLAG 0 +# define P039_RTD_OFFSET PCONFIG_FLOAT(0) +# define P039_TEMP_THRESHOLD PCONFIG_FLOAT(1) + +# define P039_TEMP_THRESHOLD_DEFAULT (-273.15f) // Default and minimum value +# define P039_TEMP_THRESHOLD_MIN P039_TEMP_THRESHOLD_DEFAULT +# define P039_TEMP_THRESHOLD_MAX (1000.0f) // Max value +# define P039_TC 0u +# define P039_RTD 1u + +# define P039_MAX6675 1 +# define P039_MAX31855 2 +# define P039_MAX31856 3 +# define P039_MAX31865 4 +# define P039_LM7x 5 + +// MAX 6675 related defines + +// bit masks to identify failures for MAX 6675 +# define MAX6675_TC_DEVID 0x0002u +# define MAX6675_TC_OC 0x0004u + +// MAX 31855 related defines + +// bit masks to identify failures for MAX 31855 +# define MAX31855_TC_OC 0x00000001u +# define MAX31855_TC_SC 0x00000002u +# define MAX31855_TC_SCVCC 0x00000004u +# define MAX31855_TC_GENFLT 0x00010000u + + +// MAX 31856 related defines + +// base address for read/write acces to MAX 31856 +# define MAX31856_READ_ADDR_BASE 0x00u +# define MAX31856_WRITE_ADDR_BASE 0x80u + +// register offset values for MAX 31856 +# define MAX31856_CR0 0u +# define MAX31856_CR1 1u +# define MAX31856_MASK 2u +# define MAX31856_CJHF 3u +# define MAX31856_CJLF 4u +# define MAX31856_LTHFTH 5u +# define MAX31856_LTHFTL 6u +# define MAX31856_LTLFTH 7u +# define MAX31856_LTLFTL 8u +# define MAX31856_CJTO 9u +# define MAX31856_CJTH 10u +# define MAX31856_CJTL 11u +# define MAX31856_LTCBH 12u +# define MAX31856_LTCBM 13u +# define MAX31856_LTCBL 14u +# define MAX31856_SR 15u + +# define MAX31856_NO_REG 16u + +// bit masks to identify failures for MAX 31856 +# define MAX31856_TC_OC 0x01u +# define MAX31856_TC_OVUV 0x02u +# define MAX31856_TC_TCLOW 0x04u +# define MAX31856_TC_TCLHIGH 0x08u +# define MAX31856_TC_CJLOW 0x10u +# define MAX31856_TC_CJHIGH 0x20u +# define MAX31856_TC_TCRANGE 0x40u +# define MAX31856_TC_CJRANGE 0x80u + +// bit masks for access of configuration bits +# define MAX31856_SET_50HZ 0x01u +# define MAX31856_CLEAR_FAULTS 0x02u +# define MAX31856_FLT_ISR_MODE 0x04u +# define MAX31856_CJ_SENS_DISABLE 0x08u +# define MAX31856_FAULT_CTRL_MASK 0x30u +# define MAX31856_SET_ONE_SHOT 0x40u +# define MAX31856_SET_CONV_AUTO 0x80u + + +// RTD related defines + +// MAX 31865 related defines + +// waiting time until "in sequence" conversion is ready (-> used in case device is set to shutdown in between call cycles) +// typically 70ms should be fine, according to datasheet maximum -> 66ms - give a little adder to "be sure" conversion is done +// alternatively ONE SHOT bit could be polled (system/SPI bus load !) +# define MAX31865_CONVERSION_TIME 70ul +# define MAX31865_BIAS_WAIT_TIME 10ul + +// MAX 31865 Main States +# define MAX31865_INIT_STATE 0u +# define MAX31865_BIAS_ON_STATE 1u +# define MAX31865_RD_STATE 2u +# define MAX31865_RDY_STATE 3u + +// sensor type +# define MAX31865_PT100 0u +# define MAX31865_PT1000 1u + +// base address for read/write acces to MAX 31865 +# define MAX31865_READ_ADDR_BASE 0x00u +# define MAX31865_WRITE_ADDR_BASE 0x80u + +// register offset values for MAX 31865 +# define MAX31865_CONFIG 0u +# define MAX31865_RTD_MSB 1u +# define MAX31865_RTD_LSB 2u +# define MAX31865_HFT_MSB 3u +# define MAX31865_HFT_LSB 4u +# define MAX31865_LFT_MSB 5u +# define MAX31865_LFT_LSB 6u +# define MAX31865_FAULT 7u + +// total number of registers in MAX 31865 +# define MAX31865_NO_REG 8u + +// bit masks to identify failures for MAX 31865 +# define MAX31865_FAULT_HIGHTHRESH 0x80u +# define MAX31865_FAULT_LOWTHRESH 0x40u +# define MAX31865_FAULT_REFINLOW 0x20u +# define MAX31865_FAULT_REFINHIGH 0x10u +# define MAX31865_FAULT_RTDINLOW 0x08u +# define MAX31865_FAULT_OVUV 0x04u + +// bit masks for access of configuration bits +# define MAX31865_SET_50HZ 0x01u +# define MAX31865_CLEAR_FAULTS 0x02u +# define MAX31865_FAULT_CTRL_MASK 0x0Cu +# define MAX31865_SET_3WIRE 0x10u +# define MAX31865_SET_ONE_SHOT 0x20u +# define MAX31865_SET_CONV_AUTO 0x40u +# define MAX31865_SET_VBIAS_ON 0x80u + +// LM7x related defines + +// LM7x subtype defines +# define LM7x_SD70 0x00u +# define LM7x_SD71 0x01u +# define LM7x_SD74 0x04u +# define LM7x_SD121 0x05u +# define LM7x_SD122 0x06u +# define LM7x_SD123 0x07u +# define LM7x_SD124 0x08u +# define LM7x_SD125 0x09u + +// bit masks for access of configuration bits +# define LM7x_CONV_RDY 0x02u + + +void P039_AddMainsFrequencyFilterSelection(struct EventStruct *event); + +boolean Plugin_039(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_039; + Device[deviceCount].Type = DEVICE_TYPE_SPI; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_039); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_039)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_output(F("CS")); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K + bitSet(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG); + break; + } + + case PLUGIN_INIT: + { + if (!bitRead(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG)) { + P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K + } + + if ((P039_MAX_TYPE < P039_MAX6675) || (P039_MAX_TYPE > P039_LM7x)) { + break; + } + + + initPluginTaskData(event->TaskIndex, new (std::nothrow) P039_data_struct()); + P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + // set the slaveSelectPin as an output: + init_SPI_CS_Pin(CS_pin_no); + + // initialize SPI: + SPI.setHwCs(false); + SPI.begin(); + + // ensure MODE3 access to SPI device + SPI.setDataMode(SPI_MODE3); + + /* + if (P039_MAX_TYPE == P039_MAX6675) { + + // SPI.setBitOrder(MSBFIRST); + } + */ + if (P039_MAX_TYPE == P039_MAX31855) { + // SPI.setBitOrder(MSBFIRST); + + if (nullptr != P039_data) { + // FIXED: c.k.i. : moved static fault flag to instance data structure + P039_data->sensorFault = false; + } + } + + + if (P039_MAX_TYPE == P039_MAX31856) { + // init string - content accoring to inital implementation of P039 - MAX31856 read function + // write to Adress 0x80 + // activate 50Hz filter in CR0, choose averaging and TC type from configuration in CR1, activate OV/UV/OC faults, write defaults to + // CJHF, CJLF, LTHFTH, LTHFTL, LTLFTH, LTLFTL, CJTO + uint8_t sendBuffer[11] = + { 0x80, static_cast(P039_RTD_FILT_TYPE), static_cast((P039_CONFIG_4 << 4) | P039_TC_TYPE), 0xFC, 0x7F, 0xC0, 0x7F, + 0xFF, 0x80, 0x00, 0x00 }; + + transfer_n_ByteSPI(CS_pin_no, 11, &sendBuffer[0]); + + if (nullptr != P039_data) { + // FIXED: c.k.i. : moved static fault flag to instance data structure + P039_data->sensorFault = false; + } + + // start on shot conversion for upcoming read cycle + change8BitRegister(CS_pin_no, + (MAX31856_READ_ADDR_BASE + MAX31856_CR0), + (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), + MAX31856_SET_ONE_SHOT, + P039_SET); + } + + + if (P039_MAX_TYPE == P039_MAX31865) { + // two step initialization buffer + uint8_t initSendBufferHFTH[3] = { (MAX31865_WRITE_ADDR_BASE + MAX31865_HFT_MSB), 0xFF, 0xFF }; + uint8_t initSendBufferLFTH[3] = { (MAX31865_WRITE_ADDR_BASE + MAX31865_HFT_MSB), 0xFF, 0xFF }; + + // write intially 0x00 to CONFIG register + write8BitRegister(CS_pin_no, (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), 0x00u); + + // activate 50Hz filter, clear all faults, no auto conversion, no conversion started + change8BitRegister(CS_pin_no, + MAX31865_RD_ADDRESS(MAX31865_CONFIG), + MAX31865_WR_ADDRESS(MAX31865_CONFIG), + MAX31865_SET_50HZ, + static_cast(P039_RTD_FILT_TYPE)); + + // configure 2/4-wire sensor connection as default + MAX31865_setConType(CS_pin_no, P039_CONFIG_4); + + // set HighFault Threshold + transfer_n_ByteSPI(CS_pin_no, 3, &initSendBufferHFTH[0]); + + // set LowFault Threshold + transfer_n_ByteSPI(CS_pin_no, 3, &initSendBufferLFTH[0]); + + // clear all faults + MAX31865_clearFaults(CS_pin_no); + + // activate BIAS short before read, to reduce power consumption + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_VBIAS_ON, + P039_SET); + + if (nullptr != P039_data) { + // save current timer for next calculation + P039_data->timer = millis(); + + // start time to follow up on BIAS activation before starting the conversion + // and start conversion sequence via TIMER API + + Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); + } + } + + /* + if (P039_MAX_TYPE == P039_LM7x) + { + // TODO: c.k.i.: more detailed inits depending on the sub devices expected , e.g. TMP 122/124 + } + */ + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P039 : %s : SPI Init - DONE"), getTaskDeviceName(event->TaskIndex).c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormSubHeader(F("Sensor Family Selection")); + + const uint8_t family = P039_FAM_TYPE; + { + const __FlashStringHelper *Foptions[2] = { F("Thermocouple"), F("RTD") }; + const int FoptionValues[2] = { P039_TC, P039_RTD }; + addFormSelector(F("Sensor Family Type"), F("famtype"), 2, Foptions, FoptionValues, family, true); // auto reload activated + } + + const uint8_t choice = P039_MAX_TYPE; + + addFormSubHeader(F("Device Type Settings")); + + if (family == P039_TC) { + { + const __FlashStringHelper *options[3] = { F("MAX 6675"), F("MAX 31855"), F("MAX 31856") }; + const int optionValues[3] = { P039_MAX6675, P039_MAX31855, P039_MAX31856 }; + addFormSelector(F("Adapter IC"), F("maxtype"), 3, options, optionValues, choice, true); // auto reload activated + } + + if (choice == P039_MAX31856) { + addFormSubHeader(F("Device Settings")); + { + const __FlashStringHelper *Toptions[10] = { F("B"), F("E"), F("J"), F("K"), F("N"), F("R"), F("S"), F("T"), F("VM8"), F("VM32") }; + + // 2021-05-17: c.k.i.: values are directly written to device register for configuration, therefore no linear values are used + // here + // MAX 31856 datasheet (page 20): + // Thermocouple Type + // 0000 = B Type + // 0001 = E Type + // 0010 = J Type + // 0011 = K Type (default) + // 0100 = N Type + // 0101 = R Type + // 0110 = S Type + // 0111 = T Type + // 10xx = Voltage Mode, Gain = 8. Code = 8 x 1.6 x 217 x VIN + // 11xx = Voltage Mode, Gain = 32. Code = 32 x 1.6 x 217 x VIN + // Where Code is 19 bit signed number from TC registers and VIN is thermocouple input voltage + + const int ToptionValues[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 12 }; + addFormSelector(F("Thermocouple type"), F("tctype"), 10, Toptions, ToptionValues, P039_TC_TYPE); + } + { + const __FlashStringHelper *Coptions[5] = { F("1"), F("2"), F("4"), F("8"), F("16") }; + const int CoptionValues[5] = { 0, 1, 2, 3, 4 }; + addFormSelector(F("Averaging"), F("contype"), 5, Coptions, CoptionValues, P039_CONFIG_4); + addUnit(F("sample(s)")); + } + P039_AddMainsFrequencyFilterSelection(event); + } + } + else { + { + const __FlashStringHelper *TPoptions[2] = { F("MAX 31865"), F("LM7x") }; + const int TPoptionValues[2] = { P039_MAX31865, P039_LM7x }; + addFormSelector(F("Adapter IC"), F("maxtype"), 2, TPoptions, TPoptionValues, choice, true); // auto reload activated + addFormNote(F("LM7x support is experimental.")); + } + + + if (choice == P039_MAX31865) + { + { + addFormSubHeader(F("Device Settings")); + } + { + const __FlashStringHelper *PToptions[2] = { F("PT100"), F("PT1000") }; + const int PToptionValues[2] = { MAX31865_PT100, MAX31865_PT1000 }; + addFormSelector(F("Resistor Type"), F("rtdtype"), 2, PToptions, PToptionValues, P039_RTD_TYPE); + } + { + const __FlashStringHelper *Coptions[2] = { F("2-/4"), F("3") }; + const int CoptionValues[2] = { 0, 1 }; + addFormSelector(F("Connection Type"), F("contype"), 2, Coptions, CoptionValues, P039_CONFIG_4); + addUnit(F("wire")); + } + + P039_AddMainsFrequencyFilterSelection(event); + + { + addFormNumericBox(F("Reference Resistor"), F("res"), P039_RTD_RES, 0); + addUnit(F("Ohm")); + addFormNote(F("PT100: typically 430 [OHM]; PT1000: typically 4300 [OHM]")); + } + { + addFormFloatNumberBox(F("Temperature Offset"), F("offset"), P039_RTD_OFFSET, -50.0f, 50.0f, 2, 0.01f); + addUnit('K'); + # ifndef BUILD_NO_DEBUG + addFormNote(F("Valid values: [-50.0...50.0 K], min. stepsize: [0.01]")); + # endif // ifndef BUILD_NO_DEBUG + } + } + + if (choice == P039_LM7x) + { + { + addFormSubHeader(F("Device Settings")); + } + + { + const __FlashStringHelper *PToptions[8] = + { F("LM70"), F("LM71"), F("LM74"), F("TMP121"), F("TMP122"), F("TMP123"), F("TMP124"), F("TMP125") }; + const int PToptionValues[8] = { LM7x_SD70, LM7x_SD71, LM7x_SD74, LM7x_SD121, LM7x_SD122, LM7x_SD123, LM7x_SD124, LM7x_SD125 }; + addFormSelector(F("LM7x device details"), F("rtd_lm_type"), 8, PToptions, PToptionValues, P039_RTD_LM_TYPE); + addFormNote(F("TMP122/124 Limited support -> fixed 12 Bit res, no advanced options")); + } + { + addFormCheckBox(F("Enable Shutdown Mode"), F("rtd_lm_shtdwn"), P039_RTD_LM_SHTDWN); + # ifndef BUILD_NO_DEBUG + addFormNote(F("Device is set to shutdown between sample cycles. Useful for very long call cycles, to save power.
" + "Without LM7x device conversion happens in between call cycles. Call Cylces should therefore not become lower than 350ms.")); + # endif // ifndef BUILD_NO_DEBUG + } + } + } + + addFormSubHeader(F("Value validation")); + + if (!bitRead(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG)) { + P039_TEMP_THRESHOLD = P039_TEMP_THRESHOLD_DEFAULT; // 0 K + } + addFormFloatNumberBox(F("Low temperature threshold"), + F("temp_thres"), + P039_TEMP_THRESHOLD, + P039_TEMP_THRESHOLD_MIN, + P039_TEMP_THRESHOLD_MAX, + 2u); + addUnit(F("°C")); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P039_FAM_TYPE = getFormItemInt(F("famtype")); + P039_MAX_TYPE = getFormItemInt(F("maxtype")); + P039_TC_TYPE = getFormItemInt(F("tctype")); + P039_RTD_TYPE = getFormItemInt(F("rtdtype")); + P039_CONFIG_4 = getFormItemInt(F("contype")); + P039_RTD_FILT_TYPE = getFormItemInt(F("filttype")); + P039_RTD_RES = getFormItemInt(F("res")); + P039_RTD_OFFSET = getFormItemFloat(F("offset")); + P039_RTD_LM_TYPE = getFormItemInt(F("rtd_lm_type")); + P039_RTD_LM_SHTDWN = isFormItemChecked(F("rtd_lm_shtdwn")); + P039_TEMP_THRESHOLD = getFormItemFloat(F("temp_thres")); + bitSet(P039_FLAGS, P039_TEMP_THRESHOLD_FLAG); // We've set a value, don't replace by default + + success = true; + break; + } + + case PLUGIN_READ: + { + // Get the MAX Type (6675 / 31855 / 31856) + uint8_t MaxType = P039_MAX_TYPE; + + float Plugin_039_Celsius = NAN; + + switch (MaxType) { + case P039_MAX6675: + Plugin_039_Celsius = readMax6675(event); + break; + case P039_MAX31855: + Plugin_039_Celsius = readMax31855(event); + break; + case P039_MAX31856: + Plugin_039_Celsius = readMax31856(event); + break; + case P039_MAX31865: + Plugin_039_Celsius = readMax31865(event); + break; + case P039_LM7x: + Plugin_039_Celsius = readLM7x(event); + break; + } + + if (isValidFloat(Plugin_039_Celsius)) + { + UserVar.setFloat(event->TaskIndex, 0, Plugin_039_Celsius); + +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat(F("P039 : %s :"), getTaskDeviceName(event->TaskIndex).c_str()); + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (uint8_t i = 0; i < valueCount; ++i) + { + log += strformat( + F(" %s: %s"), + Cache.getTaskDeviceValueName(event->TaskIndex, i).c_str(), + formatUserVarNoCheck(event, i).c_str()); + } + addLogMove(LOG_LEVEL_INFO, log); + } +# endif // ifndef BUILD_NO_DEBUG + + if (definitelyGreaterThan(Plugin_039_Celsius, P039_TEMP_THRESHOLD)) { + success = true; + } + } + else + { + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("P039 : %s : No Sensor attached!"), getTaskDeviceName(event->TaskIndex).c_str())); + } + success = false; + } + + break; + } + + case PLUGIN_TASKTIMER_IN: + { + P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + // Get the MAX Type (6675 / 31855 / 31856) + uint8_t MaxType = P039_MAX_TYPE; + + switch (MaxType) + { + case P039_MAX31865: + { + if ((nullptr != P039_data)) { + switch (event->Par1) + { + case MAX31865_BIAS_ON_STATE: + { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("P039 : %s : current state: MAX31865_BIAS_ON_STATE; delta: %d ms"), + getTaskDeviceName(event->TaskIndex).c_str(), + timePassedSince(P039_data->timer))); // calc delta since last call + } + # endif // ifndef BUILD_NO_DEBUG + + // save current timer for next calculation + P039_data->timer = millis(); + + // activate one shot conversion + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_ONE_SHOT, + P039_SET); + + // set next state in sequence -> READ STATE + // start time to follow up on conversion and read the conversion result + P039_data->convReady = false; + Scheduler.setPluginTaskTimer(MAX31865_CONVERSION_TIME, event->TaskIndex, MAX31865_RD_STATE); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("P039 : %s : Next State: %d"), + getTaskDeviceName(event->TaskIndex).c_str(), + event->Par1)); + } + # endif // ifndef BUILD_NO_DEBUG + + break; + } + case MAX31865_RD_STATE: + { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat( + F("P039 : %s : current state: MAX31865_RD_STATE; delta: %d ms"), + getTaskDeviceName(event->TaskIndex).c_str(), + timePassedSince(P039_data->timer))); // calc delta since last call + } + # endif // ifndef BUILD_NO_DEBUG + + // save current timer for next calculation + P039_data->timer = millis(); + + // read conversion result + P039_data->conversionResult = read16BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_RTD_MSB)); + + // deactivate BIAS short after read, to reduce power consumption + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_VBIAS_ON, + P039_RESET); + + // read fault register to get a full picture + P039_data->deviceFaults = read8BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_FAULT)); + + // mark conversion as ready + P039_data->convReady = true; + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + + if ((log.reserve(170u))) { // reserve value derived from example log file + log = F("P039 : "); // 7 char + log += getTaskDeviceName(event->TaskIndex); // 41 char ( max length of task device name + 1) + log += F(" : conversionResult: "); // 21 char + log += formatToHex_decimal(P039_data->conversionResult); // 11 char + log += F("; deviceFaults: "); // 16 char + log += formatToHex_decimal(P039_data->deviceFaults); // 9 char + log += F("; Next State: "); // 13 char + log += event->Par1; // 4 char + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + # endif // ifndef BUILD_NO_DEBUG + + + break; + } + case MAX31865_INIT_STATE: + default: + { + // clear all faults + MAX31865_clearFaults(CS_pin_no); + + // activate BIAS short before read, to reduce power consumption + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_VBIAS_ON, + P039_SET); + + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + + if ((log.reserve(140u))) { // reserve value derived from example log file + log = F("P039 : "); // 7 char + log += getTaskDeviceName(event->TaskIndex); // 41 char + log += F(" : "); // 3 char + log += F("current state: MAX31865_INIT_STATE, default;"); // many char - 44 + log += F(" next state: MAX31865_BIAS_ON_STATE"); // a little less char - 35 + addLogMove(LOG_LEVEL_DEBUG, log); + } + + // save current timer for next calculation + P039_data->timer = millis(); + } + # endif // ifndef BUILD_NO_DEBUG + + // start time to follow up on BIAS activation before starting the conversion + // and start conversion sequence via TIMER API + // set next state in sequence -> BIAS ON STATE + + Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); + + + break; + } + } + } + break; + } + default: + { + break; + } + } + + success = true; + break; + } + } + return success; +} + +void P039_AddMainsFrequencyFilterSelection(struct EventStruct *event) +{ + const __FlashStringHelper *FToptions[2] = { F("60"), F("50") }; + const int FToptionValues[2] = { 0, 1 }; + + addFormSelector(F("Supply Frequency Filter"), F("filttype"), 2, FToptions, FToptionValues, P039_RTD_FILT_TYPE); + addUnit(F("Hz")); + addFormNote(F("Filter power net frequency (50/60 Hz)")); +} + +float readMax6675(struct EventStruct *event) +{ + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + uint8_t messageBuffer[2] = { 0 }; + uint16_t rawvalue = 0u; + + + // "transfer" 2 bytes to SPI to get 16 Bit return value + transfer_n_ByteSPI(CS_pin_no, 2, &messageBuffer[0]); + + // merge 16Bit return value from messageBuffer + rawvalue = ((messageBuffer[0] << 8) | messageBuffer[1]); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(130u))) { // reserve value derived from example log file + log = F("P039 : MAX6675 : RAW - BIN: "); // 27 char + log += String(rawvalue, BIN); // 18 char + log += F(" HEX: "); // 5 char + log += formatToHex(rawvalue); // 4 char + log += F(" DEC: "); // 5 char + log += String(rawvalue); // 5 char + log += F(" MSB: "); // 5 char + log += formatToHex_decimal(messageBuffer[0]); // 9 char + log += F(" LSB: "); // 5 char + log += formatToHex_decimal(messageBuffer[1]); // 9 char + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + // Open Thermocouple + // Bit D2 is normally low and goes high if the thermocouple input is open. In order to allow the operation of the + // open thermocouple detector, T- must be grounded. Make the ground connection as close to the GND pin + // as possible. + // 2021-05-11: FIXED: c.k.i.: OC Flag already checked; migrated to #define for improved maintenance + const bool Plugin_039_SensorAttached = !(rawvalue & MAX6675_TC_OC); + + if (Plugin_039_SensorAttached) + { + // shift RAW value 3 Bits to the right to get the data + rawvalue >>= 3; + + // calculate Celsius with device resolution 0.25 K/bit + return rawvalue * 0.25f; + } + else + { + return NAN; + } +} + +float readMax31855(struct EventStruct *event) +{ + P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); + + uint8_t messageBuffer[4] = { 0 }; + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + // "transfer" 0x0 and read the 32 Bit conversion register from the Chip + transfer_n_ByteSPI(CS_pin_no, 4, &messageBuffer[0]); + + // merge rawvalue from 4 bytes of messageBuffer + uint32_t rawvalue = + ((static_cast(messageBuffer[0]) << + 24) | + (static_cast(messageBuffer[1]) << + 16) | (static_cast(messageBuffer[2]) << 8) | static_cast(messageBuffer[3])); + + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(200u))) { // reserve value derived from example log file + log = F("P039 : MAX31855 : RAW - BIN: "); // 35 char + log += String(rawvalue, BIN); // 16 char + log += F(" rawvalue,HEX: "); // 15 char + log += formatToHex(rawvalue); // 4 char + log += F(" rawvalue,DEC: "); // 15 char + log += rawvalue; // 5 char + log += F(" messageBuffer[],HEX:"); // 21 char + + for (size_t i = 0u; i < 4; ++i) + { + log += ' '; // 1 char + log += formatToHex_decimal(messageBuffer[i]); // 9 char + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + if (nullptr != P039_data) { + // FIXED: c.k.i. : moved static fault flag to instance data structure + + // check for fault flags in LSB of 32 Bit messageBuffer + if (P039_data->sensorFault != ((rawvalue & (MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)) == 0)) { + // Fault code changed, log them + P039_data->sensorFault = ((rawvalue & (MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)) == 0); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(120u))) { // reserve value derived from example log file + log = F("P039 : MAX31855 : "); + + if ((P039_data->sensorFault)) { + log += F("Fault resolved"); + } else { + log += F("Fault code :"); + + if (rawvalue & MAX31855_TC_OC) { + log += F(" Open (no connection)"); + } + + if (rawvalue & MAX31855_TC_SC) { + log += F(" Short-circuit to GND"); + } + + if (rawvalue & MAX31855_TC_SCVCC) { + log += F(" Short-circuit to Vcc"); + } + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + # endif // ifndef BUILD_NO_DEBUG + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(120u))) { // reserve value derived from example log file + log = F("P039 : MAX31855 : "); + log += F("rawvalue: "); + log += formatToHex_decimal(rawvalue); + log += F(" P039_data->sensorFault: "); + log += formatToHex_decimal(P039_data->sensorFault); + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + } + + // D16 - This bit reads at 1 when any of the SCV, SCG, or OC faults are active. Default value is 0. + // 2020-05-11: FIXED: c.k.i.: migrated plain flag mask to #defines to enhance maintainability; added all fault flags for safety reasons + const bool Plugin_039_SensorAttached = !(rawvalue & (MAX31855_TC_GENFLT | MAX31855_TC_SCVCC | MAX31855_TC_SC | MAX31855_TC_OC)); + + if (Plugin_039_SensorAttached) + { + // Data is D[31:18] + // Shift RAW value 18 Bits to the right to get the data + rawvalue >>= 18; + + // Check for negative Values + // +25.00 0000 0001 1001 00 + // 0.00 0000 0000 0000 00 + // -0.25 1111 1111 1111 11 + // -1.00 1111 1111 1111 00 + // -250.00 1111 0000 0110 00 + // We're left with (32 - 18 =) 14 bits + int temperature = Plugin_039_convert_two_complement(rawvalue, 14); + + // Calculate Celsius + return temperature * 0.25f; + } + else + { + // Fault state, thus output no value. + return NAN; + } +} + +float readMax31856(struct EventStruct *event) +{ + P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + + uint8_t registers[MAX31856_NO_REG] = { 0 }; + uint8_t messageBuffer[MAX31856_NO_REG + 1] = { 0 }; + + messageBuffer[0] = MAX31856_READ_ADDR_BASE; + + // "transfer" 0x0 starting at address 0x00 and read the all registers from the Chip + transfer_n_ByteSPI(CS_pin_no, (MAX31856_NO_REG + 1), &messageBuffer[0]); + + // transfer data from messageBuffer and get rid of initial address uint8_t + for (uint8_t i = 0u; i < MAX31856_NO_REG; ++i) { + registers[i] = messageBuffer[i + 1]; + } + + // configure device for next conversion + // activate frequency filter according to configuration + change8BitRegister(CS_pin_no, + (MAX31856_READ_ADDR_BASE + MAX31856_CR0), + (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), + MAX31856_SET_50HZ, + static_cast(P039_RTD_FILT_TYPE)); + + // set averaging and TC type + write8BitRegister(CS_pin_no, (MAX31856_WRITE_ADDR_BASE + MAX31856_CR1), static_cast((P039_CONFIG_4 << 4) | P039_TC_TYPE)); + + + // start on shot conversion for next read cycle + change8BitRegister(CS_pin_no, + (MAX31856_READ_ADDR_BASE + MAX31856_CR0), + (MAX31856_WRITE_ADDR_BASE + MAX31856_CR0), + MAX31856_SET_ONE_SHOT, + P039_SET); + + + // now derive raw value from respective registers + uint32_t rawvalue = static_cast(registers[MAX31856_LTCBH]); + + rawvalue = (rawvalue << 8) | static_cast(registers[MAX31856_LTCBM]); + rawvalue = (rawvalue << 8) | static_cast(registers[MAX31856_LTCBL]); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(210u))) { // reserve value derived from example log file + log = F("P039 : MAX31856 :"); + + for (uint8_t i = 0; i < MAX31856_NO_REG; ++i) { + log += ' '; + log += formatToHex_decimal(registers[i]); + } + log += F(" rawvalue: "); + log += formatToHex_decimal(rawvalue); + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + + // ignore TC Range Bit in case Voltage Modes are used + // datasheet: + // Thermocouple Out-of-Range fault. + // 0 = The Thermocouple Hot Junction temperature is within the normal operating range (see Table 1). + // 1 = The Thermocouple Hot Junction temperature is outside of the normal operating range. + // Note: The TC Range bit should be ignored in voltage mode. + uint8_t sr = registers[MAX31856_SR]; + + if ((8u == P039_TC_TYPE) || (12u == P039_TC_TYPE)) { + sr &= ~MAX31856_TC_TCRANGE; + } + + + // FIXED: c.k.i. : moved static fault flag to instance data structure + if ((nullptr != P039_data)) { + // P039_data->sensorFault = false; + + P039_data->sensorFault = (sr != 0); // Set new state + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + // FIXME TD-er: Part of expression is always false (sr == 0) + const bool faultResolved = (P039_data->sensorFault) && (sr == 0); + + if ((P039_data->sensorFault) || faultResolved) { + String log; + + if ((log.reserve(140u))) { // reserve value derived from example log file + log = F("P039 : MAX31856 : "); + + if ((P039_data->sensorFault) == 0) { + log += F("Fault resolved"); + } else { + log += F("Fault :"); + + if (sr & MAX31856_TC_OC) { + log += F(" Open (no connection)"); + } + + if (sr & MAX31856_TC_OVUV) { + log += F(" Over/Under Voltage"); + } + + if (sr & MAX31856_TC_TCLOW) { + log += F(" TC Low"); + } + + if (sr & MAX31856_TC_TCLHIGH) { + log += F(" TC High"); + } + + if (sr & MAX31856_TC_CJLOW) { + log += F(" CJ Low"); + } + + if (sr & MAX31856_TC_CJHIGH) { + log += F(" CJ High"); + } + + if (sr & MAX31856_TC_TCRANGE) { + log += F(" TC Range"); + } + + if (sr & MAX31856_TC_CJRANGE) { + log += F(" CJ Range"); + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + } + } + # endif // ifndef BUILD_NO_DEBUG + } + + + const bool Plugin_039_SensorAttached = (sr == 0); + + if (Plugin_039_SensorAttached) + { + rawvalue >>= 5; // bottom 5 bits are unused + // We're left with (24 - 5 =) 19 bits + + { + float temperature = 0; + + switch (P039_TC_TYPE) + { + case 8: + { + temperature = rawvalue / 1677721.6f; // datasheet: rawvalue = 8 x 1.6 x 2^17 x VIN -> VIN = rawvalue / (8 x 1.6 x 2^17) + break; + } + case 12: + { + temperature = rawvalue / 6710886.4f; // datasheet: rawvalue = 32 x 1.6 x 2^17 x VIN -> VIN = rawvalue / (32 x 1.6 x 2^17) + break; + } + default: + { + temperature = Plugin_039_convert_two_complement(rawvalue, 19); + + // Calculate Celsius + temperature /= 128.0f; + break; + } + } + + return temperature; + } + } + else + { + // Fault state, thus output no value. + return NAN; + } +} + +float readMax31865(struct EventStruct *event) +{ + P039_data_struct *P039_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (P039_data == nullptr) { + return NAN; + } + + uint8_t registers[MAX31865_NO_REG] = { 0 }; + uint16_t rawValue = 0u; + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(80u))) { // reserve value derived from example log file + log = F("P039 : MAX31865 :"); + log += F(" P039_data->convReady: "); + log += boolToString(P039_data->convReady); + + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + + // read conversion result and faults from plugin data structure + // if pointer exists and conversion has been finished + if (P039_data->convReady) { + rawValue = P039_data->conversionResult; + registers[MAX31865_FAULT] = P039_data->deviceFaults; + } + + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(160u))) { // reserve value derived from example log file + for (uint8_t i = 0u; i < MAX31865_NO_REG; ++i) + { + registers[i] = read8BitRegister(CS_pin_no, (MAX31865_READ_ADDR_BASE + i)); + } + + log = F("P039 : MAX31865 :"); + + for (uint8_t i = 0u; i < MAX31865_NO_REG; ++i) + { + log += ' '; + log += formatToHex_decimal(registers[i]); + } + + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + // Prepare and start next conversion, before handling faults and rawValue + // clear all faults + MAX31865_clearFaults(CS_pin_no); + + // set frequency filter + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_50HZ, + static_cast(P039_RTD_FILT_TYPE)); + + + // configure read access with configuration from web interface + MAX31865_setConType(CS_pin_no, P039_CONFIG_4); + + // activate BIAS short before read, to reduce power consumption + change8BitRegister(CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_VBIAS_ON, + P039_SET); + + // start time to follow up on BIAS activation before starting the conversion + // and start conversion sequence via TIMER API + // save current timer for next calculation + P039_data->timer = millis(); + + // set next state to MAX31865_BIAS_ON_STATE + + Scheduler.setPluginTaskTimer(MAX31865_BIAS_WAIT_TIME, event->TaskIndex, MAX31865_BIAS_ON_STATE); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + if (registers[MAX31865_FAULT]) + { + String log; + + if ((log.reserve(210u))) { // reserve value derived from example log file + log = F("P039 : MAX31865 : "); + + log += F("Fault : "); + log += formatToHex_decimal(registers[MAX31865_FAULT]); + log += F(" :"); + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_OVUV) + { + log += F(" Under/Over voltage"); + } + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_RTDINLOW) + { + log += F(" RTDIN- < 0.85 x Bias - FORCE- open"); + } + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_REFINHIGH) + { + log += F(" REFIN- < 0.85 x Bias - FORCE- open"); + } + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_REFINLOW) + { + log += F(" REFIN- > 0.85 x Bias"); + } + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_LOWTHRESH) + { + log += F(" RTD Low Threshold"); + } + + if (registers[MAX31865_FAULT] & MAX31865_FAULT_HIGHTHRESH) + { + log += F(" RTD High Threshold"); + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + } + # endif // ifndef BUILD_NO_DEBUG + + + bool ValueValid = false; + + if (registers[MAX31865_FAULT] == 0x00u) { + ValueValid = true; + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(85u))) { // reserve value derived from example log file + log = F("P039 : Temperature :"); // 20 char + log += F(" registers[MAX31865_FAULT]: "); // 33 char + log += formatToHex_decimal(registers[MAX31865_FAULT]); // 7 char + log += F(" ValueValid: "); // 13 char + log += boolToString(ValueValid); // 5 char + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + if (ValueValid) + { + rawValue >>= 1; // bottom fault bits is unused + + float temperature = Plugin_039_convert_to_temperature(rawValue, getNomResistor(P039_RTD_TYPE), P039_RTD_RES); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(110u))) { // reserve value derived from example log file + log = F("P039 : Temperature :"); // 20 char + log += F(" rawValue: "); // 11 char + log += formatToHex_decimal(rawValue); // 9 char + log += F(" temperature: "); // 14 char + log += temperature; // 11 char + log += F(" P039_RTD_TYPE: "); // 16 char + log += P039_RTD_TYPE; // 1 char + log += F(" P039_RTD_RES: "); // 15 char + log += P039_RTD_RES; // 4 char + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + // add offset handling from configuration webpage + temperature += P039_RTD_OFFSET; + + // Calculate Celsius + return temperature; + } + else + { + // Fault state, thus output no value. + return NAN; + } +} + +void MAX31865_clearFaults(int8_t l_CS_pin_no) +{ + uint8_t l_reg = 0u; + + // read in config register + l_reg = read8BitRegister(l_CS_pin_no, (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG)); + + + // clear all faults ( write "0" to D2, D3, D5; write "1" to D2) + l_reg &= ~(MAX31865_SET_ONE_SHOT | MAX31865_FAULT_CTRL_MASK); + l_reg |= MAX31865_CLEAR_FAULTS; + + // write configuration + write8BitRegister(l_CS_pin_no, (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), l_reg); +} + +void MAX31865_setConType(int8_t l_CS_pin_no, uint8_t l_conType) +{ + bool l_set_reset = false; + + // configure if 3 WIRE bit will be set/reset + switch (l_conType) + { + case 0: + l_set_reset = P039_RESET; + break; + case 1: + l_set_reset = P039_SET; + break; + default: + l_set_reset = P039_RESET; + break; + } + + // change to configuration register + change8BitRegister(l_CS_pin_no, + (MAX31865_READ_ADDR_BASE + MAX31865_CONFIG), + (MAX31865_WRITE_ADDR_BASE + MAX31865_CONFIG), + MAX31865_SET_3WIRE, + l_set_reset); +} + +/**************************************************************************/ + +/*! + @brief Read the temperature in C from the RTD through calculation of the + resistance. Uses + http://www.analog.com/media/en/technical-documentation/application-notes/AN709_0.pdf + technique + @param RTDnominal The 'nominal' resistance of the RTD sensor, usually 100 + or 1000 + @param refResistor The value of the matching reference resistor, usually + 430 or 4300 + @returns Temperature in C + */ + +/**************************************************************************/ +float Plugin_039_convert_to_temperature(uint32_t l_rawvalue, float RTDnominal, float refResistor) +{ + # define RTD_A 3.9083e-3f + # define RTD_B -5.775e-7f + + float Z1, Z2, Z3, Z4, Rt, temp; + + Rt = l_rawvalue; + Rt /= 32768u; + Rt *= refResistor; + + Z1 = -RTD_A; + Z2 = RTD_A * RTD_A - (4 * RTD_B); + Z3 = (4 * RTD_B) / RTDnominal; + Z4 = 2 * RTD_B; + + temp = Z2 + (Z3 * Rt); + temp = (sqrtf(temp) + Z1) / Z4; + + if (temp >= 0) { + return temp; + } + + Rt /= RTDnominal; + Rt *= 100; // normalize to 100 ohm + + float rpoly = Rt; + + temp = -242.02f; + temp += 2.2228f * rpoly; + rpoly *= Rt; // square + temp += 2.5859e-3f * rpoly; + rpoly *= Rt; // ^3 + temp -= 4.8260e-6f * rpoly; + rpoly *= Rt; // ^4 + temp -= 2.8183e-8f * rpoly; + rpoly *= Rt; // ^5 + temp += 1.5243e-10f * rpoly; + + return temp; +} + +uint16_t getNomResistor(uint8_t l_RType) +{ + uint16_t l_returnValue = 100u; + + switch (l_RType) + { + case MAX31865_PT100: + l_returnValue = 100u; + break; + case MAX31865_PT1000: + l_returnValue = 1000u; + break; + default: + l_returnValue = 100u; + break; + } + return l_returnValue; +} + +int Plugin_039_convert_two_complement(uint32_t value, int nr_bits) { + const bool negative = (value & (1 << (nr_bits - 1))) != 0; + int nativeInt; + + if (negative) { + // Add zeroes to the left to create the proper negative native-sized integer. + nativeInt = value | ~((1 << nr_bits) - 1); + } else { + nativeInt = value; + } + return nativeInt; +} + +float readLM7x(struct EventStruct *event) +{ + float temperature = 0.0f; + uint16_t device_id = 0u; + uint16_t rawValue = 0u; + + int8_t CS_pin_no = get_SPI_CS_Pin(event); + + // operate LM7x devices in polling mode, assuming conversion is ready with every call of this read function ( >=210ms call cycle) + // this allows usage of multiples generations of LM7x devices, that doe not provde conversion ready information in temperature register + + rawValue = readLM7xRegisters(CS_pin_no, P039_RTD_LM_TYPE, P039_RTD_LM_SHTDWN, &device_id); + + temperature = convertLM7xTemp(rawValue, P039_RTD_LM_TYPE); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + String log; + + if ((log.reserve(130u))) { // reserve value derived from example log file + log = F("P039 : LM7x : readLM7x : "); + log += F(" rawValue: "); + log += formatToHex_decimal(rawValue); + log += F(" device_id: "); + log += formatToHex(device_id); + log += F(" temperature: "); + log += temperature; + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + return temperature; +} + +float convertLM7xTemp(uint16_t l_rawValue, uint16_t l_LM7xsubtype) +{ + float l_returnValue = 0.0f; + float l_lsbvalue = 0.0f; + uint8_t l_noBits = 0u; + int l_intTemperature = 0; + + switch (l_LM7xsubtype) + { + case LM7x_SD70: + l_rawValue >>= 5; + l_lsbvalue = 0.25f; + l_noBits = 11u; + break; + case LM7x_SD71: + l_rawValue >>= 2; + l_lsbvalue = 0.03125f; + l_noBits = 14u; + break; + case LM7x_SD74: + l_rawValue >>= 3; + l_lsbvalue = 0.0625f; + l_noBits = 13u; + break; + case LM7x_SD121: + case LM7x_SD122: + case LM7x_SD123: + case LM7x_SD124: + l_rawValue >>= 4; + l_lsbvalue = 0.0625f; + l_noBits = 12u; + break; + case LM7x_SD125: + l_rawValue >>= 5; + l_lsbvalue = 0.25f; + l_noBits = 10u; + break; + default: // use lowest resolution as fallback if no device has been configured + l_rawValue >>= 5; + l_lsbvalue = 0.25f; + l_noBits = 11u; + break; + } + + l_intTemperature = Plugin_039_convert_two_complement(l_rawValue, l_noBits); + + l_returnValue = l_intTemperature * l_lsbvalue; + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(185u))) { // reserve value derived from example log file + log = F("P039 : LM7x : convertLM7xTemp : "); + log += F(" l_returnValue: "); + log += formatToHex_decimal(l_returnValue); + log += F(" l_LM7xsubtype: "); + log += formatToHex_decimal(l_LM7xsubtype); + log += F(" l_rawValue: "); + log += formatToHex_decimal(l_rawValue); + log += F(" l_noBits: "); + log += l_noBits; + log += F(" l_lsbvalue: "); + log += l_lsbvalue; + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + return l_returnValue; +} + +uint16_t readLM7xRegisters(int8_t l_CS_pin_no, uint8_t l_LM7xsubType, uint8_t l_runMode, uint16_t *l_device_id) +{ + uint16_t l_returnValue = 0u; + uint16_t l_mswaitTime = 0u; + + + switch (l_LM7xsubType) + { + case LM7x_SD70: + case LM7x_SD71: + case LM7x_SD74: + l_mswaitTime = 300; + break; + case LM7x_SD121: + case LM7x_SD122: + case LM7x_SD123: + case LM7x_SD124: + l_mswaitTime = 320; + break; + case LM7x_SD125: + l_mswaitTime = 100; + break; + default: + l_mswaitTime = 500; + break; + } + + // // activate communication -> CS low + // handle_SPI_CS_Pin(l_CS_pin_no, LOW); + + if (l_runMode) + { + // shutdown mode active -> conversion when called + uint8_t messageBuffer[12] = { 0xFF, 0xFF, 0xFF, 0X00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF }; + + // send inital 4 bytes to wake the device and start the conversion + transfer_n_ByteSPI(l_CS_pin_no, 4, &messageBuffer[0]); + + // wait specific ms for conversion to be ready (TI datasheet per devices) + delay(l_mswaitTime); + + // send remaining 8 bytes to read the device ID and shutdown the device + transfer_n_ByteSPI(l_CS_pin_no, 8, &messageBuffer[4]); + + // read temperature value (16 Bit) + l_returnValue = ((messageBuffer[4] << 8) | messageBuffer[5]); + + // read Manufatures/Device ID (16 Bit) + *(l_device_id) = ((messageBuffer[8] << 8) | messageBuffer[9]); + + // // wakeup device and start conversion + // // initial read of conversion result is obsolete + // SPI.transfer16(0xFFFF); + + // // (wakeup device with "all zero2 message in the last 8 bits + // SPI.transfer16(0xFF00); + + // //wait specific ms for conversion to be ready (TI datasheet per devices) + // delay(l_mswaitTime); + + // //read temperature value (16 Bit) + // l_returnValue = SPI.transfer16(0x0000); + // // l_returnValue <<= 8; + // // l_returnValue = SPI.transfer(0x00); + + // // set device to shutdown with "all one" message in the last 8 bits + // SPI.transfer16(0xFFFF); + + // // read Manufatures/Device ID (16 Bit) + // *(l_device_id) = SPI.transfer16(0x0000); + // // *(l_device_id) <<= 8; + // // *(l_device_id) = SPI.transfer(0x00); + + // // set device to shutdown with "all one" message in the last 8 bits ( maybe redundant, check with test) + // SPI.transfer16(0xFFFF); + } + else + { + // shutdown mode inactive -> normal background conversion during call cycle + uint8_t messageBuffer[8] = { 0x00, 0x00, 0xFF, 0XFF, 0x00, 0x00, 0x00, 0x00 }; + + transfer_n_ByteSPI(l_CS_pin_no, 8, &messageBuffer[0]); + + // read temperature value (16 Bit) + l_returnValue = ((messageBuffer[0] << 8) | messageBuffer[1]); + + // read Manufatures/Device ID (16 Bit) + *(l_device_id) = ((messageBuffer[4] << 8) | messageBuffer[5]); + + + // l_returnValue = SPI.transfer16(0x0000); //read temperature value (16 Bit) + // // l_returnValue <<= 8; + // // l_returnValue = SPI.transfer(0x00); + + // // set device to shutdown + // SPI.transfer16(0xFFFF); + + // // read Manufatures/Device ID (16 Bit) + // *(l_device_id) = SPI.transfer16(0x0000); + // // *(l_device_id) <<= 8; + // // *(l_device_id) = SPI.transfer(0x00); + + // // start conversion until next read (8 Bit sufficient) + // // 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F allowed - else device goes to test mode (not desirable here) + // SPI.transfer(0x00); + // // SPI.transfer16(0x0000); + } + + // // stop communication -> CS high + // handle_SPI_CS_Pin(l_CS_pin_no, HIGH); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(115u))) { // reserve value derived from example log file + log = F("P039 : LM7x : readLM7xRegisters : "); + log += F(" l_returnValue: "); + log += formatToHex_decimal(l_returnValue); + log += F(" l_device_id: "); + log += formatToHex(*(l_device_id)); + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + return l_returnValue; +} + +// POSSIBLE START OF GENERIC SPI HIGH LEVEL FUNCTIONS WITH POTENTIAL OF SYSTEM WIDE RE-USE + +/**************************************************************************/ + +/*! + @brief generic high level library to access SPI interface from plugins + with GPIO pin handled as CS - chri.kai.in 2021 + + Initial Revision - chri.kai.in 2021 + + TODO: c.k.i.: make it generic and carve out to generic _SPI_helper.c library + + + /**************************************************************************/ + + +/**************************************************************************/ + +/*! + + @brief Identifying the CS pin from the event basic data structure + @param event pointer to the event structure; default GPIO is chosen as GPIO 15 + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +int get_SPI_CS_Pin(struct EventStruct *event) { // If no Pin is in Config we use 15 as default -> Hardware Chip Select on ESP8266 + if (CONFIG_PIN1 != -1) { + return CONFIG_PIN1; + } + return 15; // D8 +} + +/**************************************************************************/ + +/*! + @brief Initializing GPIO as OUTPUT for CS for SPI communication + @param l_CS_pin_no the GPIO pin number used as CS + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void init_SPI_CS_Pin(int8_t l_CS_pin_no) { + // set the slaveSelectPin as an output: + pinMode(l_CS_pin_no, OUTPUT); +} + +/**************************************************************************/ + +/*! + @brief Handling GPIO as CS for SPI communication + @param l_CS_pin_no the GPIO pin number used as CS + @param l_state the state of the CS pin: "HIGH/LOW" reflecting the physical level + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void handle_SPI_CS_Pin(int8_t l_CS_pin_no, bool l_state) { + P039_CS_Delay(); // tCWH (min) >= x00ns + digitalWrite(l_CS_pin_no, l_state); + P039_CS_Delay(); // tCC (min) >= x00ns +} + +/**************************************************************************/ + +/*! + @brief write 8 bits to adress l_address on the SPI interface, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_address the register addess of the connected SPI device + @param value the unsigned 8 Bit message to be transferred + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void write8BitRegister(int8_t l_CS_pin_no, uint8_t l_address, uint8_t value) +{ + uint8_t l_messageBuffer[2] = { l_address, value }; + + transfer_n_ByteSPI(l_CS_pin_no, 2, l_messageBuffer); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(100u))) { // reserve value derived from example log file + log = F("P039 : SPI : write8BitRegister : "); + log += F("l_address: "); + log += formatToHex(l_address); + log += F(" value: "); + log += formatToHex_decimal(value); + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG +} + +/**************************************************************************/ + +/*! + @brief write 16 bits to adress l_address on the SPI interface, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_address the register addess of the connected SPI device + @param value the unsigned 16 Bit message to be transferred + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void write16BitRegister(int8_t l_CS_pin_no, uint8_t l_address, uint16_t value) +{ + uint8_t l_messageBuffer[3] = { l_address, static_cast((value >> 8) & 0xFF), static_cast(value & 0xFF) }; + + transfer_n_ByteSPI(l_CS_pin_no, 3, l_messageBuffer); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(110u))) { // reserve value derived from example log file + log = F("P039 : SPI : write16BitRegister : "); + log += F("l_address: "); + log += formatToHex(l_address); + log += F(" value: "); + log += formatToHex_decimal(value); + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG +} + +/**************************************************************************/ + +/*! + @brief read 8 bits from adress l_address on the SPI interface, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_address the register addess of the connected SPI device + + @returns the unsigned 8 Bit message read from l_address + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +uint8_t read8BitRegister(int8_t l_CS_pin_no, uint8_t l_address) +{ + uint8_t l_messageBuffer[2] = { l_address, 0x00 }; + + transfer_n_ByteSPI(l_CS_pin_no, 2, l_messageBuffer); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(100u))) { // reserve value derived from example log file + log = F("P039 : SPI : read8BitRegister : "); + log += F("l_address: "); + log += formatToHex(l_address); + log += F(" returnvalue: "); + log += formatToHex_decimal(l_messageBuffer[1]); + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + return l_messageBuffer[1]; +} + +/**************************************************************************/ + +/*! + @brief write 16 bits to adress l_address on the SPI interface, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_address the register addess of the connected SPI device + + @returns the unsigned 16 Bit message read from l_address + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +uint16_t read16BitRegister(int8_t l_CS_pin_no, uint8_t l_address) +{ + uint8_t l_messageBuffer[3] = { l_address, 0x00, 0x00 }; + uint16_t l_returnValue; + + transfer_n_ByteSPI(l_CS_pin_no, 3, l_messageBuffer); + l_returnValue = ((l_messageBuffer[1] << 8) | l_messageBuffer[2]); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(110u))) { // reserve value derived from example log file + log = F("P039 : SPI : read16BitRegister : "); + log += F("l_address: "); + log += formatToHex(l_address); + log += F(" l_returnValue: "); + log += formatToHex_decimal(l_returnValue); + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG + + return l_returnValue; +} + +/**************************************************************************/ + +/*! + @brief read from/write to dedicated number of bytes from/to SPI, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_noBytesToSend number of bytes to read/write from/to SPI + @param l_inoutMessageBuffer pointer to the messsage buffer to provide bytes to send + and provide read bytes from the SPI bus after the call + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void transfer_n_ByteSPI(int8_t l_CS_pin_no, uint8_t l_noBytesToSend, uint8_t *l_inoutMessageBuffer) +{ + // activate communication -> CS low + handle_SPI_CS_Pin(l_CS_pin_no, LOW); + + for (size_t i = 0u; i < l_noBytesToSend; i++) + { + l_inoutMessageBuffer[i] = SPI.transfer(l_inoutMessageBuffer[i]); + } + + // stop communication -> CS high + handle_SPI_CS_Pin(l_CS_pin_no, HIGH); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) + { + String log; + + if ((log.reserve(120u))) { // reserve value derived from example log file + log = F("P039 : SPI : transfer_n_ByteSPI : "); // 34 char + + for (uint8_t i = 0; i < l_noBytesToSend; ++i) + { + log += ' '; // 1 char + log += formatToHex_decimal(l_inoutMessageBuffer[i]); // 9 char + } + addLogMove(LOG_LEVEL_DEBUG_MORE, log); + } + } + + # endif // ifndef BUILD_NO_DEBUG +} + +/**************************************************************************/ + +/*! + @brief read a 16Bit register and change a flag, writing it back, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_readaddress SPI read address of the device register + @param l_writeaddress SPI write address of the device register + @param l_flagmask mask set to apply on the read register + @param l_set_reset controls if flag mask will be set (-> true) or reset ( -> false) + + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void change16BitRegister(int8_t l_CS_pin_no, uint8_t l_readaddress, uint8_t l_writeaddress, uint16_t l_flagmask, bool l_set_reset) +{ + uint16_t l_reg = 0u; + + // read in config register + l_reg = read16BitRegister(l_CS_pin_no, l_readaddress); + + if (l_set_reset) { + l_reg |= l_flagmask; + } + else + { + l_reg &= ~(l_flagmask); + } + + // write to configuration register + write16BitRegister(l_CS_pin_no, l_writeaddress, l_reg); +} + +/**************************************************************************/ + +/*! + @brief read a 8 Bit register and change a flag, writing it back, handling a GPIO CS + @param l_CS_pin_no the GPIO pin number used as CS + @param l_readaddress SPI read address of the device register + @param l_writeaddress SPI write address of the device register + @param l_flagmask mask set to apply on the read register + @param l_set_reset controls if flag mask will be set (-> true) or reset ( -> false) + + + @returns + + Initial Revision - chri.kai.in 2021 + + /**************************************************************************/ +void change8BitRegister(int8_t l_CS_pin_no, uint8_t l_readaddress, uint8_t l_writeaddress, uint8_t l_flagmask, bool l_set_reset) +{ + uint8_t l_reg = 0u; + + // read in config register + l_reg = read8BitRegister(l_CS_pin_no, l_readaddress); + + + // TODO: c.k.i.: analyze opportunity to use arduino bitSet/Clear macros instead + if (l_set_reset) { + l_reg |= l_flagmask; + } + else + { + l_reg &= ~(l_flagmask); + } + + // write to configuration register + write8BitRegister(l_CS_pin_no, l_writeaddress, l_reg); +} + +#endif // USES_P039 diff --git a/src/_P040_ID12.ino b/src/_P040_ID12.ino index 61b8cc14b..e79735227 100644 --- a/src/_P040_ID12.ino +++ b/src/_P040_ID12.ino @@ -122,9 +122,11 @@ boolean Plugin_040(uint8_t function, struct EventStruct *event, String& string) taskIndex_t index = INVALID_TASK_INDEX; constexpr pluginID_t PLUGIN_ID_P040_ID12(PLUGIN_ID_040); - for (taskIndex_t y = 0; y < TASKS_MAX; y++) - if (Settings.getPluginID_for_task(y) == PLUGIN_ID_P040_ID12) + for (taskIndex_t y = 0; y < TASKS_MAX; ++y) { + if (Settings.getPluginID_for_task(y) == PLUGIN_ID_P040_ID12) { index = y; + } + } const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(index); if (!validDeviceIndex(DeviceIndex)) { break; @@ -138,7 +140,7 @@ boolean Plugin_040(uint8_t function, struct EventStruct *event, String& string) unsigned long key = 0, old_key = 0; old_key = UserVar.getSensorTypeLong(event->TaskIndex); - for (uint8_t i = 1; i < 5; i++) key = key | (((unsigned long) code[i] << ((4 - i) * 8))); + for (uint8_t i = 1; i < 5; ++i) { key = key | (((unsigned long) code[i] << ((4 - i) * 8))); } bool new_key = false; if (old_key != key) { UserVar.setSensorTypeLong(event->TaskIndex, key); diff --git a/src/_P043_ClkOutput.ino b/src/_P043_ClkOutput.ino index 812eed4fb..352108d29 100644 --- a/src/_P043_ClkOutput.ino +++ b/src/_P043_ClkOutput.ino @@ -6,21 +6,62 @@ // #################################### Plugin 043: Clock Output ######################################### // ####################################################################################################### +/** Changelog: + * 2023-12-28 tonhuisman: Exclude some code that won't work for PLUGIN_BUILD_MINIMAL_OTA builds + * 2023-12-19 tonhuisman: Fix Value input On/Off only to behave exactly like when using a GPIO, except for changing the GPIO state + * 2023-12-16 tonhuisman: Add support for _GET_CONFIG_VALUE function, with [Clock#GetTimeX] and [Clock#GetValueX], where X is + * in range 1..Nr. of Day,Time fields (PLUGIN_EXTRACONFIGVAR_MAX = 16) + * Renamed setting PLUGIN_043_MAX_SETTINGS to P043_MAX_SETTINGS (to avoid confusion) + * 2023-12-14 tonhuisman: Fix 'Simplified' mode to behave like GPIO mode, so state Off = 0 and On = 1 + * Add support for config command: config,task,,SetTime,,[],] + * Use convention of accepting '$' for '%' in value, to prevent todays values to be used + * 2023-12-12 tonhuisman: Add option to choose simplified Off/On input instead of full numeric input for non-GPIO configuration + * 2023-12-11 tonhuisman: Put Value X input on same line as Day,Time X inputs, just like the On/Off combobox. + * Code optimization, calculating the current time to compare to only once. + * 2023-12-10 tonhuisman: Change input layout for non-LIMIT_BUILD_SIZE builds, to select a day and a time string in separate + * inputs. + * Add setting for number of Day,Time settings (range 1..16), default is 8. + * 2023-12-07 tonhuisman: Add support for %sunrise[+/-offsetHMS]% and %sunset[+/-offsetHMS]% format in constants + * also supports long offsets up to 32767 seconds using S suffix in offset + * 2023-12-06 tonhuisman: Add changelog + */ + +/** Supported command: + * config,task,,SetTime,,[],] + * : Range 1..number of Day,Time fields + * : As entered in the UI: Mon,12:34, can also be quoted: "Mon,12:34". Day name has to be 3 letters + * To enter %Sunrise% or %Sunset-1h% etc. use $Sunrise$ or $Sunset-1h$ to prevent todays values to be used + * ($ for % is a convention introduced in P036) + * : (Optional) Use 0 or 1 for GPIO configuration or when 'Value input On/Off only' is enabled + * For non-GPIO and 'Value input On/Off only' is off, then value 0 won't cause an event to be generated! + */ + +/** Supported values: + * GetTimeX: Get the time string for Day,Time line X + * GetValueX: Get the configured value for Day,Time line X. + * With a configured GPIO or 'Value input On/Off only' enabled, the value will be converted to Off=0, On=1 + * NB: X is in range 1..Nr. of Day,Time fields + */ + +# include "src/Helpers/StringGenerator_Web.h" # define PLUGIN_043 # define PLUGIN_ID_043 43 # define PLUGIN_NAME_043 "Output - Clock" -# define PLUGIN_VALUENAME_043 "Output" +# define PLUGIN_VALUENAME_043 "Output" -// #define PLUGIN_VALUENAME1_043 "Output" -// #define PLUGIN_VALUENAME2_043 "Output2" -# define PLUGIN_043_MAX_SETTINGS 8 +# define P043_SIMPLE_VALUE PCONFIG(6) +# define P043_MAX_SETTINGS PCONFIG(7) +# define P043_DEFAULT_MAX 8 # define P043_SENSOR_TYPE_INDEX 2 # define P043_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P043_SENSOR_TYPE_INDEX))) boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) { + # ifndef LIMIT_BUILD_SIZE + const String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); + # endif // ifndef LIMIT_BUILD_SIZE boolean success = false; switch (function) @@ -52,6 +93,12 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) break; } + case PLUGIN_SET_DEFAULTS: + { + P043_MAX_SETTINGS = P043_DEFAULT_MAX; + break; + } + case PLUGIN_GET_DEVICEVALUECOUNT: { event->Par1 = P043_NR_OUTPUT_VALUES; @@ -75,28 +122,70 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { - const __FlashStringHelper *options[3] = { + if (P043_MAX_SETTINGS == 0) { P043_MAX_SETTINGS = P043_DEFAULT_MAX; } + addFormNumericBox(F("Nr. of Day,Time fields"), F("vcount"), P043_MAX_SETTINGS, 1, PLUGIN_EXTRACONFIGVAR_MAX); + addUnit(concat(F("1.."), PLUGIN_EXTRACONFIGVAR_MAX)); + + addFormCheckBox(F("Value input On/Off only"), F("simpl"), P043_SIMPLE_VALUE == 1); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Page will be updated after Submit")); + # endif // ifndef LIMIT_BUILD_SIZE + + const __FlashStringHelper *options[] = { F(""), F("Off"), F("On"), }; + constexpr int optionsCount = NR_ELEMENTS(options); - for (int x = 0; x < PLUGIN_043_MAX_SETTINGS; x++) + # ifndef LIMIT_BUILD_SIZE + const unsigned int daysCount = weekDays.length() / 3; + String days[daysCount]; + + for (unsigned int n = 0; n < weekDays.length() / 3u; ++n) { + days[n] = weekDays.substring(n * 3, n * 3 + 3); + } + + datalistStart(F("timepatternlist")); + datalistAddValue(F("00:00")); + datalistAddValue(F("%sunrise%")); + datalistAddValue(F("%sunset%")); + datalistFinish(); + # endif // ifndef LIMIT_BUILD_SIZE + + for (int x = 0; x < P043_MAX_SETTINGS; x++) { - addFormTextBox( - concat(F("Day,Time "), x + 1), - concat(F("clock"), x), - timeLong2String(Cache.getTaskDevicePluginConfigLong(event->TaskIndex, x)), 32); + const String timeStr = timeLong2String(Cache.getTaskDevicePluginConfigLong(event->TaskIndex, x)); + # ifndef LIMIT_BUILD_SIZE + addRowLabel(concat(F("Day,Time "), x + 1)); + int thisDay = weekDays.indexOf(timeStr.substring(0, 3)); - if (CONFIG_PIN1 >= 0) { + if (thisDay > 0) { thisDay /= 3; } + addSelector(concat(F("day"), x), daysCount, days, nullptr, nullptr, thisDay, false, true, F("")); + addHtml(','); + addTextBox(concat(F("clock"), x), + parseString(timeStr, 2), 32 + , false, false, EMPTY_STRING, F("") + # if FEATURE_TOOLTIPS + , EMPTY_STRING + # endif // if FEATURE_TOOLTIPS + , F("timepatternlist")); + # else // ifndef LIMIT_BUILD_SIZE + addFormTextBox(concat(F("Day,Time "), x + 1), + concat(F("clock"), x), + timeStr, 32); + # endif // ifndef LIMIT_BUILD_SIZE + + if (validGpio(CONFIG_PIN1) || (P043_SIMPLE_VALUE == 1)) { addHtml(' '); const uint8_t choice = Cache.getTaskDevicePluginConfig(event->TaskIndex, x); - addSelector(concat(F("state"), x), 3, options, nullptr, nullptr, choice); + addSelector(concat(F("state"), x), optionsCount, options, nullptr, nullptr, choice); + } + else { + addHtml(strformat(F("Value %d:"), x + 1)); + addNumericBox(concat(F("state"), x), + Cache.getTaskDevicePluginConfig(event->TaskIndex, x), INT_MIN, INT_MAX); } - else { addFormNumericBox( - concat(F("Value"), x + 1), - concat(F("state"), x), - Cache.getTaskDevicePluginConfig(event->TaskIndex, x)); } } success = true; break; @@ -104,9 +193,18 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - for (int x = 0; x < PLUGIN_043_MAX_SETTINGS; x++) + P043_MAX_SETTINGS = getFormItemInt(F("vcount")); + P043_SIMPLE_VALUE = isFormItemChecked(F("simpl")) ? 1 : 0; + + for (int x = 0; x < P043_MAX_SETTINGS; x++) { - const String plugin1 = webArg(concat(F("clock"), x)); + String plugin1; + # ifndef LIMIT_BUILD_SIZE + const int day = getFormItemInt(concat(F("day"), x)); + plugin1 = strformat(F("%s,%s"), weekDays.substring(day * 3, day * 3 + 3).c_str(), webArg(concat(F("clock"), x)).c_str()); + # else // ifndef LIMIT_BUILD_SIZE + plugin1 = webArg(concat(F("clock"), x)); + # endif // ifndef LIMIT_BUILD_SIZE ExtraTaskSettings.TaskDevicePluginConfigLong[x] = string2TimeLong(plugin1); const String plugin2 = webArg(concat(F("state"), x)); ExtraTaskSettings.TaskDevicePluginConfig[x] = plugin2.toInt(); @@ -123,23 +221,43 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_CLOCK_IN: { - for (uint8_t x = 0; x < PLUGIN_043_MAX_SETTINGS; x++) + if (P043_MAX_SETTINGS == 0) { P043_MAX_SETTINGS = P043_DEFAULT_MAX; } + + unsigned long clockEvent = (unsigned long)node_time.minute() % 10 + | (unsigned long)(node_time.minute() / 10) << 4 + | (unsigned long)(node_time.hour() % 10) << 8 + | (unsigned long)(node_time.hour() / 10) << 12 + | (unsigned long)node_time.weekday() << 16; + + for (uint8_t x = 0; x < P043_MAX_SETTINGS; x++) { - unsigned long clockEvent = (unsigned long)node_time.minute() % 10 | (unsigned long)(node_time.minute() / 10) << - 4 | (unsigned long)(node_time.hour() % 10) << 8 | (unsigned long)(node_time.hour() / 10) << 12 | (unsigned long)node_time.weekday() << - 16; unsigned long clockSet = Cache.getTaskDevicePluginConfigLong(event->TaskIndex, x); + # ifndef PLUGIN_BUILD_MINIMAL_OTA + + if (bitRead(clockSet, 28) || bitRead(clockSet, 29)) { // sunrise or sunset string, apply todays values + String specialTime = timeLong2String(clockSet); + + parseSystemVariables(specialTime, false); // Parse systemvariables only, to reduce processing + clockSet = string2TimeLong(specialTime); + } + # endif // ifndef PLUGIN_BUILD_MINIMAL_OTA + if (matchClockEvent(clockEvent, clockSet)) { uint8_t state = Cache.getTaskDevicePluginConfig(event->TaskIndex, x); if (state != 0) { - if (CONFIG_PIN1 >= 0) { // if GPIO is specified, use the old behavior + const bool hasGpio = validGpio(CONFIG_PIN1); + + if (hasGpio || (P043_SIMPLE_VALUE == 1)) { // if GPIO or Yes/No selection is specified, use the old behavior state--; - pinMode(CONFIG_PIN1, OUTPUT); - digitalWrite(CONFIG_PIN1, state); + + if (hasGpio) { + pinMode(CONFIG_PIN1, OUTPUT); + digitalWrite(CONFIG_PIN1, state); + } UserVar.setFloat(event->TaskIndex, 0, state); } else { @@ -148,9 +266,7 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("TCLK : State "); - log += state; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("TCLK : State %d"), state)); } sendData(event); } @@ -158,6 +274,91 @@ boolean Plugin_043(uint8_t function, struct EventStruct *event, String& string) } break; } + + case PLUGIN_SET_CONFIG: + { + const String cmd = parseString(string, 1); + + // command: config,task,,SetTime,,, + if (equals(cmd, F("settime"))) { + String para = parseString(string, 2); + int32_t timeIndex = 0; + + if (validIntFromString(para, timeIndex) && (timeIndex > 0) && (timeIndex <= P043_MAX_SETTINGS)) { + para = parseString(string, 3); + String para4 = parseString(string, 4); + const String para5 = parseString(string, 5); + int32_t value = INT_MIN; + + if ((para.length() == 3) && !para4.isEmpty() && !para5.isEmpty()) { + // handle timeString without quotes: All,12:34, instead of "All,12:34", + para += ','; // (most compact code...) + para += para4; + para4 = para5; + } + + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("P043: Time received "), para)); + # endif // ifndef BUILD_NO_DEBUG + + para.replace('$', '%'); // Allow %Sunrise%/%Sunset% by using $ instead of % + + LoadTaskSettings(event->TaskIndex); // Not preloaded + + ExtraTaskSettings.TaskDevicePluginConfigLong[timeIndex - 1] = string2TimeLong(para); + + if (validIntFromString(para4, value)) { // Value is optional + if (validGpio(CONFIG_PIN1) || (P043_SIMPLE_VALUE == 1)) { value++; } // Off is stored as 1, On is stored as 2 for GPIO action + + ExtraTaskSettings.TaskDevicePluginConfig[timeIndex - 1] = value; + } + + Cache.updateExtraTaskSettingsCache(); + SaveTaskSettings(event->TaskIndex); // Unfortunately we have to save the settings here, or they will get lost + // Using too often will wear out the flash memory quickly! + + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, strformat(F("P043: Time received %s, value %d, stored %s, long: %d"), para.c_str(), value, + timeLong2String(ExtraTaskSettings.TaskDevicePluginConfigLong[timeIndex - 1]).c_str(), + string2TimeLong(para))); + # endif // ifndef BUILD_NO_DEBUG + success = true; + } + } + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + # define P043_GETTIME_LENGTH 7u // Length of 'gettime' + # define P043_GETVALUE_LENGTH 8u // Length of 'getvalue' + const String cmd = parseString(string, 1); + unsigned int idx = 0u; + int32_t timeIndex = -1; + + if (cmd.startsWith(F("gettime"))) { + idx = P043_GETTIME_LENGTH; + } else + if (cmd.startsWith(F("getvalue"))) { + idx = P043_GETVALUE_LENGTH; + } + + if ((idx > 0) && validIntFromString(cmd.substring(idx), timeIndex) && + (timeIndex > 0) && (timeIndex <= P043_MAX_SETTINGS)) { + LoadTaskSettings(event->TaskIndex); // Not preloaded + + if (idx == P043_GETTIME_LENGTH) { // gettime + string = timeLong2String(ExtraTaskSettings.TaskDevicePluginConfigLong[timeIndex - 1]); + } else { // getvalue + const int16_t offset = (validGpio(CONFIG_PIN1) || (P043_SIMPLE_VALUE == 1)) ? 1 : 0; + string = ExtraTaskSettings.TaskDevicePluginConfig[timeIndex - 1] - offset; + } + success = true; + } + # undef P043_GETTIME_LENGTH // No longer needed + # undef P043_GETVALUE_LENGTH + break; + } } return success; } diff --git a/src/_P044_P1WifiGateway.ino b/src/_P044_P1WifiGateway.ino index a575350df..d4465b0e5 100644 --- a/src/_P044_P1WifiGateway.ino +++ b/src/_P044_P1WifiGateway.ino @@ -1,6 +1,7 @@ #include "_Plugin_Helper.h" -#ifdef USES_P044 -//#################################### Plugin 044: P1WifiGateway ######################################## +#ifdef USES_P044_ORG + +// #################################### Plugin 044: P1WifiGateway ######################################## // // based on P020 Ser2Net, extended by Ronald Leenes romix/-at-/macuser.nl // @@ -8,28 +9,22 @@ // Wemos D1 mini (see http://wemos.cc) and // P1 wifi gateway shield (see http://www.esp8266thingies.nl for print design and kits) // See also http://domoticx.com/p1-poort-slimme-meter-hardware/ -//####################################################################################################### +// ####################################################################################################### +/** Changelog: + * 2022-10-08 tonhuisman: Disable plugin-code and merge all functionality into P020 as it was originally a modified copy of that plugin + * *** This code is deprecated *** + * 2022-10-01 tonhuisman: Add Led configuration options (Enabled, Pin, Inverted), changed device configuration + * 2022-10-01 tonhuisman: Format source using Uncrustify + */ -#include "src/Helpers/_Plugin_Helper_serial.h" -#include "src/PluginStructs/P044_data_struct.h" -#include +# include "src/Helpers/_Plugin_Helper_serial.h" +# include "src/PluginStructs/P044_data_struct.h" +# include -#define PLUGIN_044 -#define PLUGIN_ID_044 44 -#define PLUGIN_NAME_044 "Communication - P1 Wifi Gateway" - - -#define P044_SET_WIFI_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] -#define P044_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] -#define P044_GET_WIFI_SERVER_PORT Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0) -#define P044_GET_BAUDRATE Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) - - -#define P044_RX_WAIT PCONFIG(0) -#define P044_SERIAL_CONFIG PCONFIG(1) -#define P044_RESET_TARGET_PIN CONFIG_PIN1 - +# define PLUGIN_044 +# define PLUGIN_ID_044 44 +# define PLUGIN_NAME_044 "Communication - P1 Wifi Gateway" boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) @@ -38,165 +33,223 @@ boolean Plugin_044(uint8_t function, struct EventStruct *event, String& string) switch (function) { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_044; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; - Device[deviceCount].Custom = true; - Device[deviceCount].TimerOption = false; - break; - } + { + Device[++deviceCount].Number = PLUGIN_ID_044; + Device[deviceCount].Type = DEVICE_TYPE_CUSTOM2; + Device[deviceCount].Custom = true; + Device[deviceCount].TimerOption = false; + break; + } case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_044); - break; + { + string = F(PLUGIN_NAME_044); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P044_LED_PIN = P044_STATUS_LED; // Former default + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = concat(F("RST: "), formatGpioLabel(P044_RESET_TARGET_PIN, false)); + string += event->String1; + string += concat(F("LED: "), formatGpioLabel((P044_LED_ENABLED & 0x7f) == 0 ? P044_LED_PIN : -1, false)); + + if ((P044_LED_INVERTED == 1) && ((P044_LED_ENABLED & 0x7f) == 0)) { + string += F(" (inv)"); } + success = true; + break; + } case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("TCP Port"), F("p044_port"), P044_GET_WIFI_SERVER_PORT, 0); - addFormNumericBox(F("Baud Rate"), F("p044_baud"), P044_GET_BAUDRATE, 0); + { + # ifdef USES_P020 + String msg; + msg.reserve(132); + msg += F("This plugin is deprecated and will be removed in a future release. Please use P020 - "); + msg += getPluginNameFromPluginID(20); + addFormNote(msg); + # endif // ifdef USES_P020 + LoadTaskSettings(event->TaskIndex); + + { // Serial settings + addFormSubHeader(F("Serial")); uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); serialHelper_serialconfig_webformLoad(event, serialConfChoice); - // FIXME TD-er: Why isn't this using the normal pin selection functions? - addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); - - addFormNumericBox(F("RX Receive Timeout (mSec)"), F("p044_rxwait"), P044_RX_WAIT, 0); - - success = true; - break; + addFormNumericBox(F("Baud Rate"), F("pbaud"), P044_GET_BAUDRATE, 0, 115200); } + { // Device settings + addFormSubHeader(F("Device")); + + addFormNumericBox(F("TCP Port"), F("pport"), P044_GET_WIFI_SERVER_PORT, 0, 65535); + # ifndef LIMIT_BUILD_SIZE + addUnit(F("0..65535")); + # endif // ifndef LIMIT_BUILD_SIZE + + // FIXME TD-er: Why isn't this using the normal pin selection functions? + addFormPinSelect(PinSelectPurpose::Generic, F("Reset target after boot"), F("taskdevicepin1"), P044_RESET_TARGET_PIN); + + addFormNumericBox(F("RX Receive Timeout (mSec)"), F("prxwait"), P044_RX_WAIT, 0); + } + + { // Led settings + addFormSubHeader(F("Led")); + + addFormCheckBox(F("Led enabled"), F("pled"), (P044_LED_ENABLED & 0x7f) == 0); + addFormPinSelect(PinSelectPurpose::Generic, F("Led pin"), F("taskdevicepin2"), P044_LED_PIN); + addFormCheckBox(F("Led inverted"), F("pledinv"), P044_LED_INVERTED == 1); + } + + success = true; + break; + } + case PLUGIN_WEBFORM_SAVE: - { - P044_SET_WIFI_SERVER_PORT = getFormItemInt(F("p044_port")); - P044_SET_BAUDRATE = getFormItemInt(F("p044_baud")); - P044_RX_WAIT = getFormItemInt(F("p044_rxwait")); - P044_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); + { + P044_SET_WIFI_SERVER_PORT = getFormItemInt(F("pport")); + P044_SET_BAUDRATE = getFormItemInt(F("pbaud")); + P044_RX_WAIT = getFormItemInt(F("prxwait")); + P044_LED_ENABLED = 0x80 + (isFormItemChecked(F("pled")) ? 0 : 1); // Invert + set 8th bit to confirm new settings have been + // saved + P044_LED_INVERTED = isFormItemChecked(F("pledinv")) ? 1 : 0; + P044_SERIAL_CONFIG = serialHelper_serialconfig_webformSave(); - success = true; - break; - } + success = true; + break; + } case PLUGIN_INIT: - { - pinMode(P044_STATUS_LED, OUTPUT); - digitalWrite(P044_STATUS_LED, 0); + { + if (((P044_LED_ENABLED & 0x7f) == 1) && (P044_LED_PIN != -1)) { + pinMode(P044_LED_PIN, OUTPUT); + digitalWrite(P044_LED_PIN, P044_LED_INVERTED == 1 ? 1 : 0); + } - if ((P044_GET_WIFI_SERVER_PORT == 0) || (P044_GET_BAUDRATE == 0)) { - clearPluginTaskData(event->TaskIndex); - break; - } + LoadTaskSettings(event->TaskIndex); - // try to reuse to keep webserver running - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != task && task->isInit()) { - // It was already created and initialzed - // So don't recreate to keep the webserver running. - } else { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task()); - task = static_cast(getPluginTaskData(event->TaskIndex)); - } - if (nullptr == task) { - break; - } - - int rxPin; - int txPin; - // FIXME TD-er: Must use proper pin settings and standard ESPEasySerial wrapper - ESPeasySerialType::getSerialTypePins(ESPEasySerialPort::serial0, rxPin, txPin); - uint8_t serialconfig = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); - task->serialBegin(ESPEasySerialPort::not_set, rxPin, txPin, P044_GET_BAUDRATE, serialconfig); - task->startServer(P044_GET_WIFI_SERVER_PORT); - - if (!task->isInit()) { - clearPluginTaskData(event->TaskIndex); - break; - } - - if (validGpio(P044_RESET_TARGET_PIN)) { - pinMode(P044_RESET_TARGET_PIN, OUTPUT); - digitalWrite(P044_RESET_TARGET_PIN, LOW); - delay(500); - digitalWrite(P044_RESET_TARGET_PIN, HIGH); - pinMode(P044_RESET_TARGET_PIN, INPUT_PULLUP); - } - - task->blinkLED(); - if (P044_GET_BAUDRATE == 115200) { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC on")); - #endif - task->CRCcheck = true; - } else { - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); - #endif - task->CRCcheck = false; - } - - success = true; + if ((P044_GET_WIFI_SERVER_PORT == 0) || (P044_GET_BAUDRATE == 0)) { + clearPluginTaskData(event->TaskIndex); break; } + // try to reuse to keep webserver running + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != task) && task->isInit()) { + // It was already created and initialzed + // So don't recreate to keep the webserver running. + } else { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P044_Task(event)); + task = static_cast(getPluginTaskData(event->TaskIndex)); + } + + if (nullptr == task) { + break; + } + + int rxPin; + int txPin; + + // FIXME TD-er: Must use proper pin settings and standard ESPEasySerial wrapper + ESPeasySerialType::getSerialTypePins(ESPEasySerialPort::serial0, rxPin, txPin); + uint8_t serialconfig = serialHelper_convertOldSerialConfig(P044_SERIAL_CONFIG); + task->serialBegin(ESPEasySerialPort::not_set, rxPin, txPin, P044_GET_BAUDRATE, serialconfig); + task->startServer(P044_GET_WIFI_SERVER_PORT); + + if (!task->isInit()) { + clearPluginTaskData(event->TaskIndex); + break; + } + + if (validGpio(P044_RESET_TARGET_PIN)) { + pinMode(P044_RESET_TARGET_PIN, OUTPUT); + digitalWrite(P044_RESET_TARGET_PIN, LOW); + delay(500); + digitalWrite(P044_RESET_TARGET_PIN, HIGH); + pinMode(P044_RESET_TARGET_PIN, INPUT_PULLUP); + } + + task->blinkLED(); + + if (P044_GET_BAUDRATE == 115200) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 5 meter, CRC on")); + # endif // ifndef BUILD_NO_DEBUG + task->CRCcheck = true; + } else { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : DSMR version 4 meter, CRC off")); + # endif // ifndef BUILD_NO_DEBUG + task->CRCcheck = false; + } + + success = true; + break; + } + case PLUGIN_EXIT: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != task) { - task->stopServer(); - task->serialEnd(); - } - - success = true; - break; + if (nullptr != task) { + task->stopServer(); + task->serialEnd(); } + success = true; + break; + } + case PLUGIN_ONCE_A_SECOND: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { task->checkServer(); success = true; - break; } + break; + } case PLUGIN_TEN_PER_SECOND: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { if (task->hasClientConnected()) { task->discardClientIn(); } task->checkBlinkLED(); success = true; - break; } + break; + } case PLUGIN_SERIAL_IN: - { - P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == task) { - break; - } + { + P044_Task *task = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != task) { if (task->hasClientConnected()) { task->handleSerialIn(event); } else { task->discardSerialIn(); } success = true; - break; } - + break; + } } return success; } -#endif // USES_P044 + +#endif // USES_P044_ORG diff --git a/src/_P045_MPU6050.ino b/src/_P045_MPU6050.ino index 0fc8e9c31..da8d883c6 100644 --- a/src/_P045_MPU6050.ino +++ b/src/_P045_MPU6050.ino @@ -243,7 +243,7 @@ boolean Plugin_045(uint8_t function, struct EventStruct *event, String& string) uint8_t count = 0; // Counter to check if not all thresholdvalues are set to 0 or disabled uint8_t threscount = 0; // Counter to check how many tresholds have been exceeded - for (uint8_t i = 0; i < 3; i++) + for (uint8_t i = 0; i < 3; ++i) { // for each axis: if (PCONFIG(i + 2) != 0) { // not disabled, check threshold @@ -271,14 +271,14 @@ boolean Plugin_045(uint8_t function, struct EventStruct *event, String& string) // Did we count more times exceeded then the minimum detection value? if (PCONFIG_LONG(0) >= PCONFIG(5)) { - UserVar.setFloat(event->TaskIndex, 0, 1); // x times threshold exceeded within window. + UserVar.setFloat(event->TaskIndex, 0, 1.0f); // x times threshold exceeded within window. } else { - UserVar.setFloat(event->TaskIndex, 0, 0); // reset because x times threshold within window not met. + UserVar.setFloat(event->TaskIndex, 0, 0.0f); // reset because x times threshold within window not met. } // Check if UserVar changed so we do not overload homecontroller with the same readings - if (PCONFIG(7) != UserVar[event->BaseVarIndex]) { - PCONFIG(7) = UserVar[event->BaseVarIndex]; + if (PCONFIG(7) != UserVar.getFloat(event->TaskIndex, 0)) { + PCONFIG(7) = UserVar.getFloat(event->TaskIndex, 0); success = true; } else { success = false; @@ -297,7 +297,7 @@ boolean Plugin_045(uint8_t function, struct EventStruct *event, String& string) { uint8_t reqaxis = (_P045_Function - 1) % 3; // xyz -> eg: function 5(ay) (5-1) % 3 = 1 (y) uint8_t reqvar = ((_P045_Function - 1) / 3) + 2; // range, a, g -> eg: function 9(gz) ((9-1) / 3 = 2) + 2 = 4 (g) - UserVar.setFloat(event->TaskIndex, 0, float(P045_data->_axis[reqaxis][reqvar])); + UserVar.setFloat(event->TaskIndex, 0, P045_data->_axis[reqaxis][reqvar]); success = true; break; } diff --git a/src/_P047_i2c-soil-moisture-sensor.ino b/src/_P047_i2c-soil-moisture-sensor.ino index 0bcd0ff63..10fb3df92 100644 --- a/src/_P047_i2c-soil-moisture-sensor.ino +++ b/src/_P047_i2c-soil-moisture-sensor.ino @@ -12,6 +12,13 @@ // /** Changelog: + * 2024-05-09 tonhuisman: Add support for BeFlE v3.x (low power) Moisture sensor + * Code improvements + * ** Fix bug in setting a new I2C address for BeFlE sensors (needs a left-shift by 1) + * 2024-04-05 tonhuisman: Complete implementation for Afafruit I2C Capacitive Moisture sensor. + * Log sensor name and version (or 0 when not available) at plugin startup. + * 2024-03-23 tonhuisman: Start implementation of Adafruit I2C Capacitive Moisture Sensor (product ID 4026) + * From a forum request: https://www.letscontrolit.com/forum/viewtopic.php?t=10107 * 2023-04-07 tonhuisman: Correct typo BelFlE to BeFlE * 2023-04-01 tonhuisman: Implement staged reading instead of a fixed delay during PLUGIN_READ * Add range-check on save for I2C address inputs (0x01..0x7F) @@ -91,11 +98,20 @@ boolean Plugin_047(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - addFormTextBox(F("I2C Address (Hex)"), F("i2c_addr"), - formatToHex_decimal(P047_I2C_ADDR), 4); - addUnit(F("0x01..0x7F")); + # if P047_FEATURE_ADAFRUIT + + if (P047_MODEL_ADAFRUIT == static_cast(P047_MODEL)) { + const uint8_t i2cAddressValues[] = { P047_ADAFRUIT_DEFAULT_ADDR, 0x37, 0x38, 0x39 }; + + addFormSelectorI2C(F("i2c_addr"), 4, i2cAddressValues, P047_I2C_ADDR); + } else + # endif // if P047_FEATURE_ADAFRUIT + { + addFormTextBox(F("I2C Address (Hex)"), F("i2c_addr"), + formatToHex_decimal(P047_I2C_ADDR), 4); + addUnit(F("0x01..0x7F")); + } - // FIXME TD-er: Why not using addFormSelectorI2C here? break; } @@ -118,32 +134,60 @@ boolean Plugin_047(uint8_t function, struct EventStruct *event, String& string) { { const __FlashStringHelper *SensorModels[] = { - F("Catnip electronics/miceuz (default)"), - F("BeFlE"), + toString(P047_MODEL_CATNIP), + toString(P047_MODEL_BEFLE), + # if P047_FEATURE_BEFLE_V3 + toString(P047_MODEL_BEFLE_V3), + # endif // if P047_FEATURE_BEFLE_V3 + # if P047_FEATURE_ADAFRUIT + toString(P047_MODEL_ADAFRUIT), + # endif // if P047_FEATURE_ADAFRUIT }; const int SensorModelIds[] = { static_cast(P047_MODEL_CATNIP), static_cast(P047_MODEL_BEFLE), + # if P047_FEATURE_BEFLE_V3 + static_cast(P047_MODEL_BEFLE_V3), + # endif // if P047_FEATURE_BEFLE_V3 + # if P047_FEATURE_ADAFRUIT + static_cast(P047_MODEL_ADAFRUIT), + # endif // if P047_FEATURE_ADAFRUIT }; - constexpr size_t P047_MODEL_OPTIONS = sizeof(SensorModelIds) / sizeof(SensorModelIds[0]); + constexpr size_t P047_MODEL_OPTIONS = NR_ELEMENTS(SensorModelIds); addFormSelector(F("Sensor model"), F("model"), P047_MODEL_OPTIONS, SensorModels, SensorModelIds, P047_MODEL, true); addFormNote(F("Changing the Sensor model will reload the page.")); } - if (P047_MODEL_CATNIP == static_cast(P047_MODEL)) { + if ((P047_MODEL_CATNIP == static_cast(P047_MODEL)) + # if P047_FEATURE_BEFLE_V3 + || (P047_MODEL_BEFLE_V3 == static_cast(P047_MODEL)) + # endif // if P047_FEATURE_BEFLE_V3 + ) { addFormSeparator(2); - addFormCheckBox(F("Send sensor to sleep"), F("sleep"), P047_SENSOR_SLEEP); + addFormCheckBox(F("Send sensor to sleep"), F("sleep"), P047_SENSOR_SLEEP); - addFormCheckBox(F("Check sensor version"), F("version"), P047_CHECK_VERSION); + # if P047_FEATURE_BEFLE_V3 + + if (P047_MODEL_CATNIP == static_cast(P047_MODEL)) + # endif // if !P047_FEATURE_BEFLE_V3 + { + addFormCheckBox(F("Check sensor version"), F("version"), P047_CHECK_VERSION); + } } - addFormSeparator(2); + # if P047_FEATURE_ADAFRUIT - addFormCheckBox(F("Change Sensor address"), F("changeAddr"), false); - addFormTextBox(F("Change I2C Addr. to (Hex)"), F("newAddr"), - formatToHex_decimal(P047_I2C_ADDR), 4); - addUnit(F("0x01..0x7F")); + if (P047_MODEL_ADAFRUIT != static_cast(P047_MODEL)) + # endif // if P047_FEATURE_ADAFRUIT + { + addFormSeparator(2); + + addFormCheckBox(F("Change Sensor address"), F("changeAddr"), false); + addFormTextBox(F("Change I2C Addr. to (Hex)"), F("newAddr"), + formatToHex_decimal(P047_I2C_ADDR), 4); + addUnit(F("0x01..0x7F")); + } success = true; break; @@ -152,14 +196,25 @@ boolean Plugin_047(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { success = true; - String webarg = webArg(F("i2c_addr")); - int addr = static_cast(strtol(webarg.c_str(), 0, 16)); + String webarg; + int addr; - if ((addr > 0x00) && (addr < 0x80)) { - P047_I2C_ADDR = addr; - } else { - addHtmlError(F("I2C Address (Hex) error, range: 0x01..0x7F")); - success = false; + # if P047_FEATURE_ADAFRUIT + + if (P047_MODEL_ADAFRUIT == static_cast(P047_MODEL)) { + P047_I2C_ADDR = getFormItemInt(F("i2c_addr")); + } else + # endif // if P047_FEATURE_ADAFRUIT + { + webarg = webArg(F("i2c_addr")); + addr = static_cast(strtol(webarg.c_str(), 0, 16)); + + if ((addr > 0x00) && (addr < 0x80)) { + P047_I2C_ADDR = addr; + } else { + addHtmlError(F("I2C Address (Hex) error, range: 0x01..0x7F")); + success = false; + } } uint8_t model = getFormItemInt(F("model")); @@ -170,8 +225,16 @@ boolean Plugin_047(uint8_t function, struct EventStruct *event, String& string) if (P047_MODEL_CATNIP == static_cast(model)) { P047_I2C_ADDR = P047_CATNIP_DEFAULT_ADDR; strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_047)); // Gets wiped when switching nr. of values - } else { + } else if (P047_MODEL_BEFLE == static_cast(model)) { P047_I2C_ADDR = P047_BEFLE_DEFAULT_ADDR; + # if P047_FEATURE_ADAFRUIT + } else if (P047_MODEL_ADAFRUIT == static_cast(model)) { + P047_I2C_ADDR = P047_ADAFRUIT_DEFAULT_ADDR; + # endif // if P047_FEATURE_ADAFRUIT + # if P047_FEATURE_BEFLE_V3 + } else if (P047_MODEL_BEFLE_V3 == static_cast(model)) { + P047_I2C_ADDR = P047_BEFLE_V3_DEFAULT_ADDR; + # endif // if P047_FEATURE_BEFLE_V3 } } @@ -179,16 +242,25 @@ boolean Plugin_047(uint8_t function, struct EventStruct *event, String& string) P047_CHECK_VERSION = isFormItemChecked(F("version")); - webarg = webArg(F("newAddr")); - addr = static_cast(strtol(webarg.c_str(), 0, 16)); + # if P047_FEATURE_ADAFRUIT - if ((addr > 0x00) && (addr < 0x80)) { - P047_NEW_ADDR = addr; - } else { - addHtmlError(F("Change I2C Addr. to (Hex) error, range: 0x01..0x7F")); - success = false; + if (P047_MODEL_ADAFRUIT != static_cast(P047_MODEL)) + # endif // if P047_FEATURE_ADAFRUIT + { + webarg = webArg(F("newAddr")); + + if (!webarg.isEmpty()) { + addr = static_cast(strtol(webarg.c_str(), 0, 16)); + + if ((addr > 0x00) && (addr < 0x80)) { + P047_NEW_ADDR = addr; + P047_CHANGE_ADDR = isFormItemChecked(F("changeAddr")); + } else { + addHtmlError(F("Change I2C Addr. to (Hex) error, range: 0x01..0x7F")); + success = false; + } + } } - P047_CHANGE_ADDR = isFormItemChecked(F("changeAddr")); break; } diff --git a/src/_P048_Motorshield_v2.ino b/src/_P048_Motorshield_v2.ino index 74ed4bafe..abee192ac 100644 --- a/src/_P048_Motorshield_v2.ino +++ b/src/_P048_Motorshield_v2.ino @@ -112,11 +112,6 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) break; } - case PLUGIN_READ: { - success = false; - break; - } - case PLUGIN_WRITE: { # if FEATURE_I2C_DEVICE_CHECK @@ -129,13 +124,13 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) // Commands: // MotorShieldCMD,,,, - if (cmd.equalsIgnoreCase(F("MotorShieldCMD"))) + if (equals(cmd, F("motorshieldcmd"))) { - String param1 = parseString(string, 2); - String param2 = parseString(string, 3); - String param3 = parseString(string, 4); - String param4 = parseString(string, 5); - String param5 = parseString(string, 6); + const String param1 = parseString(string, 2); + const String param2 = parseString(string, 3); + const String param3 = parseString(string, 4); + const String param4 = parseString(string, 5); + const String param5 = parseString(string, 6); int32_t p2_int; int32_t p4_int; @@ -147,19 +142,17 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("MotorShield: Address: 0x"); - log += String(Plugin_048_MotorShield_address, HEX); - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, strformat(F("MotorShield: Address: 0x%x"), Plugin_048_MotorShield_address)); } # endif // ifndef BUILD_NO_DEBUG - if (param1.equalsIgnoreCase(F("DCMotor"))) { + if (equals(param1, F("dcmotor"))) { if (param2_is_int && (p2_int > 0) && (p2_int < 5)) { Adafruit_DCMotor *myMotor; myMotor = AFMS.getMotor(p2_int); - if (param3.equalsIgnoreCase(F("Forward"))) + if (equals(param3, F("forward"))) { uint8_t speed = 255; @@ -169,18 +162,14 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) AFMS.begin(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DCMotor"); - log += param2; - log += F("->Forward Speed: "); - log += speed; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Forward Speed: %d"), param2.c_str(), speed)); } myMotor->setSpeed(speed); myMotor->run(FORWARD); success = true; } - if (param3.equalsIgnoreCase(F("Backward"))) + if (equals(param3, F("backward"))) { uint8_t speed = 255; @@ -190,11 +179,7 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) AFMS.begin(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DCMotor"); - log += param2; - log += F("->Backward Speed: "); - log += speed; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Backward Speed: %d"), param2.c_str(), speed)); } myMotor->setSpeed(speed); @@ -202,15 +187,12 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) success = true; } - if (param3.equalsIgnoreCase(F("Release"))) + if (equals(param3, F("release"))) { AFMS.begin(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("DCMotor"); - log += param2; - log += F("->Release"); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("DCMotor%s->Release"), param2.c_str())); } myMotor->run(RELEASE); success = true; @@ -219,7 +201,7 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) } // MotorShieldCMD,,,,, - if (param1.equalsIgnoreCase(F("Stepper"))) + if (equals(param1, F("stepper"))) { // Stepper# is which port it is connected to. If you're using M1 and M2, its port 1. // If you're using M3 and M4 indicate port 2 @@ -232,42 +214,40 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) { - String log = F("MotorShield: StepsPerRevolution: "); - log += String(Plugin_048_MotorStepsPerRevolution); - log += F(" Stepperspeed: "); - log += String(Plugin_048_StepperSpeed); - addLogMove(LOG_LEVEL_DEBUG_MORE, log); + addLog(LOG_LEVEL_DEBUG_MORE, strformat(F("MotorShield: StepsPerRevolution: %d Stepperspeed: %d"), + Plugin_048_MotorStepsPerRevolution, + Plugin_048_StepperSpeed)); } # endif // ifndef BUILD_NO_DEBUG - if (param3.equalsIgnoreCase(F("Forward"))) + if (equals(param3, F("forward"))) { if (param4_is_int && (p4_int != 0)) { int steps = p4_int; - if (param5.equalsIgnoreCase(F("SINGLE"))) + if (equals(param5, F("single"))) { AFMS.begin(); myStepper->step(steps, FORWARD, SINGLE); success = true; } - if (param5.equalsIgnoreCase(F("DOUBLE"))) + if (equals(param5, F("double"))) { AFMS.begin(); myStepper->step(steps, FORWARD, DOUBLE); success = true; } - if (param5.equalsIgnoreCase(F("INTERLEAVE"))) + if (equals(param5, F("interleave"))) { AFMS.begin(); myStepper->step(steps, FORWARD, INTERLEAVE); success = true; } - if (param5.equalsIgnoreCase(F("MICROSTEP"))) + if (equals(param5, F("microstep"))) { AFMS.begin(); myStepper->step(steps, FORWARD, MICROSTEP); @@ -275,45 +255,39 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) } if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Stepper"); - log += param2; - log += F("->Forward Steps: "); - log += steps; - log += ' '; - log += param5; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Forward Steps: %d %s"), param2.c_str(), steps, param5.c_str())); } } } - if (param3.equalsIgnoreCase(F("Backward"))) + if (equals(param3, F("backward"))) { if (param4_is_int && (p4_int != 0)) { int steps = p4_int; - if (param5.equalsIgnoreCase(F("SINGLE"))) + if (equals(param5, F("single"))) { AFMS.begin(); myStepper->step(steps, BACKWARD, SINGLE); success = true; } - if (param5.equalsIgnoreCase(F("DOUBLE"))) + if (equals(param5, F("double"))) { AFMS.begin(); myStepper->step(steps, BACKWARD, DOUBLE); success = true; } - if (param5.equalsIgnoreCase(F("INTERLEAVE"))) + if (equals(param5, F("interleave"))) { AFMS.begin(); myStepper->step(steps, BACKWARD, INTERLEAVE); success = true; } - if (param5.equalsIgnoreCase(F("MICROSTEP"))) + if (equals(param5, F("microstep"))) { AFMS.begin(); myStepper->step(steps, BACKWARD, MICROSTEP); @@ -321,26 +295,17 @@ boolean Plugin_048(uint8_t function, struct EventStruct *event, String& string) } if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Stepper"); - log += param2; - log += F("->Backward Steps: "); - log += steps; - log += ' '; - log += param5; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Backward Steps: %d %s"), param2.c_str(), steps, param5.c_str())); } } } - if (param3.equalsIgnoreCase(F("Release"))) + if (equals(param3, F("release"))) { AFMS.begin(); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Stepper"); - log += param2; - log += F("->Release."); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Stepper%s->Release."), param2.c_str())); } myStepper->release(); success = true; diff --git a/src/_P049_MHZ19.ino b/src/_P049_MHZ19.ino index be658e7d1..69239e368 100644 --- a/src/_P049_MHZ19.ino +++ b/src/_P049_MHZ19.ino @@ -3,6 +3,11 @@ # include "src/PluginStructs/P049_data_struct.h" +/** Changelog: + * 2024-01-04 tonhuisman: Add Device[].ExitBeforeSeve = false so ABD can be enabled during settings save + * 2024-01-04 tonhuisman: Start changelog, most recent change on top + */ + /* This plug in is written by Dmitry (rel22 ___ inbox.ru) @@ -54,6 +59,7 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) Device[deviceCount].TimerOption = true; Device[deviceCount].GlobalSyncOption = true; Device[deviceCount].PluginStats = true; + Device[deviceCount].ExitTaskBeforeSave = false; break; } @@ -194,7 +200,7 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, ppm); UserVar.setFloat(event->TaskIndex, 1, temp); UserVar.setFloat(event->TaskIndex, 2, u); - success = true; + success = true; } else { success = false; } @@ -220,15 +226,9 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) if (mustLog) { // Log values in all cases - log += F("PPM value: "); - log += ppm; - log += F(" Temp/S/U values: "); - log += temp; - log += '/'; - log += s; - log += '/'; - log += u; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("PPM value: %d Temp/S/U values: %d/%d/%.2f"), + ppm, temp, s, u)); } break; @@ -248,9 +248,8 @@ boolean Plugin_049(uint8_t function, struct EventStruct *event, String& string) // log verbosely anything else that the sensor reports } else { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MHZ19: Unknown response:"); - log += P049_data->getBufferHexDump(); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + concat(F("MHZ19: Unknown response:"), P049_data->getBufferHexDump())); } // Check for stable reads and allow unstable reads the first 3 minutes after reset. diff --git a/src/_P050_TCS34725.ino b/src/_P050_TCS34725.ino index 89d08920e..972dcb596 100644 --- a/src/_P050_TCS34725.ino +++ b/src/_P050_TCS34725.ino @@ -1,515 +1,507 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P050 - -// ####################################################################################################### -// #################### Plugin 050 I2C TCS34725 RGB Color Sensor with IR filter and White LED ############ -// ####################################################################################################### -// -// RGB Color Sensor with IR filter and White LED -// like this one: https://www.adafruit.com/products/1334 -// based on this library: https://github.com/adafruit/Adafruit_TCS34725 -// this code is based on 20170331 date version of the above library -// this code is UNTESTED, because my TCS34725 sensor is still not shipped :( -// -// 2021-01-20 tonhuisman: Renamed Calibration to Transformation, fix some textual issues -// 2021-01-20 tonhuisman: Added optional events for not selected RGB outputs, compile-time optional -// 2021-01-19 tonhuisman: (Re)Added additional transformation & calculation options -// 2021-01-16 tonhuisman: Move stuff to PluginStructs, add 3x3 matrix calibration -// 2021-01-09 tonhuisman: Add R/G/B calibration factors, improved/corrected normalization -// 2021-01-03 tonhuisman: Merged most of the changes in the library, for adding the getRGB() and calculateColorTemperature_dn40(0 functions) -// - -# include "src/PluginStructs/P050_data_struct.h" - - -# define PLUGIN_050 -# define PLUGIN_ID_050 50 -# define PLUGIN_NAME_050 "Color - TCS34725" -# define PLUGIN_VALUENAME1_050 "Red" -# define PLUGIN_VALUENAME2_050 "Green" -# define PLUGIN_VALUENAME3_050 "Blue" -# define PLUGIN_VALUENAME4_050 "ColorTemperature" - -// #ifndef LIMIT_BUILD_SIZE -# define P050_OPTION_RGB_EVENTS - -// #endif - -boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_050; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 4; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_050); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_050)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_050)); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - PCONFIG(2) = 1; // RGB values: Calibrated RGB - PCONFIG(3) = 1; // Value #4: Color Temperature (DN40) - - # if FEATURE_I2C_DEVICE_CHECK - - if (!I2C_deviceCheck(0x29)) { - break; // Will return the default false for success - } - # endif // if FEATURE_I2C_DEVICE_CHECK - P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); - - if (nullptr != P050_data) { - P050_data->resetTransformation(); // Explicit reset - P050_data->saveSettings(event->TaskIndex); - delete P050_data; - } - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x29); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x29; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - uint8_t choiceMode = PCONFIG(0); - { - const __FlashStringHelper *optionsMode[] = { - F("2.4 ms"), - F("24 ms"), - F("50 ms"), - F("101 ms"), - F("154 ms"), - F("700 ms"), - }; - const int optionValuesMode[] = { - TCS34725_INTEGRATIONTIME_2_4MS, - TCS34725_INTEGRATIONTIME_24MS, - TCS34725_INTEGRATIONTIME_50MS, - TCS34725_INTEGRATIONTIME_101MS, - TCS34725_INTEGRATIONTIME_154MS, - TCS34725_INTEGRATIONTIME_700MS, - }; - addFormSelector(F("Integration Time"), F("inttime"), 6, optionsMode, optionValuesMode, choiceMode); - } - - uint8_t choiceMode2 = PCONFIG(1); - { - const __FlashStringHelper *optionsMode2[] = { - F("1x"), - F("4x"), - F("16x"), - F("60x"), - }; - const int optionValuesMode2[] = { - TCS34725_GAIN_1X, - TCS34725_GAIN_4X, - TCS34725_GAIN_16X, - TCS34725_GAIN_60X, - }; - addFormSelector(F("Gain"), F("gain"), 4, optionsMode2, optionValuesMode2, choiceMode2); - } - - addFormSubHeader(F("Output settings")); - - { - # define P050_RGB_OPTIONS 6 - const __FlashStringHelper *optionsRGB[P050_RGB_OPTIONS] = { - F("Raw RGB"), - F("Raw RGB transformed (3x3 matrix, below)"), - F("Normalized RGB (0..255)"), - F("Normalized RGB transformed (3x3 matrix, below)"), - F("Normalized RGB (0.0000..1.0000)"), - F("Normalized RGB (0.0000..1.0000) transformed (3x3 matrix, below)"), - }; - const int optionValuesRGB[P050_RGB_OPTIONS] = { 0, 1, 2, 3, 4, 5 }; - addFormSelector(F("Output RGB Values"), F("outputrgb"), P050_RGB_OPTIONS, optionsRGB, optionValuesRGB, PCONFIG(2)); - addFormNote(F("For 'normalized' or 'transformed' options, the Red/Green/Blue Decimals should best be increased.")); - -# ifdef P050_OPTION_RGB_EVENTS - addFormCheckBox(F("Generate RGB events"), F("rgbevents"), PCONFIG(5) == 1); - addFormNote(F("Eventnames: taskname + #RawRGB, #RawRGBtransformed, #NormRGB, #NormRGBtransformed, #NormSRGB, #NormSRGBtransformed")); - addFormNote(F("Only generated for not selected outputs, 3 values per event, =<r>,<g>,<b>")); -# endif // ifdef P050_OPTION_RGB_EVENTS - } - - { - # define P050_VALUE4_OPTIONS 4 - const __FlashStringHelper *optionsOutput[P050_VALUE4_OPTIONS] = { - F("Color Temperature (deprecated) [K]"), - F("Color Temperature (DN40) [K]"), - F("Ambient Light [Lux]"), - F("Clear Channel"), - }; - const int optionValuesOutput[P050_VALUE4_OPTIONS] = { 0, 1, 2, 3 }; - addFormSelector(F("Output at Values #4"), F("output4"), P050_VALUE4_OPTIONS, optionsOutput, optionValuesOutput, PCONFIG(3)); - addFormNote(F("Optionally adjust Values #4 name accordingly.")); - - addFormCheckBox(F("Generate all as events"), F("allevents"), PCONFIG(4) == 1); - addFormNote(F("Eventnames: taskname + #CCT, #CCT_DN40, #Lux, #Clear")); - } - - { - P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); - - if (nullptr != P050_data) { - addFormSubHeader(F("Transformation matrix")); - - P050_data->resetTransformation(); - P050_data->loadSettings(event->TaskIndex); - - // Display current settings - const String RGB = F("RGB"); - - for (int i = 0; i < 3; i++) { - addRowLabel(RGB.substring(i, i + 1)); - for (int j = 0; j < 3; j++) { - addHtml(strformat(F("%c%d:"), static_cast('a' + i), j + 1)); - addFloatNumberBox(P050_data_struct::generate_cal_id(i, j), - P050_data->TransformationSettings.matrix[i][j], - -255.999f, - 255.999f); - } - } - addFormNote(F("Check plugin documentation (i) on how to calibrate and how to calculate transformation matrix.")); - - addFormCheckBox(F("Reset transformation matrix"), F("resettrans"), false); - addFormNote(F("Select then Submit to confirm. Reset transformation matrix can't be un-done!")); - - // Need to delete the allocated object here - delete P050_data; - } - } - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("inttime")); - PCONFIG(1) = getFormItemInt(F("gain")); - PCONFIG(2) = getFormItemInt(F("outputrgb")); - PCONFIG(3) = getFormItemInt(F("output4")); - PCONFIG(4) = isFormItemChecked(F("allevents")) ? 1 : 0; -# ifdef P050_OPTION_RGB_EVENTS - PCONFIG(5) = isFormItemChecked(F("rgbevents")) ? 1 : 0; -# endif // ifdef P050_OPTION_RGB_EVENTS - bool resetTransformation = isFormItemChecked(F("resettrans")); - { - P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); - - if (nullptr != P050_data) { - P050_data->resetTransformation(); - P050_data->loadSettings(event->TaskIndex); - - if (resetTransformation) { - // Clear Transformation settings - P050_data->resetTransformation(); - } else { - // Save new settings - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 3; j++) { - P050_data->TransformationSettings.matrix[i][j] = - getFormItemFloat(P050_data_struct::generate_cal_id(i, j)); - } - } - } - P050_data->saveSettings(event->TaskIndex); - - // Need to delete the allocated object here - delete P050_data; - } - } - success = true; - break; - } - - case PLUGIN_INIT: - { - /* Initialise with specific int time and gain values */ - initPluginTaskData(event->TaskIndex, new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1))); - P050_data_struct *P050_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P050_data) { - P050_data->resetTransformation(); - success = true; - } - break; - } - - case PLUGIN_EXIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - P050_data_struct *P050_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P050_data) { - return success; - } - - if (P050_data->tcs.begin()) { -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Found TCS34725 sensor")); -# endif // ifndef BUILD_NO_DEBUG - - uint16_t r, g, b, c; - float value4 = 0.0f; - - P050_data->loadSettings(event->TaskIndex); - - P050_data->tcs.getRawData(&r, &g, &b, &c, true); - - switch (PCONFIG(3)) { - case 0: - value4 = P050_data->tcs.calculateColorTemperature(r, g, b); // Deprecated because of deemed inaccurate calculation, kept for - // backward compatibility - break; - case 1: - value4 = P050_data->tcs.calculateColorTemperature_dn40(r, g, b, c); - break; - case 2: - value4 = P050_data->tcs.calculateLux(r, g, b); - break; - case 3: - value4 = c; - break; - } - - float sRGBFactor = 1.0f; // (s)RGB factor 1.0 or 255.0 - uint32_t t = r + g + b; // Normalization factor - - if (t == 0) { - UserVar.setFloat(event->TaskIndex, 0, 0.0f); - UserVar.setFloat(event->TaskIndex, 1, 0.0f); - UserVar.setFloat(event->TaskIndex, 2, 0.0f); - } - - switch (PCONFIG(2)) { - case 0: - UserVar.setFloat(event->TaskIndex, 0, r); - UserVar.setFloat(event->TaskIndex, 1, g); - UserVar.setFloat(event->TaskIndex, 2, b); - break; - case 1: - - if (t != 0) { // R/G/B transformed - float r_f, g_f, b_f{}; - P050_data->applyTransformation( - r, g, b, - &r_f, &g_f, &b_f); - UserVar.setFloat(event->TaskIndex, 0, r_f); - UserVar.setFloat(event->TaskIndex, 1, g_f); - UserVar.setFloat(event->TaskIndex, 2, b_f); - - } - break; - case 2: - sRGBFactor = 255.0f; - - // Fall through - case 4: - - if (t != 0) { // r/g/b (normalized to 0.00..255.00 (but avoid divide by 0) - UserVar.setFloat(event->TaskIndex, 0, static_cast(r) / t * sRGBFactor); - UserVar.setFloat(event->TaskIndex, 1, static_cast(g) / t * sRGBFactor); - UserVar.setFloat(event->TaskIndex, 2, static_cast(b) / t * sRGBFactor); - } - break; - case 3: - sRGBFactor = 255.0f; - - // Fall through - case 5: - - if (t != 0) { // R/G/B normalized & transformed - const float nr = static_cast(r) / t * sRGBFactor; - const float ng = static_cast(g) / t * sRGBFactor; - const float nb = static_cast(b) / t * sRGBFactor; - - float r_f, g_f, b_f{}; - P050_data->applyTransformation( - nr, ng, nb, - &r_f, &g_f, &b_f); - UserVar.setFloat(event->TaskIndex, 0, r_f); - UserVar.setFloat(event->TaskIndex, 1, g_f); - UserVar.setFloat(event->TaskIndex, 2, b_f); - } - break; - } - UserVar.setFloat(event->TaskIndex, 3, value4); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("TCS34725: "); - - switch (PCONFIG(3)) { - case 0: - case 1: - log += F("Color Temp (K): "); - break; - case 2: - log += F("Lux : "); - break; - case 3: - log += F("Clear : "); - break; - } - log += strformat( - F(" %s R: %s G: %s B: %s "), - formatUserVarNoCheck(event->TaskIndex, 3).c_str(), - formatUserVarNoCheck(event->TaskIndex, 0).c_str(), - formatUserVarNoCheck(event->TaskIndex, 1).c_str(), - formatUserVarNoCheck(event->TaskIndex, 2).c_str()); - addLogMove(LOG_LEVEL_INFO, log); - } - -# ifdef P050_OPTION_RGB_EVENTS - - // First RGB events - if ((PCONFIG(5) == 1) && (t != 0)) { // Not if invalid read/data - float tr, tg, tb, nr, ng, nb; - - for (int i = 0; i < 6; i++) { - if (i != PCONFIG(2)) { // Skip currently selected RGB output to keep nr. of events a bit limited - const __FlashStringHelper* varName = F(""); - String eventValues; - sRGBFactor = 1.0f; - - switch (i) { - case 0: - varName = F("RawRGB"); - eventValues += strformat(F("%u,%u,%u"), r, g, b); - break; - case 3: - sRGBFactor = 255.0f; - - // Fall through - case 1: - case 5: - - if (i == 1) { - varName = F("RawRGBtransformed"); - P050_data->applyTransformation(r, g, b, &tr, &tg, &tb); - } else { - if (i == 3) { - varName = F("NormRGBtransformed"); - } else { - varName = F("NormSRGBtransformed"); - } - nr = static_cast(r) / t * sRGBFactor; - ng = static_cast(g) / t * sRGBFactor; - nb = static_cast(b) / t * sRGBFactor; - P050_data->applyTransformation(nr, ng, nb, &tr, &tg, &tb); - } - - eventValues += strformat(F("%.4f,%.4f,%.4f"), tr, tg, tb); - break; - case 2: - sRGBFactor = 255.0f; - - // Fall through - case 4: - - if (i == 2) { - varName = F("NormRGB"); - } else { - varName = F("NormSRGB"); - } - eventValues += strformat( - F("%.4f,%.4f,%.4f"), - static_cast(r) / t * sRGBFactor, - static_cast(g) / t * sRGBFactor, - static_cast(b) / t * sRGBFactor); - break; - default: - eventValues.clear(); - break; - } - - if (!eventValues.isEmpty()) { - eventQueue.add(event->TaskIndex, varName, eventValues); - } - } - } - } -# endif // ifdef P050_OPTION_RGB_EVENTS - - // Then Values #4 events - if (PCONFIG(4) == 1) { - for (int i = 0; i < 4; i++) { - switch (i) { - case 0: - eventQueue.add(event->TaskIndex, F("CCT"), P050_data->tcs.calculateColorTemperature(r, g, b)); - break; - case 1: - eventQueue.add(event->TaskIndex, F("CCT_DN40"), P050_data->tcs.calculateColorTemperature_dn40(r, g, b, c)); - break; - case 2: - eventQueue.add(event->TaskIndex, F("Lux"), P050_data->tcs.calculateLux(r, g, b)); - break; - case 3: - eventQueue.add(event->TaskIndex, F("Clear"), String(c)); - break; - default: - break; - } - } - } - - success = true; - } else { -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("No TCS34725 found")); -# endif // ifndef BUILD_NO_DEBUG - success = false; - } - - break; - } - } - return success; -} - -#endif // USES_P050 +#include "_Plugin_Helper.h" +#ifdef USES_P050 + +// ####################################################################################################### +// #################### Plugin 050 I2C TCS34725 RGB Color Sensor with IR filter and White LED ############ +// ####################################################################################################### +// +// RGB Color Sensor with IR filter and White LED +// like this one: https://www.adafruit.com/products/1334 +// based on this library: https://github.com/adafruit/Adafruit_TCS34725 +// this code is based on 20170331 date version of the above library +// this code is UNTESTED, because my TCS34725 sensor is still not shipped :( +// +// 2021-01-20 tonhuisman: Renamed Calibration to Transformation, fix some textual issues +// 2021-01-20 tonhuisman: Added optional events for not selected RGB outputs, compile-time optional +// 2021-01-19 tonhuisman: (Re)Added additional transformation & calculation options +// 2021-01-16 tonhuisman: Move stuff to PluginStructs, add 3x3 matrix calibration +// 2021-01-09 tonhuisman: Add R/G/B calibration factors, improved/corrected normalization +// 2021-01-03 tonhuisman: Merged most of the changes in the library, for adding the getRGB() and calculateColorTemperature_dn40(0 functions) +// + +# include "src/PluginStructs/P050_data_struct.h" + + +# define PLUGIN_050 +# define PLUGIN_ID_050 50 +# define PLUGIN_NAME_050 "Color - TCS34725" +# define PLUGIN_VALUENAME1_050 "Red" +# define PLUGIN_VALUENAME2_050 "Green" +# define PLUGIN_VALUENAME3_050 "Blue" +# define PLUGIN_VALUENAME4_050 "ColorTemperature" + +// #ifndef LIMIT_BUILD_SIZE +# define P050_OPTION_RGB_EVENTS + +// #endif + +boolean Plugin_050(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_050; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_050); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_050)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_050)); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(2) = 1; // RGB values: Calibrated RGB + PCONFIG(3) = 1; // Value #4: Color Temperature (DN40) + + # if FEATURE_I2C_DEVICE_CHECK + + if (!I2C_deviceCheck(0x29)) { + break; // Will return the default false for success + } + # endif // if FEATURE_I2C_DEVICE_CHECK + P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); + + if (nullptr != P050_data) { + P050_data->resetTransformation(); // Explicit reset + P050_data->saveSettings(event->TaskIndex); + delete P050_data; + } + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x29); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x29; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *optionsMode[] = { + F("2.4 ms"), + F("24 ms"), + F("50 ms"), + F("101 ms"), + F("154 ms"), + F("700 ms"), + }; + const int optionValuesMode[] = { + TCS34725_INTEGRATIONTIME_2_4MS, + TCS34725_INTEGRATIONTIME_24MS, + TCS34725_INTEGRATIONTIME_50MS, + TCS34725_INTEGRATIONTIME_101MS, + TCS34725_INTEGRATIONTIME_154MS, + TCS34725_INTEGRATIONTIME_700MS, + }; + addFormSelector(F("Integration Time"), F("inttime"), 6, optionsMode, optionValuesMode, PCONFIG(0)); + } + + { + const __FlashStringHelper *optionsMode2[] = { + F("1x"), + F("4x"), + F("16x"), + F("60x"), + }; + const int optionValuesMode2[] = { + TCS34725_GAIN_1X, + TCS34725_GAIN_4X, + TCS34725_GAIN_16X, + TCS34725_GAIN_60X, + }; + addFormSelector(F("Gain"), F("gain"), 4, optionsMode2, optionValuesMode2, PCONFIG(1)); + } + + addFormSubHeader(F("Output settings")); + + { + # define P050_RGB_OPTIONS 6 + const __FlashStringHelper *optionsRGB[P050_RGB_OPTIONS] = { + F("Raw RGB"), + F("Raw RGB transformed (3x3 matrix, below)"), + F("Normalized RGB (0..255)"), + F("Normalized RGB transformed (3x3 matrix, below)"), + F("Normalized RGB (0.0000..1.0000)"), + F("Normalized RGB (0.0000..1.0000) transformed (3x3 matrix, below)"), + }; + const int optionValuesRGB[P050_RGB_OPTIONS] = { 0, 1, 2, 3, 4, 5 }; + addFormSelector(F("Output RGB Values"), F("outputrgb"), P050_RGB_OPTIONS, optionsRGB, optionValuesRGB, PCONFIG(2)); + addFormNote(F("For 'normalized' or 'transformed' options, the Red/Green/Blue Decimals should best be increased.")); + +# ifdef P050_OPTION_RGB_EVENTS + addFormCheckBox(F("Generate RGB events"), F("rgbevents"), PCONFIG(5) == 1); + addFormNote(F("Eventnames: taskname + #RawRGB, #RawRGBtransformed, #NormRGB, #NormRGBtransformed, #NormSRGB, #NormSRGBtransformed")); + addFormNote(F("Only generated for not selected outputs, 3 values per event, =<r>,<g>,<b>")); +# endif // ifdef P050_OPTION_RGB_EVENTS + } + + { + # define P050_VALUE4_OPTIONS 4 + const __FlashStringHelper *optionsOutput[P050_VALUE4_OPTIONS] = { + F("Color Temperature (deprecated) [K]"), + F("Color Temperature (DN40) [K]"), + F("Ambient Light [Lux]"), + F("Clear Channel"), + }; + const int optionValuesOutput[P050_VALUE4_OPTIONS] = { 0, 1, 2, 3 }; + addFormSelector(F("Output at Values #4"), F("output4"), P050_VALUE4_OPTIONS, optionsOutput, optionValuesOutput, PCONFIG(3)); + addFormNote(F("Optionally adjust Values #4 name accordingly.")); + + addFormCheckBox(F("Generate all as events"), F("allevents"), PCONFIG(4) == 1); + addFormNote(F("Eventnames: taskname + #CCT, #CCT_DN40, #Lux, #Clear")); + } + + { + P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); + + if (nullptr != P050_data) { + addFormSubHeader(F("Transformation matrix")); + + P050_data->resetTransformation(); + P050_data->loadSettings(event->TaskIndex); + + // Display current settings + const String RGB = F("RGB"); + + for (int i = 0; i < 3; ++i) { + addRowLabel(RGB.substring(i, i + 1)); + for (int j = 0; j < 3; ++j) { + addHtml(strformat(F("%c%d:"), static_cast('a' + i), j + 1)); + addFloatNumberBox(P050_data_struct::generate_cal_id(i, j), + P050_data->TransformationSettings.matrix[i][j], + -255.999f, + 255.999f); + } + } + addFormNote(F("Check plugin documentation (i) on how to calibrate and how to calculate transformation matrix.")); + + addFormCheckBox(F("Reset transformation matrix"), F("resettrans"), false); + addFormNote(F("Select then Submit to confirm. Reset transformation matrix can't be un-done!")); + + // Need to delete the allocated object here + delete P050_data; + } + } + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("inttime")); + PCONFIG(1) = getFormItemInt(F("gain")); + PCONFIG(2) = getFormItemInt(F("outputrgb")); + PCONFIG(3) = getFormItemInt(F("output4")); + PCONFIG(4) = isFormItemChecked(F("allevents")) ? 1 : 0; +# ifdef P050_OPTION_RGB_EVENTS + PCONFIG(5) = isFormItemChecked(F("rgbevents")) ? 1 : 0; +# endif // ifdef P050_OPTION_RGB_EVENTS + bool resetTransformation = isFormItemChecked(F("resettrans")); + { + P050_data_struct *P050_data = new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1)); + + if (nullptr != P050_data) { + P050_data->resetTransformation(); + P050_data->loadSettings(event->TaskIndex); + + if (resetTransformation) { + // Clear Transformation settings + P050_data->resetTransformation(); + } else { + // Save new settings + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + P050_data->TransformationSettings.matrix[i][j] = + getFormItemFloat(P050_data_struct::generate_cal_id(i, j)); + } + } + } + P050_data->saveSettings(event->TaskIndex); + + // Need to delete the allocated object here + delete P050_data; + } + } + success = true; + break; + } + + case PLUGIN_INIT: + { + /* Initialise with specific int time and gain values */ + initPluginTaskData(event->TaskIndex, new (std::nothrow) P050_data_struct(PCONFIG(0), PCONFIG(1))); + P050_data_struct *P050_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P050_data) { + P050_data->resetTransformation(); + success = true; + } + break; + } + + case PLUGIN_READ: + { + P050_data_struct *P050_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P050_data) { + return success; + } + + if (P050_data->tcs.begin()) { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Found TCS34725 sensor")); +# endif // ifndef BUILD_NO_DEBUG + + uint16_t r, g, b, c; + float value4 = 0.0f; + + P050_data->loadSettings(event->TaskIndex); + + P050_data->tcs.getRawData(&r, &g, &b, &c, true); + + switch (PCONFIG(3)) { + case 0: + value4 = P050_data->tcs.calculateColorTemperature(r, g, b); // Deprecated because of deemed inaccurate calculation, kept for + // backward compatibility + break; + case 1: + value4 = P050_data->tcs.calculateColorTemperature_dn40(r, g, b, c); + break; + case 2: + value4 = P050_data->tcs.calculateLux(r, g, b); + break; + case 3: + value4 = c; + break; + } + + float sRGBFactor = 1.0f; // (s)RGB factor 1.0 or 255.0 + uint32_t t = r + g + b; // Normalization factor + + if (t == 0) { + UserVar.setFloat(event->TaskIndex, 0, 0.0f); + UserVar.setFloat(event->TaskIndex, 1, 0.0f); + UserVar.setFloat(event->TaskIndex, 2, 0.0f); + } + + switch (PCONFIG(2)) { + case 0: + UserVar.setFloat(event->TaskIndex, 0, r); + UserVar.setFloat(event->TaskIndex, 1, g); + UserVar.setFloat(event->TaskIndex, 2, b); + break; + case 1: + + if (t != 0) { // R/G/B transformed + float r_f, g_f, b_f{}; + P050_data->applyTransformation( + r, g, b, + &r_f, &g_f, &b_f); + UserVar.setFloat(event->TaskIndex, 0, r_f); + UserVar.setFloat(event->TaskIndex, 1, g_f); + UserVar.setFloat(event->TaskIndex, 2, b_f); + + } + break; + case 2: + sRGBFactor = 255.0f; + + // Fall through + case 4: + + if (t != 0) { // r/g/b (normalized to 0.00..255.00 (but avoid divide by 0) + UserVar.setFloat(event->TaskIndex, 0, static_cast(r) / t * sRGBFactor); + UserVar.setFloat(event->TaskIndex, 1, static_cast(g) / t * sRGBFactor); + UserVar.setFloat(event->TaskIndex, 2, static_cast(b) / t * sRGBFactor); + } + break; + case 3: + sRGBFactor = 255.0f; + + // Fall through + case 5: + + if (t != 0) { // R/G/B normalized & transformed + const float nr = static_cast(r) / t * sRGBFactor; + const float ng = static_cast(g) / t * sRGBFactor; + const float nb = static_cast(b) / t * sRGBFactor; + + float r_f, g_f, b_f{}; + P050_data->applyTransformation( + nr, ng, nb, + &r_f, &g_f, &b_f); + UserVar.setFloat(event->TaskIndex, 0, r_f); + UserVar.setFloat(event->TaskIndex, 1, g_f); + UserVar.setFloat(event->TaskIndex, 2, b_f); + } + break; + } + UserVar.setFloat(event->TaskIndex, 3, value4); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("TCS34725: "); + + switch (PCONFIG(3)) { + case 0: + case 1: + log += F("Color Temp (K): "); + break; + case 2: + log += F("Lux : "); + break; + case 3: + log += F("Clear : "); + break; + } + log += strformat( + F(" %s R: %s G: %s B: %s "), + formatUserVarNoCheck(event, 3).c_str(), + formatUserVarNoCheck(event, 0).c_str(), + formatUserVarNoCheck(event, 1).c_str(), + formatUserVarNoCheck(event, 2).c_str()); + addLogMove(LOG_LEVEL_INFO, log); + } + +# ifdef P050_OPTION_RGB_EVENTS + + // First RGB events + if ((PCONFIG(5) == 1) && (t != 0)) { // Not if invalid read/data + float tr, tg, tb, nr, ng, nb; + + for (int i = 0; i < 6; ++i) { + if (i != PCONFIG(2)) { // Skip currently selected RGB output to keep nr. of events a bit limited + const __FlashStringHelper* varName = F(""); + String eventValues; + sRGBFactor = 1.0f; + + switch (i) { + case 0: + varName = F("RawRGB"); + eventValues += strformat(F("%u,%u,%u"), r, g, b); + break; + case 3: + sRGBFactor = 255.0f; + + // Fall through + case 1: + case 5: + + if (i == 1) { + varName = F("RawRGBtransformed"); + P050_data->applyTransformation(r, g, b, &tr, &tg, &tb); + } else { + if (i == 3) { + varName = F("NormRGBtransformed"); + } else { + varName = F("NormSRGBtransformed"); + } + nr = static_cast(r) / t * sRGBFactor; + ng = static_cast(g) / t * sRGBFactor; + nb = static_cast(b) / t * sRGBFactor; + P050_data->applyTransformation(nr, ng, nb, &tr, &tg, &tb); + } + + eventValues += strformat(F("%.4f,%.4f,%.4f"), tr, tg, tb); + break; + case 2: + sRGBFactor = 255.0f; + + // Fall through + case 4: + + if (i == 2) { + varName = F("NormRGB"); + } else { + varName = F("NormSRGB"); + } + eventValues += strformat( + F("%.4f,%.4f,%.4f"), + static_cast(r) / t * sRGBFactor, + static_cast(g) / t * sRGBFactor, + static_cast(b) / t * sRGBFactor); + break; + default: + eventValues.clear(); + break; + } + + if (!eventValues.isEmpty()) { + eventQueue.add(event->TaskIndex, varName, eventValues); + } + } + } + } +# endif // ifdef P050_OPTION_RGB_EVENTS + + // Then Values #4 events + if (PCONFIG(4) == 1) { + for (int i = 0; i < 4; ++i) { + switch (i) { + case 0: + eventQueue.add(event->TaskIndex, F("CCT"), P050_data->tcs.calculateColorTemperature(r, g, b)); + break; + case 1: + eventQueue.add(event->TaskIndex, F("CCT_DN40"), P050_data->tcs.calculateColorTemperature_dn40(r, g, b, c)); + break; + case 2: + eventQueue.add(event->TaskIndex, F("Lux"), P050_data->tcs.calculateLux(r, g, b)); + break; + case 3: + eventQueue.add(event->TaskIndex, F("Clear"), String(c)); + break; + default: + break; + } + } + } + + success = true; + } else { +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("No TCS34725 found")); +# endif // ifndef BUILD_NO_DEBUG + success = false; + } + + break; + } + } + return success; +} + +#endif // USES_P050 diff --git a/src/_P051_AM2320.ino b/src/_P051_AM2320.ino index a98a224fd..c9ef5ca6c 100644 --- a/src/_P051_AM2320.ino +++ b/src/_P051_AM2320.ino @@ -1,123 +1,119 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P051 - -// ####################################################################################################### -// #################### Plugin 051 Temperature and Humidity Sensor AM2320 ############## -// ####################################################################################################### -// -// Temperature and Humidity Sensor AM2320 -// written by https://github.com/krikk -// based on this library: https://github.com/thakshak/AM2320 -// this code is based on git-version https://github.com/thakshak/AM2320/commit/ddaabaf37952d4c74f3ea70af20e5a95cfdfcadb -// of the above library -// - -/** Changelog: - * 2023-09-05 tonhuisman: Disable I2C device-check during read, as the sensor seems a bit 'itchy' about that - * 2023-09-05 tonhuisman: Add changelog - */ - - -# include - -# define PLUGIN_051 -# define PLUGIN_ID_051 51 -# define PLUGIN_NAME_051 "Environment - AM2320" -# define PLUGIN_VALUENAME1_051 "Temperature" -# define PLUGIN_VALUENAME2_051 "Humidity" - - -boolean Plugin_051(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_051; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - Device[deviceCount].I2CNoDeviceCheck = true; // Avoid device check - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_051); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_051)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_051)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x5c); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x5c; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - case PLUGIN_WEBFORM_SAVE: - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - AM2320 th; - - switch (th.Read()) { - case 2: - addLog(LOG_LEVEL_ERROR, F("AM2320: CRC failed")); - break; - case 1: - addLog(LOG_LEVEL_ERROR, F("AM2320: Sensor offline")); - break; - case 0: - { - UserVar.setFloat(event->TaskIndex, 0, th.t); - UserVar.setFloat(event->TaskIndex, 1, th.h); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("AM2320: Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("AM2320: Humidity: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - break; - } - } - } - } - return success; -} - -#endif // USES_P051 +#include "_Plugin_Helper.h" +#ifdef USES_P051 + +// ####################################################################################################### +// #################### Plugin 051 Temperature and Humidity Sensor AM2320 ############## +// ####################################################################################################### +// +// Temperature and Humidity Sensor AM2320 +// written by https://github.com/krikk +// based on this library: https://github.com/thakshak/AM2320 +// this code is based on git-version https://github.com/thakshak/AM2320/commit/ddaabaf37952d4c74f3ea70af20e5a95cfdfcadb +// of the above library +// + +/** Changelog: + * 2023-09-05 tonhuisman: Disable I2C device-check during read, as the sensor seems a bit 'itchy' about that + * 2023-09-05 tonhuisman: Add changelog + */ + + +# include + +# define PLUGIN_051 +# define PLUGIN_ID_051 51 +# define PLUGIN_NAME_051 "Environment - AM2320" +# define PLUGIN_VALUENAME1_051 "Temperature" +# define PLUGIN_VALUENAME2_051 "Humidity" + + +boolean Plugin_051(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_051; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].I2CNoDeviceCheck = true; // Avoid device check + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_051); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_051)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_051)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x5c); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x5c; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_SAVE: + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + AM2320 th; + + switch (th.Read()) { + case 2: + addLog(LOG_LEVEL_ERROR, F("AM2320: CRC failed")); + break; + case 1: + addLog(LOG_LEVEL_ERROR, F("AM2320: Sensor offline")); + break; + case 0: + { + UserVar.setFloat(event->TaskIndex, 0, th.t); + UserVar.setFloat(event->TaskIndex, 1, th.h); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("AM2320: Temperature: "), formatUserVarNoCheck(event, 0))); + addLogMove(LOG_LEVEL_INFO, concat(F("AM2320: Humidity: "), formatUserVarNoCheck(event, 1))); + } + success = true; + break; + } + } + } + } + return success; +} + +#endif // USES_P051 diff --git a/src/_P053_PMSx003.ino b/src/_P053_PMSx003.ino index 545759554..87a937c18 100644 --- a/src/_P053_PMSx003.ino +++ b/src/_P053_PMSx003.ino @@ -163,7 +163,7 @@ boolean Plugin_053(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("RST")), F("rstpin"), PLUGIN_053_RST_PIN); addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("SET")), F("pwrpin"), PLUGIN_053_PWR_PIN); - addFormNote(F("RST and SET pins on sensor are pulled up internal in the sensor")); + addFormNote(F("RST and SET pins on sensor are pulled up internally by the sensor")); # ifdef PLUGIN_053_ENABLE_EXTRA_SENSORS { addFormSubHeader(F("Device")); diff --git a/src/_P055_Chiming.ino b/src/_P055_Chiming.ino index 2bd773b55..db2b3eb27 100644 --- a/src/_P055_Chiming.ino +++ b/src/_P055_Chiming.ino @@ -117,8 +117,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = F("Driver#8: "); - string += formatGpioLabel(static_cast(Settings.TaskDevicePin[3][event->TaskIndex]), false); + string = concat(F("Driver#8: "), formatGpioLabel(static_cast(Settings.TaskDevicePin[3][event->TaskIndex]), false)); success = true; break; } @@ -182,7 +181,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) Plugin_055_Data->chimeClock = PCONFIG(2); String log = F("Chime: GPIO: "); - for (uint8_t i=0; i<4; i++) + for (uint8_t i = 0; i < 4; ++i) { int pin = Settings.TaskDevicePin[i][event->TaskIndex]; Plugin_055_Data->pin[i] = pin; @@ -258,13 +257,11 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) String tokens; uint8_t hours = node_time.hour(); - uint8_t minutes = node_time.minute(); + const uint8_t minutes = node_time.minute(); if (Plugin_055_Data->chimeClock) { - char tmpString[8] = {0}; - - sprintf_P(tmpString, PSTR("%02d%02d"), hours, minutes); + const String tmpString = strformat(F("%02d%02d"), hours, minutes); if (Plugin_055_ReadChime(tmpString, tokens)) Plugin_055_AddStringFIFO(tokens); @@ -278,9 +275,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) if (hours == 0) hours = 12; - uint8_t index = hours; - - tokens = parseString(tokens, index); + tokens = parseString(tokens, hours); Plugin_055_AddStringFIFO(tokens); } } @@ -302,7 +297,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) { if (timeDiff(millisAct, Plugin_055_Data->millisStateEnd) <= 0) // end reached? { - for (uint8_t i=0; i<4; i++) + for (uint8_t i = 0; i < 4; ++i) { if (Plugin_055_Data->pin[i] >= 0) digitalWrite(Plugin_055_Data->pin[i], Plugin_055_Data->lowActive); @@ -318,10 +313,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) char c = Plugin_055_ReadFIFO(); # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Chime: Process '"); - log += c; - log += '\''; - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, strformat(F("Chime: Process '%c'"), c)); } #endif @@ -354,7 +346,7 @@ boolean Plugin_055(uint8_t function, struct EventStruct *event, String& string) case '9': { uint8_t mask = 1; - for (uint8_t i=0; i<4; i++) + for (uint8_t i = 0; i < 4; ++i) { if (Plugin_055_Data->pin[i] >= 0) if (c & mask) diff --git a/src/_P056_SDS011-Dust.ino b/src/_P056_SDS011-Dust.ino index f374f6d26..67c9aad94 100644 --- a/src/_P056_SDS011-Dust.ino +++ b/src/_P056_SDS011-Dust.ino @@ -82,7 +82,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { // FIXME TD-er: Whether TX pin is connected should be set somewhere - if (Plugin_056_hasTxPin(event)) { + if (validGpio(CONFIG_PIN2)) { addFormNumericBox(F("Sleep time"), F("sleeptime"), PCONFIG(0), 0, 30); @@ -93,7 +93,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_WEBFORM_SAVE: { - if (Plugin_056_hasTxPin(event)) { + if (validGpio(CONFIG_PIN2)) { // Communications to device should work. const int newsleeptime = getFormItemInt(F("sleeptime")); @@ -116,11 +116,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) Plugin_056_SDS = new (std::nothrow) CjkSDS011(port, CONFIG_PIN1, CONFIG_PIN2); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SDS : Init OK ESP GPIO-pin RX:"); - log += CONFIG_PIN1; - log += F(" TX:"); - log += CONFIG_PIN2; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("SDS : Init OK ESP GPIO-pin RX:%d TX:%d"), CONFIG_PIN1, CONFIG_PIN2)); } success = true; @@ -151,22 +147,18 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) { const float pm2_5 = Plugin_056_SDS->GetPM2_5(); const float pm10 = Plugin_056_SDS->GetPM10_(); - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("SDS : act "); - log += pm2_5; - log += ' '; - log += pm10; - addLogMove(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, strformat(F("SDS : act %.2f %.2f"), pm2_5, pm10)); } - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG if (Settings.TaskDeviceTimer[event->TaskIndex] == 0) { UserVar.setFloat(event->TaskIndex, 0, pm2_5); UserVar.setFloat(event->TaskIndex, 1, pm10); - event->sensorType = Sensor_VType::SENSOR_TYPE_DUAL; + event->sensorType = Sensor_VType::SENSOR_TYPE_DUAL; sendData(event); } } @@ -186,7 +178,7 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) if (Plugin_056_SDS->ReadAverage(pm25, pm10)) { UserVar.setFloat(event->TaskIndex, 0, pm25); UserVar.setFloat(event->TaskIndex, 1, pm10); - success = true; + success = true; } break; } @@ -195,18 +187,11 @@ boolean Plugin_056(uint8_t function, struct EventStruct *event, String& string) return success; } -boolean Plugin_056_hasTxPin(struct EventStruct *event) { - const int16_t serial_tx = CONFIG_PIN2; - - return serial_tx >= 0; -} - String Plugin_056_ErrorToString(int error) { String log; if (error < 0) { - log = F("comm error: "); - log += error; + log = concat(F("comm error: "), error); } return log; } @@ -218,10 +203,9 @@ String Plugin_056_WorkingPeriodToString(int workingPeriod) { String log; if (workingPeriod > 0) { - log += workingPeriod; - log += F(" minutes"); + log = strformat(F("%d minutes"), workingPeriod); } else { - log += F(" continuous"); + log = F(" continuous"); } return log; } @@ -233,9 +217,7 @@ void Plugin_056_setWorkingPeriod(int minutes) { Plugin_056_SDS->SetWorkingPeriod(minutes); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SDS : Working Period set to: "); - log += Plugin_056_WorkingPeriodToString(minutes); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, concat(F("SDS : Working Period set to: "), Plugin_056_WorkingPeriodToString(minutes))); } } diff --git a/src/_P057_HT16K33_LED.ino b/src/_P057_HT16K33_LED.ino index ac045f458..4d1b40cd5 100644 --- a/src/_P057_HT16K33_LED.ino +++ b/src/_P057_HT16K33_LED.ino @@ -65,12 +65,12 @@ // There is no configuration here to set or manipulate the time, only to // display it. -#define PLUGIN_057 -#define PLUGIN_ID_057 57 -#define PLUGIN_NAME_057 "Display - HT16K33" +# define PLUGIN_057 +# define PLUGIN_ID_057 57 +# define PLUGIN_NAME_057 "Display - HT16K33" -#include "src/PluginStructs/P057_data_struct.h" +# include "src/PluginStructs/P057_data_struct.h" boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) { @@ -105,6 +105,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { const uint8_t i2cAddressValues[] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0)); } else { @@ -127,19 +128,18 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) addFormSubHeader(F("7-Seg. Clock")); { - int16_t choice = PCONFIG(1); - const __FlashStringHelper * options[3] = { F("none"), F("7-Seg. HH:MM (24 hour)"), F("7-Seg. HH:MM (12 hour)") }; - addFormSelector(F("Clock Type"), F("clocktype"), 3, options, nullptr, choice); + const __FlashStringHelper *options[3] = { F("none"), F("7-Seg. HH:MM (24 hour)"), F("7-Seg. HH:MM (12 hour)") }; + addFormSelector(F("Clock Type"), F("clocktype"), 3, options, nullptr, PCONFIG(1)); } - addFormNumericBox(F("Seg. for Xx:xx"), F("clocksegh10"), PCONFIG(2), 0, 7); - addFormNumericBox(F("Seg. for xX:xx"), F("clocksegh1"), PCONFIG(3), 0, 7); - addFormNumericBox(F("Seg. for xx:Xx"), F("clocksegm10"), PCONFIG(4), 0, 7); - addFormNumericBox(F("Seg. for xx:xX"), F("clocksegm1"), PCONFIG(5), 0, 7); + addFormNumericBox(F("Seg. for Xx:xx"), F("csh10"), PCONFIG(2), 0, 7); + addFormNumericBox(F("Seg. for xX:xx"), F("csh1"), PCONFIG(3), 0, 7); + addFormNumericBox(F("Seg. for xx:Xx"), F("csm10"), PCONFIG(4), 0, 7); + addFormNumericBox(F("Seg. for xx:xX"), F("csm1"), PCONFIG(5), 0, 7); - addFormNumericBox(F("Seg. for Colon"), F("clocksegcol"), PCONFIG(6), -1, 7); + addFormNumericBox(F("Seg. for Colon"), F("cscol"), PCONFIG(6), -1, 7); addHtml(F(" Value ")); - addNumericBox(F("clocksegcolval"), PCONFIG(7), 0, 255); + addNumericBox(F("cscolval"), PCONFIG(7), 0, 255); success = true; break; @@ -151,12 +151,12 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) PCONFIG(1) = getFormItemInt(F("clocktype")); - PCONFIG(2) = getFormItemInt(F("clocksegh10")); - PCONFIG(3) = getFormItemInt(F("clocksegh1")); - PCONFIG(4) = getFormItemInt(F("clocksegm10")); - PCONFIG(5) = getFormItemInt(F("clocksegm1")); - PCONFIG(6) = getFormItemInt(F("clocksegcol")); - PCONFIG(7) = getFormItemInt(F("clocksegcolval")); + PCONFIG(2) = getFormItemInt(F("csh10")); + PCONFIG(3) = getFormItemInt(F("csh1")); + PCONFIG(4) = getFormItemInt(F("csm10")); + PCONFIG(5) = getFormItemInt(F("csm1")); + PCONFIG(6) = getFormItemInt(F("cscol")); + PCONFIG(7) = getFormItemInt(F("cscolval")); success = true; break; @@ -181,7 +181,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) if (equals(command, F("mprint"))) { - String text = parseStringToEnd(string, 2); + const String text = parseStringToEnd(string, 2); if (!text.isEmpty()) { uint8_t seg = 0; @@ -197,6 +197,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) P057_data->ledMatrix.SetDigit(seg, c, setDot); seg++; txt++; + if (setDot) { txt++; } // extra increment to skip past the dot } P057_data->ledMatrix.TransmitRowBuffer(); @@ -204,8 +205,8 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) } } else if (equals(command, F("mbr"))) { - String param = parseString(string, 2); - int32_t brightness; + const String param = parseString(string, 2); + int32_t brightness; if (validIntFromString(param, brightness)) { if ((brightness >= 0) && (brightness <= 255)) { @@ -219,7 +220,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) String param; String paramKey; String paramVal; - uint8_t paramIdx = 2; + uint8_t paramIdx = 2; uint8_t seg = 0; uint16_t value = 0; @@ -229,24 +230,24 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) lowerString.replace(F(" ="), F("=")); lowerString.replace(F("= "), F("=")); - param = parseString(lowerString, paramIdx++); + param = parseStringKeepCase(lowerString, paramIdx++); if (param.length()) { while (param.length()) { - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG_MORE, param); - #endif + # endif // ifndef BUILD_NO_DEBUG if (equals(param, F("log"))) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("MX : "); - for (uint8_t i = 0; i < 8; i++) + for (uint8_t i = 0; i < 8; ++i) { - log += String(P057_data->ledMatrix.GetRow(i), 16); + log += formatToHex_no_prefix(P057_data->ledMatrix.GetRow(i)); log += F("h, "); } addLogMove(LOG_LEVEL_INFO, log); @@ -256,7 +257,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) else if (equals(param, F("test"))) { - for (uint8_t i = 0; i < 8; i++) { + for (uint8_t i = 0; i < 8; ++i) { P057_data->ledMatrix.SetRow(i, 1 << i); } success = true; @@ -310,7 +311,7 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) seg++; } - param = parseString(lowerString, paramIdx++); + param = parseStringKeepCase(lowerString, paramIdx++); } } else @@ -329,15 +330,15 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_CLOCK_IN: { - P057_data_struct *P057_data = + P057_data_struct *P057_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == P057_data || (PCONFIG(1) == 0)) { + if ((nullptr == P057_data) || (PCONFIG(1) == 0)) { break; } - uint8_t hours = node_time.hour(); - uint8_t minutes = node_time.minute(); + uint8_t hours = node_time.hour(); + const uint8_t minutes = node_time.minute(); // P057_data->ledMatrix.ClearRowBuffer(); P057_data->ledMatrix.SetDigit(PCONFIG(5), minutes % 10); @@ -380,16 +381,16 @@ boolean Plugin_057(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_TEN_PER_SECOND: { - P057_data_struct *P057_data = + P057_data_struct *P057_data = static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr == P057_data || (PCONFIG(1) == 0)) { // clock enabled? + if ((nullptr == P057_data) || (PCONFIG(1) == 0)) { // clock enabled? break; } if (PCONFIG(6) >= 0) // colon used? { - uint8_t act = ((uint16_t)millis() >> 9) & 1; // blink with about 2 Hz + const uint8_t act = ((uint16_t)millis() >> 9) & 1; // blink with about 2 Hz static uint8_t last = 0; if (act != last) diff --git a/src/_P058_HT16K33_KeyPad.ino b/src/_P058_HT16K33_KeyPad.ino index 3688c6223..ac3fa7897 100644 --- a/src/_P058_HT16K33_KeyPad.ino +++ b/src/_P058_HT16K33_KeyPad.ino @@ -29,13 +29,13 @@ // Note: The HT16K33-LED-plugin and the HT16K33-key-plugin can be used at the same time with the same I2C address -#define PLUGIN_058 -#define PLUGIN_ID_058 58 -#define PLUGIN_NAME_058 "Keypad - HT16K33" -#define PLUGIN_VALUENAME1_058 "ScanCode" +# define PLUGIN_058 +# define PLUGIN_ID_058 58 +# define PLUGIN_NAME_058 "Keypad - HT16K33" +# define PLUGIN_VALUENAME1_058 "ScanCode" -#include "src/PluginStructs/P058_data_struct.h" +# include "src/PluginStructs/P058_data_struct.h" boolean Plugin_058(uint8_t function, struct EventStruct *event, String& string) @@ -77,6 +77,7 @@ boolean Plugin_058(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { const uint8_t i2cAddressValues[] = { 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77 }; + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0)); } else { @@ -125,12 +126,10 @@ boolean Plugin_058(uint8_t function, struct EventStruct *event, String& string) if (P058_data->readKey(key)) { UserVar.setFloat(event->TaskIndex, 0, key); - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Mkey : key=0x"); - log += String(key, 16); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Mkey : key=0x%x"), key)); } sendData(event); diff --git a/src/_P059_Encoder.ino b/src/_P059_Encoder.ino index 45d1b7d96..cb34f16f5 100644 --- a/src/_P059_Encoder.ino +++ b/src/_P059_Encoder.ino @@ -117,7 +117,7 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) String log = F("QEI : GPIO: "); - for (uint8_t i = 0; i < 3; i++) + for (uint8_t i = 0; i < 3; ++i) { int pin = PIN(i); @@ -163,9 +163,7 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("QEI : "); - log += c; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, concat(F("QEI : "), c)); } sendData(event); @@ -196,9 +194,7 @@ boolean Plugin_059(uint8_t function, struct EventStruct *event, String& string) if (event->Par1 >= 0) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("QEI : "); - log += string; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, concat(F("QEI : "), string)); } P_059_sensordefs[event->TaskIndex]->write(event->Par1); Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); diff --git a/src/_P060_MCP3221.ino b/src/_P060_MCP3221.ino index 6bec24853..396ae0a15 100644 --- a/src/_P060_MCP3221.ino +++ b/src/_P060_MCP3221.ino @@ -1,174 +1,180 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P060 - -// ####################################################################################################### -// #################################### Plugin 060: MCP3221 ############################################## -// ####################################################################################################### - -// Plugin to read 12-bit-values from ADC chip MCP3221. It is used e.g. in MinipH pH interface to sample a pH probe in an aquarium -// written by Jochen Krapf (jk@nerd2nerd.org) - - -# include "src/PluginStructs/P060_data_struct.h" - -# define PLUGIN_060 -# define PLUGIN_ID_060 60 -# define PLUGIN_NAME_060 "Analog input - MCP3221" -# define PLUGIN_VALUENAME1_060 "Analog" - - -boolean Plugin_060(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_060; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_060); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_060)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x4D, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4E, 0x4F }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0)); - } else { - success = intArrayContains(8, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - addFormCheckBox(F("Oversampling"), F("oversampling"), PCONFIG(1)); - - addFormSubHeader(F("Two Point Calibration")); - - addFormCheckBox(F("Calibration Enabled"), F("cal"), PCONFIG(3)); - - addFormNumericBox(F("Point 1"), F("adc1"), PCONFIG_LONG(0), 0, 4095); - html_add_estimate_symbol(); - addTextBox(F("out1"), toString(PCONFIG_FLOAT(0), 3), 10); - - addFormNumericBox(F("Point 2"), F("adc2"), PCONFIG_LONG(1), 0, 4095); - html_add_estimate_symbol(); - addTextBox(F("out2"), toString(PCONFIG_FLOAT(1), 3), 10); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - - PCONFIG(1) = isFormItemChecked(F("oversampling")); - - PCONFIG(3) = isFormItemChecked(F("cal")); - - PCONFIG_LONG(0) = getFormItemInt(F("adc1")); - PCONFIG_FLOAT(0) = getFormItemFloat(F("out1")); - - PCONFIG_LONG(1) = getFormItemInt(F("adc2")); - PCONFIG_FLOAT(1) = getFormItemFloat(F("out2")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P060_data_struct(PCONFIG(0))); - break; - } - - - case PLUGIN_TEN_PER_SECOND: - { - if (PCONFIG(1)) // Oversampling? - { - P060_data_struct *P060_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P060_data) { - P060_data->overSampleRead(); - success = true; - } - } - break; - } - - case PLUGIN_READ: - { - P060_data_struct *P060_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P060_data) { - UserVar.setFloat(event->TaskIndex, 0, P060_data->getValue()); - - String log = F("ADMCP: Analog value: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - - if (PCONFIG(3)) // Calibration? - { - int adc1 = PCONFIG_LONG(0); - int adc2 = PCONFIG_LONG(1); - float out1 = PCONFIG_FLOAT(0); - float out2 = PCONFIG_FLOAT(1); - - if (adc1 != adc2) - { - const float normalized = (UserVar[event->BaseVarIndex] - adc1) / static_cast(adc2 - adc1); - UserVar.setFloat(event->TaskIndex, 0, normalized * (out2 - out1) + out1); - - log += F(" = "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - } - } - - addLogMove(LOG_LEVEL_INFO, log); - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P060 +#include "_Plugin_Helper.h" +#ifdef USES_P060 + +// ####################################################################################################### +// #################################### Plugin 060: MCP3221 ############################################## +// ####################################################################################################### + +// Plugin to read 12-bit-values from ADC chip MCP3221. It is used e.g. in MinipH pH interface to sample a pH probe in an aquarium +// written by Jochen Krapf (jk@nerd2nerd.org) + + +# include "src/PluginStructs/P060_data_struct.h" + +# define PLUGIN_060 +# define PLUGIN_ID_060 60 +# define PLUGIN_NAME_060 "Analog input - MCP3221" +# define PLUGIN_VALUENAME1_060 "Analog" + + +boolean Plugin_060(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_060; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_060); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_060)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0), 0x4D); + } else { + success = intArrayContains(8, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(0) = 0x4D; // Default address + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormCheckBox(F("Oversampling"), F("oversampling"), PCONFIG(1)); + + addFormSubHeader(F("Two Point Calibration")); + + addFormCheckBox(F("Calibration Enabled"), F("cal"), PCONFIG(3)); + + addFormNumericBox(F("Point 1"), F("adc1"), PCONFIG_LONG(0), 0, 4095); + html_add_estimate_symbol(); + addTextBox(F("out1"), toString(PCONFIG_FLOAT(0), 3), 10); + + addFormNumericBox(F("Point 2"), F("adc2"), PCONFIG_LONG(1), 0, 4095); + html_add_estimate_symbol(); + addTextBox(F("out2"), toString(PCONFIG_FLOAT(1), 3), 10); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + + PCONFIG(1) = isFormItemChecked(F("oversampling")); + + PCONFIG(3) = isFormItemChecked(F("cal")); + + PCONFIG_LONG(0) = getFormItemInt(F("adc1")); + PCONFIG_FLOAT(0) = getFormItemFloat(F("out1")); + + PCONFIG_LONG(1) = getFormItemInt(F("adc2")); + PCONFIG_FLOAT(1) = getFormItemFloat(F("out2")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P060_data_struct(PCONFIG(0))); + break; + } + + + case PLUGIN_TEN_PER_SECOND: + { + if (PCONFIG(1)) // Oversampling? + { + P060_data_struct *P060_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P060_data) { + P060_data->overSampleRead(); + success = true; + } + } + break; + } + + case PLUGIN_READ: + { + P060_data_struct *P060_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P060_data) { + UserVar.setFloat(event->TaskIndex, 0, P060_data->getValue()); + + String log = concat(F("ADMCP: Analog value: "), formatUserVarNoCheck(event, 0)); + + if (PCONFIG(3)) // Calibration? + { + int adc1 = PCONFIG_LONG(0); + int adc2 = PCONFIG_LONG(1); + float out1 = PCONFIG_FLOAT(0); + float out2 = PCONFIG_FLOAT(1); + + if (adc1 != adc2) + { + const float normalized = (UserVar[event->BaseVarIndex] - adc1) / static_cast(adc2 - adc1); + UserVar.setFloat(event->TaskIndex, 0, normalized * (out2 - out1) + out1); + + log += concat(F(" = "), formatUserVarNoCheck(event, 0)); + } + } + + addLogMove(LOG_LEVEL_INFO, log); + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P060 diff --git a/src/_P061_KeyPad.ino b/src/_P061_KeyPad.ino index 2de91b791..8e73305a8 100644 --- a/src/_P061_KeyPad.ino +++ b/src/_P061_KeyPad.ino @@ -157,7 +157,7 @@ boolean Plugin_061(uint8_t function, struct EventStruct *event, String& string) F("PCF8575 (Direct 16)") # endif // ifdef P061_ENABLE_PCF8575 }; - int optionsCount = + const int optionsCount = # ifdef P061_ENABLE_PCF8575 6; # else // ifdef P061_ENABLE_PCF8575 diff --git a/src/_P062_MPR121_KeyPad.ino b/src/_P062_MPR121_KeyPad.ino index 367be87a4..d329e99a6 100644 --- a/src/_P062_MPR121_KeyPad.ino +++ b/src/_P062_MPR121_KeyPad.ino @@ -106,9 +106,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) touch_treshold = P062_DEFAULT_TOUCH_TRESHOLD; // default value } addFormNumericBox(F("Touch treshold (1..255)"), F("touch_treshold"), touch_treshold, 0, 255); - String unit_ = F("Default: "); - unit_ += P062_DEFAULT_TOUCH_TRESHOLD; - addUnit(unit_); + addUnit(concat(F("Default: "), P062_DEFAULT_TOUCH_TRESHOLD)); } { @@ -118,9 +116,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) release_treshold = P062_DEFAULT_RELEASE_TRESHOLD; // default value } addFormNumericBox(F("Release treshold (1..255)"), F("release_treshold"), release_treshold, 0, 255); - String unit_ = F("Default: "); - unit_ += P062_DEFAULT_RELEASE_TRESHOLD; - addUnit(unit_); + addUnit(concat(F("Default: "), P062_DEFAULT_RELEASE_TRESHOLD)); } { const __FlashStringHelper *sensitivityOptions[] = { @@ -161,7 +157,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) html_table_header(F("Max")); } - for (int objectNr = 0; objectNr < P062_MaxTouchObjects; objectNr++) { + for (int objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) { html_TR_TD(); addHtml(F(" ")); addHtmlInt(objectNr + 1); @@ -228,7 +224,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) } P062_data->loadTouchObjects(event->TaskIndex); - for (int objectNr = 0; objectNr < P062_MaxTouchObjects; objectNr++) { + for (int objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) { P062_data->StoredSettings.TouchObjects[objectNr].touch = getFormItemIntCustomArgName(objectNr + 100); P062_data->StoredSettings.TouchObjects[objectNr].release = getFormItemIntCustomArgName(objectNr + 200); } @@ -290,7 +286,7 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) P062_data->setThresholds(touch_treshold, release_treshold); // Set custom tresholds, ignore default values } - for (uint8_t objectNr = 0; objectNr < P062_MaxTouchObjects; objectNr++) { + for (uint8_t objectNr = 0; objectNr < P062_MaxTouchObjects; ++objectNr) { if ((P062_data->StoredSettings.TouchObjects[objectNr].touch != 0) && (P062_data->StoredSettings.TouchObjects[objectNr].release != 0)) { P062_data->setThreshold(objectNr, @@ -317,27 +313,27 @@ boolean Plugin_062(uint8_t function, struct EventStruct *event, String& string) if (P062_data->readKey(key)) { UserVar.setFloat(event->TaskIndex, 0, key); - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("Tkey : "); + log.reserve(22); if (PCONFIG(1)) { - log = F("ScanCode=0x"); + log += F("ScanCode="); } else { - log = F("KeyMap=0x"); + log += F("KeyMap="); } - log += String(key, 16); + log += formatToHex(key); addLogMove(LOG_LEVEL_INFO, log); bool tbUseCalibration = bitRead(P062_CONFIG_FLAGS, P062_FLAGS_USE_CALIBRATION); if (tbUseCalibration) { uint16_t colMask = 0x01; - log.reserve(55); - for (uint8_t col = 0; col < P062_MaxTouchObjects; col++) + for (uint8_t col = 0; col < P062_MaxTouchObjects; ++col) { if (key & colMask) // this key pressed? { @@ -345,15 +341,9 @@ 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); - log = F("P062 touch #"); - log += col; - log += F(" current: "); - log += current; - log += F(" min: "); - log += min; - log += F(" max: "); - log += max; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("P062 touch #%d current: %d min: %d max: %d"), + col, current, min, max)); if (!PCONFIG(1)) { break; diff --git a/src/_P063_TTP229_KeyPad.ino b/src/_P063_TTP229_KeyPad.ino index cfb6b505b..9d5f1a252 100644 --- a/src/_P063_TTP229_KeyPad.ino +++ b/src/_P063_TTP229_KeyPad.ino @@ -44,13 +44,14 @@ uint16_t readTTP229(int16_t pinSCL, int16_t pinSDO) delayMicroseconds(10); pinMode(pinSDO, INPUT); - for (uint8_t i = 0; i < 16; i++) + for (uint8_t i = 0; i < 16; ++i) { digitalWrite(pinSCL, HIGH); delayMicroseconds(1); digitalWrite(pinSCL, LOW); - if (!digitalRead(pinSDO)) + if (!digitalRead(pinSDO)) { value |= mask; + } delayMicroseconds(1); mask <<= 1; } @@ -125,14 +126,10 @@ boolean Plugin_063(uint8_t function, struct EventStruct *event, String& string) int16_t pinSDO = CONFIG_PIN2; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Tkey : GPIO: "); - log += pinSCL; - log += ' '; - log += pinSDO; - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("Tkey : GPIO: %d %d"), pinSCL, pinSDO)); } - if (pinSCL >= 0 && pinSDO >= 0) + if (validGpio(pinSCL) && validGpio(pinSDO)) { pinMode(pinSCL, OUTPUT); digitalWrite(pinSCL, LOW); @@ -158,9 +155,9 @@ boolean Plugin_063(uint8_t function, struct EventStruct *event, String& string) newStatus.state = 0; savePortStatus(key,newStatus); //setPinState(PLUGIN_ID_063, pinSDO, PIN_MODE_INPUT, 0); + success = true; } - success = true; break; } @@ -170,42 +167,39 @@ boolean Plugin_063(uint8_t function, struct EventStruct *event, String& string) int16_t pinSCL = CONFIG_PIN1; int16_t pinSDO = CONFIG_PIN2; - if (pinSCL >= 0 && pinSDO >= 0) + uint16_t key = readTTP229(pinSCL, pinSDO); + + if (key && PCONFIG(1)) { - uint16_t key = readTTP229(pinSCL, pinSDO); - - if (key && PCONFIG(1)) + uint16_t colMask = 0x01; + for (uint8_t col = 1; col <= 16; ++col) { - uint16_t colMask = 0x01; - for (uint8_t col = 1; col <= 16; col++) + if (key & colMask) // this key pressed? { - if (key & colMask) // this key pressed? - { - key = col; - break; - } - colMask <<= 1; + key = col; + break; } + colMask <<= 1; + } + } + + if (keyLast != key) + { + keyLast = key; + UserVar.setFloat(event->TaskIndex, 0, key); + event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("Tkey : "); + if (PCONFIG(1)) + log = F("ScanCode="); + else + log = F("KeyMap="); + log += formatToHex(key); + addLogMove(LOG_LEVEL_INFO, log); } - if (keyLast != key) - { - keyLast = key; - UserVar.setFloat(event->TaskIndex, 0, key); - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Tkey : "); - if (PCONFIG(1)) - log = F("ScanCode=0x"); - else - log = F("KeyMap=0x"); - log += String(key, 16); - addLogMove(LOG_LEVEL_INFO, log); - } - - sendData(event); - } + sendData(event); } success = true; diff --git a/src/_P064_APDS9960.ino b/src/_P064_APDS9960.ino index 42f89fc86..c25fc4948 100644 --- a/src/_P064_APDS9960.ino +++ b/src/_P064_APDS9960.ino @@ -17,6 +17,8 @@ // Note: The chip has a wide view-of-angle. If housing is in this angle the chip blocks! +// 2024-03-30 tonhuisman: Add 'Separate Gesture event' option (#Swipe=) so it doesn't interfere with light/color +// measurement // 2022-08-12 tonhuisman: Remove [DEVELOPMENT] tag // 2022-08-05 tonhuisman: Remove [TESTING] tag, Improvement: INIT, 10/sec and READ events now return false if errors occur during processing // 2022-06-17 tonhuisman: Remove I2C address selector, as there is nothing to choose... @@ -29,7 +31,7 @@ // Added settings for Gain (Gesture, Proximity, Ambient Light Sensor), Led Power (Gesture and Proximity/ALS) and Led Boost (Gesture) // to allow better tuning for use of the sensor. Also adapted the SparkFun_APDS9960 driver for enabling this. // R/G/B Colors mode has it's settings shared with the Gesture/Proximity/ALS as they are the exact same parameters, but with different -// labels only. +// labels only. # define PLUGIN_064 @@ -45,7 +47,9 @@ # define PLUGIN_MODE_GPL_064 0 // GPL = Gesture/Proximity/(Ambient) Light Sensor mode # define PLUGIN_MODE_RGB_064 1 // RGB = R/G/B Colors mode +# define P064_I2C_ADDRESS 0x39 +# define P064_GESTURE_EVENT PCONFIG(0) # define P064_MODE PCONFIG(1) # define P064_GGAIN PCONFIG(2) # define P064_GLDRIVE PCONFIG(3) @@ -107,7 +111,7 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: { - success = (event->Par1 == 0x39); + success = (event->Par1 == P064_I2C_ADDRESS); break; } @@ -115,7 +119,7 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) # if FEATURE_I2C_GET_ADDRESS case PLUGIN_I2C_GET_ADDRESS: { - event->Par1 = 0x39; + event->Par1 = P064_I2C_ADDRESS; success = true; break; } @@ -164,20 +168,20 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) { // Gain options, multiple gain optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here - const __FlashStringHelper *optionsGain[4] = { + const __FlashStringHelper *optionsGain[] = { F("1x"), F("2x"), F("4x (default)"), F("8x") }; - const int optionsGainValues[4] = { PGAIN_1X, PGAIN_2X, PGAIN_4X, PGAIN_8X }; // Also used for optionsALSGain + const int optionsGainValues[] = { PGAIN_1X, PGAIN_2X, PGAIN_4X, PGAIN_8X }; // Also used for optionsALSGain // Led_Drive options, all Led_Drive optionsets in SparkFun_APDS9960.h have the same valueset, so only defined once here - const __FlashStringHelper *optionsLedDrive[4] = { + const __FlashStringHelper *optionsLedDrive[] = { F("100 mA (default)"), F("50 mA"), F("25 mA"), F("12.5 mA") }; - const int optionsLedDriveValues[4] = { LED_DRIVE_100MA, LED_DRIVE_50MA, LED_DRIVE_25MA, LED_DRIVE_12_5MA }; + const int optionsLedDriveValues[] = { LED_DRIVE_100MA, LED_DRIVE_50MA, LED_DRIVE_25MA, LED_DRIVE_12_5MA }; String lightSensorGainLabel; @@ -186,23 +190,38 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode addFormSubHeader(F("Gesture parameters")); - addFormSelector(F("Gesture Gain"), F("ggain"), 4, optionsGain, optionsGainValues, P064_GGAIN); + addFormSelector(F("Gesture Gain"), + F("ggain"), + NR_ELEMENTS(optionsGainValues), + optionsGain, + optionsGainValues, + P064_GGAIN); - addFormSelector(F("Gesture LED Drive"), F("gldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_GLDRIVE); + addFormSelector(F("Gesture LED Drive"), + F("gldrive"), + NR_ELEMENTS(optionsLedDriveValues), + optionsLedDrive, + optionsLedDriveValues, + P064_GLDRIVE); { // Gesture Led-boost values - const __FlashStringHelper *optionsLedBoost[4] = { + const __FlashStringHelper *optionsLedBoost[] = { F("100 %"), F("150 %"), F("200 %"), F("300 % (default)") }; - const int optionsLedBoostValues[4] = { LED_BOOST_100, LED_BOOST_150, LED_BOOST_200, LED_BOOST_300 }; - addFormSelector(F("Gesture LED Boost"), F("lboost"), 4, optionsLedBoost, optionsLedBoostValues, P064_LED_BOOST); + const int optionsLedBoostValues[] = { LED_BOOST_100, LED_BOOST_150, LED_BOOST_200, LED_BOOST_300 }; + addFormSelector(F("Gesture LED Boost"), + F("lboost"), + NR_ELEMENTS(optionsLedBoostValues), + optionsLedBoost, + optionsLedBoostValues, + P064_LED_BOOST); } addFormSubHeader(F("Proximity & Ambient Light Sensor parameters")); - addFormSelector(F("Proximity Gain"), F("pgain"), 4, optionsGain, optionsGainValues, P064_PGAIN); + addFormSelector(F("Proximity Gain"), F("pgain"), NR_ELEMENTS(optionsGainValues), optionsGain, optionsGainValues, P064_PGAIN); lightSensorGainLabel = F("Ambient Light Sensor Gain"); lightSensorDriveLabel = F("Proximity & ALS LED Drive"); @@ -214,15 +233,26 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) } { // Ambient Light Sensor Gain options, values are equal to PGAIN values, so again avoid duplication - const __FlashStringHelper *optionsALSGain[4] = { + const __FlashStringHelper *optionsALSGain[] = { F("1x"), F("4x (default)"), F("16x"), F("64x") }; - addFormSelector(lightSensorGainLabel, F("again"), 4, optionsALSGain, optionsGainValues, P064_AGAIN); + addFormSelector(lightSensorGainLabel, F("again"), NR_ELEMENTS(optionsGainValues), optionsALSGain, optionsGainValues, P064_AGAIN); } - addFormSelector(lightSensorDriveLabel, F("ldrive"), 4, optionsLedDrive, optionsLedDriveValues, P064_LDRIVE); + addFormSelector(lightSensorDriveLabel, + F("ldrive"), + NR_ELEMENTS(optionsLedDriveValues), + optionsLedDrive, + optionsLedDriveValues, + P064_LDRIVE); } + + addFormSubHeader(F("Event generation")); + + addFormCheckBox(F("Separate Gesture events"), F("gevent"), P064_GESTURE_EVENT == 1); + addFormNote(F("Generates event: <Taskname>#Swipe=<gesture>")); + success = true; break; } @@ -240,6 +270,8 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) P064_AGAIN = getFormItemInt(F("again")); P064_LDRIVE = getFormItemInt(F("ldrive")); + P064_GESTURE_EVENT = isFormItemChecked(F("gevent")) ? 1 : 0; + success = true; break; } @@ -259,27 +291,26 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) P064_data->sensor.enablePower(); if (!P064_data->sensor.enableLightSensor(false)) { - log += F("Error during light sensor init!"); + log += F(" Error during light sensor init!"); success = false; } - if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode - if (!P064_data->sensor.enableProximitySensor(false)) { - log += F("Error during proximity sensor init!"); - success = false; - } + // Always enable the proximity/gesture sensor. + if (!P064_data->sensor.enableProximitySensor(false)) { + log += F(" Error during proximity sensor init!"); + success = false; + } - if (!P064_data->sensor.enableGestureSensor(false, P064_LED_BOOST)) { - log += F("Error during gesture sensor init!"); - success = false; - } + if (!P064_data->sensor.enableGestureSensor(false, P064_LED_BOOST)) { + log += F(" Error during gesture sensor init!"); + success = false; } } else { log += F("Error during APDS-9960 init!"); success = false; } - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(success ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, log); } break; } @@ -288,7 +319,7 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) { P064_data_struct *P064_data = static_cast(getPluginTaskData(event->TaskIndex)); - if ((nullptr == P064_data) || (P064_MODE != PLUGIN_MODE_GPL_064) || !P064_data->sensor.isGestureAvailable()) { + if ((nullptr == P064_data) || !P064_data->sensor.isGestureAvailable()) { break; } @@ -309,17 +340,22 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) case DIR_FAR: log += F("FAR"); break; default: log += F("NONE"); break; } - log += F(" ("); - log += gesture; - log += ')'; + log += strformat(F(" (%d)"), gesture); addLogMove(LOG_LEVEL_DEBUG, log); } # endif // ifndef BUILD_NO_DEBUG - UserVar.setFloat(event->TaskIndex, 0, static_cast(gesture)); - event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH; + if (P064_MODE == PLUGIN_MODE_GPL_064) { + UserVar.setFloat(event->TaskIndex, 0, static_cast(gesture)); + } + + if (P064_GESTURE_EVENT == 1) { + const String eventvalues = strformat(F("%d"), gesture); + eventQueue.add(event->TaskIndex, F("Swipe"), eventvalues); + } else if (P064_MODE == PLUGIN_MODE_GPL_064) { + sendData(event); // Process immediately + } - sendData(event); // Process immediately success = true; } @@ -336,11 +372,11 @@ boolean Plugin_064(uint8_t function, struct EventStruct *event, String& string) if (P064_IS_GPL_SENSOR) { // Gesture/Proximity/ALS mode uint8_t proximity_data = 0; - success = success && P064_data->sensor.readProximity(proximity_data); + success = success && P064_data->sensor.readProximity(proximity_data); UserVar.setFloat(event->TaskIndex, 1, static_cast(proximity_data)); uint16_t ambient_light = 0; - success = success && P064_data->sensor.readAmbientLight(ambient_light); + success = success && P064_data->sensor.readAmbientLight(ambient_light); UserVar.setFloat(event->TaskIndex, 2, static_cast(ambient_light)); } else { uint16_t red_light = 0; diff --git a/src/_P065_DRF0299_MP3.ino b/src/_P065_DRF0299_MP3.ino index 803083959..0fcfec595 100644 --- a/src/_P065_DRF0299_MP3.ino +++ b/src/_P065_DRF0299_MP3.ino @@ -185,10 +185,10 @@ boolean Plugin_065(uint8_t function, struct EventStruct *event, String& string) break; } - String command = parseString(string, 1); - String param = parseString(string, 2); - int32_t value; - bool valueValid = validIntFromString(param, value); + const String command = parseString(string, 1); + const String param = parseString(string, 2); + int32_t value; + const bool valueValid = validIntFromString(param, value); if (valueValid && equals(command, F("play"))) { @@ -231,8 +231,7 @@ boolean Plugin_065(uint8_t function, struct EventStruct *event, String& string) if (success && loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(20); - log = F("MP3 : "); - log += command; + log = concat(F("MP3 : "), command); if (!equals(command, F("stop"))) { log += '='; @@ -310,6 +309,7 @@ void Plugin_065_SendCmd(uint8_t cmd, int16_t data) if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { String log = F("MP3 : Send Cmd "); + log.reserve(46); for (uint8_t i = 0; i < 10; i++) { log += String(buffer[i], 16); diff --git a/src/_P066_VEML6040.ino b/src/_P066_VEML6040.ino index 118e956f7..d4dcf1ac9 100644 --- a/src/_P066_VEML6040.ino +++ b/src/_P066_VEML6040.ino @@ -12,17 +12,17 @@ // Application Note: www.vishay.com/doc?84331 -#define PLUGIN_066 -#define PLUGIN_ID_066 66 -#define PLUGIN_NAME_066 "Color - VEML6040" -#define PLUGIN_VALUENAME1_066 "R" -#define PLUGIN_VALUENAME2_066 "G" -#define PLUGIN_VALUENAME3_066 "B" -#define PLUGIN_VALUENAME4_066 "W" +# define PLUGIN_066 +# define PLUGIN_ID_066 66 +# define PLUGIN_NAME_066 "Color - VEML6040" +# define PLUGIN_VALUENAME1_066 "R" +# define PLUGIN_VALUENAME2_066 "G" +# define PLUGIN_VALUENAME3_066 "B" +# define PLUGIN_VALUENAME4_066 "W" -#define VEML6040_ADDR 0x10 +# define VEML6040_ADDR 0x10 -#include +# include boolean Plugin_066(uint8_t function, struct EventStruct *event, String& string) { @@ -67,6 +67,7 @@ boolean Plugin_066(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { const uint8_t i2cAddressValues[] = { VEML6040_ADDR }; + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { addFormSelectorI2C(F("i2c_addr"), 1, i2cAddressValues, VEML6040_ADDR); // Only for display I2C address } else { @@ -87,13 +88,14 @@ boolean Plugin_066(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { { - const __FlashStringHelper * optionsMode[6] = { F("40ms (16496)"), F("80ms (8248)"), F("160ms (4124)"), F("320ms (2062)"), F("640ms (1031)"), F( - "1280ms (515)") }; + const __FlashStringHelper *optionsMode[6] = { F("40ms (16496)"), F("80ms (8248)"), F("160ms (4124)"), F("320ms (2062)"), F( + "640ms (1031)"), F( + "1280ms (515)") }; addFormSelector(F("Integration Time (Max Lux)"), F("itime"), 6, optionsMode, nullptr, PCONFIG(1)); } { - const __FlashStringHelper * optionsVarMap[6] = { + const __FlashStringHelper *optionsVarMap[6] = { F("R, G, B, W"), F("r, g, b, W - relative rgb [%]"), F("r, g, b, W - relative rgb^Gamma [%]"), @@ -213,8 +215,8 @@ float VEML6040_GetValue(uint8_t reg) if (Wire.available() == 2) { - uint16_t lsb = Wire.read(); - uint16_t msb = Wire.read(); + const uint16_t lsb = Wire.read(); + const uint16_t msb = Wire.read(); return static_cast((msb << 8) | lsb); } return -1.0f; @@ -227,19 +229,19 @@ void VEML6040_Init(uint8_t it) float Plugin_066_CalcCCT(float R, float G, float B) { - if (G == 0) { - return 0; + if (essentiallyZero(G)) { + return 0.0f; } - float CCTi = (R - B) / G + 0.5f; - float CCT = 4278.6f * powf(CCTi, -1.2455f); + const float CCTi = (R - B) / G + 0.5f; + const float CCT = 4278.6f * powf(CCTi, -1.2455f); return CCT; } float Plugin_066_CalcAmbientLight(float G, uint8_t it) { - float Sensitivity[6] = { 0.25168f, 0.12584f, 0.06292f, 0.03146f, 0.01573f, 0.007865f }; //-V624 + const float Sensitivity[6] = { 0.25168f, 0.12584f, 0.06292f, 0.03146f, 0.01573f, 0.007865f }; // -V624 return G * Sensitivity[it]; } diff --git a/src/_P068_SHT3x.ino b/src/_P068_SHT3x.ino index 5ce5ef31c..19c38a831 100644 --- a/src/_P068_SHT3x.ino +++ b/src/_P068_SHT3x.ino @@ -1,152 +1,146 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P068 - -# include "src/PluginStructs/P068_data_struct.h" - -# include "src/Helpers/Convert.h" -# include "src/Helpers/ESPEasy_math.h" - -// ####################################################################################################### -// ################ Plugin 68: SHT30/SHT31/SHT35 Temperature and Humidity Sensor (I2C) ################### -// ####################################################################################################### -// ######################## Library source code for Arduino by WeMos, 2016 ############################### -// ####################################################################################################### -// ###################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################## -// ############################### http://www.beicelectronics.com ######################################## -// ####################################################################################################### -// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### -// ####################################################################################################### - -// Changelog: -// 2023-04-28 @iz8mbw: Rename sensor to SHT3x from SHT30/31/35 -// 2021-06-12 @tonhuisman: Add temperature offset setting, with humidity compensation method 'borrowed' from BME280 sensor -// 2020-?? @TD-er: Maitenance updates -// 2017-07-18 @JK-de: Plugin adaption for ESPEasy 2.0 - -# define PLUGIN_068 -# define PLUGIN_ID_068 68 -# define PLUGIN_NAME_068 "Environment - SHT3x" -# define PLUGIN_VALUENAME1_068 "Temperature" -# define PLUGIN_VALUENAME2_068 "Humidity" - - -// ============================================== -// PLUGIN -// ============================================= - -boolean Plugin_068(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_068; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_068); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_068)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_068)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x44, 0x45 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - addFormNumericBox(F("Temperature offset"), F("tempoffset"), PCONFIG(1)); - addUnit(F("x 0.1C")); - addFormNote(F("Offset in units of 0.1 degree Celsius")); - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - PCONFIG(1) = getFormItemInt(F("tempoffset")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P068_SHT3X(PCONFIG(0))); - break; - } - - case PLUGIN_READ: - { - P068_SHT3X *sht3x = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == sht3x) { - addLog(LOG_LEVEL_ERROR, F("SHT3x: not initialised!")); - return success; - } - - sht3x->tmpOff = PCONFIG(1) / 10.0f; - sht3x->readFromSensor(); - UserVar.setFloat(event->TaskIndex, 0, sht3x->tmp); - UserVar.setFloat(event->TaskIndex, 1, sht3x->hum); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(25)) { - log = F("SHT3x: Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("SHT3x: Humidity: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - } - success = true; - break; - } - } - return success; -} - -#endif // USES_P068 +#include "_Plugin_Helper.h" +#ifdef USES_P068 + +# include "src/PluginStructs/P068_data_struct.h" + +# include "src/Helpers/Convert.h" +# include "src/Helpers/ESPEasy_math.h" + +// ####################################################################################################### +// ################ Plugin 68: SHT30/SHT31/SHT35 Temperature and Humidity Sensor (I2C) ################### +// ####################################################################################################### +// ######################## Library source code for Arduino by WeMos, 2016 ############################### +// ####################################################################################################### +// ###################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################## +// ############################### http://www.beicelectronics.com ######################################## +// ####################################################################################################### +// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### +// ####################################################################################################### + +// Changelog: +// 2023-04-28 @iz8mbw: Rename sensor to SHT3x from SHT30/31/35 +// 2021-06-12 @tonhuisman: Add temperature offset setting, with humidity compensation method 'borrowed' from BME280 sensor +// 2020-?? @TD-er: Maitenance updates +// 2017-07-18 @JK-de: Plugin adaption for ESPEasy 2.0 + +# define PLUGIN_068 +# define PLUGIN_ID_068 68 +# define PLUGIN_NAME_068 "Environment - SHT3x" +# define PLUGIN_VALUENAME1_068 "Temperature" +# define PLUGIN_VALUENAME2_068 "Humidity" + + +// ============================================== +// PLUGIN +// ============================================= + +boolean Plugin_068(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_068; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_068); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_068)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_068)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x44, 0x45 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("Temperature offset"), F("tempoffset"), PCONFIG(1)); + addUnit(F("x 0.1C")); + # ifndef BUILD_NO_DEBUG + addFormNote(F("Offset in units of 0.1 degree Celsius")); + # endif // ifndef BUILD_NO_DEBUG + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + PCONFIG(1) = getFormItemInt(F("tempoffset")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P068_SHT3X(PCONFIG(0))); + break; + } + + case PLUGIN_READ: + { + P068_SHT3X *sht3x = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == sht3x) { + addLog(LOG_LEVEL_ERROR, F("SHT3x: not initialised!")); + return success; + } + + sht3x->tmpOff = PCONFIG(1) / 10.0f; + sht3x->readFromSensor(); + UserVar.setFloat(event->TaskIndex, 0, sht3x->tmp); + UserVar.setFloat(event->TaskIndex, 1, sht3x->hum); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("SHT3x: Temperature: "), formatUserVarNoCheck(event, 0))); + addLogMove(LOG_LEVEL_INFO, concat(F("SHT3x: Humidity: "), formatUserVarNoCheck(event, 1))); + } + success = true; + break; + } + } + return success; +} + +#endif // USES_P068 diff --git a/src/_P069_LM75A.ino b/src/_P069_LM75A.ino index 77b820272..695dd11fd 100644 --- a/src/_P069_LM75A.ino +++ b/src/_P069_LM75A.ino @@ -1,133 +1,129 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P069 - -// ####################################################################################################### -// ########################### Plugin 69: LM75A Temperature Sensor (I2C) ################################# -// ####################################################################################################### -// ###################### Library source code for Arduino by QuentinCG, 2016 ############################# -// ####################################################################################################### -// ##################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################### -// ############################## http://www.beicelectronics.com ######################################### -// ####################################################################################################### -// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### -// ####################################################################################################### - - -#define PLUGIN_069 -#define PLUGIN_ID_069 69 -#define PLUGIN_NAME_069 "Environment - LM75A" -#define PLUGIN_VALUENAME1_069 "Temperature" - - -#include "src/PluginStructs/P069_data_struct.h" - - -boolean Plugin_069(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_069; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_069); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_069)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F }; - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0)); - } else { - success = intArrayContains(8, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P069_data_struct(static_cast(PCONFIG(0)))); - break; - } - - case PLUGIN_READ: - { - P069_data_struct *P069_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P069_data) { - return success; - } - - P069_data->setAddress((uint8_t)PCONFIG(0)); - - const float tempC = P069_data->getTemperatureInDegrees(); - UserVar.setFloat(event->TaskIndex, 0, tempC); - success = !isnan(tempC); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - if (!success) { - addLog(LOG_LEVEL_INFO, F("LM75A: No reading!")); - } - else - { - String log = F("LM75A: Temperature: "); - log += tempC; - addLogMove(LOG_LEVEL_INFO, log); - } - } - break; - } - } - return success; -} - -#endif // USES_P069 +#include "_Plugin_Helper.h" +#ifdef USES_P069 + +// ####################################################################################################### +// ########################### Plugin 69: LM75A Temperature Sensor (I2C) ################################# +// ####################################################################################################### +// ###################### Library source code for Arduino by QuentinCG, 2016 ############################# +// ####################################################################################################### +// ##################### Plugin for ESP Easy by B.E.I.C. ELECTRONICS, 2017 ############################### +// ############################## http://www.beicelectronics.com ######################################### +// ####################################################################################################### +// ########################## Adapted to ESPEasy 2.0 by Jochen Krapf ##################################### +// ####################################################################################################### + + +#define PLUGIN_069 +#define PLUGIN_ID_069 69 +#define PLUGIN_NAME_069 "Environment - LM75A" +#define PLUGIN_VALUENAME1_069 "Temperature" + + +#include "src/PluginStructs/P069_data_struct.h" + + +boolean Plugin_069(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_069; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_069); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_069)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F }; + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, PCONFIG(0)); + } else { + success = intArrayContains(8, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P069_data_struct(static_cast(PCONFIG(0)))); + break; + } + + case PLUGIN_READ: + { + P069_data_struct *P069_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P069_data) { + return success; + } + + const float tempC = P069_data->getTemperatureInDegrees(); + UserVar.setFloat(event->TaskIndex, 0, tempC); + success = !isnan(tempC); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + if (!success) { + addLog(LOG_LEVEL_INFO, F("LM75A: No reading!")); + } + else + { + addLogMove(LOG_LEVEL_INFO, concat(F("LM75A: Temperature: "), formatUserVarNoCheck(event,0))); + } + } + break; + } + } + return success; +} + +#endif // USES_P069 diff --git a/src/_P070_NeoPixel_Clock.ino b/src/_P070_NeoPixel_Clock.ino index b52800647..1c1a8381f 100644 --- a/src/_P070_NeoPixel_Clock.ino +++ b/src/_P070_NeoPixel_Clock.ino @@ -141,54 +141,34 @@ boolean Plugin_070(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WRITE: { - String lowerString = string; - lowerString.toLowerCase(); - String command = parseString(lowerString, 1); - String param1 = parseString(lowerString, 2); - String param2 = parseString(lowerString, 3); - String param3 = parseString(lowerString, 4); + const String command = parseString(string, 1); P070_data_struct *P070_data = static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P070_data) && (equals(command, F("clock")))) { - int32_t val_Mode{}; + int32_t val_{}; - if (validIntFromString(param1, val_Mode)) { - if ((val_Mode > -1) && (val_Mode < 2)) { - P070_data->display_enabled = val_Mode; - PCONFIG(0) = P070_data->display_enabled; + if (validIntFromString(parseString(string, 2), val_)) { + if ((val_ > -1) && (val_ < 2)) { + P070_data->display_enabled = val_; + PCONFIG(0) = val_; } } - int32_t val_Bright{}; - if (validIntFromString(param2, val_Bright)) { - if ((val_Bright > -1) && (val_Bright < 256)) { - P070_data->brightness = val_Bright; - PCONFIG(1) = P070_data->brightness; + if (validIntFromString(parseString(string, 3), val_)) { + if ((val_ > -1) && (val_ < 256)) { + P070_data->brightness = val_; + PCONFIG(1) = val_; } } - int32_t val_Marks{}; - if (validIntFromString(param3, val_Marks)) { - if ((val_Marks > -1) && (val_Marks < 256)) { - P070_data->brightness_hour_marks = val_Marks; - PCONFIG(2) = P070_data->brightness_hour_marks; + if (validIntFromString(parseString(string, 4), val_)) { + if ((val_ > -1) && (val_ < 256)) { + P070_data->brightness_hour_marks = val_; + PCONFIG(2) = val_; } } - /* //Command debuging routine - String log = F("Clock: "); - addLog(LOG_LEVEL_INFO,log); - log = F(" Enabled = "); - log += param1; - addLog(LOG_LEVEL_INFO,log); - log = F(" Brightness = "); - log += param2; - addLog(LOG_LEVEL_INFO,log); - log = F(" Marks = "); - log += param3; - addLog(LOG_LEVEL_INFO,log); - */ success = true; } break; diff --git a/src/_P071_Kamstrup401.ino b/src/_P071_Kamstrup401.ino index 06f7c8d9f..5b813b3e6 100644 --- a/src/_P071_Kamstrup401.ino +++ b/src/_P071_Kamstrup401.ino @@ -11,6 +11,10 @@ //Device pin 1 = RX //Device pin 2 = TX +/** Changelog: + * 2024-01-06 tonhuisman: Disable unused variables and some unused code, log optimizations + * 2024-01-06 tonhuisman: Start changelog, newest entry on top + */ #include @@ -22,9 +26,9 @@ #define PLUGIN_VALUENAME1_071 "Heat" #define PLUGIN_VALUENAME2_071 "Volume" -boolean Plugin_071_init = false; -uint8_t PIN_KAMSER_RX = 0; -uint8_t PIN_KAMSER_TX = 0; +// boolean Plugin_071_init = false; +// uint8_t PIN_KAMSER_RX = 0; +// uint8_t PIN_KAMSER_TX = 0; boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) { @@ -76,7 +80,7 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { - Plugin_071_init = true; + // Plugin_071_init = true; success = true; break; @@ -95,27 +99,27 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { - PIN_KAMSER_RX = CONFIG_PIN1; - PIN_KAMSER_TX = CONFIG_PIN2; + // PIN_KAMSER_RX = CONFIG_PIN1; + // PIN_KAMSER_TX = CONFIG_PIN2; const ESPEasySerialPort port = static_cast(CONFIG_PORT); - ESPeasySerial kamSer(port, PIN_KAMSER_RX, PIN_KAMSER_TX, false); // Initialize serial + ESPeasySerial kamSer(port, CONFIG_PIN1, CONFIG_PIN2, false); // Initialize serial - pinMode(PIN_KAMSER_RX,INPUT); - pinMode(PIN_KAMSER_TX,OUTPUT); + pinMode(CONFIG_PIN1,INPUT); + pinMode(CONFIG_PIN2,OUTPUT); //read Kamstrup uint8_t sendmsg1[] = { 175,163,177 }; // /#1 with even parity uint8_t r = 0; uint8_t to = 0; - uint8_t i; + uint8_t i = 0; char message[255]; - int parityerrors; + int parityerrors = 0; kamSer.begin(300); - for (int x = 0; x < 3; x++) { + for (int x = 0; x < 3; ++x) { kamSer.write(sendmsg1[x]); } @@ -123,14 +127,14 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) //kamSer.end(); kamSer.begin(1200); - to = 0; - r = 0; - i = 0; - parityerrors = 0; + // to = 0; + // r = 0; + // i = 0; + // parityerrors = 0; char *tmpstr; ESPEASY_RULES_FLOAT_TYPE m_energy, m_volume; - float m_tempin, m_tempout, m_tempdiff, m_power; - long m_hours, m_flow; + // float m_tempin, m_tempout, m_tempdiff, m_power; + // long m_hours, m_flow; while(r != 0x0A) { @@ -153,9 +157,9 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) delay(25); } - if (i>=79) + if (i >= 79) { - if ( parityerrors == 0 ) + if (parityerrors == 0 ) { // serialPrint("OK: " ); // serialPrintln(message); @@ -163,7 +167,7 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) tmpstr = strtok(message, " "); if (tmpstr){ - m_energy = atol(tmpstr)/3.6*1000; + m_energy = atol(tmpstr) / 3.6 * 1000; } else m_energy = 0; @@ -174,96 +178,86 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) else m_volume = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_hours = atol(tmpstr); - else - m_hours = 0; + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_hours = atol(tmpstr); + // else + // m_hours = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_tempin = atol(tmpstr)/100.0f; - else - m_tempin = 0; + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_tempin = atol(tmpstr) / 100.0f; + // else + // m_tempin = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_tempout = atol(tmpstr)/100.0f; - else - m_tempout = 0; + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_tempout = atol(tmpstr) / 100.0f; + // else + // m_tempout = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_tempdiff = atol(tmpstr)/100.0f; - else - m_tempdiff = 0; + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_tempdiff = atol(tmpstr) / 100.0f; + // else + // m_tempdiff = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_power = atol(tmpstr)/10.0f; - else - m_power = 0; + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_power = atol(tmpstr) / 10.0f; + // else + // m_power = 0; - tmpstr = strtok(nullptr, " "); - if (tmpstr) - m_flow = atol(tmpstr); - else - m_flow = 0; - { - String log = F("Kamstrup output: "); - log += m_energy; - log += F(" MJ; "); - log += m_volume; - log += F(" L; "); - log += m_hours; - log += F(" h; "); - log += m_tempin; - log += F(" C; "); - log += m_tempout; - log += F(" C; "); - log += m_tempdiff; - log += F(" C; "); - log += m_power; - log += ' '; - log += m_flow; - log += F(" L/H"); -// addLog(LOG_LEVEL_INFO, log); - } + // tmpstr = strtok(nullptr, " "); + // if (tmpstr) + // m_flow = atol(tmpstr); + // else + // m_flow = 0; +// { +// String log = F("Kamstrup output: "); +// log += m_energy; +// log += F(" MJ; "); +// log += m_volume; +// log += F(" L; "); +// log += m_hours; +// log += F(" h; "); +// log += m_tempin; +// log += F(" C; "); +// log += m_tempout; +// log += F(" C; "); +// log += m_tempdiff; +// log += F(" C; "); +// log += m_power; +// log += ' '; +// log += m_flow; +// log += F(" L/H"); +// // addLog(LOG_LEVEL_INFO, log); +// } UserVar.setFloat(event->TaskIndex, 0, m_energy); //gives energy in Wh - UserVar.setFloat(event->TaskIndex, 1, m_volume); //gives volume in liters + UserVar.setFloat(event->TaskIndex, 1, m_volume); //gives volume in liters if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Kamstrup : Heat value: "); - log += m_energy/1000; - log += F(" kWh"); - addLogMove(LOG_LEVEL_INFO, log); - log = F("Kamstrup : Volume value: "); - log += m_volume; - log += F(" Liter"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("Kamstrup : Heat value: %.3f kWh"), m_energy / 1000)); + addLogMove(LOG_LEVEL_INFO, strformat(F("Kamstrup : Volume value: %d Liter"), m_volume)); } } else { message[i] = 0; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ERR(PARITY):" ); - serialPrint("par"); - log += message; - addLogMove(LOG_LEVEL_INFO, log); + serialPrint("par"); // FIXME ? Why this ? + addLogMove(LOG_LEVEL_INFO, concat(F("ERR(PARITY):" ), String(message))); } //UserVar.setFloat(event->TaskIndex, 0, NAN); //UserVar.setFloat(event->TaskIndex, 1, NAN); } break; } - if (to>100) + if (to > 100) { message[i] = 0; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ERR(TIMEOUT):" ); - log += message; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("ERR(TIMEOUT):" ), String(message))); } //UserVar.setFloat(event->TaskIndex, 0, NAN); @@ -283,19 +277,19 @@ boolean Plugin_071(uint8_t function, struct EventStruct *event, String& string) } bool parity_check(unsigned input) { - bool inputparity = input & 128; - int x = input & 127; + bool inputparity = input & 128; + int x = input & 127; - int parity = 0; - while(x != 0) { - parity ^= x; - x >>= 1; - } + int parity = 0; + while(x != 0) { + parity ^= x; + x >>= 1; + } - if ( (parity & 0x1) != inputparity ) - return(1); - else - return(0); + if ( (parity & 0x1) != inputparity ) + return(1); + else + return(0); } #endif // USES_P071 diff --git a/src/_P072_HDC1080.ino b/src/_P072_HDC1080.ino index b765f1784..c3ac8fd2b 100644 --- a/src/_P072_HDC1080.ino +++ b/src/_P072_HDC1080.ino @@ -1,128 +1,128 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P072 - -// ###################################################################################################### -// ####################### Plugin 072: Temperature and Humidity sensor HDC10xx (I2C) #################### -// ###################################################################################################### - -/** Changelog: - * 2023-02-09 tonhuisman: Fix typo in temperature calculation (was 65526.0f instead of 65536.0f (2^16)) - * 2023-02-08 tonhuisman: Add PLUGIN_I2C_GET_ADDRESS support - * 2023-02-09 tonhuisman: Start changelog - */ - -# define PLUGIN_072 -# define PLUGIN_ID_072 72 -# define PLUGIN_NAME_072 "Environment - HDC10xx (I2C)" -# define PLUGIN_VALUENAME1_072 "Temperature" -# define PLUGIN_VALUENAME2_072 "Humidity" - - -# define HDC1080_I2C_ADDRESS 0x40 // I2C address for the sensor - -boolean Plugin_072(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_072; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - Device[deviceCount].I2CNoDeviceCheck = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_072); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_072)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_072)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == HDC1080_I2C_ADDRESS); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = HDC1080_I2C_ADDRESS; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_INIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - uint8_t hdc1080_msb, hdc1080_lsb; - uint16_t hdc1080_rawtemp, hdc1080_rawhum; - float hdc1080_temp, hdc1080_hum; - - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x02); // sends HDC1080_CONFIGURATION - Wire.write(0b00000000); // set resolution to 14bits both for T and H - Wire.write(0x00); // **reserved** - Wire.endTransmission(); // end transmission - delay(10); - - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x00); // sends HDC1080_TEMPERATURE - Wire.endTransmission(); // end transmission - delay(9); - Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for temperature - hdc1080_msb = Wire.read(); - hdc1080_lsb = Wire.read(); - hdc1080_rawtemp = hdc1080_msb << 8 | hdc1080_lsb; - hdc1080_temp = (static_cast(hdc1080_rawtemp) / 65536.0f) * 165.0f - 40.0f; - - Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device - Wire.write(0x01); // sends HDC1080_HUMIDITY - Wire.endTransmission(); // end transmission - delay(9); - Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for humidity - hdc1080_msb = Wire.read(); - hdc1080_lsb = Wire.read(); - hdc1080_rawhum = hdc1080_msb << 8 | hdc1080_lsb; - hdc1080_hum = (static_cast(hdc1080_rawhum) / 65536.0f) * 100.0f; - - UserVar.setFloat(event->TaskIndex, 0, hdc1080_temp); - UserVar.setFloat(event->TaskIndex, 1, hdc1080_hum); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Temperature: "), formatUserVarNoCheck(event->TaskIndex, 0))); - addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Humidity: "), formatUserVarNoCheck(event->TaskIndex, 1))); - } - success = true; - break; - } - } - return success; -} - -#endif // USES_P072 +#include "_Plugin_Helper.h" +#ifdef USES_P072 + +// ###################################################################################################### +// ####################### Plugin 072: Temperature and Humidity sensor HDC10xx (I2C) #################### +// ###################################################################################################### + +/** Changelog: + * 2023-02-09 tonhuisman: Fix typo in temperature calculation (was 65526.0f instead of 65536.0f (2^16)) + * 2023-02-08 tonhuisman: Add PLUGIN_I2C_GET_ADDRESS support + * 2023-02-09 tonhuisman: Start changelog + */ + +# define PLUGIN_072 +# define PLUGIN_ID_072 72 +# define PLUGIN_NAME_072 "Environment - HDC10xx (I2C)" +# define PLUGIN_VALUENAME1_072 "Temperature" +# define PLUGIN_VALUENAME2_072 "Humidity" + + +# define HDC1080_I2C_ADDRESS 0x40 // I2C address for the sensor + +boolean Plugin_072(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_072; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].I2CNoDeviceCheck = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_072); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_072)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_072)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == HDC1080_I2C_ADDRESS); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = HDC1080_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_INIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + uint8_t hdc1080_msb, hdc1080_lsb; + uint16_t hdc1080_rawtemp, hdc1080_rawhum; + float hdc1080_temp, hdc1080_hum; + + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x02); // sends HDC1080_CONFIGURATION + Wire.write(0b00000000); // set resolution to 14bits both for T and H + Wire.write(0x00); // **reserved** + Wire.endTransmission(); // end transmission + delay(10); + + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x00); // sends HDC1080_TEMPERATURE + Wire.endTransmission(); // end transmission + delay(9); + Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for temperature + hdc1080_msb = Wire.read(); + hdc1080_lsb = Wire.read(); + hdc1080_rawtemp = hdc1080_msb << 8 | hdc1080_lsb; + hdc1080_temp = (static_cast(hdc1080_rawtemp) / 65536.0f) * 165.0f - 40.0f; + + Wire.beginTransmission(HDC1080_I2C_ADDRESS); // start transmission to device + Wire.write(0x01); // sends HDC1080_HUMIDITY + Wire.endTransmission(); // end transmission + delay(9); + Wire.requestFrom(HDC1080_I2C_ADDRESS, 2); // read 2 bytes for humidity + hdc1080_msb = Wire.read(); + hdc1080_lsb = Wire.read(); + hdc1080_rawhum = hdc1080_msb << 8 | hdc1080_lsb; + hdc1080_hum = (static_cast(hdc1080_rawhum) / 65536.0f) * 100.0f; + + UserVar.setFloat(event->TaskIndex, 0, hdc1080_temp); + UserVar.setFloat(event->TaskIndex, 1, hdc1080_hum); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Temperature: "), formatUserVarNoCheck(event, 0))); + addLogMove(LOG_LEVEL_INFO, concat(F("HDC10xx: Humidity: "), formatUserVarNoCheck(event, 1))); + } + success = true; + break; + } + } + return success; +} + +#endif // USES_P072 diff --git a/src/_P073_7DGT.ino b/src/_P073_7DGT.ino index ac50107fe..1e3102f1b 100644 --- a/src/_P073_7DGT.ino +++ b/src/_P073_7DGT.ino @@ -1071,7 +1071,7 @@ void tm1637_i2cWrite(uint8_t clk_pin, for (i = 0; i < 8; i++) { CLK_LOW(); - if (bytetoprint & B00000001) { + if (bytetoprint & 0b00000001) { DIO_HIGH(); } else { DIO_LOW(); diff --git a/src/_P074_TSL2591.ino b/src/_P074_TSL2591.ino index 0dcae1620..05ce4ec4d 100644 --- a/src/_P074_TSL2591.ino +++ b/src/_P074_TSL2591.ino @@ -143,14 +143,10 @@ boolean Plugin_074(uint8_t function, struct EventStruct *event, String& string) P074_data->setGain(PCONFIG(2)); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("TSL2591: Address: 0x"); - log += String(TSL2591_ADDR, HEX); - log += F(": Integration Time: "); - log += String((P074_data->tsl.getTiming() + 1) * 100, DEC); - log += F(" ms"); + String log = strformat(F("TSL2591: Address: 0x%02x: Integration Time: %d ms Gain: "), + TSL2591_ADDR, (P074_data->tsl.getTiming() + 1) * 100); /* Display the gain and integration time for reference sake */ - log += (F(" Gain: ")); switch (P074_data->tsl.getGain()) { default: @@ -196,13 +192,9 @@ boolean Plugin_074(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 3, ir); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log += concat(F("TSL2591: Lux: "), toString(lux)); - log += concat(F(" Full: "), static_cast(full)); - log += concat(F(" Visible: "), static_cast(visible)); - log += concat(F(" IR: "), static_cast(ir)); - log += concat(F(" duration: "), static_cast(P074_data->duration)); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("TSL2591: Lux: %.2f Full: %d Visible: %d IR: %d duration: %d"), + lux, full, visible, ir, P074_data->duration)); } // Update was succesfull, schedule a read. diff --git a/src/_P075_Nextion.ino b/src/_P075_Nextion.ino index 0c1850e27..472746989 100644 --- a/src/_P075_Nextion.ino +++ b/src/_P075_Nextion.ino @@ -1,501 +1,481 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P075 - -# include "src/PluginStructs/P075_data_struct.h" - -#include "src/ESPEasyCore/ESPEasyWifi.h" - -// ####################################################################################################### -// ####################################################################################################### -// ################################### Plugin 075: Nextion ########################### -// ################################### Created on the work of majklovec ########################### -// ################################### Revisions by BertB, ThomasB and others ########################### -// ################################### Last Revision: 2022-09-27 ########################### -// ####################################################################################################### -// - -/** Changelog: - * 2022-09-27 tonhuisman: Use Changelog formatted updates - * Extend nr. of lines available for text/commands to 20, minor code improvements - * Updated: Oct-03-2018, ThomasB. - * Added P075_DEBUG_LOG define to reduce info log messages and prevent serial log flooding. - * Added SendStatus() to post log message on browser to acknowledge HTTP write. - * Added reserve() to minimize string memory allocations. - */ - -// ***************************************************************************************************** -// Defines start here -// ***************************************************************************************************** - -// #define P075_DEBUG_LOG // Enable this to include additional info messages in log output. - - -// Plug-In defines -# define PLUGIN_075 -# define PLUGIN_ID_075 75 -# define PLUGIN_NAME_075 "Display - Nextion" -# define PLUGIN_DEFAULT_NAME "NEXTION" -# define PLUGIN_VALUENAME1_075 "idx" -# define PLUGIN_VALUENAME2_075 "value" - - -// ***************************************************************************************************** -// PlugIn starts here -// ***************************************************************************************************** - -boolean Plugin_075(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) { - case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_075; - Device[deviceCount].Type = DEVICE_TYPE_SERIAL; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; // Pullup is not used. - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; // Allow user to disable interval function. - Device[deviceCount].GlobalSyncOption = true; - - // FIXME TD-er: Not sure if access to any existing task data is needed when saving - Device[deviceCount].ExitTaskBeforeSave = false; - - break; - } - - - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_075); - break; - } - - - case PLUGIN_GET_DEVICEVALUENAMES: { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_075)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_075)); - break; - } - - - case PLUGIN_GET_DEVICEGPIONAMES: { - serialHelper_getGpioNames(event); - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: - { - const __FlashStringHelper *options[4] = { - F("9600"), - F("38400"), - F("57600"), - F("115200") - }; - - addFormSelector(F("Baud Rate"), F("baud"), 4, options, nullptr, P075_BaudRate); - addUnit(F("baud")); - break; - } - - case PLUGIN_WEBFORM_LOAD: { - // ** DEVELOPER DEBUG MESSAGE AREA ** - // int datax = static_cast(Settings.TaskDeviceEnabled[event->TaskIndex]); // Debug value. - // String Data = "Debug. Plugin Enable State: "; - // Data += String(datax); - // addFormNote(Data); - - addFormSubHeader(F("")); // Blank line, vertical space. - addFormHeader(F("Nextion Command Statements (Optional)")); - P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P075_data) { - P075_data->loadDisplayLines(event->TaskIndex); - - for (int varNr = 0; varNr < P75_Nlines; varNr++) { - addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), P075_data->displayLines[varNr], P75_Nchars - 1); - } - } - - if (Settings.TaskDeviceTimer[event->TaskIndex] == 0) { // Is interval timer disabled? - addFormNote(concat(F("Interval Timer OFF, Nextion Lines (above)"), P075_IncludeValues - ? F(" and Values (below) NOT scheduled for updates") - : F(" NOT scheduled for updates"))); - } - - addFormSeparator(2); - addFormSubHeader(F("Interval Options")); - addFormCheckBox(F("Resend Values (below) at Interval"), F("IncludeValues"), P075_IncludeValues); - - success = true; - break; - } - - - case PLUGIN_WEBFORM_SAVE: { - { - // FIXME TD-er: This is a huge object allocated on the Stack. - char deviceTemplate[P75_Nlines][P75_Nchars] = {}; - String error; - - for (uint8_t varNr = 0; varNr < P75_Nlines; varNr++) - { - if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P75_Nchars)) { - error += getCustomTaskSettingsError(varNr); - } - } - - if (error.length() > 0) { - addHtmlError(error); - } - SaveCustomTaskSettings(event->TaskIndex, (uint8_t *)&deviceTemplate, sizeof(deviceTemplate)); - } - - if (getTaskDeviceName(event->TaskIndex).isEmpty()) { // Check to see if user entered device name. - strcpy(ExtraTaskSettings.TaskDeviceName, PLUGIN_DEFAULT_NAME); // Name missing, populate default name. - } - - // PCONFIG(0) = isFormItemChecked(F("AdvHwSerial")); - P075_BaudRate = getFormItemInt(F("baud")); - P075_IncludeValues = isFormItemChecked(F("IncludeValues")); - - /* Task will be stopped and restarted, so no reason to reload the display here - P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P075_data) { - P075_data->loadDisplayLines(event->TaskIndex); - } - */ - success = true; - break; - } - - - case PLUGIN_INIT: { - uint8_t BaudCode = P075_BaudRate; - - if (BaudCode > P075_B115200) { BaudCode = P075_B9600; } - const uint32_t BaudArray[4] = { 9600UL, 38400UL, 57600UL, 115200UL }; - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - initPluginTaskData(event->TaskIndex, new (std::nothrow) P075_data_struct(port, CONFIG_PIN1, CONFIG_PIN2, BaudArray[BaudCode])); - P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P075_data) { - P075_data->loadDisplayLines(event->TaskIndex); - addLog(LOG_LEVEL_INFO, P075_data->getLogString()); - success = true; - } - break; - } - - - case PLUGIN_READ: { - P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P075_data) { - String newString; - - // Get optional LINE command statements. Special RSSIBAR bargraph keyword is supported. - for (uint8_t x = 0; x < P75_Nlines; x++) { - if (P075_data->displayLines[x].length()) { - int RssiIndex; - { - String UcTmpString(P075_data->displayLines[x]); - UcTmpString.toUpperCase(); - RssiIndex = UcTmpString.indexOf(F("RSSIBAR")); // RSSI bargraph Keyword found, wifi value in dBm. - } - if (RssiIndex >= 0) { - newString = concat( - P075_data->displayLines[x].substring(0, RssiIndex), - GetRSSI_quality() * 10); - } - else { - String tmpString(P075_data->displayLines[x]); - newString = parseTemplate(tmpString); - } - - P075_sendCommand(event->TaskIndex, newString.c_str()); - # ifdef P075_DEBUG_LOG - String log; - log.reserve(P75_Nchars + 50); // Prevent re-allocation - log += concat(F("NEXTION075 : Cmd Statement Line-"), x + 1); - log += concat(F(" Sent: "), newString); - addLogMove(LOG_LEVEL_INFO, log); - # endif // ifdef P075_DEBUG_LOG - } - } - - // At Interval timer, send idx & value data only if user enabled "values" interval mode. - if (P075_IncludeValues) { - # ifdef P075_DEBUG_LOG - String log; - log.reserve(120); // Prevent re-allocation - log += concat(F("NEXTION075: Interval values data enabled, resending idx="), formatUserVarNoCheck(event->TaskIndex, 0)); - log += concat(F(", value="), formatUserVarNoCheck(event->TaskIndex, 1)); - addLogMove(LOG_LEVEL_INFO, log); - # endif // ifdef P075_DEBUG_LOG - - success = true; - } - else { - # ifdef P075_DEBUG_LOG - addLog(LOG_LEVEL_INFO, F("NEXTION075: Interval values data disabled, idx & value not resent")); - # endif // ifdef P075_DEBUG_LOG - - success = false; - } - } - break; - } - - // Nextion commands received from events (including http) get processed here. PLUGIN_WRITE - // does NOT process publish commands that are sent. - case PLUGIN_WRITE: { - const String command = parseString(string, 1); - - // If device names match we have a command to write. - if (command.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex))) { - success = true; // Set true only if plugin found a command to execute. - const String nextionArguments = parseStringToEndKeepCase(string, 2); - P075_sendCommand(event->TaskIndex, nextionArguments.c_str()); - { - String log = concat(F("NEXTION075 : WRITE = "), nextionArguments); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, log); - # endif // ifndef BUILD_NO_DEBUG - SendStatus(event, log); // Reply (echo) to sender. This will print message on browser. - } - - // Enable addLog() code below to help debug plugin write problems. - - /* - String log; - log.reserve(140); // Prevent re-allocation - String log = F("Nextion arg0: "); - log += command; - log += F(", TaskDeviceName: "); - log += getTaskDeviceName(event->TaskIndex); - log += F(", event->TaskIndex: "); - log += String(event->TaskIndex); - log += F(", nextionArguments: "); - log += nextionArguments; - addLog(LOG_LEVEL_INFO, log); - */ - } - break; - } - - case PLUGIN_ONCE_A_SECOND: { - success = true; - break; - } - - - case PLUGIN_TEN_PER_SECOND: { - P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P075_data) { - break; - } - - if (P075_data->rxPin < 0) { - addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing RxD Pin, aborted serial receive")); - break; - } - - if (P075_data->easySerial == nullptr) { - break; // P075_data->easySerial missing, exit. - } - { - uint16_t i; - uint8_t c; - String Vidx; - String Nvalue; - String Svalue; - String Nswitch; - char __buffer[RXBUFFSZ + 1]; // Staging buffer. - uint8_t charCount = P075_data->easySerial->available(); // Prime the Soft Serial engine. - - if (charCount >= RXBUFFWARN) { - String log; - log.reserve(70); // Prevent re-allocation - log += concat(F("NEXTION075 : RxD P075_data->easySerial Buffer capacity warning, "), charCount); - log += F(" bytes"); - addLogMove(LOG_LEVEL_INFO, log); - } - uint32_t baudrate_delay_unit = P075_data->baudrate / 9600; - - if (baudrate_delay_unit == 0) { - baudrate_delay_unit = 1; - } - - while (charCount) { // This is the serial engine. It processes the serial Rx stream. - c = P075_data->easySerial->read(); - - if (c == 0x65) { - if (charCount < 6) { delay((5 / (baudrate_delay_unit)) + 1); // Let's wait for a few more chars to arrive. - } - charCount = P075_data->easySerial->available(); - - if (charCount >= 6) { - __buffer[0] = c; // Store in staging buffer. - - for (i = 1; i < 7; i++) { - __buffer[i] = P075_data->easySerial->read(); - } - - __buffer[i] = 0x00; - - // FIXME TD-er: (PVS Studio) A part of conditional expression is always false: (0xFF == __buffer[4]). The value range of char type: [-128, 127]. - if ((0xFF == __buffer[4]) && (0xFF == __buffer[5]) && (0xFF == __buffer[6])) { - UserVar.setFloat(event->TaskIndex, 0, (__buffer[1] * 256) + __buffer[2] + TOUCH_BASE); - UserVar.setFloat(event->TaskIndex, 1, __buffer[3]); - sendData(event); - - # ifdef P075_DEBUG_LOG - String log; - log.reserve(70); // Prevent re-allocation - log += F("NEXTION075 : code: "); - log += __buffer[1]; - log += ','; - log += __buffer[2]; - log += ','; - log += __buffer[3]; - addLogMove(LOG_LEVEL_INFO, log); - # endif // ifdef P075_DEBUG_LOG - } - } - } - else { - if (c == '|') { - __buffer[0] = c; // Store in staging buffer. - - if (charCount < 8) { delay((9 / (baudrate_delay_unit)) + 1); // Let's wait for more chars to arrive. - } - else { delay((3 / (baudrate_delay_unit)) + 1); // Short wait for tardy chars. - } - charCount = P075_data->easySerial->available(); - - i = 1; - - while (P075_data->easySerial->available() > 0 && i < RXBUFFSZ) { // Copy global serial buffer to local buffer. - __buffer[i] = P075_data->easySerial->read(); - - if ((__buffer[i] == 0x0a) || (__buffer[i] == 0x0d)) { break; } - i++; - } - - __buffer[i] = 0x00; - - String tmpString = __buffer; - - # ifdef P075_DEBUG_LOG - String log; - log.reserve(50); // Prevent re-allocation - log += concat(F("NEXTION075 : Code = "), tmpString); - addLogMove(LOG_LEVEL_INFO, log); - # endif // ifdef P075_DEBUG_LOG - - int argIndex = tmpString.indexOf(F(",i")); - int argEnd = tmpString.indexOf(',', argIndex + 1); - - if (argIndex) { Vidx = tmpString.substring(argIndex + 2, argEnd); } - - boolean GotPipeCmd = false; - - switch (__buffer[1]) { - case 'u': - GotPipeCmd = true; - argIndex = argEnd; - argEnd = tmpString.indexOf(',', argIndex + 1); - - if (argIndex) { Nvalue = tmpString.substring(argIndex + 2, argEnd); } - argIndex = argEnd; - argEnd = tmpString.indexOf(0x0a); - - if (argIndex) { Svalue = tmpString.substring(argIndex + 2, argEnd); } - break; - case 's': - GotPipeCmd = true; - argIndex = argEnd; - argEnd = tmpString.indexOf(0x0a); - - if (argIndex) { Nvalue = tmpString.substring(argIndex + 2, argEnd); } - - if (equals(Nvalue, F("On"))) { Svalue = '1'; } - - if (equals(Nvalue, F("Off"))) { Svalue = '0'; } - break; - } - - if (GotPipeCmd) { - float Vidx_f{}; - float Svalue_f{}; - - validFloatFromString(Vidx, Vidx_f); - validFloatFromString(Svalue, Svalue_f); - UserVar.setFloat(event->TaskIndex, 0, Vidx_f); - UserVar.setFloat(event->TaskIndex, 1, Svalue_f); - sendData(event); - - # ifdef P075_DEBUG_LOG - String log; - log.reserve(80); // Prevent re-allocation - log += F("NEXTION075 : Pipe Command Sent: "); - log += __buffer; - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - # endif // ifdef P075_DEBUG_LOG - } - else { - # ifdef P075_DEBUG_LOG - addLog(LOG_LEVEL_INFO, F("NEXTION075 : Unknown Pipe Command, skipped")); - # endif // ifdef P075_DEBUG_LOG - } - } - } - charCount = P075_data->easySerial->available(); - } - } - - success = true; - break; - } - } - return success; -} - -void P075_sendCommand(taskIndex_t taskIndex, const char *cmd) -{ - P075_data_struct *P075_data = static_cast(getPluginTaskData(taskIndex)); - - if (!P075_data) { return; } - - if (P075_data->txPin < 0) { - addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing TxD Pin Number, aborted sendCommand")); - } - else - { - if (P075_data->easySerial != nullptr) { - P075_data->easySerial->print(cmd); - P075_data->easySerial->write(0xff); - P075_data->easySerial->write(0xff); - P075_data->easySerial->write(0xff); - } - else { - addLog(LOG_LEVEL_INFO, F("NEXTION075 : P075_data->easySerial error, aborted sendCommand")); - } - } -} - -#endif // USES_P075 +#include "_Plugin_Helper.h" +#ifdef USES_P075 + +# include "src/PluginStructs/P075_data_struct.h" + +# include "src/ESPEasyCore/ESPEasyWifi.h" + +// ####################################################################################################### +// ####################################################################################################### +// ################################### Plugin 075: Nextion ########################### +// ################################### Created on the work of majklovec ########################### +// ################################### Revisions by BertB, ThomasB and others ########################### +// ################################### Last Revision: 2022-09-27 ########################### +// ####################################################################################################### +// + +/** Changelog: + * 2022-09-27 tonhuisman: Use Changelog formatted updates + * Extend nr. of lines available for text/commands to 20, minor code improvements + * Updated: Oct-03-2018, ThomasB. + * Added P075_DEBUG_LOG define to reduce info log messages and prevent serial log flooding. + * Added SendStatus() to post log message on browser to acknowledge HTTP write. + * Added reserve() to minimize string memory allocations. + */ + +// ***************************************************************************************************** +// Defines start here +// ***************************************************************************************************** + +// #define P075_DEBUG_LOG // Enable this to include additional info messages in log output. + + +// Plug-In defines +# define PLUGIN_075 +# define PLUGIN_ID_075 75 +# define PLUGIN_NAME_075 "Display - Nextion" +# define PLUGIN_DEFAULT_NAME "NEXTION" +# define PLUGIN_VALUENAME1_075 "idx" +# define PLUGIN_VALUENAME2_075 "value" + + +// ***************************************************************************************************** +// PlugIn starts here +// ***************************************************************************************************** + +boolean Plugin_075(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_075; + Device[deviceCount].Type = DEVICE_TYPE_SERIAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; // Pullup is not used. + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; // Allow user to disable interval function. + Device[deviceCount].GlobalSyncOption = true; + + // FIXME TD-er: Not sure if access to any existing task data is needed when saving + Device[deviceCount].ExitTaskBeforeSave = false; + + break; + } + + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_075); + break; + } + + + case PLUGIN_GET_DEVICEVALUENAMES: { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_075)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_075)); + break; + } + + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event); + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: + { + const __FlashStringHelper *options[4] = { + F("9600"), + F("38400"), + F("57600"), + F("115200") + }; + + addFormSelector(F("Baud Rate"), F("baud"), 4, options, nullptr, P075_BaudRate); + addUnit(F("baud")); + break; + } + + case PLUGIN_WEBFORM_LOAD: { + // ** DEVELOPER DEBUG MESSAGE AREA ** + // int datax = static_cast(Settings.TaskDeviceEnabled[event->TaskIndex]); // Debug value. + // String Data = "Debug. Plugin Enable State: "; + // Data += String(datax); + // addFormNote(Data); + + addFormSubHeader(F("")); // Blank line, vertical space. + addFormHeader(F("Nextion Command Statements (Optional)")); + P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P075_data) { + P075_data->loadDisplayLines(event->TaskIndex); + + for (int varNr = 0; varNr < P75_Nlines; varNr++) { + addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), P075_data->displayLines[varNr], P75_Nchars - 1); + } + } + + if (Settings.TaskDeviceTimer[event->TaskIndex] == 0) { // Is interval timer disabled? + addFormNote(concat(F("Interval Timer OFF, Nextion Lines (above)"), P075_IncludeValues + ? F(" and Values (below) NOT scheduled for updates") + : F(" NOT scheduled for updates"))); + } + + addFormSeparator(2); + addFormSubHeader(F("Interval Options")); + addFormCheckBox(F("Resend Values (below) at Interval"), F("IncludeValues"), P075_IncludeValues); + + success = true; + break; + } + + + case PLUGIN_WEBFORM_SAVE: { + { + // FIXME TD-er: This is a huge object allocated on the Stack. + char deviceTemplate[P75_Nlines][P75_Nchars] = {}; + String error; + + for (uint8_t varNr = 0; varNr < P75_Nlines; varNr++) + { + if (!safe_strncpy(deviceTemplate[varNr], webArg(getPluginCustomArgName(varNr)), P75_Nchars)) { + error += getCustomTaskSettingsError(varNr); + } + } + + if (error.length() > 0) { + addHtmlError(error); + } + SaveCustomTaskSettings(event->TaskIndex, (uint8_t *)&deviceTemplate, sizeof(deviceTemplate)); + } + + if (getTaskDeviceName(event->TaskIndex).isEmpty()) { // Check to see if user entered device name. + strcpy(ExtraTaskSettings.TaskDeviceName, PLUGIN_DEFAULT_NAME); // Name missing, populate default name. + } + + // PCONFIG(0) = isFormItemChecked(F("AdvHwSerial")); + P075_BaudRate = getFormItemInt(F("baud")); + P075_IncludeValues = isFormItemChecked(F("IncludeValues")); + + /* Task will be stopped and restarted, so no reason to reload the display here + P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P075_data) { + P075_data->loadDisplayLines(event->TaskIndex); + } + */ + success = true; + break; + } + + + case PLUGIN_INIT: { + uint8_t BaudCode = P075_BaudRate; + + if (BaudCode > P075_B115200) { BaudCode = P075_B9600; } + const uint32_t BaudArray[4] = { 9600UL, 38400UL, 57600UL, 115200UL }; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P075_data_struct(port, CONFIG_PIN1, CONFIG_PIN2, BaudArray[BaudCode])); + P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P075_data) { + P075_data->loadDisplayLines(event->TaskIndex); + addLog(LOG_LEVEL_INFO, P075_data->getLogString()); + success = true; + } + break; + } + + + case PLUGIN_READ: { + P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P075_data) { + String newString; + + // Get optional LINE command statements. Special RSSIBAR bargraph keyword is supported. + for (uint8_t x = 0; x < P75_Nlines; x++) { + if (P075_data->displayLines[x].length()) { + int RssiIndex; + { + String UcTmpString(P075_data->displayLines[x]); + UcTmpString.toUpperCase(); + RssiIndex = UcTmpString.indexOf(F("RSSIBAR")); // RSSI bargraph Keyword found, wifi value in dBm. + } + + if (RssiIndex >= 0) { + newString = concat( + P075_data->displayLines[x].substring(0, RssiIndex), + GetRSSI_quality() * 10); + } + else { + String tmpString(P075_data->displayLines[x]); + newString = parseTemplate(tmpString); + } + + P075_sendCommand(event->TaskIndex, newString.c_str()); + # ifdef P075_DEBUG_LOG + addLog(LOG_LEVEL_INFO, strformat(F("NEXTION075 : Cmd Statement Line-%d Sent: %s"), x + 1, newString.c_str())); + # endif // ifdef P075_DEBUG_LOG + } + } + + // At Interval timer, send idx & value data only if user enabled "values" interval mode. + if (P075_IncludeValues) { + # ifdef P075_DEBUG_LOG + addLogMove(LOG_LEVEL_INFO, + strformat(F("NEXTION075: Interval values data enabled, resending idx=%s, value=%s"), + formatUserVarNoCheck(event, 0).c_str(), + formatUserVarNoCheck(event, 1).c_str())); + # endif // ifdef P075_DEBUG_LOG + + success = true; + } + else { + # ifdef P075_DEBUG_LOG + addLog(LOG_LEVEL_INFO, F("NEXTION075: Interval values data disabled, idx & value not resent")); + # endif // ifdef P075_DEBUG_LOG + + success = false; + } + } + break; + } + + // Nextion commands received from events (including http) get processed here. PLUGIN_WRITE + // does NOT process publish commands that are sent. + case PLUGIN_WRITE: { + const String command = parseString(string, 1); + + // If device names match we have a command to write. + if (command.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex))) { + success = true; // Set true only if plugin found a command to execute. + const String nextionArguments = parseStringToEndKeepCase(string, 2); + P075_sendCommand(event->TaskIndex, nextionArguments.c_str()); + { + const String log = concat(F("NEXTION075 : WRITE = "), nextionArguments); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, log); + # endif // ifndef BUILD_NO_DEBUG + SendStatus(event, log); // Reply (echo) to sender. This will print message on browser. + } + + // Enable addLog() code below to help debug plugin write problems. + + /* + String log; + log.reserve(140); // Prevent re-allocation + String log = F("Nextion arg0: "); + log += command; + log += F(", TaskDeviceName: "); + log += getTaskDeviceName(event->TaskIndex); + log += F(", event->TaskIndex: "); + log += String(event->TaskIndex); + log += F(", nextionArguments: "); + log += nextionArguments; + addLog(LOG_LEVEL_INFO, log); + */ + } + break; + } + + case PLUGIN_TEN_PER_SECOND: { + P075_data_struct *P075_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P075_data) { + break; + } + + if (P075_data->rxPin < 0) { + addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing RxD Pin, aborted serial receive")); + break; + } + + if (P075_data->easySerial == nullptr) { + break; // P075_data->easySerial missing, exit. + } + { + uint16_t i; + uint8_t c; + String Vidx; + String Nvalue; + String Svalue; + String Nswitch; + char __buffer[RXBUFFSZ + 1]; // Staging buffer. + uint8_t charCount = P075_data->easySerial->available(); // Prime the Soft Serial engine. + + if (charCount >= RXBUFFWARN) { + addLog(LOG_LEVEL_INFO, strformat(F("NEXTION075 : RxD P075_data->easySerial Buffer capacity warning, %d bytes"), charCount)); + } + uint32_t baudrate_delay_unit = P075_data->baudrate / 9600; + + if (baudrate_delay_unit == 0) { + baudrate_delay_unit = 1; + } + + while (charCount) { // This is the serial engine. It processes the serial Rx stream. + c = P075_data->easySerial->read(); + + if (c == 0x65) { + if (charCount < 6) { delay((5 / (baudrate_delay_unit)) + 1); // Let's wait for a few more chars to arrive. + } + charCount = P075_data->easySerial->available(); + + if (charCount >= 6) { + __buffer[0] = c; // Store in staging buffer. + + for (i = 1; i < 7; i++) { + __buffer[i] = P075_data->easySerial->read(); + } + + __buffer[i] = 0x00; + + // FIXME TD-er: (PVS Studio) A part of conditional expression is always false: (0xFF == __buffer[4]). The value range of char + // type: [-128, 127]. + if ((0xFF == __buffer[4]) && (0xFF == __buffer[5]) && (0xFF == __buffer[6])) { + UserVar.setFloat(event->TaskIndex, 0, (__buffer[1] * 256) + __buffer[2] + TOUCH_BASE); + UserVar.setFloat(event->TaskIndex, 1, __buffer[3]); + sendData(event); + + # ifdef P075_DEBUG_LOG + addLogMove(LOG_LEVEL_INFO, + strformat(F("NEXTION075 : code: %c,%c,%c"), + __buffer[1], + __buffer[2], + __buffer[3])); + # endif // ifdef P075_DEBUG_LOG + } + } + } + else { + if (c == '|') { + __buffer[0] = c; // Store in staging buffer. + + if (charCount < 8) { delay((9 / (baudrate_delay_unit)) + 1); // Let's wait for more chars to arrive. + } + else { delay((3 / (baudrate_delay_unit)) + 1); // Short wait for tardy chars. + } + charCount = P075_data->easySerial->available(); + + i = 1; + + while (P075_data->easySerial->available() > 0 && i < RXBUFFSZ) { // Copy global serial buffer to local buffer. + __buffer[i] = P075_data->easySerial->read(); + + if ((__buffer[i] == 0x0a) || (__buffer[i] == 0x0d)) { break; } + i++; + } + + __buffer[i] = 0x00; + + String tmpString = __buffer; + + # ifdef P075_DEBUG_LOG + addLogMove(LOG_LEVEL_INFO, concat(F("NEXTION075 : Code = "), tmpString)); + # endif // ifdef P075_DEBUG_LOG + + int argIndex = tmpString.indexOf(F(",i")); + int argEnd = tmpString.indexOf(',', argIndex + 1); + + if (argIndex) { Vidx = tmpString.substring(argIndex + 2, argEnd); } + + bool GotPipeCmd = false; + + switch (__buffer[1]) { + case 'u': + GotPipeCmd = true; + argIndex = argEnd; + argEnd = tmpString.indexOf(',', argIndex + 1); + + if (argIndex) { Nvalue = tmpString.substring(argIndex + 2, argEnd); } + argIndex = argEnd; + argEnd = tmpString.indexOf(0x0a); + + if (argIndex) { Svalue = tmpString.substring(argIndex + 2, argEnd); } + break; + case 's': + GotPipeCmd = true; + argIndex = argEnd; + argEnd = tmpString.indexOf(0x0a); + + if (argIndex) { Nvalue = tmpString.substring(argIndex + 2, argEnd); } + + if (equals(Nvalue, F("On"))) { Svalue = '1'; } + + if (equals(Nvalue, F("Off"))) { Svalue = '0'; } + break; + } + + if (GotPipeCmd) { + float Vidx_f{}; + float Svalue_f{}; + + validFloatFromString(Vidx, Vidx_f); + validFloatFromString(Svalue, Svalue_f); + UserVar.setFloat(event->TaskIndex, 0, Vidx_f); + UserVar.setFloat(event->TaskIndex, 1, Svalue_f); + sendData(event); + + # ifdef P075_DEBUG_LOG + String log; + log.reserve(80); // Prevent re-allocation + log += F("NEXTION075 : Pipe Command Sent: "); + log += __buffer; + log += formatUserVarNoCheck(event, 0); + addLogMove(LOG_LEVEL_INFO, log); + # endif // ifdef P075_DEBUG_LOG + } + else { + # ifdef P075_DEBUG_LOG + addLog(LOG_LEVEL_INFO, F("NEXTION075 : Unknown Pipe Command, skipped")); + # endif // ifdef P075_DEBUG_LOG + } + } + } + charCount = P075_data->easySerial->available(); + } + } + + success = true; + break; + } + } + return success; +} + +void P075_sendCommand(taskIndex_t taskIndex, const char *cmd) +{ + P075_data_struct *P075_data = static_cast(getPluginTaskData(taskIndex)); + + if (!P075_data) { return; } + + if (!validGpio(P075_data->txPin)) { + addLog(LOG_LEVEL_INFO, F("NEXTION075 : Missing TxD Pin Number, aborted sendCommand")); + } + else + { + if (P075_data->easySerial != nullptr) { + P075_data->easySerial->print(cmd); + P075_data->easySerial->write(0xff); + P075_data->easySerial->write(0xff); + P075_data->easySerial->write(0xff); + } + else { + addLog(LOG_LEVEL_INFO, F("NEXTION075 : P075_data->easySerial error, aborted sendCommand")); + } + } +} + +#endif // USES_P075 diff --git a/src/_P076_HLW8012.ino b/src/_P076_HLW8012.ino index eb85b04a6..041dcd285 100644 --- a/src/_P076_HLW8012.ino +++ b/src/_P076_HLW8012.ino @@ -68,14 +68,15 @@ float p076_hpowfact{}; # define P076_Gosund 9 # define P076_Shelly_PLUG_S 10 -#if ESP_IDF_VERSION_MAJOR >= 5 +# if ESP_IDF_VERSION_MAJOR >= 5 + // FIXME TD-er: Must check if older (and ESP8266) envs need IRAM_ATTR in the function declaration. -void p076_hlw8012_cf1_interrupt(); -void p076_hlw8012_cf_interrupt(); -#else +void p076_hlw8012_cf1_interrupt(); +void p076_hlw8012_cf_interrupt(); +# else // if ESP_IDF_VERSION_MAJOR >= 5 void IRAM_ATTR p076_hlw8012_cf1_interrupt(); void IRAM_ATTR p076_hlw8012_cf_interrupt(); -#endif +# endif // if ESP_IDF_VERSION_MAJOR >= 5 bool p076_getDeviceParameters(int device, @@ -86,17 +87,17 @@ bool p076_getDeviceParameters(int device, uint8_t& CF_Trigger, uint8_t& CF1_Trigger) { switch (device) { - case P076_Custom: SEL_Pin = 0; CF_Pin = 0; CF1_Pin = 0; Cur_read = LOW; CF_Trigger = LOW; CF1_Trigger = LOW; break; - case P076_Sonoff: SEL_Pin = 5; CF_Pin = 14; CF1_Pin = 13; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; - case P076_Huafan: SEL_Pin = 13; CF_Pin = 14; CF1_Pin = 12; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; - case P076_KMC: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; - case P076_Aplic: //SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; - case P076_SK03: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; - case P076_BlitzWolf: //SEL_Pin = 12; CF_Pin = 5; CF1_Pin = 14; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; - case P076_TeckinUS: //SEL_Pin = 12; CF_Pin = 5; CF1_Pin = 14; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; + case P076_Custom: SEL_Pin = 0; CF_Pin = 0; CF1_Pin = 0; Cur_read = LOW; CF_Trigger = LOW; CF1_Trigger = LOW; break; + case P076_Sonoff: SEL_Pin = 5; CF_Pin = 14; CF1_Pin = 13; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; + case P076_Huafan: SEL_Pin = 13; CF_Pin = 14; CF1_Pin = 12; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; + case P076_KMC: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = HIGH; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; + case P076_Aplic: // SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; + case P076_SK03: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = CHANGE; CF1_Trigger = CHANGE; break; + case P076_BlitzWolf: // SEL_Pin = 12; CF_Pin = 5; CF1_Pin = 14; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; + case P076_TeckinUS: // SEL_Pin = 12; CF_Pin = 5; CF1_Pin = 14; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; case P076_Shelly_PLUG_S: SEL_Pin = 12; CF_Pin = 5; CF1_Pin = 14; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; - case P076_Teckin: //SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; - case P076_Gosund: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; + case P076_Teckin: // SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; + case P076_Gosund: SEL_Pin = 12; CF_Pin = 4; CF1_Pin = 5; Cur_read = LOW; CF_Trigger = FALLING; CF1_Trigger = CHANGE; break; default: return false; } @@ -153,7 +154,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) addFormSubHeader(F("Predefined Pin settings")); { // Place this in a scope, to keep memory usage low. - const __FlashStringHelper * predefinedNames[] = { + const __FlashStringHelper *predefinedNames[] = { F("Custom"), F("Sonoff Pow (r1)"), F("Huafan SS"), @@ -232,25 +233,25 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) if (Plugin076_LoadMultipliers(event->TaskIndex, current, voltage, power)) { addFormSubHeader(F("Calibration Values")); addFormTextBox(F("Current Multiplier"), F("currmult"), - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE doubleToString(current, 2) - #else + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE floatToString(current, 2) - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE , 25); addFormTextBox(F("Voltage Multiplier"), F("voltmult"), - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE doubleToString(voltage, 2) - #else + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE floatToString(voltage, 2) - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE , 25); addFormTextBox(F("Power Multiplier"), F("powmult"), - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE doubleToString(power, 2) - #else + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE floatToString(power, 2) - #endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE , 25); } @@ -290,9 +291,9 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) if ((hlwMultipliers[0] > 1.0) && (hlwMultipliers[1] > 1.0) && (hlwMultipliers[2] > 1.0)) { SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&hlwMultipliers), sizeof(hlwMultipliers)); - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Saved Calibration from Config Page")); - # endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Saved Calibration from Config Page")); + # endif // if PLUGIN_076_DEBUG if (Plugin_076_hlw) { Plugin_076_hlw->setCurrentMultiplier(hlwMultipliers[0]); @@ -300,22 +301,15 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) Plugin_076_hlw->setPowerMultiplier(hlwMultipliers[2]); } - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Multipliers Reassigned")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Multipliers Reassigned")); + # endif // if PLUGIN_076_DEBUG } - #if PLUGIN_076_DEBUG - String log = F("P076: PIN Settings "); - - log += F(" curr_read: "); - log += PCONFIG(4); - log += F(" cf_edge: "); - log += PCONFIG(5); - log += F(" cf1_edge: "); - log += PCONFIG(6); - addLogMove(LOG_LEVEL_INFO, log); - #endif + # if PLUGIN_076_DEBUG + addLogMove(LOG_LEVEL_INFO, strformat(F("P076: PIN Settings curr_read: %d cf_edge: %d cf1_edge: %d") + PCONFIG(4), PCONFIG(5), PCONFIG(6))); + # endif // if PLUGIN_076_DEBUG success = true; break; @@ -325,6 +319,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) if (Plugin_076_hlw) { bool valid = false; + switch (p076_read_stage) { case 0: // The stage where we have to wait for a measurement to be started. @@ -371,20 +366,24 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) // ++p076_read_stage; // } else if (p076_read_stage > 3) { bool valid = false; - p076_hpower = Plugin_076_hlw->getActivePower(valid); + p076_hpower = Plugin_076_hlw->getActivePower(valid); + if (valid) { success = true; } p076_hvoltage = Plugin_076_hlw->getVoltage(valid); + if (valid) { success = true; } p076_hcurrent = Plugin_076_hlw->getCurrent(valid); + if (valid) { success = true; } p076_hpowfact = static_cast(100 * Plugin_076_hlw->getPowerFactor(valid)); + if (valid) { success = true; } @@ -397,21 +396,13 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) // Measurement is complete. p076_read_stage = 0; - #if PLUGIN_076_DEBUG - String log = F("P076: Read values"); - log += F(" - V="); - log += p076_hvoltage; - log += F(" - A="); - log += p076_hcurrent; - log += F(" - W="); - log += p076_hpower; - log += F(" - Pf%="); - log += p076_hpowfact; - addLogMove(LOG_LEVEL_INFO, log); - #endif - + # if PLUGIN_076_DEBUG + addLogMove(LOG_LEVEL_INFO, + strformat(F("P076: Read values - V=%.2f - A=%.2f - W=%.2f - Pf%%=%.2f"), + p076_hvoltage, p076_hcurrent, p076_hpower, p076_hpowfact)); + # endif // if PLUGIN_076_DEBUG + // Plugin_076_hlw->toggleMode(); - } } break; @@ -434,29 +425,29 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) Plugin_076_hlw = new (std::nothrow) HLW8012; if (Plugin_076_hlw) { - uint8_t currentRead = PCONFIG(4); - uint8_t cf_trigger = PCONFIG(5); - uint8_t cf1_trigger = PCONFIG(6); + const uint8_t currentRead = PCONFIG(4); + const uint8_t cf_trigger = PCONFIG(5); + const uint8_t cf1_trigger = PCONFIG(6); Plugin_076_hlw->begin(CF_PIN, CF1_PIN, SEL_PIN, currentRead, true); // set use_interrupts to true to use // interrupts to monitor pulse widths - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Init object done")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Init object done")); + # endif // if PLUGIN_076_DEBUG Plugin_076_hlw->setResistors(HLW_CURRENT_RESISTOR, HLW_VOLTAGE_RESISTOR_UP, HLW_VOLTAGE_RESISTOR_DOWN); - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Init Basic Resistor Values done")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Init Basic Resistor Values done")); + # endif // if PLUGIN_076_DEBUG ESPEASY_RULES_FLOAT_TYPE current, voltage, power; if (Plugin076_LoadMultipliers(event->TaskIndex, current, voltage, power)) { - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Saved Calibration after INIT")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Saved Calibration after INIT")); + # endif // if PLUGIN_076_DEBUG Plugin_076_hlw->setCurrentMultiplier(current); Plugin_076_hlw->setVoltageMultiplier(voltage); @@ -465,9 +456,9 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) Plugin076_ResetMultipliers(); } - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Applied Calibration after INIT")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Applied Calibration after INIT")); + # endif // if PLUGIN_076_DEBUG StoredTaskIndex = event->TaskIndex; // store task index value in order to // use it in the PLUGIN_WRITE routine @@ -504,16 +495,11 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) validFloatFromString(parseString(string, 4), CalibAcPwr); } } - #if PLUGIN_076_DEBUG - String log = F("P076: Calibration to values"); - log += F(" - Expected-V="); - log += CalibVolt; - log += F(" - Expected-A="); - log += CalibCurr; - log += F(" - Expected-W="); - log += CalibAcPwr; - addLogMove(LOG_LEVEL_INFO, log); - #endif + # if PLUGIN_076_DEBUG + addLogMove(LOG_LEVEL_INFO, + strformat(F("P076: Calibration to values - Expected-V=%.2f - Expected-A=%.2f - Expected-W=%.2f"), + CalibVolt, CalibCurr, CalibAcPwr)); + # endif // if PLUGIN_076_DEBUG bool changed = false; if (CalibVolt != 0) { @@ -526,7 +512,7 @@ boolean Plugin_076(uint8_t function, struct EventStruct *event, String& string) changed = true; } - if (!essentiallyEqual(CalibAcPwr, 0.0f)) { + if (!essentiallyZero(CalibAcPwr)) { Plugin_076_hlw->expectedActivePower(CalibAcPwr); changed = true; } @@ -548,9 +534,9 @@ void Plugin076_ResetMultipliers() { if (Plugin_076_hlw) { Plugin_076_hlw->resetMultipliers(); Plugin076_SaveMultipliers(); - #if PLUGIN_076_DEBUG - addLog(LOG_LEVEL_INFO, F("P076: Reset Multipliers to DEFAULT")); - #endif + # if PLUGIN_076_DEBUG + addLog(LOG_LEVEL_INFO, F("P076: Reset Multipliers to DEFAULT")); + # endif // if PLUGIN_076_DEBUG } } @@ -561,10 +547,10 @@ void Plugin076_SaveMultipliers() { ESPEASY_RULES_FLOAT_TYPE hlwMultipliers[3]{}; if (Plugin076_ReadMultipliers(hlwMultipliers[0], hlwMultipliers[1], hlwMultipliers[2])) { -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + # if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast(&hlwMultipliers), sizeof(hlwMultipliers)); -#else + # else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE double hlwMultipliers_d[3]{}; hlwMultipliers_d[0] = hlwMultipliers[0]; hlwMultipliers_d[1] = hlwMultipliers[1]; @@ -572,7 +558,7 @@ void Plugin076_SaveMultipliers() { SaveCustomTaskSettings(StoredTaskIndex, reinterpret_cast(&hlwMultipliers_d), sizeof(hlwMultipliers_d)); -#endif + # endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE } } @@ -590,7 +576,10 @@ bool Plugin076_ReadMultipliers(ESPEASY_RULES_FLOAT_TYPE& current, ESPEASY_RULES_ return false; } -bool Plugin076_LoadMultipliers(taskIndex_t TaskIndex, ESPEASY_RULES_FLOAT_TYPE& current, ESPEASY_RULES_FLOAT_TYPE& voltage, ESPEASY_RULES_FLOAT_TYPE& power) { +bool Plugin076_LoadMultipliers(taskIndex_t TaskIndex, + ESPEASY_RULES_FLOAT_TYPE& current, + ESPEASY_RULES_FLOAT_TYPE& voltage, + ESPEASY_RULES_FLOAT_TYPE& power) { // If multipliers are empty load default ones and save all of them as // "CustomTaskSettings" if (!Plugin076_ReadMultipliers(current, voltage, power)) { diff --git a/src/_P077_CSE7766.ino b/src/_P077_CSE7766.ino index 4e1f3f393..5129c0a43 100644 --- a/src/_P077_CSE7766.ino +++ b/src/_P077_CSE7766.ino @@ -113,9 +113,9 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string) const float value = P077_data->getValue(query); int nrDecimals = 2; - if ((query == P077_query::P077_QUERY_PULSES)) { + if (query == P077_query::P077_QUERY_PULSES) { nrDecimals = 0; - } else if ((query == P077_query::P077_QUERY_KWH)) { + } else if (query == P077_query::P077_QUERY_KWH) { nrDecimals = 3; } @@ -293,18 +293,10 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CSE voltage: "); - log += P077_data->getValue(P077_query::P077_QUERY_VOLTAGE); - addLogMove(LOG_LEVEL_DEBUG, log); - log = F("CSE power: "); - log += P077_data->getValue(P077_query::P077_QUERY_ACTIVE_POWER); - addLogMove(LOG_LEVEL_DEBUG, log); - log = F("CSE current: "); - log += P077_data->getValue(P077_query::P077_QUERY_CURRENT); - addLogMove(LOG_LEVEL_DEBUG, log); - log = F("CSE pulses: "); - log += P077_data->cf_pulses; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CSE voltage: "), P077_data->getValue(P077_query::P077_QUERY_VOLTAGE))); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CSE power: "), P077_data->getValue(P077_query::P077_QUERY_ACTIVE_POWER))); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CSE current: "), P077_data->getValue(P077_query::P077_QUERY_CURRENT))); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CSE pulses: "), P077_data->cf_pulses)); } # endif // ifndef BUILD_NO_DEBUG } @@ -312,23 +304,14 @@ boolean Plugin_077(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CSE: time "); - log += P077_data->t_max; - log += '/'; - log += P077_data->t_pkt; - log += '/'; - log += P077_data->t_all; - addLogMove(LOG_LEVEL_DEBUG, log); - log = F("CSE: bytes "); - log += P077_data->count_bytes; - log += '/'; - log += P077_data->count_max; - log += '/'; - log += P077_data->serial_Available(); - addLogMove(LOG_LEVEL_DEBUG, log); - log = F("CSE: nr "); - log += P077_data->count_pkt; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("CSE: time %d/%d/%d"), + P077_data->t_max, P077_data->t_pkt, P077_data->t_all)); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("CSE: bytes %d/%d/%d"), + P077_data->count_bytes, P077_data->count_max, P077_data->serial_Available())); + addLogMove(LOG_LEVEL_DEBUG, + concat(F("CSE: nr "), P077_data->count_pkt)); } # endif // ifndef BUILD_NO_DEBUG P077_data->t_all = 0; diff --git a/src/_P078_Eastron.ino b/src/_P078_Eastron.ino index 4da954514..2accc8445 100644 --- a/src/_P078_Eastron.ino +++ b/src/_P078_Eastron.ino @@ -136,20 +136,16 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) addFormNumericBox(F("Modbus Address"), P078_DEV_ID_LABEL, P078_DEV_ID, 1, 247); - #ifdef ESP32 + # ifdef ESP32 addFormCheckBox(F("Enable Collision Detection"), F(P078_FLAG_COLL_DETECT_LABEL), P078_GET_FLAG_COLL_DETECT); addFormNote(F("/RE connected to GND, only supported on hardware serial")); - #endif - + # endif // ifdef ESP32 if (Plugin_078_SDM != nullptr) { addRowLabel(F("Checksum (pass/fail)")); - String chksumStats; - chksumStats = Plugin_078_SDM->getSuccCount(); - chksumStats += '/'; - chksumStats += Plugin_078_SDM->getErrCount(); - addHtml(chksumStats); + addHtml(strformat(F("%d/%d"), + Plugin_078_SDM->getSuccCount(), Plugin_078_SDM->getErrCount())); } break; @@ -205,9 +201,9 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) P078_DEV_ID = getFormItemInt(P078_DEV_ID_LABEL); P078_MODEL = getFormItemInt(P078_MODEL_LABEL); P078_BAUDRATE = getFormItemInt(P078_BAUDRATE_LABEL); - #ifdef ESP32 + # ifdef ESP32 P078_SET_FLAG_COLL_DETECT(isFormItemChecked(F(P078_FLAG_COLL_DETECT_LABEL))); - #endif + # endif // ifdef ESP32 Plugin_078_init = false; // Force device setup next time success = true; @@ -280,7 +276,6 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) // Need a few seconds to read the first sample, so trigger a new read a few seconds after init. Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 2000); - } break; } @@ -293,15 +288,12 @@ boolean Plugin_078(uint8_t function, struct EventStruct *event, String& string) Plugin_078_init = false; - if (Plugin_078_ESPEasySerial != nullptr) { - delete Plugin_078_ESPEasySerial; - Plugin_078_ESPEasySerial = nullptr; - } + delete Plugin_078_ESPEasySerial; + Plugin_078_ESPEasySerial = nullptr; + + delete Plugin_078_SDM; + Plugin_078_SDM = nullptr; - if (Plugin_078_SDM != nullptr) { - delete Plugin_078_SDM; - Plugin_078_SDM = nullptr; - } break; } diff --git a/src/_P079_Wemos_Motorshield.ino b/src/_P079_Wemos_Motorshield.ino index b83e937fe..336e63095 100644 --- a/src/_P079_Wemos_Motorshield.ino +++ b/src/_P079_Wemos_Motorshield.ino @@ -274,19 +274,19 @@ boolean Plugin_079(uint8_t function, struct EventStruct *event, String& string) parse_error = true; } - if (paramDirection.equalsIgnoreCase(F("Stop"))) { + if (equals(paramDirection, F("stop"))) { motor_dir = MOTOR_STATES::MOTOR_STOP; } - else if (paramDirection.equalsIgnoreCase(F("Forward"))) { + else if (equals(paramDirection, F("forward"))) { motor_dir = MOTOR_STATES::MOTOR_FWD; } - else if ((paramDirection.equalsIgnoreCase(F("Backward")))) { + else if (equals(paramDirection, F("backward"))) { motor_dir = MOTOR_STATES::MOTOR_REV; } - else if (paramDirection.equalsIgnoreCase(F("Standby"))) { + else if (equals(paramDirection, F("standby"))) { motor_dir = MOTOR_STATES::MOTOR_STBY; } - else if (paramDirection.equalsIgnoreCase(F("Brake"))) { + else if (equals(paramDirection, F("brake"))) { motor_dir = MOTOR_STATES::MOTOR_BRAKE; } else { @@ -315,7 +315,7 @@ boolean Plugin_079(uint8_t function, struct EventStruct *event, String& string) if ((motor_speed < 0) || (motor_speed > 100)) { motor_speed = 100; # ifdef VERBOSE_P079 - addLog(LOG_LEVEL_INFO, ModeStr + F(": Warning, invalid speed: Now using 100")); + addLog(LOG_LEVEL_INFO, strformat(F("%s: Warning, invalid speed: Now using 100"), ModeStr)); # endif // ifdef VERBOSE_P079 } } @@ -393,15 +393,10 @@ boolean Plugin_079(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - ModeStr += F(": Addr="); - ModeStr += formatToHex(I2C_ADDR_PCFG_P079); - ModeStr += F(": Mtr="); - ModeStr += paramMotor; - ModeStr += F(", Dir="); - ModeStr += paramDirection; - ModeStr += F(", Spd="); - ModeStr += paramSpeed; - addLogMove(LOG_LEVEL_INFO, ModeStr); + 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())); } success = true; diff --git a/src/_P080_DallasIButton.ino b/src/_P080_DallasIButton.ino index 4dacedb3b..a75a43c00 100644 --- a/src/_P080_DallasIButton.ino +++ b/src/_P080_DallasIButton.ino @@ -1,157 +1,206 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P080 - -// ####################################################################################################### -// #################################### Plugin 080: iButton Sensor DS1990A ########################### -// ####################################################################################################### - -// Maxim Integrated - -# include "src/Helpers/Dallas1WireHelper.h" - -# define PLUGIN_080 -# define PLUGIN_ID_080 80 -# define PLUGIN_NAME_080 "Input - iButton" -# define PLUGIN_VALUENAME1_080 "iButton" - - -int8_t Plugin_080_DallasPin; - -boolean Plugin_080(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_080; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_ULONG; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_080); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_080)); - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_bidirectional(F("1-Wire")); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormNote(F("External pull up resistor is needed, see docs!")); - - // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. - Plugin_080_DallasPin = CONFIG_PIN1; - - if (validGpio(Plugin_080_DallasPin)) { - Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_080_DallasPin, Plugin_080_DallasPin); - } - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - // save the address for selected device and store into extra tasksettings - Dallas_addr_selector_webform_save(event->TaskIndex, CONFIG_PIN1, CONFIG_PIN1); - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - uint8_t addr[8]; - Dallas_plugin_get_addr(addr, event->TaskIndex); - string = Dallas_format_address(addr); - success = true; - break; - } - case PLUGIN_INIT: - { - Plugin_080_DallasPin = CONFIG_PIN1; - - if (validGpio(Plugin_080_DallasPin)) { - uint8_t addr[8]; - - // Explicitly set the pinMode using the "slow" pinMode function - // This way we know for sure the state of any pull-up or -down resistor is known. - pinMode(Plugin_080_DallasPin, INPUT); - - Dallas_plugin_get_addr(addr, event->TaskIndex); - Dallas_startConversion(addr, Plugin_080_DallasPin, Plugin_080_DallasPin); - - delay(800); // give it time to do intial conversion - success = true; - } - break; - } - - case PLUGIN_TEN_PER_SECOND: // PLUGIN_READ: - { - uint8_t addr[8]; - Dallas_plugin_get_addr(addr, event->TaskIndex); - - if (addr[0] != 0) { - Plugin_080_DallasPin = CONFIG_PIN1; - - if (Dallas_readiButton(addr, Plugin_080_DallasPin, Plugin_080_DallasPin)) - { - UserVar.setUint32(event->TaskIndex, 0, 1); - success = true; - } - else - { - UserVar.setUint32(event->TaskIndex, 0, 0); - } - Dallas_startConversion(addr, Plugin_080_DallasPin, Plugin_080_DallasPin); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("DS : iButton: "); - - if (success) { - log += formatUserVarNoCheck(event->TaskIndex, 0); - } else { - log += F("Not Present!"); - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG - } - break; - } - case PLUGIN_READ: - { - success = UserVar.getUint32(event->TaskIndex, 0) != UserVar.getUint32(event->TaskIndex, 2); // Changed? - - // Keep previous state - UserVar.setUint32(event->TaskIndex, 2, UserVar.getUint32(event->TaskIndex, 0)); - break; - } - } - return success; -} - -#endif // USES_P080 +#include "_Plugin_Helper.h" +#ifdef USES_P080 + +// ####################################################################################################### +// #################################### Plugin 080: iButton Sensor DS1990A ########################### +// ####################################################################################################### + +// Maxim Integrated + +/** Changelog: + * 2024-05-11 tonhuisman: Dallas_StartConversion() call not needed for iButton. + * Reduce logging in Dallas_readiButton() function to on-change (only used for this plugin) + * 2024-05-10 tonhuisman: Add support for Event with iButton address, + * generating event: #Address=[,,], + * enabling address to be processed in rules + * Fix plugin VType setting as SENSOR_TYPE_ULONG isn't needed here, only storing 0/1 state + * Make Interval optional, as with Event processing enabled, this state is not useful + * 2024-05 tonhuisman: Start changelog + */ + +# include "src/Helpers/Dallas1WireHelper.h" + +# define PLUGIN_080 +# define PLUGIN_ID_080 80 +# define PLUGIN_NAME_080 "Input - iButton" +# define PLUGIN_VALUENAME1_080 "iButton" + +# define P080_ADDRESS_EVENT PCONFIG(0) +# define P080_EVENT_NAME "Address" + +boolean Plugin_080(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_080; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_080); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_080)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_bidirectional(F("1-Wire")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNote(F("External pull up resistor is needed, see docs!")); + + // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. + const int8_t Plugin_080_DallasPin = CONFIG_PIN1; + + if (validGpio(Plugin_080_DallasPin)) { + Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_080_DallasPin, Plugin_080_DallasPin); + + addFormCheckBox(F("Event with iButton address"), F("iaddr"), P080_ADDRESS_EVENT); + addFormNote(F("When checked, Device Address should be '- None -'")); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + // save the address for selected device and store into extra tasksettings + Dallas_addr_selector_webform_save(event->TaskIndex, CONFIG_PIN1, CONFIG_PIN1); + P080_ADDRESS_EVENT = isFormItemChecked(F("iaddr")); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + uint8_t addr[8]; + Dallas_plugin_get_addr(addr, event->TaskIndex); + string = Dallas_format_address(addr); + success = true; + break; + } + + case PLUGIN_INIT: + { + const int8_t Plugin_080_DallasPin = CONFIG_PIN1; + + if (validGpio(Plugin_080_DallasPin)) { + uint8_t addr[8]; + + // Explicitly set the pinMode using the "slow" pinMode function + // This way we know for sure the state of any pull-up or -down resistor is known. + pinMode(Plugin_080_DallasPin, INPUT); + + Dallas_plugin_get_addr(addr, event->TaskIndex); + + if (Settings.TaskDeviceTimer[event->TaskIndex] == 0) { // Trigger at least once a PLUGIN_READ + UserVar.setFloat(event->TaskIndex, 2, -1); + } + + success = true; + } + break; + } + + case PLUGIN_TEN_PER_SECOND: // PLUGIN_READ: + { + const int8_t Plugin_080_DallasPin = CONFIG_PIN1; + uint8_t addr[8]; + Dallas_plugin_get_addr(addr, event->TaskIndex); + + if ((0x00 == addr[0]) && P080_ADDRESS_EVENT) { // Respond to any iButton presented? + uint32_t state = 0; + Dallas_reset(Plugin_080_DallasPin, Plugin_080_DallasPin); + Dallas_reset_search(); + + while (Dallas_search(addr, Plugin_080_DallasPin, Plugin_080_DallasPin)) { + if (addr[0] == 0x01) { // Respond to first iButton device + state = 1; + break; + } + } + + if (0x01 == addr[0]) { + if (state != UserVar.getFloat(event->TaskIndex, 0)) { + UserVar.setFloat(event->TaskIndex, 0, state); + eventQueue.add(event->TaskIndex, F(P080_EVENT_NAME), + strformat(F("%d,0x%s,0x%s"), // Address split in 2 hex parts + state, + formatToHex_array(addr, 4).c_str(), + formatToHex_array(&addr[4], 4).c_str())); + } + } else { + if (state != UserVar.getFloat(event->TaskIndex, 0)) { + UserVar.setFloat(event->TaskIndex, 0, state); + eventQueue.add(event->TaskIndex, F(P080_EVENT_NAME), + state); + } + } + + // No (debug) logging is added as the generated event is already logged at INFO level. + success = true; + addr[0] = 0; // Ignore other devices on the wire + } else + + if (0 != addr[0]) { + if (Dallas_readiButton(addr, Plugin_080_DallasPin, Plugin_080_DallasPin, UserVar.getFloat(event->TaskIndex, 0))) { + UserVar.setFloat(event->TaskIndex, 0, 1); + success = true; + } else { + UserVar.setFloat(event->TaskIndex, 0, 0); + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("DS : iButton: "); + + if (success) { + log += formatUserVarNoCheck(event, 0); + } else { + log += F("Not Present!"); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + } + break; + } + case PLUGIN_READ: + { + success = UserVar.getFloat(event->TaskIndex, 0) != UserVar.getFloat(event->TaskIndex, 2); // Changed? + + // Keep previous state + UserVar.setFloat(event->TaskIndex, 2, UserVar.getFloat(event->TaskIndex, 0)); + break; + } + } + return success; +} + +#endif // USES_P080 diff --git a/src/_P081_Cron.ino b/src/_P081_Cron.ino index 901bf1c56..0a80a9a4b 100644 --- a/src/_P081_Cron.ino +++ b/src/_P081_Cron.ino @@ -1,191 +1,191 @@ -#include "_Plugin_Helper.h" - -// ####################################################################################################### -// #################################### Plugin 081: CRON tasks Scheduler ########################### -// ####################################################################################################### - -// -V::795 - -#ifdef USES_P081 - - -# include "src/PluginStructs/P081_data_struct.h" - -# define PLUGIN_081 -# define PLUGIN_ID_081 81 // plugin id -# define PLUGIN_NAME_081 "Generic - CRON" // "Plugin Name" is what will be displayed in the selection list -# define PLUGIN_VALUENAME1_081 "LastExecution" -# define PLUGIN_VALUENAME2_081 "NextExecution" - - -boolean Plugin_081(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - // This case defines the device characteristics, edit appropriately - - Device[++deviceCount].Number = PLUGIN_ID_081; - Device[deviceCount].Type = DEVICE_TYPE_DUMMY; // how the device is connected - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; // type of value the plugin will return, used only for - // Domoticz - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 2; // number of output variables. The value should match the number of keys - // PLUGIN_VALUENAME1_xxx - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - Device[deviceCount].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].DecimalsOnly = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - // return the device name - string = F(PLUGIN_NAME_081); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - // called when the user opens the module configuration page - // it allows to add a new row for each output variable of the plugin - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_081)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_081)); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("Schedule")); - addFormTextBox(F("CRON Expression") - , F("p081_cron_exp") - , P081_getCronExpr(event->TaskIndex) - , 39); - - addFormNote(F("S M H DoM Month DoW")); - - P081_html_show_cron_expr(event); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - String expression = webArg(F("p081_cron_exp")); - String log; - { - char expression_c[PLUGIN_081_EXPRESSION_SIZE] = {}; - safe_strncpy(expression_c, expression, PLUGIN_081_EXPRESSION_SIZE); - log = SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&expression_c), PLUGIN_081_EXPRESSION_SIZE); - } - - if (log.length() > 0) - { - addLog(LOG_LEVEL_ERROR, String(PSTR(PLUGIN_NAME_081)) + F(": Saving ") + log); - } - - clearPluginTaskData(event->TaskIndex); - P081_setCronExecTimes(event, CRON_INVALID_INSTANT, CRON_INVALID_INSTANT); - success = true; - break; - } - - case PLUGIN_FORMAT_USERVAR: - { - switch (event->idx) { - case 0: - string = P081_formatExecTime(event->TaskIndex, LASTEXECUTION); - break; - case 1: - string = P081_formatExecTime(event->TaskIndex, NEXTEXECUTION); - break; - } - success = string.length() > 0; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P081_data_struct(P081_getCronExpr(event->TaskIndex))); - P081_data_struct *P081_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P081_data) { - return success; - } - - if (P081_data->isInitialized()) { - P081_check_or_init(event); - success = true; - } else { - clearPluginTaskData(event->TaskIndex); - } - break; - } - - - case PLUGIN_READ: - { - // Need to return true here, so the last and next exec times are stored in RTC. - success = true; - break; - } - - case PLUGIN_TIME_CHANGE: - case PLUGIN_ONCE_A_SECOND: - { - // code to be executed once a second. Tasks which do not require fast response can be added here - if (node_time.systemTimePresent()) { - P081_check_or_init(event); - time_t next_exec_time = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION); - - if (next_exec_time != CRON_INVALID_INSTANT) { - const time_t current_time = P081_getCurrentTime(); - const bool cron_elapsed = (next_exec_time <= current_time); - - if (cron_elapsed) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Cron Elapsed")); - # endif // ifndef BUILD_NO_DEBUG - - time_t last_exec_time = next_exec_time; - next_exec_time = P081_computeNextCronTime(event->TaskIndex, current_time); - P081_setCronExecTimes(event, last_exec_time, next_exec_time); - - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, String(F("Next execution:")) + formatDateTimeString(*gmtime(&next_exec_time))); - # endif // ifndef BUILD_NO_DEBUG - - if (function != PLUGIN_TIME_CHANGE) { - if (Settings.UseRules) { - eventQueue.addMove(concat(F("Cron#"), getTaskDeviceName(event->TaskIndex))); - } - success = true; - } - } - } else { - addLog(LOG_LEVEL_ERROR, F("CRON: INVALID INSTANT")); - } - } else { - addLog(LOG_LEVEL_ERROR, F("CRON: Time not synced")); - } - - - break; - } - } // switch - - return success; -} // function - - -#endif // USES_P081 +#include "_Plugin_Helper.h" + +// ####################################################################################################### +// #################################### Plugin 081: CRON tasks Scheduler ########################### +// ####################################################################################################### + +// -V::795 + +#ifdef USES_P081 + + +# include "src/PluginStructs/P081_data_struct.h" + +# define PLUGIN_081 +# define PLUGIN_ID_081 81 // plugin id +# define PLUGIN_NAME_081 "Generic - CRON" // "Plugin Name" is what will be displayed in the selection list +# define PLUGIN_VALUENAME1_081 "LastExecution" +# define PLUGIN_VALUENAME2_081 "NextExecution" + + +boolean Plugin_081(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + // This case defines the device characteristics, edit appropriately + + Device[++deviceCount].Number = PLUGIN_ID_081; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; // how the device is connected + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; // type of value the plugin will return, used only for + // Domoticz + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 2; // number of output variables. The value should match the number of keys + // PLUGIN_VALUENAME1_xxx + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + Device[deviceCount].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].HasFormatUserVar = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + // return the device name + string = F(PLUGIN_NAME_081); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + // called when the user opens the module configuration page + // it allows to add a new row for each output variable of the plugin + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_081)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_081)); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormSubHeader(F("Schedule")); + addFormTextBox(F("CRON Expression") + , F("cron_exp") + , P081_getCronExpr(event->TaskIndex) + , 39); + + addFormNote(F("S M H DoM Month DoW")); + + P081_html_show_cron_expr(event); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + const String expression = webArg(F("cron_exp")); + String log; + { + char expression_c[PLUGIN_081_EXPRESSION_SIZE] = {}; + safe_strncpy(expression_c, expression, PLUGIN_081_EXPRESSION_SIZE); + log = SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&expression_c), PLUGIN_081_EXPRESSION_SIZE); + } + + if (!log.isEmpty()) + { + addLog(LOG_LEVEL_ERROR, concat(F(PLUGIN_NAME_081 ": Saving "), log)); + } + + clearPluginTaskData(event->TaskIndex); + P081_setCronExecTimes(event, CRON_INVALID_INSTANT, CRON_INVALID_INSTANT); + success = true; + break; + } + + case PLUGIN_FORMAT_USERVAR: + { + switch (event->idx) { + case 0: + string = P081_formatExecTime(event->TaskIndex, LASTEXECUTION); + break; + case 1: + string = P081_formatExecTime(event->TaskIndex, NEXTEXECUTION); + break; + } + success = !string.isEmpty(); + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P081_data_struct(P081_getCronExpr(event->TaskIndex))); + P081_data_struct *P081_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P081_data) { + return success; + } + + if (P081_data->isInitialized()) { + P081_check_or_init(event); + success = true; + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + + case PLUGIN_READ: + { + // Need to return true here, so the last and next exec times are stored in RTC. + success = true; + break; + } + + case PLUGIN_TIME_CHANGE: + case PLUGIN_ONCE_A_SECOND: + { + // code to be executed once a second. Tasks which do not require fast response can be added here + if (node_time.systemTimePresent()) { + P081_check_or_init(event); + time_t next_exec_time = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION); + + if (next_exec_time != CRON_INVALID_INSTANT) { + const time_t current_time = P081_getCurrentTime(); + const bool cron_elapsed = (next_exec_time <= current_time); + + if (cron_elapsed) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Cron Elapsed")); + # endif // ifndef BUILD_NO_DEBUG + + time_t last_exec_time = next_exec_time; + next_exec_time = P081_computeNextCronTime(event->TaskIndex, current_time); + P081_setCronExecTimes(event, last_exec_time, next_exec_time); + + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("Next execution:"), formatDateTimeString(*gmtime(&next_exec_time)))); + # endif // ifndef BUILD_NO_DEBUG + + if (function != PLUGIN_TIME_CHANGE) { + if (Settings.UseRules) { + eventQueue.addMove(concat(F("Cron#"), getTaskDeviceName(event->TaskIndex))); + } + success = true; + } + } + } else { + addLog(LOG_LEVEL_ERROR, F("CRON: INVALID INSTANT")); + } + } else { + addLog(LOG_LEVEL_ERROR, F("CRON: Time not synced")); + } + + + break; + } + } // switch + + return success; +} // function + +#endif // USES_P081 diff --git a/src/_P082_GPS.ino b/src/_P082_GPS.ino index 442c2e9ec..143576107 100644 --- a/src/_P082_GPS.ino +++ b/src/_P082_GPS.ino @@ -1,801 +1,774 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P082 - -// ####################################################################################################### -// #################### Plugin 082 GPS ################################################################### -// ####################################################################################################### -// -// Read a GPS module connected via (Software)Serial -// Based on the library TinyGPS++ -// http://arduiniana.org/libraries/tinygpsplus/ -// -// - -# include -# include - -# include "src/DataStructs/ESPEasy_packed_raw_data.h" -# include "src/Globals/ESPEasy_time.h" -# include "src/Helpers/ESPEasy_time_calc.h" - -# include "src/PluginStructs/P082_data_struct.h" - -# define PLUGIN_082 -# define PLUGIN_ID_082 82 -# define PLUGIN_NAME_082 "Position - GPS" -# define PLUGIN_VALUENAME1_082 "Longitude" -# define PLUGIN_VALUENAME2_082 "Latitude" -# define PLUGIN_VALUENAME3_082 "Altitude" -# define PLUGIN_VALUENAME4_082 "Speed" - - - -// Must use volatile declared variable (which will end up in iRAM) -volatile unsigned long P082_pps_time = 0; -void Plugin_082_interrupt() IRAM_ATTR; - -boolean Plugin_082(uint8_t function, struct EventStruct *event, String& string) { - boolean success = false; - - switch (function) { - case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_082; - Device[deviceCount].Type = DEVICE_TYPE_SERIAL_PLUS1; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 4; - Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_082); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: { - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < P082_NR_OUTPUT_VALUES) { - const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; - P082_query choice = static_cast(PCONFIG(pconfigIndex)); - ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_082_valuename(choice, false)); - - switch (choice) { - case P082_query::P082_QUERY_LONG: - case P082_query::P082_QUERY_LAT: - ExtraTaskSettings.TaskDeviceValueDecimals[i] = 6; - break; - default: - ExtraTaskSettings.TaskDeviceValueDecimals[i] = 2; - break; - } - } else { - ExtraTaskSettings.clearTaskDeviceValueName(i); - } - } - break; - } - - case PLUGIN_WEBFORM_SHOW_VALUES: - { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->isInitialized()) { - uint8_t varNr = VARS_PER_TASK; - pluginWebformShowValue(event->TaskIndex, varNr++, F("Fix"), String(P082_data->hasFix(P082_TIMEOUT) ? 1 : 0)); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Tracked"), - String(P082_data->gps->satellitesStats.nrSatsTracked())); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Best SNR"), String(P082_data->gps->satellitesStats.getBestSNR()), true); - - // success = true; - } - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: { - serialHelper_getGpioNames(event, false, true); // TX optional - event->String3 = formatGpioName_input_optional(F("PPS")); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P082_TIMEOUT = P082_DEFAULT_FIX_TIMEOUT; - P082_DISTANCE = P082_DISTANCE_DFLT; - P082_QUERY1 = static_cast(P082_QUERY1_DFLT); - P082_QUERY2 = static_cast(P082_QUERY2_DFLT); - P082_QUERY3 = static_cast(P082_QUERY3_DFLT); - P082_QUERY4 = static_cast(P082_QUERY4_DFLT); - - success = true; - break; - } - - case PLUGIN_GET_CONFIG_VALUE: - { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->isInitialized()) { - const P082_query query = Plugin_082_from_valuename(string); - - if (query != P082_query::P082_NR_OUTPUT_OPTIONS) { - const float value = P082_data->_cache[static_cast(query)]; - int nrDecimals = 2; - - if ((query == P082_query::P082_QUERY_LONG) || (query == P082_query::P082_QUERY_LAT)) { - nrDecimals = 6; - } else if ((query == P082_query::P082_QUERY_SATVIS) || - (query == P082_query::P082_QUERY_SATUSE) || - (query == P082_query::P082_QUERY_FIXQ) || - (query == P082_query::P082_QUERY_CHKSUM_FAIL)) { - nrDecimals = 0; - } - - string = toString(value, nrDecimals); - success = true; - } - } - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - { - const __FlashStringHelper *options[static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)]; - - for (uint8_t i = 0; i < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++i) { - options[i] = Plugin_082_valuename(static_cast(i), true); - } - - for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; - sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, static_cast(P082_query::P082_NR_OUTPUT_OPTIONS), options); - } - break; - } - - case PLUGIN_WEBFORM_LOAD: { - /* - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - if (nullptr != P082_data && P082_data->isInitialized()) { - String detectedString = F("Detected: "); - detectedString += String(P082_data->easySerial->baudRate()); - addUnit(detectedString); - */ - - addFormNumericBox(F("Fix Timeout"), P082_TIMEOUT_LABEL, P082_TIMEOUT, 100, 10000); - addUnit(F("ms")); - -# ifdef P082_USE_U_BLOX_SPECIFIC - - addFormSubHeader(F("U-Blox specific")); - - { - const __FlashStringHelper *options[3] = { - toString(P082_PowerMode::Max_Performance), - toString(P082_PowerMode::Power_Save), - toString(P082_PowerMode::Eco) - }; - const int indices[3] = { - static_cast(P082_PowerMode::Max_Performance), - static_cast(P082_PowerMode::Power_Save), - static_cast(P082_PowerMode::Eco) - }; - addFormSelector(F("Power Mode"), F("pwrmode"), 3, options, indices, P082_POWER_MODE); - } - - { - const __FlashStringHelper *options[10] = { - toString(P082_DynamicModel::Portable), - toString(P082_DynamicModel::Stationary), - toString(P082_DynamicModel::Pedestrian), - toString(P082_DynamicModel::Automotive), - toString(P082_DynamicModel::Sea), - toString(P082_DynamicModel::Airborne_1g), - toString(P082_DynamicModel::Airborne_2g), - toString(P082_DynamicModel::Airborne_4g), - toString(P082_DynamicModel::Wrist), - toString(P082_DynamicModel::Bike) - }; - const int indices[10] = { - static_cast(P082_DynamicModel::Portable), - static_cast(P082_DynamicModel::Stationary), - static_cast(P082_DynamicModel::Pedestrian), - static_cast(P082_DynamicModel::Automotive), - static_cast(P082_DynamicModel::Sea), - static_cast(P082_DynamicModel::Airborne_1g), - static_cast(P082_DynamicModel::Airborne_2g), - static_cast(P082_DynamicModel::Airborne_4g), - static_cast(P082_DynamicModel::Wrist), - static_cast(P082_DynamicModel::Bike) - }; - addFormSelector(F("Dynamic Platform Model"), F("dynmodel"), 10, options, indices, P082_DYNAMIC_MODEL); - } -# endif // P082_USE_U_BLOX_SPECIFIC - - addFormSubHeader(F("Current Sensor Data")); - - P082_html_show_stats(event); - - // Settings to add: - // Speed unit - // Altitude unit - // Set system time - // Timeout in msec to consider still active fix. - // Update interval: seconds, distance travelled - // Position filtering - // Speed filtering - // - // What to do with: - // nr satellites - // HDOP - // fixQuality, fixMode - // statistics (chars processed, failed checksum) - - { - addFormSubHeader(F("Reference Point")); - - addFormFloatNumberBox(F("Latitude"), F("lat_ref"), P082_LAT_REF, -90.0f, 90.0f); - addFormFloatNumberBox(F("Longitude"), F("lng_ref"), P082_LONG_REF, -180.0f, 180.0f); - } - - addFormNumericBox(F("Distance Update Interval"), P082_DISTANCE_LABEL, P082_DISTANCE, 0, 10000); - addUnit('m'); - addFormNote(F("0 = disable update based on distance travelled")); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: { - # ifdef P082_USE_U_BLOX_SPECIFIC - P082_POWER_MODE = getFormItemInt(F("pwrmode")); - P082_DYNAMIC_MODEL = getFormItemInt(F("dynmodel")); - # endif // P082_USE_U_BLOX_SPECIFIC - P082_TIMEOUT = getFormItemInt(P082_TIMEOUT_LABEL); - P082_DISTANCE = getFormItemInt(P082_DISTANCE_LABEL); - - P082_LONG_REF = getFormItemFloat(F("lng_ref")); - P082_LAT_REF = getFormItemFloat(F("lat_ref")); - - // Save output selector parameters. - for (int i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; - const P082_query choice = static_cast(PCONFIG(pconfigIndex)); - sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, Plugin_082_valuename(choice, false)); - } - - success = true; - break; - } - -# if FEATURE_PLUGIN_STATS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: - { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P082_data) { - #if FEATURE_CHART_JS - P082_data->webformLoad_show_position_scatterplot(event); - #endif - for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; - - if (P082_data->webformLoad_show_stats(event, i, static_cast(PCONFIG(pconfigIndex)))) { - success = true; // Something added - } - } - } - break; - } -# endif // if FEATURE_PLUGIN_STATS - - case PLUGIN_INIT: { - if (P082_TIMEOUT < 100) { - P082_TIMEOUT = P082_DEFAULT_FIX_TIMEOUT; - } - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - const int16_t serial_rx = CONFIG_PIN1; - const int16_t serial_tx = CONFIG_PIN2; - const int16_t pps_pin = CONFIG_PIN3; - - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - initPluginTaskData(event->TaskIndex, new (std::nothrow) P082_data_struct()); - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P082_data) { - return success; - } - - if (P082_data->init(port, serial_rx, serial_tx)) { - success = true; - serialHelper_log_GpioDescription(port, serial_rx, serial_tx); - - if (validGpio(pps_pin)) { - // pinMode(pps_pin, INPUT_PULLUP); - attachInterrupt(pps_pin, Plugin_082_interrupt, RISING); - } - # ifdef P082_USE_U_BLOX_SPECIFIC - P082_data->setPowerMode(static_cast(P082_POWER_MODE)); - P082_data->setDynamicModel(static_cast(P082_DYNAMIC_MODEL)); - # endif // P082_USE_U_BLOX_SPECIFIC - } else { - clearPluginTaskData(event->TaskIndex); - } - break; - } - - case PLUGIN_EXIT: { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P082_data) { - P082_data->powerDown(); - } - - const int16_t pps_pin = CONFIG_PIN3; - - if (validGpio(pps_pin)) { - detachInterrupt(pps_pin); - } - success = true; - break; - } - - case PLUGIN_FIFTY_PER_SECOND: { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->loop()) { - P082_setSystemTime(event); -# ifdef P082_SEND_GPS_TO_LOG - - if (P082_data->_lastSentence.substring(0, 10).indexOf(F("TXT")) != -1) { - addLog(LOG_LEVEL_INFO, P082_data->_lastSentence); - } else { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, P082_data->_lastSentence); - # endif // ifndef BUILD_NO_DEBUG - } -# endif // ifdef P082_SEND_GPS_TO_LOG - Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); - delay(0); // Processing a full sentence may take a while, run some - // background tasks. - } - success = true; - break; - } - - case PLUGIN_READ: { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->isInitialized()) { - static bool activeFix = P082_data->hasFix(P082_TIMEOUT); - const bool curFixStatus = P082_data->hasFix(P082_TIMEOUT); - - if (activeFix != curFixStatus) { - // Fix status changed, send events. - if (Settings.UseRules) { - eventQueue.add(curFixStatus ? F("GPS#GotFix") : F("GPS#LostFix")); - } - activeFix = curFixStatus; - } - ESPEASY_RULES_FLOAT_TYPE distance{}; - - if (curFixStatus) { - if (P082_data->gps->location.isUpdated()) { - const float lng = P082_data->gps->location.lng(); - const float lat = P082_data->gps->location.lat(); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_LONG), lng); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_LAT), lat); - - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DISTANCE), P082_data->_distance); - const float dist_ref = P082_data->gps->distanceBetween(P082_LAT_REF, P082_LONG_REF, lat, lng); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DIST_REF), dist_ref); - - - if (P082_DISTANCE > 0) { - distance = P082_data->distanceSinceLast(P082_TIMEOUT); - } - success = true; - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("GPS: Position update.")); - # endif // ifndef BUILD_NO_DEBUG - } - - if (P082_data->gps->altitude.isUpdated()) { - // ToDo make unit selectable - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_ALT), P082_data->gps->altitude.meters()); - success = true; - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("GPS: Altitude update.")); - # endif // ifndef BUILD_NO_DEBUG - } - - if (P082_data->gps->speed.isUpdated()) { - // ToDo make unit selectable - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SPD), P082_data->gps->speed.mps()); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("GPS: Speed update.")); - # endif // ifndef BUILD_NO_DEBUG - success = true; - } - } - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SATVIS), P082_data->gps->satellitesStats.nrSatsVisible()); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SATUSE), P082_data->gps->satellitesStats.nrSatsTracked()); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_HDOP), P082_data->gps->hdop.value() / 100.0f); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_FIXQ), P082_data->gps->location.Quality()); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DB_MAX), P082_data->gps->satellitesStats.getBestSNR()); - P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_CHKSUM_FAIL), P082_data->gps->failedChecksum()); - - P082_logStats(event); - - if (success) { - bool distance_passed = false; - bool interval_passed = false; - - if (P082_DISTANCE > 0) { - // Check travelled distance. - if ((distance > static_cast(P082_DISTANCE)) || (distance < 0)) { - if (P082_data->storeCurPos(P082_TIMEOUT)) { - distance_passed = true; - - // Add sanity check for distance travelled - if (distance > static_cast(P082_DISTANCE)) { - if (Settings.UseRules) { - eventQueue.addMove(strformat(F("GPS#travelled=%f"), distance)); - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("GPS: Distance trigger : %f m"), distance)); - } - } - } - } - } - - if (P082_data->_last_measurement == 0) { - interval_passed = true; - } else if (timeOutReached(P082_data->_last_measurement + (Settings.TaskDeviceTimer[event->TaskIndex] * 1000))) { - interval_passed = true; - } - success = (distance_passed || interval_passed); - - if (success) { - P082_data->_last_measurement = millis(); - } - } - } - break; - } - case PLUGIN_WRITE: - { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->isInitialized()) { - const String command = parseString(string, 1); - const String subcommand = parseString(string, 2); - - if (equals(command, F("gps"))) { - if (equals(subcommand, F("wake"))) { - success = P082_data->wakeUp(); - } else if (equals(subcommand, F("sleep"))) { - success = P082_data->powerDown(); - } -# ifdef P082_USE_U_BLOX_SPECIFIC - else if (equals(subcommand, F("maxperf"))) { - success = P082_data->setPowerMode(P082_PowerMode::Max_Performance); - } else if (equals(subcommand, F("powersave"))) { - success = P082_data->setPowerMode(P082_PowerMode::Power_Save); - } else if (equals(subcommand, F("eco"))) { - success = P082_data->setPowerMode(P082_PowerMode::Eco); - } -# endif // P082_USE_U_BLOX_SPECIFIC - } - } - - break; - } -# if FEATURE_PACKED_RAW_DATA - case PLUGIN_GET_PACKED_RAW_DATA: - { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P082_data) && P082_data->isInitialized()) { - // Matching JS code: - // return decode(bytes, [header, latLng, latLng, altitude, uint16_1e2, hdop, uint8, uint8, uint24, uint24_1e1], - // ['header', 'latitude', 'longitude', 'altitude', 'speed', 'hdop', 'max_snr', 'sat_tracked', 'distance_total', - // 'distance_ref']); - // altitude type: return +(int16(bytes) / 4 - 1000).toFixed(1); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_LAT)], PackedData_latLng); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_LONG)], PackedData_latLng); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_ALT)], PackedData_altitude); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_SPD)], PackedData_uint16_1e2); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_HDOP)], PackedData_hdop); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DB_MAX)], PackedData_uint8); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_SATUSE)], PackedData_uint8); - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DISTANCE)] / 1000, PackedData_uint24_1e2); // - // Max - // 167772.16 - // km - event->Par1 = 8; - - if (P082_referencePointSet(event)) { - string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DIST_REF)], PackedData_uint24_1e1); // Max - // 1677.7216 - // km - event->Par1 = 9; - } - - success = true; - } - break; - } -# endif // if FEATURE_PACKED_RAW_DATA - } - return success; -} - -bool P082_referencePointSet(struct EventStruct *event) { - return !((P082_LONG_REF < 0.1f) && (P082_LONG_REF > -0.1f) - && (P082_LAT_REF < 0.1f) && (P082_LAT_REF > -0.1f)); -} - -void P082_setOutputValue(struct EventStruct *event, uint8_t outputType, float value) { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P082_data) || !P082_data->isInitialized()) { - return; - } - - if (outputType < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)) { - P082_data->_cache[outputType] = value; - } - - for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { - const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; - - if (PCONFIG(pconfigIndex) == outputType) { - UserVar.setFloat(event->TaskIndex, i, value); - } - } -} - -void P082_logStats(struct EventStruct *event) { - # ifndef BUILD_NO_DEBUG - - if (!loglevelActiveFor(LOG_LEVEL_DEBUG)) { return; } - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P082_data) || !P082_data->isInitialized()) { - return; - } - String log; - - if (log.reserve(128)) { - log = F("GPS:"); - log += F(" Fix: "); - log += P082_data->hasFix(P082_TIMEOUT) ? 1 : 0; - log += F(" #sat: "); - log += P082_data->gps->satellites.value(); - log += F(" #SNR: "); - log += P082_data->gps->satellitesStats.getBestSNR(); - log += F(" HDOP: "); - log += P082_data->gps->hdop.value() / 100.0f; - log += F(" Chksum(pass/fail): "); - log += P082_data->gps->passedChecksum(); - log += '/'; - log += P082_data->gps->failedChecksum(); - log += F(" invalid: "); - log += P082_data->gps->invalidData(); - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG -} - -void P082_html_show_satStats(struct EventStruct *event, bool tracked, bool onlyGPS) { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P082_data) || !P082_data->isInitialized()) { - return; - } - - bool first = true; - - for (uint8_t i = 0; i < _GPS_MAX_ARRAY_LENGTH; ++i) { - uint8_t id = P082_data->gps->satellitesStats.id[i]; - uint8_t snr = P082_data->gps->satellitesStats.snr[i]; - - if (id > 0) { - if (((id <= 32) == onlyGPS) && ((snr > 0) == tracked)) { - if (first) { - first = false; - String label; - label.reserve(32); - - if (onlyGPS) { - label = F("GPS"); - } else { - label = F("Other"); - } - label += F(" sat. "); - - if (tracked) { - label += F("tracked - id(SNR)"); - } else { - label += F("in view - id"); - } - addRowLabel(label); - } else { - addHtml(',', ' '); - } - addHtmlInt(id); - - if (tracked) { - addHtml(' ', '('); - addHtmlInt(snr); - addHtml(')'); - } - } - } - } - - if (!first) { - // Something was added, so add the unit here - if (tracked) { - html_I(F(" - SNR in dBHz")); - } - } -} - -void P082_html_show_stats(struct EventStruct *event) { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P082_data) || !P082_data->isInitialized()) { - return; - } - addRowLabel(F("Fix")); - addEnabled(P082_data->hasFix(P082_TIMEOUT)); - - addRowLabel(F("Fix Quality")); - - switch (P082_data->gps->location.Quality()) { - case 0: addHtml(F("Invalid")); break; - case 1: addHtml(F("GPS")); break; - case 2: addHtml(F("DGPS")); break; - case 3: addHtml(F("PPS")); break; - case 4: addHtml(F("RTK")); break; - case 5: addHtml(F("FloatRTK")); break; - case 6: addHtml(F("Estimated")); break; - case 7: addHtml(F("Manual")); break; - case 8: addHtml(F("Simulated")); break; - default: - addHtml(F("Unknown")); - break; - } - - addRowLabel(F("Satellites tracked")); - addHtmlInt(P082_data->gps->satellitesStats.nrSatsTracked()); - - addRowLabel(F("Satellites visible")); - addHtmlInt(P082_data->gps->satellitesStats.nrSatsVisible()); - - addRowLabel(F("Best SNR")); - addHtmlInt(P082_data->gps->satellitesStats.getBestSNR()); - addHtml(F(" dBHz")); - - // Satellites tracked or in view. - P082_html_show_satStats(event, true, true); - P082_html_show_satStats(event, false, true); - P082_html_show_satStats(event, true, false); - P082_html_show_satStats(event, false, false); - - addRowLabel(F("HDOP")); - addHtmlFloat(P082_data->gps->hdop.value() / 100.0f); - - addRowLabel(F("UTC Time")); - struct tm dateTime; - uint32_t age; - bool updated; - bool pps_sync; - - if (P082_data->getDateTime(dateTime, age, updated, pps_sync)) { - dateTime = node_time.addSeconds(dateTime, (age / 1000), false); - addHtml(formatDateTimeString(dateTime)); - } else { - addHtml('-'); - } - - addRowLabel(F("Distance Travelled")); - addHtmlInt(static_cast(P082_data->_cache[static_cast(P082_query::P082_QUERY_DISTANCE)])); - addUnit('m'); - - if (P082_referencePointSet(event)) { - addRowLabel(F("Distance from Ref. Point")); - addHtmlInt(static_cast(P082_data->_cache[static_cast(P082_query::P082_QUERY_DIST_REF)])); - addUnit('m'); - } - - addRowLabel(F("Checksum (pass/fail/invalid)")); - { - String chksumStats; - - chksumStats = P082_data->gps->passedChecksum(); - chksumStats += '/'; - chksumStats += P082_data->gps->failedChecksum(); - chksumStats += '/'; - chksumStats += P082_data->gps->invalidData(); - addHtml(chksumStats); - } -} - -void P082_setSystemTime(struct EventStruct *event) { - P082_data_struct *P082_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P082_data) || !P082_data->isInitialized()) { - return; - } - - if ((timeSource_t::GPS_time_source == node_time.timeSource) && - (P082_data->_last_setSystemTime != 0) && - (timePassedSince(P082_data->_last_setSystemTime) < EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC)) - { - // Only update the system time every hour from the same time source. - return; - } - - struct tm dateTime; - uint32_t age; - bool updated; - bool pps_sync; - - P082_data->_pps_time = P082_pps_time; // Must copy the interrupt gathered time first. - - if (P082_data->getDateTime(dateTime, age, updated, pps_sync)) { - if (updated) { - // Use floating point precision to use the time since last update from GPS - // and the given offset in centisecond. - ESPEASY_RULES_FLOAT_TYPE time = makeTime(dateTime); - time += (static_cast(age) / static_cast(1000)); - node_time.setExternalTimeSource(time, timeSource_t::GPS_time_source); - P082_data->_last_setSystemTime = millis(); - } - } - P082_pps_time = 0; -} - -void Plugin_082_interrupt() { - P082_pps_time = millis(); -} - -#endif // USES_P082 +#include "_Plugin_Helper.h" + +#ifdef USES_P082 + +// ####################################################################################################### +// #################### Plugin 082 GPS ################################################################### +// ####################################################################################################### +// +// Read a GPS module connected via (Software)Serial +// Based on the library TinyGPS++ +// http://arduiniana.org/libraries/tinygpsplus/ +// +// + +# include +# include + +# include "src/DataStructs/ESPEasy_packed_raw_data.h" +# include "src/Globals/ESPEasy_time.h" +# include "src/Helpers/ESPEasy_time_calc.h" + +# include "src/PluginStructs/P082_data_struct.h" + +# define PLUGIN_082 +# define PLUGIN_ID_082 82 +# define PLUGIN_NAME_082 "Position - GPS" +# define PLUGIN_VALUENAME1_082 "Longitude" +# define PLUGIN_VALUENAME2_082 "Latitude" +# define PLUGIN_VALUENAME3_082 "Altitude" +# define PLUGIN_VALUENAME4_082 "Speed" + + + +// Must use volatile declared variable (which will end up in iRAM) +volatile unsigned long P082_pps_time = 0; +void Plugin_082_interrupt() IRAM_ATTR; + +boolean Plugin_082(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_082; + Device[deviceCount].Type = DEVICE_TYPE_SERIAL_PLUS1; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_082); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: { + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < P082_NR_OUTPUT_VALUES) { + const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; + P082_query choice = static_cast(PCONFIG(pconfigIndex)); + ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_082_valuename(choice, false)); + + switch (choice) { + case P082_query::P082_QUERY_LONG: + case P082_query::P082_QUERY_LAT: + ExtraTaskSettings.TaskDeviceValueDecimals[i] = 6; + break; + default: + ExtraTaskSettings.TaskDeviceValueDecimals[i] = 2; + break; + } + } else { + ExtraTaskSettings.clearTaskDeviceValueName(i); + } + } + break; + } + + case PLUGIN_WEBFORM_SHOW_VALUES: + { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->isInitialized()) { + uint8_t varNr = VARS_PER_TASK; + pluginWebformShowValue(event->TaskIndex, varNr++, F("Fix"), String(P082_data->hasFix(P082_TIMEOUT) ? 1 : 0)); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Tracked"), + String(P082_data->gps->satellitesStats.nrSatsTracked())); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Best SNR"), String(P082_data->gps->satellitesStats.getBestSNR()), true); + + // success = true; + } + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event, false, true); // TX optional + event->String3 = formatGpioName_input_optional(F("PPS")); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P082_TIMEOUT = P082_DEFAULT_FIX_TIMEOUT; + P082_DISTANCE = P082_DISTANCE_DFLT; + P082_QUERY1 = static_cast(P082_QUERY1_DFLT); + P082_QUERY2 = static_cast(P082_QUERY2_DFLT); + P082_QUERY3 = static_cast(P082_QUERY3_DFLT); + P082_QUERY4 = static_cast(P082_QUERY4_DFLT); + + success = true; + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->isInitialized()) { + const P082_query query = Plugin_082_from_valuename(string); + + if (query != P082_query::P082_NR_OUTPUT_OPTIONS) { + const float value = P082_data->_cache[static_cast(query)]; + int nrDecimals = 2; + + if ((query == P082_query::P082_QUERY_LONG) || (query == P082_query::P082_QUERY_LAT)) { + nrDecimals = 6; + } else if ((query == P082_query::P082_QUERY_SATVIS) || + (query == P082_query::P082_QUERY_SATUSE) || + (query == P082_query::P082_QUERY_FIXQ) || + (query == P082_query::P082_QUERY_CHKSUM_FAIL)) { + nrDecimals = 0; + } + + string = toString(value, nrDecimals); + success = true; + } + } + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + const __FlashStringHelper *options[static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)]; + + for (uint8_t i = 0; i < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++i) { + options[i] = Plugin_082_valuename(static_cast(i), true); + } + + for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; + sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, static_cast(P082_query::P082_NR_OUTPUT_OPTIONS), options); + } + break; + } + + case PLUGIN_WEBFORM_LOAD: { + /* + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + if (nullptr != P082_data && P082_data->isInitialized()) { + String detectedString = F("Detected: "); + detectedString += String(P082_data->easySerial->baudRate()); + addUnit(detectedString); + */ + + addFormNumericBox(F("Fix Timeout"), P082_TIMEOUT_LABEL, P082_TIMEOUT, 100, 10000); + addUnit(F("ms")); + +# ifdef P082_USE_U_BLOX_SPECIFIC + + addFormSubHeader(F("U-Blox specific")); + + { + const __FlashStringHelper *options[3] = { + toString(P082_PowerMode::Max_Performance), + toString(P082_PowerMode::Power_Save), + toString(P082_PowerMode::Eco) + }; + const int indices[3] = { + static_cast(P082_PowerMode::Max_Performance), + static_cast(P082_PowerMode::Power_Save), + static_cast(P082_PowerMode::Eco) + }; + addFormSelector(F("Power Mode"), F("pwrmode"), 3, options, indices, P082_POWER_MODE); + } + + { + const __FlashStringHelper *options[10] = { + toString(P082_DynamicModel::Portable), + toString(P082_DynamicModel::Stationary), + toString(P082_DynamicModel::Pedestrian), + toString(P082_DynamicModel::Automotive), + toString(P082_DynamicModel::Sea), + toString(P082_DynamicModel::Airborne_1g), + toString(P082_DynamicModel::Airborne_2g), + toString(P082_DynamicModel::Airborne_4g), + toString(P082_DynamicModel::Wrist), + toString(P082_DynamicModel::Bike) + }; + const int indices[10] = { + static_cast(P082_DynamicModel::Portable), + static_cast(P082_DynamicModel::Stationary), + static_cast(P082_DynamicModel::Pedestrian), + static_cast(P082_DynamicModel::Automotive), + static_cast(P082_DynamicModel::Sea), + static_cast(P082_DynamicModel::Airborne_1g), + static_cast(P082_DynamicModel::Airborne_2g), + static_cast(P082_DynamicModel::Airborne_4g), + static_cast(P082_DynamicModel::Wrist), + static_cast(P082_DynamicModel::Bike) + }; + addFormSelector(F("Dynamic Platform Model"), F("dynmodel"), 10, options, indices, P082_DYNAMIC_MODEL); + } +# endif // P082_USE_U_BLOX_SPECIFIC + + addFormSubHeader(F("Current Sensor Data")); + + P082_html_show_stats(event); + + // Settings to add: + // Speed unit + // Altitude unit + // Set system time + // Timeout in msec to consider still active fix. + // Update interval: seconds, distance travelled + // Position filtering + // Speed filtering + // + // What to do with: + // nr satellites + // HDOP + // fixQuality, fixMode + // statistics (chars processed, failed checksum) + + { + addFormSubHeader(F("Reference Point")); + + addFormFloatNumberBox(F("Latitude"), F("lat_ref"), P082_LAT_REF, -90.0f, 90.0f); + addFormFloatNumberBox(F("Longitude"), F("lng_ref"), P082_LONG_REF, -180.0f, 180.0f); + } + + addFormNumericBox(F("Distance Update Interval"), P082_DISTANCE_LABEL, P082_DISTANCE, 0, 10000); + addUnit('m'); + addFormNote(F("0 = disable update based on distance travelled")); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: { + # ifdef P082_USE_U_BLOX_SPECIFIC + P082_POWER_MODE = getFormItemInt(F("pwrmode")); + P082_DYNAMIC_MODEL = getFormItemInt(F("dynmodel")); + # endif // P082_USE_U_BLOX_SPECIFIC + P082_TIMEOUT = getFormItemInt(P082_TIMEOUT_LABEL); + P082_DISTANCE = getFormItemInt(P082_DISTANCE_LABEL); + + P082_LONG_REF = getFormItemFloat(F("lng_ref")); + P082_LAT_REF = getFormItemFloat(F("lat_ref")); + + // Save output selector parameters. + for (int i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; + const P082_query choice = static_cast(PCONFIG(pconfigIndex)); + sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, Plugin_082_valuename(choice, false)); + } + + success = true; + break; + } + +# if FEATURE_PLUGIN_STATS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: + { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P082_data) { + #if FEATURE_CHART_JS + P082_data->webformLoad_show_position_scatterplot(event); + #endif + for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; + + if (P082_data->webformLoad_show_stats(event, i, static_cast(PCONFIG(pconfigIndex)))) { + success = true; // Something added + } + } + } + break; + } +# endif // if FEATURE_PLUGIN_STATS + + case PLUGIN_INIT: { + if (P082_TIMEOUT < 100) { + P082_TIMEOUT = P082_DEFAULT_FIX_TIMEOUT; + } + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + const int16_t serial_rx = CONFIG_PIN1; + const int16_t serial_tx = CONFIG_PIN2; + const int16_t pps_pin = CONFIG_PIN3; + + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + initPluginTaskData(event->TaskIndex, new (std::nothrow) P082_data_struct()); + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P082_data) { + return success; + } + + if (P082_data->init(port, serial_rx, serial_tx)) { + success = true; + serialHelper_log_GpioDescription(port, serial_rx, serial_tx); + + if (validGpio(pps_pin)) { + // pinMode(pps_pin, INPUT_PULLUP); + attachInterrupt(pps_pin, Plugin_082_interrupt, RISING); + } + # ifdef P082_USE_U_BLOX_SPECIFIC + P082_data->setPowerMode(static_cast(P082_POWER_MODE)); + P082_data->setDynamicModel(static_cast(P082_DYNAMIC_MODEL)); + # endif // P082_USE_U_BLOX_SPECIFIC + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + case PLUGIN_EXIT: { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P082_data) { + P082_data->powerDown(); + } + + const int16_t pps_pin = CONFIG_PIN3; + + if (validGpio(pps_pin)) { + detachInterrupt(pps_pin); + } + success = true; + break; + } + + case PLUGIN_FIFTY_PER_SECOND: { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->loop()) { + P082_setSystemTime(event); +# ifdef P082_SEND_GPS_TO_LOG + + if (P082_data->_lastSentence.substring(0, 10).indexOf(F("TXT")) != -1) { + addLog(LOG_LEVEL_INFO, P082_data->_lastSentence); + } else { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, P082_data->_lastSentence); + # endif // ifndef BUILD_NO_DEBUG + } +# endif // ifdef P082_SEND_GPS_TO_LOG + Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); + delay(0); // Processing a full sentence may take a while, run some + // background tasks. + } + success = true; + break; + } + + case PLUGIN_READ: { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->isInitialized()) { + static bool activeFix = P082_data->hasFix(P082_TIMEOUT); + const bool curFixStatus = P082_data->hasFix(P082_TIMEOUT); + + if (activeFix != curFixStatus) { + // Fix status changed, send events. + if (Settings.UseRules) { + eventQueue.add(curFixStatus ? F("GPS#GotFix") : F("GPS#LostFix")); + } + activeFix = curFixStatus; + } + ESPEASY_RULES_FLOAT_TYPE distance{}; + + if (curFixStatus) { + if (P082_data->gps->location.isUpdated()) { + const float lng = P082_data->gps->location.lng(); + const float lat = P082_data->gps->location.lat(); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_LONG), lng); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_LAT), lat); + + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DISTANCE), P082_data->_distance); + const float dist_ref = P082_data->gps->distanceBetween(P082_LAT_REF, P082_LONG_REF, lat, lng); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DIST_REF), dist_ref); + + + if (P082_DISTANCE > 0) { + distance = P082_data->distanceSinceLast(P082_TIMEOUT); + } + success = true; + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("GPS: Position update.")); + # endif // ifndef BUILD_NO_DEBUG + } + + if (P082_data->gps->altitude.isUpdated()) { + // ToDo make unit selectable + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_ALT), P082_data->gps->altitude.meters()); + success = true; + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("GPS: Altitude update.")); + # endif // ifndef BUILD_NO_DEBUG + } + + if (P082_data->gps->speed.isUpdated()) { + // ToDo make unit selectable + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SPD), P082_data->gps->speed.mps()); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("GPS: Speed update.")); + # endif // ifndef BUILD_NO_DEBUG + success = true; + } + } + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SATVIS), P082_data->gps->satellitesStats.nrSatsVisible()); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_SATUSE), P082_data->gps->satellitesStats.nrSatsTracked()); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_HDOP), P082_data->gps->hdop.value() / 100.0f); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_FIXQ), P082_data->gps->location.Quality()); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_DB_MAX), P082_data->gps->satellitesStats.getBestSNR()); + P082_setOutputValue(event, static_cast(P082_query::P082_QUERY_CHKSUM_FAIL), P082_data->gps->failedChecksum()); + + P082_logStats(event); + + if (success) { + bool distance_passed = false; + bool interval_passed = false; + + if (P082_DISTANCE > 0) { + // Check travelled distance. + if ((distance > static_cast(P082_DISTANCE)) || (distance < 0)) { + if (P082_data->storeCurPos(P082_TIMEOUT)) { + distance_passed = true; + + // Add sanity check for distance travelled + if (distance > static_cast(P082_DISTANCE)) { + if (Settings.UseRules) { + eventQueue.addMove(strformat(F("GPS#travelled=%f"), distance)); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("GPS: Distance trigger : %f m"), distance)); + } + } + } + } + } + + if (P082_data->_last_measurement == 0) { + interval_passed = true; + } else if (timeOutReached(P082_data->_last_measurement + (Settings.TaskDeviceTimer[event->TaskIndex] * 1000))) { + interval_passed = true; + } + success = (distance_passed || interval_passed); + + if (success) { + P082_data->_last_measurement = millis(); + } + } + } + break; + } + case PLUGIN_WRITE: + { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->isInitialized()) { + const String command = parseString(string, 1); + const String subcommand = parseString(string, 2); + + if (equals(command, F("gps"))) { + if (equals(subcommand, F("wake"))) { + success = P082_data->wakeUp(); + } else if (equals(subcommand, F("sleep"))) { + success = P082_data->powerDown(); + } +# ifdef P082_USE_U_BLOX_SPECIFIC + else if (equals(subcommand, F("maxperf"))) { + success = P082_data->setPowerMode(P082_PowerMode::Max_Performance); + } else if (equals(subcommand, F("powersave"))) { + success = P082_data->setPowerMode(P082_PowerMode::Power_Save); + } else if (equals(subcommand, F("eco"))) { + success = P082_data->setPowerMode(P082_PowerMode::Eco); + } +# endif // P082_USE_U_BLOX_SPECIFIC + } + } + + break; + } +# if FEATURE_PACKED_RAW_DATA + case PLUGIN_GET_PACKED_RAW_DATA: + { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P082_data) && P082_data->isInitialized()) { + // Matching JS code: + // return decode(bytes, [header, latLng, latLng, altitude, uint16_1e2, hdop, uint8, uint8, uint24, uint24_1e1], + // ['header', 'latitude', 'longitude', 'altitude', 'speed', 'hdop', 'max_snr', 'sat_tracked', 'distance_total', + // 'distance_ref']); + // altitude type: return +(int16(bytes) / 4 - 1000).toFixed(1); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_LAT)], PackedData_latLng); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_LONG)], PackedData_latLng); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_ALT)], PackedData_altitude); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_SPD)], PackedData_uint16_1e2); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_HDOP)], PackedData_hdop); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DB_MAX)], PackedData_uint8); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_SATUSE)], PackedData_uint8); + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DISTANCE)] / 1000, PackedData_uint24_1e2); // + // Max + // 167772.16 + // km + event->Par1 = 8; + + if (P082_referencePointSet(event)) { + string += LoRa_addFloat(P082_data->_cache[static_cast(P082_query::P082_QUERY_DIST_REF)], PackedData_uint24_1e1); // Max + // 1677.7216 + // km + event->Par1 = 9; + } + + success = true; + } + break; + } +# endif // if FEATURE_PACKED_RAW_DATA + } + return success; +} + +bool P082_referencePointSet(struct EventStruct *event) { + return !((P082_LONG_REF < 0.1f) && (P082_LONG_REF > -0.1f) + && (P082_LAT_REF < 0.1f) && (P082_LAT_REF > -0.1f)); +} + +void P082_setOutputValue(struct EventStruct *event, uint8_t outputType, float value) { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P082_data) || !P082_data->isInitialized()) { + return; + } + + if (outputType < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)) { + P082_data->_cache[outputType] = value; + } + + for (uint8_t i = 0; i < P082_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P082_QUERY1_CONFIG_POS; + + if (PCONFIG(pconfigIndex) == outputType) { + UserVar.setFloat(event->TaskIndex, i, value); + } + } +} + +void P082_logStats(struct EventStruct *event) { + # ifndef BUILD_NO_DEBUG + + if (!loglevelActiveFor(LOG_LEVEL_DEBUG)) { return; } + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P082_data) || !P082_data->isInitialized()) { + return; + } + String log; + + if (log.reserve(128)) { + log = F("GPS:"); + log += F(" Fix: "); + log += P082_data->hasFix(P082_TIMEOUT) ? 1 : 0; + log += F(" #sat: "); + log += P082_data->gps->satellites.value(); + log += F(" #SNR: "); + log += P082_data->gps->satellitesStats.getBestSNR(); + log += F(" HDOP: "); + log += P082_data->gps->hdop.value() / 100.0f; + log += F(" Chksum(pass/fail): "); + log += P082_data->gps->passedChecksum(); + log += '/'; + log += P082_data->gps->failedChecksum(); + log += F(" invalid: "); + log += P082_data->gps->invalidData(); + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG +} + +void P082_html_show_satStats(struct EventStruct *event, bool tracked, bool onlyGPS) { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P082_data) || !P082_data->isInitialized()) { + return; + } + + bool first = true; + + for (uint8_t i = 0; i < _GPS_MAX_ARRAY_LENGTH; ++i) { + uint8_t id = P082_data->gps->satellitesStats.id[i]; + uint8_t snr = P082_data->gps->satellitesStats.snr[i]; + + if (id > 0) { + if (((id <= 32) == onlyGPS) && ((snr > 0) == tracked)) { + if (first) { + first = false; + String label; + label.reserve(32); + + if (onlyGPS) { + label = F("GPS"); + } else { + label = F("Other"); + } + label += F(" sat. "); + + if (tracked) { + label += F("tracked - id(SNR)"); + } else { + label += F("in view - id"); + } + addRowLabel(label); + } else { + addHtml(',', ' '); + } + addHtmlInt(id); + + if (tracked) { + addHtml(' ', '('); + addHtmlInt(snr); + addHtml(')'); + } + } + } + } + + if (!first) { + // Something was added, so add the unit here + if (tracked) { + html_I(F(" - SNR in dBHz")); + } + } +} + +void P082_html_show_stats(struct EventStruct *event) { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P082_data) || !P082_data->isInitialized()) { + return; + } + addRowLabel(F("Fix")); + addEnabled(P082_data->hasFix(P082_TIMEOUT)); + + addRowLabel(F("Fix Quality")); + + switch (P082_data->gps->location.Quality()) { + case 0: addHtml(F("Invalid")); break; + case 1: addHtml(F("GPS")); break; + case 2: addHtml(F("DGPS")); break; + case 3: addHtml(F("PPS")); break; + case 4: addHtml(F("RTK")); break; + case 5: addHtml(F("FloatRTK")); break; + case 6: addHtml(F("Estimated")); break; + case 7: addHtml(F("Manual")); break; + case 8: addHtml(F("Simulated")); break; + default: + addHtml(F("Unknown")); + break; + } + + addRowLabel(F("Satellites tracked")); + addHtmlInt(P082_data->gps->satellitesStats.nrSatsTracked()); + + addRowLabel(F("Satellites visible")); + addHtmlInt(P082_data->gps->satellitesStats.nrSatsVisible()); + + addRowLabel(F("Best SNR")); + addHtmlInt(P082_data->gps->satellitesStats.getBestSNR()); + addHtml(F(" dBHz")); + + // Satellites tracked or in view. + P082_html_show_satStats(event, true, true); + P082_html_show_satStats(event, false, true); + P082_html_show_satStats(event, true, false); + P082_html_show_satStats(event, false, false); + + addRowLabel(F("HDOP")); + addHtmlFloat(P082_data->gps->hdop.value() / 100.0f); + + addRowLabel(F("UTC Time")); + struct tm dateTime; + if (P082_data->getDateTime(dateTime)) { + addHtml(formatDateTimeString(dateTime)); + } else { + addHtml('-'); + } + + addRowLabel(F("Distance Travelled")); + addHtmlInt(static_cast(P082_data->_cache[static_cast(P082_query::P082_QUERY_DISTANCE)])); + addUnit('m'); + + if (P082_referencePointSet(event)) { + addRowLabel(F("Distance from Ref. Point")); + addHtmlInt(static_cast(P082_data->_cache[static_cast(P082_query::P082_QUERY_DIST_REF)])); + addUnit('m'); + } + + addRowLabel(F("Checksum (pass/fail/invalid)")); + { + String chksumStats; + + chksumStats = P082_data->gps->passedChecksum(); + chksumStats += '/'; + chksumStats += P082_data->gps->failedChecksum(); + chksumStats += '/'; + chksumStats += P082_data->gps->invalidData(); + addHtml(chksumStats); + } +} + +void P082_setSystemTime(struct EventStruct *event) { + P082_data_struct *P082_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P082_data) || !P082_data->isInitialized()) { + return; + } + + P082_data->_pps_time = P082_pps_time; // Must copy the interrupt gathered time first. + P082_pps_time = 0; + + P082_data->tryUpdateSystemTime(); +} + +void Plugin_082_interrupt() { + P082_pps_time = millis(); +} + +#endif // USES_P082 diff --git a/src/_P083_SGP30.ino b/src/_P083_SGP30.ino index 687ad6edf..209aeaa8e 100644 --- a/src/_P083_SGP30.ino +++ b/src/_P083_SGP30.ino @@ -9,19 +9,19 @@ \*********************************************************************************************/ -#include "src/PluginStructs/P083_data_struct.h" +# include "src/PluginStructs/P083_data_struct.h" -#define PLUGIN_083 -#define PLUGIN_ID_083 83 -#define PLUGIN_NAME_083 "Gases - SGP30 TVOC/eCO2" -#define PLUGIN_VALUENAME1_083 "TVOC" -#define PLUGIN_VALUENAME2_083 "eCO2" +# define PLUGIN_083 +# define PLUGIN_ID_083 83 +# define PLUGIN_NAME_083 "Gases - SGP30 TVOC/eCO2" +# define PLUGIN_VALUENAME1_083 "TVOC" +# define PLUGIN_VALUENAME2_083 "eCO2" -#define P083_TVOC event->TaskIndex, 0 -#define P083_ECO2 event->TaskIndex, 1 -#define P083_TVOC_BASELINE event->TaskIndex, 2 -#define P083_ECO2_BASELINE event->TaskIndex, 3 +# define P083_TVOC event->TaskIndex, 0 +# define P083_ECO2 event->TaskIndex, 1 +# define P083_TVOC_BASELINE event->TaskIndex, 2 +# define P083_ECO2_BASELINE event->TaskIndex, 3 boolean Plugin_083(uint8_t function, struct EventStruct *event, String& string) @@ -134,7 +134,7 @@ boolean Plugin_083(uint8_t function, struct EventStruct *event, String& string) { UserVar.setFloat(P083_TVOC, P083_data->sgp.TVOC); UserVar.setFloat(P083_ECO2, P083_data->sgp.eCO2); - success = true; + success = true; // For the first 15s after the sgp30_iaq_init command the sensor is // in an initialization phase during which a sgp30_measure_iaq command @@ -171,12 +171,8 @@ boolean Plugin_083(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SGP30: TVOC: "); - log += UserVar.getFloat(P083_TVOC); - addLogMove(LOG_LEVEL_INFO, log); - log = F("SGP30: eCO2: "); - log += UserVar.getFloat(P083_ECO2); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("SGP30: TVOC: "), formatUserVarNoCheck(P083_TVOC))); + addLogMove(LOG_LEVEL_INFO, concat(F("SGP30: eCO2: "), formatUserVarNoCheck(P083_ECO2))); } success = true; break; diff --git a/src/_P084_VEML6070.ino b/src/_P084_VEML6070.ino index 58ca6933b..255692598 100644 --- a/src/_P084_VEML6070.ino +++ b/src/_P084_VEML6070.ino @@ -1,213 +1,210 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P084 - -// ####################################################################################################### -// #################################### Plugin 084: VEML6070 UV ########################################## -// ####################################################################################################### - -// ESPEasy Plugin to for UV with chip VEML6070 -// written by Remco van Essen (https://github.com/RemCom) -// Based on VEML6070 plugin from Sonoff-Tasmota (https://github.com/arendst/Sonoff-Tasmota) -// Datasheet: https://www.vishay.com/docs/84277/veml6070.pdf - - -#define PLUGIN_084 -#define PLUGIN_ID_084 84 -#define PLUGIN_NAME_084 "UV - VEML6070" -#define PLUGIN_VALUENAME1_084 "Raw" -#define PLUGIN_VALUENAME2_084 "Risk" -#define PLUGIN_VALUENAME3_084 "Power" - -#define VEML6070_ADDR_H 0x39 -#define VEML6070_ADDR_L 0x38 -#define VEML6070_RSET_DEFAULT 270000 // 270K default resistor value 270000 ohm, range from 220K..1Meg -#define VEML6070_UV_MAX_INDEX 15 // normal 11, internal on weather laboratories and NASA it's 15 so far the sensor is linear -#define VEML6070_UV_MAX_DEFAULT 11 // 11 = public default table values -#define VEML6070_POWER_COEFFCIENT 0.025f // based on calculations from Karel Vanicek and reorder by hand -#define VEML6070_TABLE_COEFFCIENT 32.86270591f // calculated by hand with help from a friend of mine, a professor which works in aero space - // things - // (resistor, differences, power coefficients and official UV index calculations (LAT & LONG - // will be added later) - -#define VEML6070_base_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (1) -#define VEML6070_max_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (VEML6070_UV_MAX_INDEX) - -boolean Plugin_084(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_084; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].Ports = 0; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = false; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_084); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_084)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_084)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_084)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x38); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x38; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - const __FlashStringHelper * optionsMode[4] = { F("1/2T"), F("1T"), F("2T"), F("4T (Default)") }; - addFormSelector(F("Refresh Time Determination"), F("itime"), 4, optionsMode, nullptr, PCONFIG(0)); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("itime")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - success = VEML6070_Init(PCONFIG(0)); - - if (!success) { - addLog(LOG_LEVEL_INFO, F("VEML6070: Not available!")); - } - - break; - } - - case PLUGIN_READ: - { - uint16_t uv_raw; - ESPEASY_RULES_FLOAT_TYPE uv_risk, uv_power; - bool read_status; - - uv_raw = VEML6070_ReadUv(&read_status); // get UV raw values - uv_risk = VEML6070_UvRiskLevel(uv_raw); // get UV risk level - uv_power = VEML6070_UvPower(uv_risk); // get UV power in W/m2 - - if (isnan(uv_raw) || (uv_raw == 65535) || !read_status) { - addLog(LOG_LEVEL_INFO, F("VEML6070: no data read!")); - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - UserVar.setFloat(event->TaskIndex, 2, NAN); - success = false; - } else { - UserVar.setFloat(event->TaskIndex, 0, uv_raw); - UserVar.setFloat(event->TaskIndex, 1, uv_risk); - UserVar.setFloat(event->TaskIndex, 2, uv_power); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("VEML6070: UV: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - } - - success = true; - } - - break; - } - } - return success; -} - -////////////// -// VEML6070 // -////////////// - -// get UV raw values -uint16_t VEML6070_ReadUv(bool *status) -{ - uint16_t uv_raw = 0; - bool wire_status = false; - - uv_raw = I2C_read8(VEML6070_ADDR_H, &wire_status); - *status = wire_status; - uv_raw <<= 8; - uv_raw |= I2C_read8(VEML6070_ADDR_L, &wire_status); - *status &= wire_status; - - return uv_raw; -} - -bool VEML6070_Init(uint8_t it) -{ - boolean succes = I2C_write8(VEML6070_ADDR_L, ((it << 2) | 0x02)); - - return succes; -} - -// Definition of risk numbers -// 0.0 - 2.9 "Low" = sun->fun -// 3.0 - 5.9 "Mid" = sun->glases advised -// 6.0 - 7.9 "High" = sun->glases a must -// 8.0 - 10.9 "Danger" = sun->skin burns Level 1 -// 11.0 - 12.9 "BurnL1/2" = sun->skin burns level 1..2 -// 13.0 - 25.0 "BurnL3" = sun->skin burns with level 3 - -ESPEASY_RULES_FLOAT_TYPE VEML6070_UvRiskLevel(uint16_t uv_level) -{ - ESPEASY_RULES_FLOAT_TYPE risk{}; - - constexpr ESPEASY_RULES_FLOAT_TYPE max_value = VEML6070_max_value; - - if (uv_level < max_value) { - constexpr ESPEASY_RULES_FLOAT_TYPE factor = VEML6070_base_value; - return (ESPEASY_RULES_FLOAT_TYPE)uv_level / factor; - } else { - // out of range and much to high - it must be outerspace or sensor damaged - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("VEML6070 out of range: "); - log += risk; - addLogMove(LOG_LEVEL_INFO, log); - } - - return 99; - } -} - -ESPEASY_RULES_FLOAT_TYPE VEML6070_UvPower(ESPEASY_RULES_FLOAT_TYPE uvrisk) -{ - // based on calculations for effective irradiation from Karel Vanicek - return VEML6070_POWER_COEFFCIENT * uvrisk; -} - -#endif // USES_P084 +#include "_Plugin_Helper.h" +#ifdef USES_P084 + +// ####################################################################################################### +// #################################### Plugin 084: VEML6070 UV ########################################## +// ####################################################################################################### + +// ESPEasy Plugin to for UV with chip VEML6070 +// written by Remco van Essen (https://github.com/RemCom) +// Based on VEML6070 plugin from Sonoff-Tasmota (https://github.com/arendst/Sonoff-Tasmota) +// Datasheet: https://www.vishay.com/docs/84277/veml6070.pdf + + +# define PLUGIN_084 +# define PLUGIN_ID_084 84 +# define PLUGIN_NAME_084 "UV - VEML6070" +# define PLUGIN_VALUENAME1_084 "Raw" +# define PLUGIN_VALUENAME2_084 "Risk" +# define PLUGIN_VALUENAME3_084 "Power" + +# define VEML6070_ADDR_H 0x39 +# define VEML6070_ADDR_L 0x38 +# define VEML6070_RSET_DEFAULT 270000 // 270K default resistor value 270000 ohm, range from 220K..1Meg +# define VEML6070_UV_MAX_INDEX 15 // normal 11, internal on weather laboratories and NASA it's 15 so far the sensor is + // linear +# define VEML6070_UV_MAX_DEFAULT 11 // 11 = public default table values +# define VEML6070_POWER_COEFFCIENT 0.025f // based on calculations from Karel Vanicek and reorder by hand +# define VEML6070_TABLE_COEFFCIENT 32.86270591f // calculated by hand with help from a friend of mine, a professor which works in aero + // space + // things + // (resistor, differences, power coefficients and official UV index calculations (LAT & + // LONG + // will be added later) + +# define VEML6070_base_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (1) +# define VEML6070_max_value ((VEML6070_RSET_DEFAULT / VEML6070_TABLE_COEFFCIENT) / VEML6070_UV_MAX_DEFAULT) * (VEML6070_UV_MAX_INDEX) + +boolean Plugin_084(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_084; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].Ports = 0; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = false; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_084); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_084)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_084)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_084)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x38); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x38; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + const __FlashStringHelper *optionsMode[4] = { F("1/2T"), F("1T"), F("2T"), F("4T (Default)") }; + addFormSelector(F("Refresh Time Determination"), F("itime"), 4, optionsMode, nullptr, PCONFIG(0)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(0) = getFormItemInt(F("itime")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + success = VEML6070_Init(PCONFIG(0)); + + if (!success) { + addLog(LOG_LEVEL_ERROR, F("VEML6070: Not available!")); + } + + break; + } + + case PLUGIN_READ: + { + uint16_t uv_raw; + ESPEASY_RULES_FLOAT_TYPE uv_risk, uv_power; + bool read_status; + + uv_raw = VEML6070_ReadUv(&read_status); // get UV raw values + uv_risk = VEML6070_UvRiskLevel(uv_raw); // get UV risk level + uv_power = VEML6070_UvPower(uv_risk); // get UV power in W/m2 + + if (isnan(uv_raw) || (uv_raw == 65535) || !read_status) { + addLog(LOG_LEVEL_ERROR, F("VEML6070: no data read!")); + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + UserVar.setFloat(event->TaskIndex, 2, NAN); + success = false; + } else { + UserVar.setFloat(event->TaskIndex, 0, uv_raw); + UserVar.setFloat(event->TaskIndex, 1, uv_risk); + UserVar.setFloat(event->TaskIndex, 2, uv_power); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("VEML6070: UV: "), formatUserVarNoCheck(event, 0))); + } + + success = true; + } + + break; + } + } + return success; +} + +////////////// +// VEML6070 // +////////////// + +// get UV raw values +uint16_t VEML6070_ReadUv(bool *status) +{ + uint16_t uv_raw = 0; + bool wire_status = false; + + uv_raw = I2C_read8(VEML6070_ADDR_H, &wire_status); + *status = wire_status; + uv_raw <<= 8; + uv_raw |= I2C_read8(VEML6070_ADDR_L, &wire_status); + *status &= wire_status; + + return uv_raw; +} + +bool VEML6070_Init(uint8_t it) +{ + return I2C_write8(VEML6070_ADDR_L, ((it << 2) | 0x02)); +} + +// Definition of risk numbers +// 0.0 - 2.9 "Low" = sun->fun +// 3.0 - 5.9 "Mid" = sun->glases advised +// 6.0 - 7.9 "High" = sun->glases a must +// 8.0 - 10.9 "Danger" = sun->skin burns Level 1 +// 11.0 - 12.9 "BurnL1/2" = sun->skin burns level 1..2 +// 13.0 - 25.0 "BurnL3" = sun->skin burns with level 3 + +ESPEASY_RULES_FLOAT_TYPE VEML6070_UvRiskLevel(uint16_t uv_level) +{ + ESPEASY_RULES_FLOAT_TYPE risk{}; + + constexpr ESPEASY_RULES_FLOAT_TYPE max_value = VEML6070_max_value; + + if (uv_level < max_value) { + constexpr ESPEASY_RULES_FLOAT_TYPE factor = VEML6070_base_value; + return (ESPEASY_RULES_FLOAT_TYPE)uv_level / factor; + } else { + // out of range and much to high - it must be outerspace or sensor damaged + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("VEML6070 out of range: "), risk)); + } + + return (ESPEASY_RULES_FLOAT_TYPE)99; + } +} + +ESPEASY_RULES_FLOAT_TYPE VEML6070_UvPower(ESPEASY_RULES_FLOAT_TYPE uvrisk) +{ + // based on calculations for effective irradiation from Karel Vanicek + return VEML6070_POWER_COEFFCIENT * uvrisk; +} + +#endif // USES_P084 diff --git a/src/_P085_AcuDC243.ino b/src/_P085_AcuDC243.ino index 50a6b7f4d..478e8ac0f 100644 --- a/src/_P085_AcuDC243.ino +++ b/src/_P085_AcuDC243.ino @@ -131,13 +131,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string) addRowLabel(F("Checksum (pass/fail/nodata)")); uint32_t reads_pass, reads_crc_failed, reads_nodata; P085_data->modbus.getStatistics(reads_pass, reads_crc_failed, reads_nodata); - String chksumStats; - chksumStats = reads_pass; - chksumStats += '/'; - chksumStats += reads_crc_failed; - chksumStats += '/'; - chksumStats += reads_nodata; - addHtml(chksumStats); + addHtml(strformat(F("%d/%d/%d"), reads_pass, reads_crc_failed, reads_nodata)); addFormSubHeader(F("Calibration")); @@ -218,24 +212,24 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string) static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P085_data) && P085_data->isInitialized()) { - uint16_t log_enabled = isFormItemChecked(F("en_log")) ? 1 : 0; - P085_data->modbus.writeMultipleRegisters(0x500, log_enabled); + uint16_t value = isFormItemChecked(F("en_log")) ? 1 : 0; + P085_data->modbus.writeMultipleRegisters(0x500, value); delay(1); - uint16_t log_int = getFormItemInt(F("log_int")); - P085_data->modbus.writeMultipleRegisters(0x502, log_int); + value = getFormItemInt(F("log_int")); + P085_data->modbus.writeMultipleRegisters(0x502, value); delay(1); - uint16_t current = getFormItemInt(F("fr_curr")); - P085_data->modbus.writeMultipleRegisters(0x104, current); + value = getFormItemInt(F("fr_curr")); + P085_data->modbus.writeMultipleRegisters(0x104, value); delay(1); - uint16_t shunt = getFormItemInt(F("fr_shunt")); - P085_data->modbus.writeMultipleRegisters(0x105, shunt); + value = getFormItemInt(F("fr_shunt")); + P085_data->modbus.writeMultipleRegisters(0x105, value); delay(1); - uint16_t voltage = getFormItemInt(F("fr_volt")); - P085_data->modbus.writeMultipleRegisters(0x107, voltage); + value = getFormItemInt(F("fr_volt")); + P085_data->modbus.writeMultipleRegisters(0x107, value); if (isFormItemChecked(F("clear_log"))) { @@ -288,7 +282,7 @@ boolean Plugin_085(uint8_t function, struct EventStruct *event, String& string) if ((nullptr != P085_data) && P085_data->isInitialized()) { for (int i = 0; i < P085_NR_OUTPUT_VALUES; ++i) { - UserVar.setFloat(event->TaskIndex, i, p085_readValue(PCONFIG(i + P085_QUERY1_CONFIG_POS), event)); + UserVar.setFloat(event->TaskIndex, i, p085_readValue(PCONFIG(i + P085_QUERY1_CONFIG_POS), event)); delay(1); } diff --git a/src/_P086_Homie.ino b/src/_P086_Homie.ino index 9f9f64d66..a999e3d09 100644 --- a/src/_P086_Homie.ino +++ b/src/_P086_Homie.ino @@ -1,340 +1,340 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P086 -//####################################################################################################### -//################################## Plugin 086: Homie receiver########################################## -//####################################################################################################### - - -#define PLUGIN_086 -#define PLUGIN_ID_086 86 -#define PLUGIN_NAME_086 "Generic - Homie receiver" - -// empty default names because settings will be ignored / not used if value name is empty -#define PLUGIN_VALUENAME1_086 "" -#define PLUGIN_VALUENAME2_086 "" -#define PLUGIN_VALUENAME3_086 "" -#define PLUGIN_VALUENAME4_086 "" - -#define PLUGIN_086_VALUE_INTEGER 0 -#define PLUGIN_086_VALUE_FLOAT 1 -#define PLUGIN_086_VALUE_BOOLEAN 2 -#define PLUGIN_086_VALUE_STRING 3 -#define PLUGIN_086_VALUE_ENUM 4 -#define PLUGIN_086_VALUE_RGB 5 -#define PLUGIN_086_VALUE_HSV 6 - -#define PLUGIN_086_VALUE_TYPES 7 -#define PLUGIN_086_VALUE_MAX 4 - -#define PLUGIN_086_DEBUG true - -boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_086; - Device[deviceCount].Type = DEVICE_TYPE_DUMMY; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = true; - Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = false; - Device[deviceCount].GlobalSyncOption = false; - Device[deviceCount].Custom = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_086); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_086)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_086)); - - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormNote(F("Translation Plugin for controllers able to receive value updates according to the Homie convention.")); - - uint8_t choice = 0; - String labelText; - String keyName; - const __FlashStringHelper * options[PLUGIN_086_VALUE_TYPES] = { - F("integer"), - F("float"), - F("boolean"), - F("string"), - F("enum"), - F("rgb"), - F("hsv") - }; - const int optionValues[PLUGIN_086_VALUE_TYPES] = { - PLUGIN_086_VALUE_INTEGER, - PLUGIN_086_VALUE_FLOAT, - PLUGIN_086_VALUE_BOOLEAN, - PLUGIN_086_VALUE_STRING, - PLUGIN_086_VALUE_ENUM, - PLUGIN_086_VALUE_RGB, - PLUGIN_086_VALUE_HSV - }; - for (int i=0;iTaskIndex, i), NAME_FORMULA_LENGTH_MAX); - labelText = F("Parameter Type"); - keyName = F("valueType"); - keyName += i; - addFormSelector(labelText, keyName, PLUGIN_086_VALUE_TYPES, options, optionValues, choice ); - keyName += F("_min"); - addFormNumericBox(F("Min"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); - keyName = F("valueType"); - keyName += i; - keyName += F("_max"); - addFormNumericBox(F("Max"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i+PLUGIN_086_VALUE_MAX)); - if (i==0) addFormNote(F("min max values only valid for numeric parameter")); - keyName = F("decimals"); - keyName += i; - addFormNumericBox(F("Decimals"),keyName,Cache.getTaskDeviceValueDecimals(event->TaskIndex, i) ,0,8); - if (i==0) addFormNote(F("Decimal counts for float parameter")); - keyName = F("string"); - keyName += i; - addFormTextBox(F("String or enum"), keyName, Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); - if (i==0) addFormNote(F("Default string or enumumeration list (comma seperated).")); - } - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - String keyName; - for (int i=0;iTaskIndex, x); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - break; - } - - case PLUGIN_WRITE: - { - String command = parseString(string, 1); - if (equals(command, F("homievalueset"))) - { - const taskVarIndex_t taskVarIndex = event->Par2 - 1; - const userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; - if (validTaskIndex(event->TaskIndex) && - validTaskVarIndex(taskVarIndex) && - validUserVarIndex(userVarIndex) && - (event->Par1 == (event->TaskIndex + 1))) {// make sure that this instance is the target - String parameter = parseStringToEndKeepCase(string,4); - String log; -/* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log = F("P086 : Acknowledge :"); - log += string; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceName; - log += F(" / "); - log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; - log += F(" sensorType:"); - log += event->sensorType; - log += F(" Source:"); - log += event->Source; - log += F(" idx:"); - log += event->idx; - log += F(" S1:"); - log += event->String1; - log += F(" S2:"); - log += event->String2; - log += F(" S3:"); - log += event->String3; - log += F(" S4:"); - log += event->String4; - log += F(" S5:"); - log += event->String5; - log += F(" P1:"); - log += event->Par1; - log += F(" P2:"); - log += event->Par2; - log += F(" P3:"); - log += event->Par3; - log += F(" P4:"); - log += event->Par4; - log += F(" P5:"); - log += event->Par5; - addLog(LOG_LEVEL_DEBUG, log); - } */ - float floatValue = 0.0f; - String enumList; - int i = 0; - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("P086 : deviceNr:"); - log += event->TaskIndex + 1; - log += F(" valueNr:"); - log += event->Par2; - log += F(" valueType:"); - log += Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]; - } - - switch (Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]) { - case PLUGIN_086_VALUE_INTEGER: - case PLUGIN_086_VALUE_FLOAT: - if (!parameter.isEmpty()) { - if (string2float(parameter,floatValue)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" integer/float set to "); - log += floatValue; - addLogMove(LOG_LEVEL_INFO, log); - } - UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); - } else { // float conversion failed! - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - log += F(" parameter:"); - log += parameter; - log += F(" not a float value!"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - } else { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" value:"); - log += UserVar[userVarIndex]; - addLogMove(LOG_LEVEL_INFO, log); - } - } - break; - - case PLUGIN_086_VALUE_BOOLEAN: - if (parameter=="false") { - floatValue = 0.0f; - } else { - floatValue = 1.0f; - } - UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" boolean set to "); - log += floatValue; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_STRING: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" string set to "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_ENUM: - enumList = Cache.getTaskDeviceFormula(event->TaskIndex, taskVarIndex); - i = 1; - while (!parseString(enumList,i).isEmpty()) { // lookup result in enum List - if (parseString(enumList,i)==parameter) { - floatValue = i; - break; - } - i++; - } - UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" enum set to "); - log += floatValue; - log += ' '; - log += wrap_braces(parameter); - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_RGB: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" RGB received "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - - case PLUGIN_086_VALUE_HSV: - //String values not stored to conserve flash memory - //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" HSV received "); - log += parameter; - addLogMove(LOG_LEVEL_INFO, log); - } - break; - } - success = true; - } - } - } - break; - } - return success; -} -#endif // USES_P086 +#include "_Plugin_Helper.h" +#ifdef USES_P086 +//####################################################################################################### +//################################## Plugin 086: Homie receiver########################################## +//####################################################################################################### + + +#define PLUGIN_086 +#define PLUGIN_ID_086 86 +#define PLUGIN_NAME_086 "Generic - Homie receiver" + +// empty default names because settings will be ignored / not used if value name is empty +#define PLUGIN_VALUENAME1_086 "" +#define PLUGIN_VALUENAME2_086 "" +#define PLUGIN_VALUENAME3_086 "" +#define PLUGIN_VALUENAME4_086 "" + +#define PLUGIN_086_VALUE_INTEGER 0 +#define PLUGIN_086_VALUE_FLOAT 1 +#define PLUGIN_086_VALUE_BOOLEAN 2 +#define PLUGIN_086_VALUE_STRING 3 +#define PLUGIN_086_VALUE_ENUM 4 +#define PLUGIN_086_VALUE_RGB 5 +#define PLUGIN_086_VALUE_HSV 6 + +#define PLUGIN_086_VALUE_TYPES 7 +#define PLUGIN_086_VALUE_MAX 4 + +#define PLUGIN_086_DEBUG true + +boolean Plugin_086(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_086; + Device[deviceCount].Type = DEVICE_TYPE_DUMMY; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = true; + Device[deviceCount].ValueCount = PLUGIN_086_VALUE_MAX; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + Device[deviceCount].GlobalSyncOption = false; + Device[deviceCount].Custom = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_086); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_086)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_086)); + + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNote(F("Translation Plugin for controllers able to receive value updates according to the Homie convention.")); + + uint8_t choice = 0; + String labelText; + String keyName; + const __FlashStringHelper * options[PLUGIN_086_VALUE_TYPES] = { + F("integer"), + F("float"), + F("boolean"), + F("string"), + F("enum"), + F("rgb"), + F("hsv") + }; + const int optionValues[PLUGIN_086_VALUE_TYPES] = { + PLUGIN_086_VALUE_INTEGER, + PLUGIN_086_VALUE_FLOAT, + PLUGIN_086_VALUE_BOOLEAN, + PLUGIN_086_VALUE_STRING, + PLUGIN_086_VALUE_ENUM, + PLUGIN_086_VALUE_RGB, + PLUGIN_086_VALUE_HSV + }; + for (int i=0;iTaskIndex, i), NAME_FORMULA_LENGTH_MAX); + labelText = F("Parameter Type"); + keyName = F("valueType"); + keyName += i; + addFormSelector(labelText, keyName, PLUGIN_086_VALUE_TYPES, options, optionValues, choice ); + keyName += F("_min"); + addFormNumericBox(F("Min"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i)); + keyName = F("valueType"); + keyName += i; + keyName += F("_max"); + addFormNumericBox(F("Max"),keyName,Cache.getTaskDevicePluginConfig(event->TaskIndex, i+PLUGIN_086_VALUE_MAX)); + if (i==0) addFormNote(F("min max values only valid for numeric parameter")); + keyName = F("decimals"); + keyName += i; + addFormNumericBox(F("Decimals"),keyName,Cache.getTaskDeviceValueDecimals(event->TaskIndex, i) ,0,8); + if (i==0) addFormNote(F("Decimal counts for float parameter")); + keyName = F("string"); + keyName += i; + addFormTextBox(F("String or enum"), keyName, Cache.getTaskDeviceFormula(event->TaskIndex, i), NAME_FORMULA_LENGTH_MAX); + if (i==0) addFormNote(F("Default string or enumumeration list (comma seperated).")); + } + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + String keyName; + for (int i=0;iPar2 - 1; + const userVarIndex_t userVarIndex = event->BaseVarIndex + taskVarIndex; + if (validTaskIndex(event->TaskIndex) && + validTaskVarIndex(taskVarIndex) && + validUserVarIndex(userVarIndex) && + (event->Par1 == (event->TaskIndex + 1))) {// make sure that this instance is the target + String parameter = parseStringToEndKeepCase(string,4); + String log; +/* if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log = F("P086 : Acknowledge :"); + log += string; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceName; + log += F(" / "); + log += ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]; + log += F(" sensorType:"); + log += event->sensorType; + log += F(" Source:"); + log += event->Source; + log += F(" idx:"); + log += event->idx; + log += F(" S1:"); + log += event->String1; + log += F(" S2:"); + log += event->String2; + log += F(" S3:"); + log += event->String3; + log += F(" S4:"); + log += event->String4; + log += F(" S5:"); + log += event->String5; + log += F(" P1:"); + log += event->Par1; + log += F(" P2:"); + log += event->Par2; + log += F(" P3:"); + log += event->Par3; + log += F(" P4:"); + log += event->Par4; + log += F(" P5:"); + log += event->Par5; + addLog(LOG_LEVEL_DEBUG, log); + } */ + float floatValue = 0.0f; + String enumList; + int i = 0; + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log = F("P086 : deviceNr:"); + log += event->TaskIndex + 1; + log += F(" valueNr:"); + log += event->Par2; + log += F(" valueType:"); + log += Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]; + } + + switch (Settings.TaskDevicePluginConfig[event->TaskIndex][taskVarIndex]) { + case PLUGIN_086_VALUE_INTEGER: + case PLUGIN_086_VALUE_FLOAT: + if (!parameter.isEmpty()) { + if (string2float(parameter,floatValue)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" integer/float set to "); + log += floatValue; + addLogMove(LOG_LEVEL_INFO, log); + } + UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); + } else { // float conversion failed! + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + log += F(" parameter:"); + log += parameter; + log += F(" not a float value!"); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + } else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" value:"); + log += UserVar[userVarIndex]; + addLogMove(LOG_LEVEL_INFO, log); + } + } + break; + + case PLUGIN_086_VALUE_BOOLEAN: + if (parameter=="false") { + floatValue = 0.0f; + } else { + floatValue = 1.0f; + } + UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" boolean set to "); + log += floatValue; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_STRING: + //String values not stored to conserve flash memory + //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" string set to "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_ENUM: + enumList = Cache.getTaskDeviceFormula(event->TaskIndex, taskVarIndex); + i = 1; + while (!parseString(enumList,i).isEmpty()) { // lookup result in enum List + if (parseString(enumList,i)==parameter) { + floatValue = i; + break; + } + i++; + } + UserVar.setFloat(event->TaskIndex, taskVarIndex, floatValue); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" enum set to "); + log += floatValue; + log += ' '; + log += wrap_braces(parameter); + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_RGB: + //String values not stored to conserve flash memory + //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" RGB received "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + + case PLUGIN_086_VALUE_HSV: + //String values not stored to conserve flash memory + //safe_strncpy(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex], parameter.c_str(), sizeof(ExtraTaskSettings.TaskDeviceFormula[taskVarIndex])); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" HSV received "); + log += parameter; + addLogMove(LOG_LEVEL_INFO, log); + } + break; + } + success = true; + } + } + } + break; + } + return success; +} +#endif // USES_P086 diff --git a/src/_P087_SerialProxy.ino b/src/_P087_SerialProxy.ino index 0fb2a0298..0fc929179 100644 --- a/src/_P087_SerialProxy.ino +++ b/src/_P087_SerialProxy.ino @@ -11,6 +11,12 @@ /** * Changelog: + * 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 + * 2024-02-25 tonhuisman: Add command serialproxy_test, to test as if serial data was received + * Add Get Config Value support for retrieving the last regex-parsed data: + * - By group: [#group,] (groupnr is 0-base!) + * - By name: [#next,] if the is found, the next group-data is returned * 2023-03-25 tonhuisman: Change serialproxy_writemix to handle 0x00 also, by implementing parseHexTextData() * 2023-03-22 tonhuisman: Add command serialproxy_writemix to handle mixed hex characters and text to send * using parseHexTextString() @@ -144,7 +150,7 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) { addFormNumericBox(F("Baudrate"), P087_BAUDRATE_LABEL, P087_BAUDRATE, 300, 115200); addUnit(F("baud")); - uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P087_SERIAL_CONFIG); + const uint8_t serialConfChoice = serialHelper_convertOldSerialConfig(P087_SERIAL_CONFIG); serialHelper_serialconfig_webformLoad(event, serialConfChoice); break; } @@ -168,7 +174,7 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P087_data) { - for (uint8_t varNr = 0; varNr < P87_Nlines; varNr++) + for (uint8_t varNr = 0; varNr < P87_Nlines; ++varNr) { P087_data->setLine(varNr, webArg(getPluginCustomArgName(varNr))); } @@ -225,14 +231,13 @@ 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 BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_DEBUG, event->String2); -# endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG success = true; } } - if ((nullptr != P087_data)) {} break; } @@ -240,26 +245,49 @@ boolean Plugin_087(uint8_t function, struct EventStruct *event, String& string) P087_data_struct *P087_data = static_cast(getPluginTaskData(event->TaskIndex)); - if ((nullptr != P087_data)) { - String cmd = parseString(string, 1); + if (nullptr != P087_data) { + const String cmd = parseString(string, 1); if (equals(cmd, F("serialproxy_write"))) { - String param1 = parseStringKeepCase(string, 2, ',', false); // Don't trim off white-space - parseSystemVariables(param1, false); // FIXME tonhuisman: Doesn't seem to be needed? + String param1 = parseStringKeepCaseNoTrim(string, 2); // Don't trim off white-space + parseSystemVariables(param1, false); // FIXME tonhuisman: Doesn't seem to be needed? P087_data->sendString(param1); - addLogMove(LOG_LEVEL_INFO, param1); // FIXME tonhuisman: Should we always want to write to the log? + addLogMove(LOG_LEVEL_INFO, param1); // FIXME tonhuisman: Should we always want to write to the log? success = true; } else if (equals(cmd, F("serialproxy_writemix"))) { std::vector param1 = parseHexTextData(string); - if (param1.size()) + + if (param1.size()) { P087_data->sendData(¶m1[0], param1.size()); + } + success = true; + } else + if (equals(cmd, F("serialproxy_test"))) { // Test-parse data as if received via serial + const String param1 = parseStringKeepCaseNoTrim(string, 2); + + if (!param1.isEmpty()) { + P087_data->setLastSentence(param1); + 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; } + + case PLUGIN_GET_CONFIG_VALUE: + { + P087_data_struct *P087_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P087_data) { + success = P087_data->plugin_get_config_value(event, string); + } + break; + } } return success; } @@ -279,7 +307,7 @@ bool Plugin_087_match_all(taskIndex_t taskIndex, String& received) return true; } - bool res = P087_data->matchRegexp(received); + const bool res = P087_data->matchRegexp(received); if (P087_data->invertMatch()) { addLog(LOG_LEVEL_INFO, F("Serial Proxy: invert filter")); @@ -290,9 +318,9 @@ bool Plugin_087_match_all(taskIndex_t taskIndex, String& received) String Plugin_087_valuename(uint8_t value_nr, bool displayString) { switch (value_nr) { - case P087_QUERY_VALUE: return displayString ? F("Value") : F("v"); + case P087_QUERY_VALUE: return displayString ? F("Value") : F("v"); } - return ""; + return EMPTY_STRING; } void P087_html_show_matchForms(struct EventStruct *event) { @@ -342,7 +370,7 @@ void P087_html_show_matchForms(struct EventStruct *event) { for (uint8_t varNr = P087_FIRST_FILTER_POS; varNr < P87_Nlines; ++varNr) { - String id = getPluginCustomArgName(varNr); + const String id = getPluginCustomArgName(varNr); switch (varNr % 3) { case 0: @@ -350,10 +378,7 @@ void P087_html_show_matchForms(struct EventStruct *event) { // Label + first parameter filter = P087_data->getFilter(lineNr, capture, comparator); ++lineNr; - String label; - label = F("Capture Filter "); - label += String(lineNr); - addRowLabel_tr_id(label, id); + addRowLabel_tr_id(concat(F("Capture Filter "), lineNr), id); addNumericBox(id, capture, -1, P87_MAX_CAPTURE_INDEX); break; @@ -364,7 +389,7 @@ void P087_html_show_matchForms(struct EventStruct *event) { const __FlashStringHelper *options[2]; options[P087_Filter_Comp::Equal] = F("=="); options[P087_Filter_Comp::NotEqual] = F("!="); - int optionValues[2] = { P087_Filter_Comp::Equal, P087_Filter_Comp::NotEqual }; + const int optionValues[2] = { P087_Filter_Comp::Equal, P087_Filter_Comp::NotEqual }; addSelector(id, 2, options, optionValues, nullptr, static_cast(comparator), false, true, F("")); break; } @@ -395,13 +420,9 @@ void P087_html_show_stats(struct EventStruct *event) { { addRowLabel(F("Sentences (pass/fail)")); - String chksumStats; uint32_t success, error, length_last; P087_data->getSentencesReceived(success, error, length_last); - chksumStats = success; - chksumStats += '/'; - chksumStats += error; - addHtml(chksumStats); + addHtml(strformat(F("%d/%d"), success, error)); addRowLabel(F("Length Last Sentence")); addHtmlInt(length_last); } diff --git a/src/_P090_CCS811.ino b/src/_P090_CCS811.ino index 9f46c1d69..fa68997b4 100644 --- a/src/_P090_CCS811.ino +++ b/src/_P090_CCS811.ino @@ -135,45 +135,49 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) int frequencyChoice = P090_READ_INTERVAL; const __FlashStringHelper *frequencyOptions[3] = { F("1 second"), F("10 seconds"), F("60 seconds") }; const int frequencyValues[3] = { 1, 2, 3 }; - addFormSelector(F("Take reading every"), F("read_frequency"), 3, frequencyOptions, frequencyValues, frequencyChoice); + addFormSelector(F("Take reading every"), F("temp_freq"), 3, frequencyOptions, frequencyValues, frequencyChoice); } addFormSeparator(2); { // mode - addFormCheckBox(F("Enable temp/humid compensation"), F("enable_compensation"), P090_COMPENSATE_ENABLE); + addFormCheckBox(F("Enable temp/humid compensation"), F("en_comp"), P090_COMPENSATE_ENABLE); + # ifndef BUILD_NO_DEBUG addFormNote(F("If this is enabled, the Temperature and Humidity values below need to be configured.")); + # endif // ifndef BUILD_NO_DEBUG // temperature addRowLabel(F("Temperature")); - addTaskSelect(F("temperature_task"), P090_TEMPERATURE_TASK_INDEX); + addTaskSelect(F("temp_task"), P090_TEMPERATURE_TASK_INDEX); + if (validTaskIndex(P090_TEMPERATURE_TASK_INDEX)) { addRowLabel(F("Temperature Value:")); - addTaskValueSelect(F("temperature_value"), P090_TEMPERATURE_TASK_VALUE, P090_TEMPERATURE_TASK_INDEX); + addTaskValueSelect(F("temp_val"), P090_TEMPERATURE_TASK_VALUE, P090_TEMPERATURE_TASK_INDEX); // temperature scale int temperatureScale = P090_TEMPERATURE_SCALE; addRowLabel(F("Temperature Scale")); // checked - addHtml(F("") : F(">")); addHtml(F("   ")); - addHtml(F("") : F(">")); addHtml(F("
")); // humidity addRowLabel(F("Humidity")); - addTaskSelect(F("humidity_task"), P090_HUMIDITY_TASK_INDEX); + addTaskSelect(F("hum_task"), P090_HUMIDITY_TASK_INDEX); + if (validTaskIndex(P090_HUMIDITY_TASK_INDEX)) { addRowLabel(F("Humidity Value")); - addTaskValueSelect(F("humidity_value"), P090_HUMIDITY_TASK_VALUE, P090_HUMIDITY_TASK_INDEX); + addTaskValueSelect(F("hum_val"), P090_HUMIDITY_TASK_VALUE, P090_HUMIDITY_TASK_INDEX); } } } // addFormSeparator(string); - addFormSeparator(2); + // addFormSeparator(2); success = true; break; @@ -182,13 +186,13 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { P090_I2C_ADDR = getFormItemInt(F("i2c_addr")); - P090_COMPENSATE_ENABLE = isFormItemChecked(F("enable_compensation")); - P090_TEMPERATURE_TASK_INDEX = getFormItemInt(F("temperature_task")); - P090_TEMPERATURE_TASK_VALUE = getFormItemInt(F("temperature_value")); - P090_HUMIDITY_TASK_INDEX = getFormItemInt(F("humidity_task")); - P090_HUMIDITY_TASK_VALUE = getFormItemInt(F("humidity_value")); - P090_TEMPERATURE_SCALE = getFormItemInt(F("temperature_scale")); - P090_READ_INTERVAL = getFormItemInt(F("read_frequency")); + P090_COMPENSATE_ENABLE = isFormItemChecked(F("en_comp")); + P090_TEMPERATURE_TASK_INDEX = getFormItemInt(F("temp_task")); + P090_TEMPERATURE_TASK_VALUE = getFormItemInt(F("temp_val")); + P090_HUMIDITY_TASK_INDEX = getFormItemInt(F("hum_task")); + P090_HUMIDITY_TASK_VALUE = getFormItemInt(F("hum_val")); + P090_TEMPERATURE_SCALE = getFormItemInt(F("temp_scale")); + P090_READ_INTERVAL = getFormItemInt(F("temp_freq")); success = true; break; @@ -211,9 +215,7 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CCS811 : Begin exited with: "); - log += P090_data->myCCS811.getDriverError(returnCode); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CCS811 : Begin exited with: "), P090_data->myCCS811.getDriverError(returnCode))); } # endif // ifndef BUILD_NO_DEBUG UserVar.setFloat(event->TaskIndex, 0, NAN); @@ -228,14 +230,12 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) returnCode = P090_data->myCCS811.setDriveMode(P090_READ_INTERVAL); if (returnCode != CCS811Core::SENSOR_SUCCESS) { - # ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CCS811 : Mode request exited with: "); - log += P090_data->myCCS811.getDriverError(returnCode); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, concat(F("CCS811 : Mode request exited with: "), P090_data->myCCS811.getDriverError(returnCode))); } - # endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG } else { success = true; } @@ -266,14 +266,11 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) } else { UserVar.setFloat(event->TaskIndex, 0, P090_data->myCCS811.getTVOC()); UserVar.setFloat(event->TaskIndex, 1, P090_data->myCCS811.getCO2()); - P090_data->newReadingAvailable = true; + P090_data->newReadingAvailable = true; if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("CCS811 : tVOC: "); - log += P090_data->myCCS811.getTVOC(); - log += F(", eCO2: "); - log += P090_data->myCCS811.getCO2(); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("CCS811 : tVOC: %d, eCO2: %d"), P090_data->myCCS811.getTVOC(), P090_data->myCCS811.getCO2())); } } } @@ -295,34 +292,35 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) if (P090_COMPENSATE_ENABLE) { // we're checking a var from another task, so calculate that basevar - uint8_t TaskIndex = P090_TEMPERATURE_TASK_INDEX; + uint8_t TaskIndex = P090_TEMPERATURE_TASK_INDEX; + if (validTaskIndex(TaskIndex)) { - uint8_t BaseVarIndex = TaskIndex * VARS_PER_TASK + P090_TEMPERATURE_TASK_VALUE; - float temperature = UserVar[BaseVarIndex]; // in degrees C + uint8_t BaseVarIndex = TaskIndex * VARS_PER_TASK + P090_TEMPERATURE_TASK_VALUE; + float temperature = UserVar[BaseVarIndex]; // in degrees C // convert to celsius if required int temperature_in_fahrenheit = P090_TEMPERATURE_SCALE; String temp; - temp += 'C'; + temp = 'C'; if (temperature_in_fahrenheit) { temperature = ((temperature - 32) * 5.0f) / 9.0f; - temp = F("F"); + temp = 'F'; } - uint8_t TaskIndex2 = P090_HUMIDITY_TASK_INDEX; - if (validTaskIndex(TaskIndex2)) { - uint8_t BaseVarIndex2 = TaskIndex2 * VARS_PER_TASK + P090_HUMIDITY_TASK_VALUE; - float humidity = UserVar[BaseVarIndex2]; // in % relative + uint8_t TaskIndex2 = P090_HUMIDITY_TASK_INDEX; - #ifndef BUILD_NO_DEBUG + if (validTaskIndex(TaskIndex2)) { + uint8_t BaseVarIndex2 = TaskIndex2 * VARS_PER_TASK + P090_HUMIDITY_TASK_VALUE; + float humidity = UserVar[BaseVarIndex2]; // in % relative + + # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CCS811 : Compensating for Temperature: "); - log += toString(temperature) + temp + F(" & Humidity: ") + toString(humidity) + F("%"); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("CCS811 : Compensating for Temperature: %s%s & Humidity: %s%%"), + toString(temperature).c_str(), temp.c_str(), toString(humidity).c_str())); } - #endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG P090_data->myCCS811.setEnvironmentalData(humidity, temperature); } @@ -341,19 +339,10 @@ boolean Plugin_090(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_ERROR)) { // If the CCS811 found an internal error, print it. - String log = F("CCS811 : Error: "); - log += errorMsg; - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, concat(F("CCS811 : Error: "), errorMsg)); } } - /* - else - { - addLog(LOG_LEVEL_ERROR, F("CCS811 : No values found.")); - } - */ - break; } } // switch diff --git a/src/_P091_SerSwitch.ino b/src/_P091_SerSwitch.ino index 9e77805d3..fdf159d1f 100644 --- a/src/_P091_SerSwitch.ino +++ b/src/_P091_SerSwitch.ino @@ -149,8 +149,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) F("Exclude/Blinds mode"), F("Simultaneous mode"), }; - const int modeoptionValues[3] = { 0, 1, 2 }; - addFormSelector(F("Relay working mode"), F("mode"), 3, modeoptions, modeoptionValues, PCONFIG(1)); + addFormSelector(F("Relay working mode"), F("mode"), 3, modeoptions, nullptr, PCONFIG(1)); } if (PCONFIG(0) == SER_SWITCH_LCTECH) @@ -221,7 +220,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) { String log; Plugin_091_ownindex = event->TaskIndex; - Settings.UseSerial = true; // make sure that serial enabled + Settings.UseSerial = true; // FIXME This is most likely very wrong... make sure that serial enabled Settings.SerialLogLevel = 0; // and logging disabled ESPEASY_SERIAL_0.setDebugOutput(false); // really, disable it! log = F("SerSW : Init "); @@ -232,9 +231,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) ESPEASY_SERIAL_0.setRxBufferSize(BUFFER_SIZE); // Arduino core for ESP8266 WiFi chip 2.4.0 delay(1); getmcustate(); // request status on startup - log += F(" Yewe "); - log += Plugin_091_numrelay; - log += F(" btn"); + log += strformat(F(" Yewe %d btn"), Plugin_091_numrelay); } else if (PCONFIG(0) == SER_SWITCH_SONOFFDUAL) { @@ -247,43 +244,10 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) Plugin_091_numrelay = PCONFIG(1); Plugin_091_cmddbl = PCONFIG(3); Plugin_091_ipd = PCONFIG(4); - unsigned long Plugin_091_speed = 9600; - switch (PCONFIG(2)) { - case 1: { - Plugin_091_speed = 19200; - break; - } - case 2: { - Plugin_091_speed = 115200; - break; - } - case 3: { - Plugin_091_speed = 1200; - break; - } - case 4: { - Plugin_091_speed = 2400; - break; - } - case 5: { - Plugin_091_speed = 4800; - break; - } - case 6: { - Plugin_091_speed = 38400; - break; - } - case 7: { - Plugin_091_speed = 57600; - break; - } - } + const int bauds[] = {9600,19200,115200,1200,2400,4800,38400,57600}; + unsigned long Plugin_091_speed = bauds[PCONFIG(2)]; ESPEASY_SERIAL_0.begin(Plugin_091_speed, SERIAL_8N1); - log += F(" LCTech "); - log += Plugin_091_speed; - log += F(" baud "); - log += Plugin_091_numrelay; - log += F(" btn"); + log += strformat(F(" LCTech %d baud %d btn"), Plugin_091_speed, Plugin_091_numrelay); } else if (PCONFIG(0) == SER_SWITCH_WIFIDIMMER) { @@ -423,10 +387,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) if (Plugin_091_ostate[i] != Plugin_091_switchstate[i]) { UserVar.setFloat(event->TaskIndex, i, Plugin_091_switchstate[i]); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F(" r"); - log += i; - log += ':'; - log += Plugin_091_switchstate[i]; + log += strformat(F(" r%d:%d"), i, Plugin_091_switchstate[i]); } } } @@ -455,32 +416,28 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) case 0: { if (Plugin_091_numrelay > 0) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" r0:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" r0:"), Plugin_091_switchstate[btnnum]); } break; } case 1: { if (Plugin_091_numrelay > 1) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" r1:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" r1:"), Plugin_091_switchstate[btnnum]); } break; } case 2: { if (Plugin_091_numrelay > 2) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" r2:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" r2:"), Plugin_091_switchstate[btnnum]); } break; } case 3: { if (Plugin_091_numrelay > 3) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" r3:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" r3:"), Plugin_091_switchstate[btnnum]); } break; } @@ -505,16 +462,14 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) case 1: { if (Plugin_091_numrelay > 1) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" d1:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" d1:"), Plugin_091_switchstate[btnnum]); } break; } case 2: { if (Plugin_091_numrelay > 2) { UserVar.setFloat(event->TaskIndex, btnnum, Plugin_091_switchstate[btnnum]); - log += F(" d2:"); - log += Plugin_091_switchstate[btnnum]; + log += concat(F(" d2:"), Plugin_091_switchstate[btnnum]); } break; } @@ -616,11 +571,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) } } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SerSW : SetSwitch r"); - log += rnum; - log += ':'; - log += rcmd; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("SerSW : SetSwitch r%d:%d"), rnum, rcmd)); } } else @@ -665,14 +616,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SerSW : SetSwitchPulse r"); - log += rnum; - log += ':'; - log += rcmd; - log += F(" Pulsed for "); - log += String(event->Par3); - log += F(" mS"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("SerSW : SetSwitchPulse r%d:%d Pulsed for %d mS"), rnum, rcmd, event->Par3)); } } else @@ -718,14 +662,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SerSW : SetSwitchPulse r"); - log += rnum; - log += ':'; - log += rcmd; - log += F(" Pulse for "); - log += String(event->Par3); - log += F(" sec"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("SerSW : SetSwitchPulse r%d:%d Pulse for %d sec"), rnum, rcmd, event->Par3)); } } else if ( equals(command, F("ydim")) ) // deal with dimmer command @@ -750,9 +687,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) sendData(event); } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SerSW : SetDim "); - log += event->Par1; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("SerSW : SetDim "), event->Par1)); } } else { SendStatus(event, F("\nYDim not supported")); @@ -800,12 +735,7 @@ boolean Plugin_091(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SerSW : SetSwitchPulse r"); - log += rnum; - log += ':'; - log += rcmd; - log += F(" Pulse ended"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("SerSW : SetSwitchPulse r%d:%d Pulse ended"), rnum, rcmd)); } break; @@ -877,7 +807,7 @@ void sendmcucommand(uint8_t btnnum, uint8_t state, uint8_t swtype, uint8_t btnum c_d = 2; } Plugin_091_switchstate[btnnum] = state; - for (uint8_t x = 0; x < c_d; x++) // try twice to be sure + for (uint8_t x = 0; x < c_d; ++x) // try twice to be sure { if (x > 0) { delay(1); diff --git a/src/_P092_DLbus.ino b/src/_P092_DLbus.ino index f549fdac1..4a2c0d001 100644 --- a/src/_P092_DLbus.ino +++ b/src/_P092_DLbus.ino @@ -24,7 +24,7 @@ @tonhuisman 2022-09-24 Optimizations, suppress some logging for stressed builds - @uwekaditz 2022-09-04 CHG: #ifdef INPUT_PULLDOWN and all its dependencies removed + @uwekaditz 2022-09-04 CHG: #ifdef INPUT_PULLDOWN and all its dependencies removed @uwekaditz 2022-05-04 CHG: Logging reduced for LIMIT_BUILD_SIZE @tonhuisman 2022-03-26 Add support for UVR42 (Very similar to an UVR31, has 1 extra sensor value and 1 extra digital value) @@ -391,13 +391,14 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) } # endif // PLUGIN_092_DEBUG UserVar.setFloat(event->TaskIndex, 0, NAN); - success = true; + success = true; break; } case PLUGIN_INIT: { # ifndef P092_LIMIT_BUILD_SIZE + if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("PLUGIN_092_INIT Task:"), event->TaskIndex)); } @@ -442,22 +443,16 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) P092_init = true; } - success = true; + success = true; UserVar.setFloat(event->TaskIndex, 0, NAN); break; } case PLUGIN_ONCE_A_SECOND: { - if (!NetworkConnected()) { - return false; - } - - if (!P092_init) { - return false; - } - - if (nullptr == P092_data) { + if (!NetworkConnected() + || !P092_init + || (nullptr == P092_data)) { return false; } @@ -465,7 +460,7 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) // on a CHANGE on the data pin P092_Pin_changed is called P092_data->DLbus_Data->attachDLBusInterrupt(); # ifndef P092_LIMIT_BUILD_SIZE - addLog(LOG_LEVEL_INFO, F("P092 ISR set")); + addLog(LOG_LEVEL_INFO, F("P092 ISR set")); # endif // ifndef P092_LIMIT_BUILD_SIZE } @@ -490,6 +485,7 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) if (success) { P092_data->P092_LastReceived = millis(); # ifndef P092_LIMIT_BUILD_SIZE + if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("Received data OK TI:"), event->TaskIndex)); } @@ -513,6 +509,7 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_READ: { # ifndef P092_LIMIT_BUILD_SIZE + if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, concat(F("PLUGIN_092_READ Task:"), event->TaskIndex)); } @@ -536,12 +533,9 @@ boolean Plugin_092(uint8_t function, struct EventStruct *event, String& string) if (P092_data->DLbus_Data->ISR_DLB_Pin != CONFIG_PIN1) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - log += F("## P092_read: Error DL-Bus: Device Pin setting not correct! DLB_Pin:"); - log += P092_data->DLbus_Data->ISR_DLB_Pin; - log += F(" Setting:"); - log += CONFIG_PIN1; - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, + strformat(F("## P092_read: Error DL-Bus: Device Pin setting not correct! DLB_Pin:%d Setting:%d"), + P092_data->DLbus_Data->ISR_DLB_Pin, CONFIG_PIN1)); } return false; } diff --git a/src/_P093_MitsubishiHP.ino b/src/_P093_MitsubishiHP.ino index 2c7db6e17..8fee38dbb 100644 --- a/src/_P093_MitsubishiHP.ino +++ b/src/_P093_MitsubishiHP.ino @@ -115,7 +115,7 @@ boolean Plugin_093(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_WRITE: { - if (parseString(string, 1).equalsIgnoreCase(F("MitsubishiHP"))) { + if (equals(parseString(string, 1), F("mitsubishihp"))) { P093_data_struct *heatPump = static_cast(getPluginTaskData(event->TaskIndex)); if (heatPump != nullptr) { diff --git a/src/_P094_CULReader.ino b/src/_P094_CULReader.ino index d07d8c359..a4df00af3 100644 --- a/src/_P094_CULReader.ino +++ b/src/_P094_CULReader.ino @@ -1,465 +1,585 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P094 - -// ####################################################################################################### -// #################### Plugin 094 Brick4U CUL Reader #################################################### -// ####################################################################################################### -// -// Interact with Brick4U CUL receiver -// Allows to control the mode of the CUL receiver -// - - -#include "src/Helpers/ESPEasy_Storage.h" -#include "src/Helpers/StringConverter.h" -#include "src/PluginStructs/P094_data_struct.h" - -#include - -#define PLUGIN_094 -#define PLUGIN_ID_094 94 -#define PLUGIN_NAME_094 "Communication - CUL Reader" - - -#define P094_BAUDRATE PCONFIG_LONG(0) -#define P094_BAUDRATE_LABEL PCONFIG_LABEL(0) - -#define P094_DEBUG_SENTENCE_LENGTH PCONFIG_LONG(1) -#define P094_DEBUG_SENTENCE_LABEL PCONFIG_LABEL(1) - -#define P094_APPEND_RECEIVE_SYSTIME PCONFIG(0) - -#define P094_QUERY_VALUE 0 // Temp placement holder until we know what selectors are needed. -#define P094_NR_OUTPUT_OPTIONS 1 - -#define P094_NR_OUTPUT_VALUES 1 -#define P094_QUERY1_CONFIG_POS 3 - -#define P094_DEFAULT_BAUDRATE 38400 - - -// Plugin settings: -// Validate: -// - [0..9] -// - "+", "-", "." -// - [A..Z] -// - [a..z] -// - ASCII 32 - 217 -// Sentence start: char -// Sentence end: CR/CRLF/LF/char -// Max length sentence: 1k max -// Interpret as: -// - Float -// - int -// - String -// Init string (incl parsing CRLF like characters) -// Timeout between sentences. - - -boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) { - boolean success = false; - - switch (function) { - case PLUGIN_DEVICE_ADD: { - Device[++deviceCount].Number = PLUGIN_ID_094; - Device[deviceCount].Type = DEVICE_TYPE_SERIAL; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = false; - Device[deviceCount].DuplicateDetection = true; - // FIXME TD-er: Not sure if access to any existing task data is needed when saving - Device[deviceCount].ExitTaskBeforeSave = false; - break; - } - - case PLUGIN_GET_DEVICENAME: { - string = F(PLUGIN_NAME_094); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: { - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (i < P094_NR_OUTPUT_VALUES) { - const uint8_t pconfigIndex = i + P094_QUERY1_CONFIG_POS; - uint8_t choice = PCONFIG(pconfigIndex); - ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_094_valuename(choice, false)); - } else { - ExtraTaskSettings.clearTaskDeviceValueName(i); - } - } - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: { - serialHelper_getGpioNames(event, false, true); // TX optional - break; - } - - case PLUGIN_WEBFORM_SHOW_VALUES: - { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data) && P094_data->isInitialized()) { - uint32_t success, error, length_last; - P094_data->getSentencesReceived(success, error, length_last); - uint8_t varNr = VARS_PER_TASK; - pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"), String(success)); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"), String(error)); - pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true); - - // success = true; - } - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P094_BAUDRATE = P094_DEFAULT_BAUDRATE; - P094_DEBUG_SENTENCE_LENGTH = 0; - - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - string += serialHelper_getSerialTypeLabel(event); - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: - { - addFormNumericBox(F("Baudrate"), P094_BAUDRATE_LABEL, P094_BAUDRATE, 2400, 115200); - addUnit(F("baud")); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("Filtering")); - P094_html_show_matchForms(event); - - addFormSubHeader(F("Statistics")); - P094_html_show_stats(event); - - addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); - - addFormCheckBox(F("Append system time"), F("systime"), P094_APPEND_RECEIVE_SYSTIME); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: { - P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); - P094_DEBUG_SENTENCE_LENGTH = getFormItemInt(P094_DEBUG_SENTENCE_LABEL); - - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P094_data) { - for (uint8_t varNr = 0; varNr < P94_Nlines; varNr++) - { - P094_data->setLine(varNr, webArg(getPluginCustomArgName(varNr))); - } - - addHtmlError(SaveCustomTaskSettings(event->TaskIndex, P094_data->_lines, P94_Nlines, 0)); - success = true; - } - - P094_APPEND_RECEIVE_SYSTIME = isFormItemChecked(F("systime")); - - break; - } - - case PLUGIN_INIT: { - const int16_t serial_rx = CONFIG_PIN1; - const int16_t serial_tx = CONFIG_PIN2; - const ESPEasySerialPort port = static_cast(CONFIG_PORT); - initPluginTaskData(event->TaskIndex, new (std::nothrow) P094_data_struct()); - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P094_data) { - return success; - } - - if (P094_data->init(port, serial_rx, serial_tx, P094_BAUDRATE)) { - LoadCustomTaskSettings(event->TaskIndex, P094_data->_lines, P94_Nlines, 0); - P094_data->post_init(); - success = true; - - serialHelper_log_GpioDescription(port, serial_rx, serial_tx); - } else { - clearPluginTaskData(event->TaskIndex); - } - break; - } - - case PLUGIN_FIFTY_PER_SECOND: { - if (Settings.TaskDeviceEnabled[event->TaskIndex]) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data) && P094_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. - P094_data->getSentence(event->String2, P094_APPEND_RECEIVE_SYSTIME); - - if (event->String2.length() > 0) { - if (Plugin_094_match_all(event->TaskIndex, event->String2)) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(128)) { - log = F("CUL Reader: Sending: "); - const size_t messageLength = event->String2.length(); - if (messageLength < 100) { - log += event->String2; - } else { - // Split string so we get start and end - log += event->String2.substring(0, 40); - log += F("..."); - log += event->String2.substring(messageLength - 40); - } - addLogMove(LOG_LEVEL_INFO, log); - } - } - // Filter length options: - // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit sending meters, which normaly is a fault) - // - 38 char, The exact message, because we have 2 uint8_t from the value payload - //sendData_checkDuplicates(event, event->String2.substring(0, 22)); - sendData(event); - } - } - } - success = true; - } - break; - } - - case PLUGIN_READ: { - if (P094_DEBUG_SENTENCE_LENGTH > 0) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data)) { - const uint32_t debug_count = P094_data->getDebugCounter(); - event->String2.reserve(P094_DEBUG_SENTENCE_LENGTH); - event->String2 += String(debug_count); - event->String2 += '_'; - const char c = '0' + debug_count % 10; - for (long i = event->String2.length(); i < P094_DEBUG_SENTENCE_LENGTH; ++i) { - event->String2 += c; - } - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("CUL Reader: Sending: "); - log += event->String2.substring(0, 20); - log += F("..."); - addLogMove(LOG_LEVEL_INFO, log); - } -// sendData_checkDuplicates(event, event->String2.substring(0, 22)); - sendData(event); - } - } - break; - } - - case PLUGIN_WRITE: { - String cmd = parseString(string, 1); - - if (cmd.startsWith(F("culreader"))) { - if (equals(cmd, F("culreader_write"))) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data)) { - String param1 = parseStringKeepCase(string, 2); - parseSystemVariables(param1, false); - P094_data->sendString(param1); - addLogMove(LOG_LEVEL_INFO, param1); - success = true; - } - } - } - - - break; - } - } - return success; -} - -bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received) -{ - P094_data_struct *P094_data = - static_cast(getPluginTaskData(taskIndex)); - - if ((nullptr == P094_data)) { - return false; - } - - - if (P094_data->disableFilterWindowActive()) { - addLog(LOG_LEVEL_INFO, F("CUL Reader: Disable Filter Window active")); - return true; - } - - bool res = P094_data->parsePacket(received); - - if (P094_data->invertMatch()) { - addLog(LOG_LEVEL_INFO, F("CUL Reader: invert filter")); - return !res; - } - return res; -} - -String Plugin_094_valuename(uint8_t value_nr, bool displayString) { - switch (value_nr) { - case P094_QUERY_VALUE: return displayString ? F("Value") : F("v"); - } - return EMPTY_STRING; -} - -void P094_html_show_matchForms(struct EventStruct *event) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P094_data)) { - addFormNumericBox(F("Filter Off Window after send"), - getPluginCustomArgName(P094_FILTER_OFF_WINDOW_POS), - P094_data->getFilterOffWindowTime(), - 0, - 60000); - addUnit(F("msec")); - addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); - - { - const __FlashStringHelper * options[P094_Match_Type_NR_ELEMENTS]; - int optionValues[P094_Match_Type_NR_ELEMENTS]; - - for (int i = 0; i < P094_Match_Type_NR_ELEMENTS; ++i) { - P094_Match_Type matchType = static_cast(i); - options[i] = P094_data_struct::MatchType_toString(matchType); - optionValues[i] = matchType; - } - P094_Match_Type choice = P094_data->getMatchType(); - addFormSelector(F("Filter Mode"), - getPluginCustomArgName(P094_MATCH_TYPE_POS), - P094_Match_Type_NR_ELEMENTS, - options, - optionValues, - choice, - false); - } - - - uint8_t filterSet = 0; - uint32_t optional = 0; - P094_Filter_Value_Type capture = P094_Filter_Value_Type::P094_packet_length; - P094_Filter_Comp comparator = P094_Filter_Comp::P094_Equal_OR; - String filter; - - for (uint8_t filterLine = 0; filterLine < P094_NR_FILTERS; ++filterLine) - { - // Filter parameter number on a filter line. - bool newLine = (filterLine % P094_AND_FILTER_BLOCK) == 0; - - for (uint8_t filterLinePar = 0; filterLinePar < P094_ITEMS_PER_FILTER; ++filterLinePar) - { - String id = getPluginCustomArgName(P094_data_struct::P094_Get_filter_base_index(filterLine) + filterLinePar); - - switch (filterLinePar) { - case 0: - { - filter = P094_data->getFilter(filterLine, capture, optional, comparator); - - if (newLine) { - // Label + first parameter - ++filterSet; - addRowLabel_tr_id(concat(F("Filter "), static_cast(filterSet)), id); - } else { - html_B(F("AND")); - html_BR(); - } - - // Combo box with filter types - { - const __FlashStringHelper * options[P094_FILTER_VALUE_Type_NR_ELEMENTS]; - int optionValues[P094_FILTER_VALUE_Type_NR_ELEMENTS]; - - for (int i = 0; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) { - P094_Filter_Value_Type filterValueType = static_cast(i); - options[i] = P094_data_struct::P094_FilterValueType_toString(filterValueType); - optionValues[i] = filterValueType; - } - addSelector(id, P094_FILTER_VALUE_Type_NR_ELEMENTS, options, optionValues, nullptr, capture, false, true, F("")); - } - - break; - } - case 1: - { - // Optional numerical value - addNumericBox(id, optional, 0, 1024); - break; - } - case 2: - { - // Comparator - const __FlashStringHelper * options[P094_FILTER_COMP_NR_ELEMENTS]; - int optionValues[P094_FILTER_COMP_NR_ELEMENTS]; - - for (int i = 0; i < P094_FILTER_COMP_NR_ELEMENTS; ++i) { - P094_Filter_Comp enumValue = static_cast(i); - options[i] = P094_data_struct::P094_FilterComp_toString(enumValue); - optionValues[i] = enumValue; - } - addSelector(id, P094_FILTER_COMP_NR_ELEMENTS, options, optionValues, nullptr, comparator, false, true, F("")); - break; - } - case 3: - { - // Compare with - addTextBox(id, filter, 8, false, false, EMPTY_STRING, F("")); - break; - } - } - } - } - } -} - -void P094_html_show_stats(struct EventStruct *event) { - P094_data_struct *P094_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr == P094_data) || !P094_data->isInitialized()) { - return; - } - { - addRowLabel(F("Current Sentence")); - addHtml(P094_data->peekSentence()); - } - - { - addRowLabel(F("Sentences (pass/fail)")); - uint32_t success, error, length_last; - P094_data->getSentencesReceived(success, error, length_last); - addHtmlInt(success); - addHtml('/'); - addHtmlInt(error); - addRowLabel(F("Length Last Sentence")); - addHtmlInt(length_last); - } -} - -#endif // USES_P094 \ No newline at end of file +#include "_Plugin_Helper.h" +#ifdef USES_P094 + +// ####################################################################################################### +// #################### Plugin 094 Brick4U CUL Reader #################################################### +// ####################################################################################################### +// +// Interact with Brick4U CUL receiver +// Allows to control the mode of the CUL receiver +// + +# include "src/ESPEasyCore/ESPEasyNetwork.h" + +# include "src/Helpers/ESPEasy_Storage.h" +# include "src/Helpers/StringConverter.h" +# include "src/PluginStructs/P094_data_struct.h" + +# include + +# define PLUGIN_094 +# define PLUGIN_ID_094 94 +# define PLUGIN_NAME_094 "Communication - CUL Reader" +# define PLUGIN_VALUENAME1_094 "v" + + +bool Plugin_094_match_all(taskIndex_t taskIndex, + const String& received, + const String& source, + bool fromCUL); + +void Plugin_094_setFlags(struct EventStruct *event); + +// Plugin settings: +// Validate: +// - [0..9] +// - "+", "-", "." +// - [A..Z] +// - [a..z] +// - ASCII 32 - 217 +// Sentence start: char +// Sentence end: CR/CRLF/LF/char +// Max length sentence: 1k max +// Interpret as: +// - Float +// - int +// - String +// Init string (incl parsing CRLF like characters) +// Timeout between sentences. + + +boolean Plugin_094(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) { + case PLUGIN_DEVICE_ADD: { + Device[++deviceCount].Number = PLUGIN_ID_094; + Device[deviceCount].Type = DEVICE_TYPE_SERIAL; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_STRING; + Device[deviceCount].OutputDataType = Output_Data_type_t::Default; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = false; + Device[deviceCount].DuplicateDetection = true; + + // FIXME TD-er: Not sure if access to any existing task data is needed when saving + Device[deviceCount].ExitTaskBeforeSave = true; + break; + } + + case PLUGIN_GET_DEVICENAME: { + string = F(PLUGIN_NAME_094); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_094)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: { + serialHelper_getGpioNames(event, false, true); // TX optional + break; + } + + case PLUGIN_WEBFORM_SHOW_VALUES: + { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data) && P094_data->isInitialized()) { + uint32_t success, error, length_last; + P094_data->getSentencesReceived(success, error, length_last); + uint8_t varNr = VARS_PER_TASK; + pluginWebformShowValue(event->TaskIndex, varNr++, F("Success"), String(success)); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Error"), String(error)); + pluginWebformShowValue(event->TaskIndex, varNr++, F("Length Last"), String(length_last), true); + + // success = true; + } + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P094_BAUDRATE = P094_DEFAULT_BAUDRATE; + P094_DEBUG_SENTENCE_LENGTH = 0; + + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + string += serialHelper_getSerialTypeLabel(event); + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: + { + addFormNumericBox(F("Baudrate"), P094_BAUDRATE_LABEL, P094_BAUDRATE, 2400, 115200); + addUnit(F("baud")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormCheckBox(F("Append system time"), F("systime"), P094_GET_APPEND_RECEIVE_SYSTIME); + +# if P094_DEBUG_OPTIONS + addFormSubHeader(F("Debug Options")); + addFormNumericBox(F("(debug) Generated length"), P094_DEBUG_SENTENCE_LABEL, P094_DEBUG_SENTENCE_LENGTH, 0, 1024); + addFormCheckBox(F("(debug) Generate CUL data"), F("debug_data"), P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS + + addFormSubHeader(F("Filtering")); + addFormCheckBox(F("Mute Messages"), F("mute"), P094_GET_MUTE_MESSAGES); + P094_html_show_matchForms(event); + addFormCheckBox(F("Enable Interval Filter"), F("interval_filter"), P094_GET_INTERVAL_FILTER); + + addFormSubHeader(F("Statistics")); + addFormCheckBox(F("Collect W-MBus Stats"), F("collect_stats"), P094_GET_COLLECT_STATS); + addFormNote(F("Collect reception statistics of W-MBus devices received by the CUL reader")); + + + P094_html_show_stats(event); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: { + P094_BAUDRATE = getFormItemInt(P094_BAUDRATE_LABEL); + P094_DEBUG_SENTENCE_LENGTH = getFormItemInt(P094_DEBUG_SENTENCE_LABEL); + + P094_DISABLE_WINDOW_TIME_MS = getFormItemInt(F("disableTime")); + P094_NR_FILTERS = getFormItemInt(F("nrfilters")); + + + P094_SET_APPEND_RECEIVE_SYSTIME(isFormItemChecked(F("systime"))); +# if P094_DEBUG_OPTIONS + P094_SET_GENERATE_DEBUG_CUL_DATA(isFormItemChecked(F("debug_data"))); +# endif // if P094_DEBUG_OPTIONS + P094_SET_INTERVAL_FILTER(isFormItemChecked(F("interval_filter"))); + P094_SET_MUTE_MESSAGES(isFormItemChecked(F("mute"))); + P094_SET_COLLECT_STATS(isFormItemChecked(F("collect_stats"))); + + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + + const bool localAllocated = nullptr == P094_data; + + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); + } + + if ((nullptr != P094_data)) { + P094_data->WebformSaveFilters(event, P094_NR_FILTERS); + success = true; + + if (localAllocated) { + delete P094_data; + } + } + + break; + } + + case PLUGIN_INIT: { + const int16_t serial_rx = CONFIG_PIN1; + const int16_t serial_tx = CONFIG_PIN2; + const ESPEasySerialPort port = static_cast(CONFIG_PORT); + initPluginTaskData(event->TaskIndex, new (std::nothrow) P094_data_struct()); + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P094_data) { + return success; + } + + if (P094_data->init( + port, + serial_rx, + serial_tx, + P094_BAUDRATE)) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + P094_data->loadFilters(event, P094_NR_FILTERS); +# if P094_DEBUG_OPTIONS + P094_data->setGenerate_DebugCulData(P094_GET_GENERATE_DEBUG_CUL_DATA); +# endif // if P094_DEBUG_OPTIONS + success = true; + + serialHelper_log_GpioDescription(port, serial_rx, serial_tx); + } else { + clearPluginTaskData(event->TaskIndex); + } + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + P094_data->interval_filter_purgeExpired(); + + if (P094_data->dump_next_stats(event->String2)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL Reader: "), event->String2)); + } + + // Do not create events for dumping stats. + const bool sendEvents = false; + sendData(event, sendEvents); + } + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: { + if (Settings.TaskDeviceEnabled[event->TaskIndex]) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data) && P094_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. + P094_data->getSentence(event->String2, P094_GET_APPEND_RECEIVE_SYSTIME); + + if (event->String2.length() > 0) { + const bool fromCUL = true; + const String source = NetworkGetHostname(); + + if (Plugin_094_match_all(event->TaskIndex, event->String2, source, fromCUL)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(128)) { + log = F("CUL Reader: Sending: "); + const size_t messageLength = event->String2.length(); + + if (messageLength < 100) { + log += event->String2; + } else { + // Split string so we get start and end + log += event->String2.substring(0, 40); + log += F("..."); + log += event->String2.substring(messageLength - 40); + } + addLogMove(LOG_LEVEL_INFO, log); + } + } + + // Filter length options: + // - 22 char, for hash-value then we filter the exact meter including serial and meter type, (that will also prevent very quit + // sending meters, which normaly is a fault) + // - 38 char, The exact message, because we have 2 uint8_t from the value payload + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); + sendData(event); + } + } + } + success = true; + } + break; + } + + case PLUGIN_READ: { + if (P094_DEBUG_SENTENCE_LENGTH > 0) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P094_data)) { + # if P094_DEBUG_OPTIONS + const uint32_t debug_count = P094_data->getDebugCounter(); + event->String2.reserve(P094_DEBUG_SENTENCE_LENGTH); + event->String2 += String(debug_count); + event->String2 += '_'; + const char c = '0' + debug_count % 10; + + for (long i = event->String2.length(); i < P094_DEBUG_SENTENCE_LENGTH; ++i) { + event->String2 += c; + } + # endif // if P094_DEBUG_OPTIONS + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("CUL Reader: Sending: "); + log += event->String2.substring(0, 20); + log += F("..."); + addLogMove(LOG_LEVEL_INFO, log); + } + + // sendData_checkDuplicates(event, event->String2.substring(0, 22)); + sendData(event); + } + } + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + const String command = parseString(string, 1); + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + if (equals(command, F("getfiltermd5"))) { + // to output a MD5 of the currently active filters. + string = P094_data->getFiltersMD5(); + success = true; + } else if (equals(command, F("getfilterenabled"))) { + string = P094_GET_INTERVAL_FILTER; + success = true; + } else if (equals(command, F("getmuteenabled"))) { + string = P094_GET_MUTE_MESSAGES; + success = true; + } + } + break; + } + + + case PLUGIN_WRITE: { + const String cmd = parseString(string, 1); + const String subcmd = parseString(string, 2); + + if (cmd.startsWith(F("culreader"))) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (equals(subcmd, F("enablefilter"))) { + // culreader,enablefilter + P094_SET_INTERVAL_FILTER(1); + success = true; + } else if (equals(subcmd, F("disablefilter"))) { + // culreader,disablefilter + P094_SET_INTERVAL_FILTER(0); + success = true; + } else if (equals(subcmd, F("mute"))) { + // culreader,mute + P094_SET_MUTE_MESSAGES(1); + success = true; + } else if (equals(subcmd, F("unmute"))) { + // culreader,unmute + P094_SET_MUTE_MESSAGES(0); + success = true; + } else if ((nullptr != P094_data)) { + if (equals(cmd, F("culreader_write")) || + equals(subcmd, F("write"))) { + // culreader,write, + String param1 = parseStringKeepCase(string, 2); + parseSystemVariables(param1, false); + P094_data->sendString(param1); + addLogMove(LOG_LEVEL_INFO, param1); + success = true; + } else if (equals(subcmd, F("dumpstats"))) { + // culreader,dumpstats + P094_data->prepare_dump_stats(); + success = true; + } else if (equals(subcmd, F("clearfilters"))) { + // culreader,clearfilters + P094_data->clearFilters(); + success = true; + } else if (equals(subcmd, F("savefilters"))) { + // culreader,savefilters + P094_data->saveFilters(event); + SaveSettings(); + success = true; + } else if (equals(subcmd, F("addfilter"))) { + // culreader,addfilter, + // Examples for a filter definition + // EBZ.02.12345678;all + // *.02.*;15m + // TCH.44.*;once + // *.*.*;5m + success = true; + P094_data->addFilter(event, parseString(string, 3)); + } else if (equals(subcmd, F("setfilters"))) { + // culreader,setfilters,|...| + // Examples for a filter definition + // culreader,setfilters,EBZ.02.12345678;all|*.02.*;15m|TCH.44.*;once|*.*.*;5m + success = true; + P094_data->clearFilters(); + const String argument = parseString(string, 3); + + if (!argument.isEmpty()) { + int argNr = 1; + + while (argNr > 0) { + const String filter = parseString(argument, argNr, '|'); + + if (!filter.isEmpty()) + { + P094_data->addFilter(event, filter); + ++argNr; + } else { + argNr = 0; + } + } + } + P094_data->saveFilters(event); + SaveSettings(); + } + } + + if (success) { + Plugin_094_setFlags(event); + } + } + + + break; + } +#ifdef USES_ESPEASY_NOW + case PLUGIN_FILTEROUT_CONTROLLER_DATA: + { + // event->String1 => topic; + // event->String2 => payload; + if (Settings.TaskDeviceEnabled[event->TaskIndex]) { + const bool fromCUL = false; + + if (!Plugin_094_match_all(event->TaskIndex, event->String2, event->String1, fromCUL)) { + // Inverse as we check for filtering 'out' the messages. + success = true; + } + } + + break; + } +#endif + } + return success; +} + +bool Plugin_094_match_all(taskIndex_t taskIndex, const String& received, const String& source, bool fromCUL) +{ + P094_data_struct *P094_data = + static_cast(getPluginTaskData(taskIndex)); + + if ((nullptr == P094_data)) { + return false; + } + + if (P094_data->disableFilterWindowActive()) { + addLog(LOG_LEVEL_INFO, F("CUL Reader: Disable Filter Window active")); + return true; + } + + mBusPacket_t packet; + bool res = P094_data->parsePacket(received, packet); + + # ifdef ESP8266 + + if (res && fromCUL) { + # endif // ifdef ESP8266 + + // Only collect stats from the actual CUL receiver, not when processing forwarded packets. + // On ESP8266: only collect stats on the filtered nodes or else we will likely run out of memory + P094_data->collect_stats_add(packet, source); + # ifdef ESP8266 +} + + # endif // ifdef ESP8266 + + return res; +} + +void Plugin_094_setFlags(struct EventStruct *event) +{ + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P094_data) { + P094_data->setFlags( + P094_DISABLE_WINDOW_TIME_MS, + P094_GET_INTERVAL_FILTER, + P094_GET_MUTE_MESSAGES, + P094_GET_COLLECT_STATS); + } +} + +void P094_html_show_matchForms(struct EventStruct *event) { + addFormNumericBox(F("Filter Off Window after send"), + F("disableTime"), + P094_DISABLE_WINDOW_TIME_MS, + 0, + 60000); + addUnit(F("msec")); + addFormNote(F("0 = Do not turn off filter after sending to the connected device.")); + + addFormNumericBox( + F("Nr Filters"), + F("nrfilters"), + P094_NR_FILTERS, + 0, + P094_MAX_NR_FILTERS); + + + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + const bool localAllocated = nullptr == P094_data; + + if (localAllocated) { + P094_data = new (std::nothrow) P094_data_struct(); + + if (nullptr != P094_data) { + P094_data->loadFilters(event, P094_NR_FILTERS); + } + } + + if ((nullptr != P094_data)) { + P094_data->WebformLoadFilters(P094_NR_FILTERS); + + if (localAllocated) { + delete P094_data; + } + } +} + +void P094_html_show_stats(struct EventStruct *event) { + P094_data_struct *P094_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr == P094_data) || !P094_data->isInitialized()) { + return; + } + + P094_data->html_show_interval_filter_stats(); + + P094_data->html_show_mBus_stats(); + + { + addRowLabel(F("Current Sentence")); + addHtml(P094_data->peekSentence()); + } + + { + addRowLabel(F("Sentences (pass/fail)")); + uint32_t success, error, length_last; + P094_data->getSentencesReceived(success, error, length_last); + addHtmlInt(success); + addHtml('/'); + addHtmlInt(error); + addRowLabel(F("Length Last Sentence")); + addHtmlInt(length_last); + } +} + +#endif // USES_P094 diff --git a/src/_P095_ILI9341.ino b/src/_P095_ILI9341.ino index 21012fa65..f69cbc7f1 100644 --- a/src/_P095_ILI9341.ino +++ b/src/_P095_ILI9341.ino @@ -15,6 +15,10 @@ /** * Changelog: + * 2024-07-07 tonhuisman: Remove explicit support for ILI9486 as all ILI9486 displays tested so far work with the ILI9488 driver, + * sometimes with Invert display setting enabled (or they are actually ILI9488 displays...) + * 2024-06-23 tonhuisman: Add support for ILI9488 displays by implementing an adapted library (jaretburkett/ILI9488) + * Add Default font selection setting, if AdafruitGFX_Helper fonts are included * 2022-10-24 tonhuisman: Add Invert display option in settings to accomodate displays that swap foreground and background colors * (f.e. M5Stack Core2 using ILI9342C), or just to invert the colors at user choice. * 2022-07-20 tonhuisman: Made support for ILI9486/ILI9488 optional and excluded by default as these are not available as @@ -238,10 +242,11 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) ILI9xxx_type_toString(ILI9xxx_type_e::ILI9481_RGB_320x480), ILI9xxx_type_toString(ILI9xxx_type_e::ILI9481_CMI7_320x480), ILI9xxx_type_toString(ILI9xxx_type_e::ILI9481_CMI8_320x480), - # ifdef P095_ENABLE_ILI948X - ILI9xxx_type_toString(ILI9xxx_type_e::ILI9486_320x480), + # if P095_ENABLE_ILI948X + + // ILI9xxx_type_toString(ILI9xxx_type_e::ILI9486_320x480), ILI9xxx_type_toString(ILI9xxx_type_e::ILI9488_320x480), - # endif // ifdef P095_ENABLE_ILI948X + # endif // if P095_ENABLE_ILI948X }; constexpr int hardwareOptions[] = { static_cast(ILI9xxx_type_e::ILI9341_240x320), @@ -254,10 +259,11 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) static_cast(ILI9xxx_type_e::ILI9481_RGB_320x480), static_cast(ILI9xxx_type_e::ILI9481_CMI7_320x480), static_cast(ILI9xxx_type_e::ILI9481_CMI8_320x480), - # ifdef P095_ENABLE_ILI948X - static_cast(ILI9xxx_type_e::ILI9486_320x480), + # if P095_ENABLE_ILI948X + + // static_cast(ILI9xxx_type_e::ILI9486_320x480), static_cast(ILI9xxx_type_e::ILI9488_320x480), - # endif // ifdef P095_ENABLE_ILI948X + # endif // if P095_ENABLE_ILI948X }; addFormSelector(F("TFT display model"), F("dsptype"), @@ -275,6 +281,10 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) AdaGFXFormTextPrintMode(F("tpmode"), P095_CONFIG_FLAG_GET_MODE); + # if ADAGFX_FONTS_INCLUDED + AdaGFXFormDefaultFont(F("deffont"), P095_CONFIG_DEFAULT_FONT); + # endif // if ADAGFX_FONTS_INCLUDED + AdaGFXFormFontScaling(F("fontscale"), P095_CONFIG_FLAG_GET_FONTSCALE); # ifdef P095_SHOW_SPLASH @@ -289,20 +299,20 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) F("ili9341"), F("ili9342"), F("ili9481"), - # ifdef P095_ENABLE_ILI948X + # if P095_ENABLE_ILI948X F("ili9486"), F("ili9488"), - # endif // ifdef P095_ENABLE_ILI948X + # endif // if P095_ENABLE_ILI948X }; constexpr int commandTriggerOptions[] = { static_cast(P095_CommandTrigger::tft), static_cast(P095_CommandTrigger::ili9341), static_cast(P095_CommandTrigger::ili9342), static_cast(P095_CommandTrigger::ili9481), - # ifdef P095_ENABLE_ILI948X + # if P095_ENABLE_ILI948X static_cast(P095_CommandTrigger::ili9486), static_cast(P095_CommandTrigger::ili9488), - # endif // ifdef P095_ENABLE_ILI948X + # endif // if P095_ENABLE_ILI948X }; addFormSelector(F("Write Command trigger"), F("commandtrigger"), @@ -337,7 +347,7 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) F("pbgcolor"), P095_CONFIG_GET_COLOR_BACKGROUND); - uint16_t remain = DAT_TASKS_CUSTOM_SIZE; + uint16_t remain = DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE; { String strings[P095_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P095_Nlines, 0); @@ -345,9 +355,9 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) for (uint8_t varNr = 0; varNr < P095_Nlines; varNr++) { addFormTextBox( - concat(F("Line "), varNr + 1), - getPluginCustomArgName(varNr), - strings[varNr], + concat(F("Line "), varNr + 1), + getPluginCustomArgName(varNr), + strings[varNr], P095_Nchars); remain -= (strings[varNr].length() + 1); } @@ -368,6 +378,9 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) P095_CONFIG_DISPLAY_TIMEOUT = getFormItemInt(F("timer")); P095_CONFIG_BACKLIGHT_PIN = getFormItemInt(F("backlight")); P095_CONFIG_BACKLIGHT_PERCENT = getFormItemInt(F("backpercentage")); + # if ADAGFX_FONTS_INCLUDED + P095_CONFIG_DEFAULT_FONT = getFormItemInt(F("deffont")); + # endif // if ADAGFX_FONTS_INCLUDED uint32_t lSettings = 0; bitWrite(lSettings, P095_CONFIG_FLAG_NO_WAKE, !isFormItemChecked(F("NoDisplay"))); // Bit 0 NoDisplayOnReceivingText, reverse @@ -435,6 +448,12 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { if (Settings.InitSPI != 0) { + # if P095_ENABLE_ILI948X + + if (10 == P095_CONFIG_FLAG_GET_TYPE) { // If ILI9486 was selected, reset to ILI9488 + set4BitToUL(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_TYPE, static_cast(ILI9xxx_type_e::ILI9488_320x480)); + } + # endif // if P095_ENABLE_ILI948X initPluginTaskData(event->TaskIndex, new (std::nothrow) P095_data_struct(static_cast(P095_CONFIG_FLAG_GET_TYPE), P095_CONFIG_ROTATION, @@ -447,7 +466,12 @@ boolean Plugin_095(uint8_t function, struct EventStruct *event, String& string) P095_CONFIG_FLAG_GET_CMD_TRIGGER)), P095_CONFIG_GET_COLOR_FOREGROUND, P095_CONFIG_GET_COLOR_BACKGROUND, - bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_BACK_FILL) == 0)); + bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_BACK_FILL) == 0 + # if ADAGFX_FONTS_INCLUDED + , + P095_CONFIG_DEFAULT_FONT + # endif // if ADAGFX_FONTS_INCLUDED + )); P095_data_struct *P095_data = static_cast(getPluginTaskData(event->TaskIndex)); if (nullptr != P095_data) { diff --git a/src/_P096_eInk.ino b/src/_P096_eInk.ino index b1ad73c9f..3f0489f93 100644 --- a/src/_P096_eInk.ino +++ b/src/_P096_eInk.ino @@ -410,7 +410,7 @@ boolean Plugin_096(uint8_t function, struct EventStruct *event, String& string) String strings[P096_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P096_Nlines, 0); - uint16_t remain = DAT_TASKS_CUSTOM_SIZE; + uint16_t remain = DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE; for (uint8_t varNr = 0; varNr < P096_Nlines; varNr++) { addFormTextBox(concat(F("Line "), (varNr + 1)), getPluginCustomArgName(varNr), strings[varNr], P096_Nchars); diff --git a/src/_P097_Esp32Touch.ino b/src/_P097_Esp32Touch.ino index c220b8514..f482a7604 100644 --- a/src/_P097_Esp32Touch.ino +++ b/src/_P097_Esp32Touch.ino @@ -104,7 +104,7 @@ boolean Plugin_097(uint8_t function, struct EventStruct *event, String& string) // Show current value addRowLabel(F("Current Pressure")); - addHtml(String(touchRead(CONFIG_PIN1))); + addHtmlInt(touchRead(CONFIG_PIN1)); success = true; break; @@ -127,6 +127,7 @@ boolean Plugin_097(uint8_t function, struct EventStruct *event, String& string) success = true; break; } + case PLUGIN_TEN_PER_SECOND: { if ((p097_pinTouched != 0) || (p097_pinTouchedPrev != 0)) { @@ -179,11 +180,7 @@ boolean Plugin_097(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, raw_value); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Touch : "); - log += formatGpioName_ADC(CONFIG_PIN1); - log += F(": "); - log += raw_value; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("Touch : %s: %d"), formatGpioName_ADC(CONFIG_PIN1).c_str(), raw_value)); } success = true; break; @@ -244,102 +241,78 @@ void P097_got_T12() IRAM_ATTR; void P097_got_T13() IRAM_ATTR; void P097_got_T14() IRAM_ATTR; #endif +void P097_got_Touched(int pin) IRAM_ATTR; #if HAS_T0_INPUT void P097_got_T0() { - bitSet(p097_pinTouched, 0); - - if (p097_timestamp[0] == 0) { p097_timestamp[0] = millis(); } + P097_got_Touched(0); } #endif void P097_got_T1() { - bitSet(p097_pinTouched, 1); - - if (p097_timestamp[1] == 0) { p097_timestamp[1] = millis(); } + P097_got_Touched(1); } void P097_got_T2() { - bitSet(p097_pinTouched, 2); - - if (p097_timestamp[2] == 0) { p097_timestamp[2] = millis(); } + P097_got_Touched(2); } void P097_got_T3() { - bitSet(p097_pinTouched, 3); - - if (p097_timestamp[3] == 0) { p097_timestamp[3] = millis(); } + P097_got_Touched(3); } void P097_got_T4() { - bitSet(p097_pinTouched, 4); - - if (p097_timestamp[4] == 0) { p097_timestamp[4] = millis(); } + P097_got_Touched(4); } void P097_got_T5() { - bitSet(p097_pinTouched, 5); - - if (p097_timestamp[5] == 0) { p097_timestamp[5] = millis(); } + P097_got_Touched(6); } void P097_got_T6() { - bitSet(p097_pinTouched, 6); - - if (p097_timestamp[6] == 0) { p097_timestamp[6] = millis(); } + P097_got_Touched(6); } void P097_got_T7() { - bitSet(p097_pinTouched, 7); - - if (p097_timestamp[7] == 0) { p097_timestamp[7] = millis(); } + P097_got_Touched(7); } void P097_got_T8() { - bitSet(p097_pinTouched, 8); - - if (p097_timestamp[8] == 0) { p097_timestamp[8] = millis(); } + P097_got_Touched(8); } void P097_got_T9() { - bitSet(p097_pinTouched, 9); - - if (p097_timestamp[9] == 0) { p097_timestamp[9] = millis(); } + P097_got_Touched(9); } #if HAS_T10_TO_T14 void P097_got_T10() { - bitSet(p097_pinTouched, 10); - - if (p097_timestamp[10] == 0) { p097_timestamp[10] = millis(); } + P097_got_Touched(10); } void P097_got_T11() { - bitSet(p097_pinTouched, 11); - - if (p097_timestamp[11] == 0) { p097_timestamp[11] = millis(); } + P097_got_Touched(11); } void P097_got_T12() { - bitSet(p097_pinTouched, 12); - - if (p097_timestamp[12] == 0) { p097_timestamp[12] = millis(); } + P097_got_Touched(12); } void P097_got_T13() { - bitSet(p097_pinTouched, 13); - - if (p097_timestamp[13] == 0) { p097_timestamp[13] = millis(); } + P097_got_Touched(13); } void P097_got_T14() { - bitSet(p097_pinTouched, 14); - - if (p097_timestamp[14] == 0) { p097_timestamp[14] = millis(); } + P097_got_Touched(14); } #endif +void P097_got_Touched(int pin) { + bitSet(p097_pinTouched, pin); + + if (p097_timestamp[pin] == 0) { p097_timestamp[pin] = millis(); } +} #endif diff --git a/src/_P100_DS2423_counter.ino b/src/_P100_DS2423_counter.ino index bceb21107..03e707cb2 100644 --- a/src/_P100_DS2423_counter.ino +++ b/src/_P100_DS2423_counter.ino @@ -1,161 +1,157 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P100 - -// ####################################################################################################### -// #################################### Plugin 100: Counter Dallas DS2423 ############################### -// ####################################################################################################### - -// Maxim Integrated (ex Dallas) DS2423 datasheet : https://datasheets.maximintegrated.com/en/ds/DS2423.pdf - -# include "src/Helpers/Dallas1WireHelper.h" - -# define PLUGIN_100 -# define PLUGIN_ID_100 100 -# define PLUGIN_NAME_100 "Pulse Counter - DS2423" -# define PLUGIN_VALUENAME1_100 "CountDelta" - -boolean Plugin_100(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_100; - Device[deviceCount].Type = DEVICE_TYPE_SINGLE; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_100); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_100)); - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_bidirectional(F("1-Wire")); - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormNote(F("External pull up resistor is needed, see docs!")); - - // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. - int8_t Plugin_100_DallasPin = CONFIG_PIN1; - - if (validGpio(Plugin_100_DallasPin)) { - Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_100_DallasPin, Plugin_100_DallasPin); - - // Counter select - const __FlashStringHelper * resultsOptions[2] = { F("A"), F("B") }; - int resultsOptionValues[2] = { 0, 1 }; - addFormSelector(F("Counter"), F("counter"), 2, resultsOptions, resultsOptionValues, PCONFIG(0)); - addFormNote(F("Counter value is incremental")); - } - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - // Counter choice - PCONFIG(0) = getFormItemInt(F("counter")); - - // 1-wire device address - Dallas_addr_selector_webform_save(event->TaskIndex, CONFIG_PIN1, CONFIG_PIN1); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SHOW_CONFIG: - { - uint8_t addr[8]; - Dallas_plugin_get_addr(addr, event->TaskIndex); - string = Dallas_format_address(addr); - success = true; - break; - } - - case PLUGIN_INIT: - { - UserVar.setFloat(event->TaskIndex, 0, 0); - UserVar.setFloat(event->TaskIndex, 1, 0); - UserVar.setFloat(event->TaskIndex, 2, 0); - - if (validGpio(CONFIG_PIN1)) { - // Explicitly set the pinMode using the "slow" pinMode function - // This way we know for sure the state of any pull-up or -down resistor is known. - pinMode(CONFIG_PIN1, INPUT); - } - - success = true; - break; - } - - case PLUGIN_READ: - { - uint8_t addr[8]; - Dallas_plugin_get_addr(addr, event->TaskIndex); - - if (addr[0] != 0) { - if (validGpio(CONFIG_PIN1)) { - float value = 0; - - if (Dallas_readCounter(addr, &value, CONFIG_PIN1, CONFIG_PIN1, PCONFIG(0))) - { - UserVar.setFloat(event->TaskIndex, 0, UserVar[event->BaseVarIndex + 2] != 0 - ? value - UserVar[event->BaseVarIndex + 1] - : 0); - UserVar.setFloat(event->TaskIndex, 2, 1); - UserVar.setFloat(event->TaskIndex, 1, value); - success = true; - } - else - { - UserVar.setFloat(event->TaskIndex, 0, NAN); - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("[P100]DS : Counter "); - log += PCONFIG(0) == 0 ? F("A") : F("B"); - log += F(": "); - - if (success) { - log += formatUserVarNoCheck(event->TaskIndex, 0); - } else { - log += F("Error!"); - } - log += F(" ("); - log += Dallas_format_address(addr); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - } - } - } - break; - } - } - return success; -} - - -#endif // USES_P100 +#include "_Plugin_Helper.h" +#ifdef USES_P100 + +// ####################################################################################################### +// #################################### Plugin 100: Counter Dallas DS2423 ############################### +// ####################################################################################################### + +// Maxim Integrated (ex Dallas) DS2423 datasheet : https://datasheets.maximintegrated.com/en/ds/DS2423.pdf + +# include "src/Helpers/Dallas1WireHelper.h" + +# define PLUGIN_100 +# define PLUGIN_ID_100 100 +# define PLUGIN_NAME_100 "Pulse Counter - DS2423" +# define PLUGIN_VALUENAME1_100 "CountDelta" + +boolean Plugin_100(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_100; + Device[deviceCount].Type = DEVICE_TYPE_SINGLE; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 1; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_100); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_100)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_bidirectional(F("1-Wire")); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNote(F("External pull up resistor is needed, see docs!")); + + // Scan the onewire bus and fill dropdown list with devicecount on this GPIO. + int8_t Plugin_100_DallasPin = CONFIG_PIN1; + + if (validGpio(Plugin_100_DallasPin)) { + Dallas_addr_selector_webform_load(event->TaskIndex, Plugin_100_DallasPin, Plugin_100_DallasPin); + + // Counter select + const __FlashStringHelper * resultsOptions[2] = { F("A"), F("B") }; + int resultsOptionValues[2] = { 0, 1 }; + addFormSelector(F("Counter"), F("counter"), 2, resultsOptions, resultsOptionValues, PCONFIG(0)); + addFormNote(F("Counter value is incremental")); + } + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + // Counter choice + PCONFIG(0) = getFormItemInt(F("counter")); + + // 1-wire device address + Dallas_addr_selector_webform_save(event->TaskIndex, CONFIG_PIN1, CONFIG_PIN1); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SHOW_CONFIG: + { + uint8_t addr[8]; + Dallas_plugin_get_addr(addr, event->TaskIndex); + string = Dallas_format_address(addr); + success = true; + break; + } + + case PLUGIN_INIT: + { + UserVar.setFloat(event->TaskIndex, 0, 0); + UserVar.setFloat(event->TaskIndex, 1, 0); + UserVar.setFloat(event->TaskIndex, 2, 0); + + if (validGpio(CONFIG_PIN1)) { + // Explicitly set the pinMode using the "slow" pinMode function + // This way we know for sure the state of any pull-up or -down resistor is known. + pinMode(CONFIG_PIN1, INPUT); + } + + success = true; + break; + } + + case PLUGIN_READ: + { + uint8_t addr[8]; + Dallas_plugin_get_addr(addr, event->TaskIndex); + + if (addr[0] != 0) { + if (validGpio(CONFIG_PIN1)) { + float value = 0.0f; + + if (Dallas_readCounter(addr, &value, CONFIG_PIN1, CONFIG_PIN1, PCONFIG(0))) + { + UserVar.setFloat(event->TaskIndex, 0, UserVar[event->BaseVarIndex + 2] != 0 + ? value - UserVar[event->BaseVarIndex + 1] + : 0); + UserVar.setFloat(event->TaskIndex, 2, 1); + UserVar.setFloat(event->TaskIndex, 1, value); + success = true; + } + else + { + UserVar.setFloat(event->TaskIndex, 0, NAN); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = strformat(F("[P100]DS : Counter %c :"), PCONFIG(0) == 0 ? 'A' : 'B'); + + if (success) { + log += formatUserVarNoCheck(event, 0); + } else { + log += F("Error!"); + } + log += concat(F(" (%s)"), Dallas_format_address(addr).c_str()); + addLogMove(LOG_LEVEL_INFO, log); + } + } + } + break; + } + } + return success; +} + + +#endif // USES_P100 diff --git a/src/_P101_WakeOnLan.ino b/src/_P101_WakeOnLan.ino index f9ba5d10b..6fb8ea5f5 100644 --- a/src/_P101_WakeOnLan.ino +++ b/src/_P101_WakeOnLan.ino @@ -50,48 +50,48 @@ // ************************************************************************************************ -#include +# include // Plugin defines -#define PLUGIN_101 -#define PLUGIN_ID_101 101 -#define PLUGIN_NAME_101 "Communication - Wake On LAN" +# define PLUGIN_101 +# define PLUGIN_ID_101 101 +# define PLUGIN_NAME_101 "Communication - Wake On LAN" // Config Setting defines -#define CUSTOMTASK_STR_SIZE_P101 20 -#define DEF_TASK_NAME_P101 "WAKE_ON_LAN" -#define SET_UDP_PORT_P101 ExtraTaskSettings.TaskDevicePluginConfigLong[1] -#define GET_UDP_PORT_P101 Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) -#define FORM_PORT_P101 "pport" +# define CUSTOMTASK_STR_SIZE_P101 20 +# define DEF_TASK_NAME_P101 "WAKE_ON_LAN" +# define SET_UDP_PORT_P101 ExtraTaskSettings.TaskDevicePluginConfigLong[1] +# define GET_UDP_PORT_P101 Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) +# define FORM_PORT_P101 "pport" -// Command keyword defines -#define CMD_NAME_P101 "WAKEONLAN" +// Command keyword defines, checked in lowercase +# define CMD_NAME_P101 "wakeonlan" // MAC Defines. -#define MAC_ADDR_SIZE_P101 17 // MAC Addr String size (fixed length). e.g. FA:39:09:67:89:AB -#define MAC_BUFF_SIZE_P101 18 // MAC Addr Buffer size, including NULL terminator. -#define MAC_SEP_CHAR_P101 ':' // MAC Addr segments are separated by a colon. -#define MAC_SEP_CNT_P101 5 // MAC Addr colon separator count for valid address. -#define MAC_STR_DEF_P101 "00:00:00:00:00:00" -#define MAC_STR_EXP_P101 "5d:89:22:56:9c:60" +# define MAC_ADDR_SIZE_P101 17 // MAC Addr String size (fixed length). e.g. FA:39:09:67:89:AB +# define MAC_BUFF_SIZE_P101 18 // MAC Addr Buffer size, including NULL terminator. +# define MAC_SEP_CHAR_P101 ':' // MAC Addr segments are separated by a colon. +# define MAC_SEP_CNT_P101 5 // MAC Addr colon separator count for valid address. +# define MAC_STR_DEF_P101 "00:00:00:00:00:00" +# define MAC_STR_EXP_P101 "5d:89:22:56:9c:60" // IP Defines. -#define IP_ADDR_SIZE_P101 15 // IPv4 Addr String size (max length). e.g. 192.168.001.255 -#define IP_BUFF_SIZE_P101 16 // IPv4 Addr Buffer size, including NULL terminator. -#define IP_MIN_SIZE_P101 7 // IPv4 Addr Minimum size, allows IP strings as short as 0.0.0.0 -#define IP_SEP_CHAR_P101 '.' // IPv4 Addr segments are separated by a dot. -#define IP_SEP_CNT_P101 3 // IPv4 Addr dot separator count for valid address. -#define IP_STR_DEF_P101 "255.255.255.255" +# define IP_ADDR_SIZE_P101 15 // IPv4 Addr String size (max length). e.g. 192.168.001.255 +# define IP_BUFF_SIZE_P101 16 // IPv4 Addr Buffer size, including NULL terminator. +# define IP_MIN_SIZE_P101 7 // IPv4 Addr Minimum size, allows IP strings as short as 0.0.0.0 +# define IP_SEP_CHAR_P101 '.' // IPv4 Addr segments are separated by a dot. +# define IP_SEP_CNT_P101 3 // IPv4 Addr dot separator count for valid address. +# define IP_STR_DEF_P101 "255.255.255.255" // Port Defines. -#define PORT_DEF_P101 9 -#define PORT_MAX_P101 65535 +# define PORT_DEF_P101 9 +# define PORT_MAX_P101 65535 // Misc Defines -#define LOG_NAME_P101 "WAKE ON LAN: " -#define NAME_MISSING 0 -#define NAME_SAFE 1 -#define NAME_UNSAFE 2 +# define LOG_NAME_P101 "WAKE ON LAN: " +# define NAME_MISSING 0 +# define NAME_SAFE 1 +# define NAME_UNSAFE 2 // ************************************************************************************************ // WOL Objects @@ -142,43 +142,35 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_WEBFORM_LOAD: { - char ipString[IP_BUFF_SIZE_P101] = {0}; - char macString[MAC_BUFF_SIZE_P101] = {0}; - addFormSubHeader(""); // Blank line, vertical space. + addFormSubHeader(EMPTY_STRING); // Blank line, vertical space. addFormHeader(F("Default Settings")); String strings[2]; LoadCustomTaskSettings(event->TaskIndex, strings, 2, CUSTOMTASK_STR_SIZE_P101); - safe_strncpy(macString, strings[1], MAC_BUFF_SIZE_P101); - addFormTextBox(F("MAC Address"), getPluginCustomArgName(1), macString, MAC_ADDR_SIZE_P101); + addFormTextBox(F("MAC Address"), getPluginCustomArgName(1), strings[1], MAC_ADDR_SIZE_P101); addFormNote(F("Format Example, " MAC_STR_EXP_P101)); - addFormSubHeader(""); // Blank line, vertical space. + addFormSubHeader(EMPTY_STRING); // Blank line, vertical space. - safe_strncpy(ipString, strings[0], IP_BUFF_SIZE_P101); - addFormTextBox(F("IPv4 Address"), getPluginCustomArgName(0), ipString, IP_ADDR_SIZE_P101); + addFormTextBox(F("IPv4 Address"), getPluginCustomArgName(0), strings[0], IP_ADDR_SIZE_P101); addFormNumericBox(F("UDP Port"), F(FORM_PORT_P101), GET_UDP_PORT_P101, 0, PORT_MAX_P101); addFormNote( - concat(F("Typical Installations use IP Address "), F(IP_STR_DEF_P101)) + - concat(F(", Port "), PORT_DEF_P101)); + concat(F("Typical Installations use IP Address " IP_STR_DEF_P101 + ", Port "), PORT_DEF_P101)); success = true; break; } case PLUGIN_WEBFORM_SAVE: { - char ipString[IP_BUFF_SIZE_P101] {0}; - char macString[MAC_BUFF_SIZE_P101] {0}; char deviceTemplate[2][CUSTOMTASK_STR_SIZE_P101] {}; String errorStr; - String msgStr; - const String wolStr = F(LOG_NAME_P101); // Check Task Name. uint8_t nameCode = safeName(event->TaskIndex); - if ((nameCode == NAME_MISSING) || (nameCode == NAME_UNSAFE)) { // Check to see if user submitted safe device name. - strcpy(ExtraTaskSettings.TaskDeviceName, String(F(DEF_TASK_NAME_P101)).c_str()); // Use default name. + if ((nameCode == NAME_MISSING) || (nameCode == NAME_UNSAFE)) { // Check to see if user submitted safe device name. + strcpy(ExtraTaskSettings.TaskDeviceName, PSTR(DEF_TASK_NAME_P101)); // Use default name. if (nameCode == NAME_UNSAFE) { errorStr = F("ALERT, Renamed Unsafe Task Name. "); @@ -186,75 +178,56 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) } // Check IP Address. - if (!safe_strncpy(ipString, webArg(getPluginCustomArgName(0)), IP_BUFF_SIZE_P101)) { + if (!safe_strncpy(deviceTemplate[0], webArg(getPluginCustomArgName(0)), IP_BUFF_SIZE_P101)) { // msgStr = getCustomTaskSettingsError(0); // Report string too long. // errorStr += msgStr; // msgStr = wolStr + msgStr; // addLog(LOG_LEVEL_INFO, msgStr); } - if (strlen(ipString) == 0) { // IP Address missing, use default value (without webform warning). - strcpy_P(ipString, String(F(IP_STR_DEF_P101)).c_str()); + if (strlen(deviceTemplate[0]) == 0) { // IP Address missing, use default value (without webform warning). + strcpy_P(deviceTemplate[0], PSTR(IP_STR_DEF_P101)); - msgStr = wolStr; - msgStr += F("Loaded Default IP = "); - msgStr += F(IP_STR_DEF_P101); - addLogMove(LOG_LEVEL_INFO, msgStr); + addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Loaded Default IP = " IP_STR_DEF_P101)); } - else if (strlen(ipString) < IP_MIN_SIZE_P101) { // IP Address too short, load default value. Warn User. - strcpy_P(ipString, String(F(IP_STR_DEF_P101)).c_str()); + 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). "); - msgStr = concat(wolStr, F("Provided IP Invalid (Using Default). ")); - msgStr += '['; - msgStr += F(IP_STR_DEF_P101); - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Provided IP Invalid (Using Default). [" IP_STR_DEF_P101 "]")); } - else if (!validateIp(ipString)) { // Unexpected IP Address value. Leave as-is, but Warn User. + else if (!validateIp(deviceTemplate[0])) { // Unexpected IP Address value. Leave as-is, but Warn User. errorStr += F("WARNING, Please Review IP Address. "); - msgStr = concat(wolStr, F("WARNING, Please Review IP Address. ")); - msgStr += '['; - msgStr += ipString; - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + addLogMove(LOG_LEVEL_INFO, strformat(F(LOG_NAME_P101 "WARNING, Please Review IP Address. [%s]"), deviceTemplate[0])); } // Check MAC Address. - if (!safe_strncpy(macString, webArg(getPluginCustomArgName(1)), MAC_BUFF_SIZE_P101)) { + if (!safe_strncpy(deviceTemplate[1], webArg(getPluginCustomArgName(1)), MAC_BUFF_SIZE_P101)) { // msgStr += getCustomTaskSettingsError(1); // Report string too long. // errorStr += msgStr; // msgStr = wolStr + msgStr; // addLog(LOG_LEVEL_INFO, msgStr); } - if (strlen(macString) == 0) { // MAC Address missing, use default value. - strcpy_P(macString, String(F(MAC_STR_DEF_P101)).c_str()); + if (strlen(deviceTemplate[1]) == 0) { // MAC Address missing, use default value. + strcpy_P(deviceTemplate[1], PSTR(MAC_STR_DEF_P101)); errorStr += F("MAC Address Not Provided, Populated with Zero Values. "); - addLogMove(LOG_LEVEL_INFO, concat(wolStr, 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. ")); } - else if (!validateMac(macString)) { // Suspicious MAC Address. Leave as-is, but warn User. + else if (!validateMac(deviceTemplate[1])) { // Suspicious MAC Address. Leave as-is, but warn User. errorStr += F("ERROR, MAC Address Invalid. "); - msgStr = concat(wolStr, F("ERROR, MAC Address Invalid. ")); - msgStr += '['; - msgStr += macString; - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + addLogMove(LOG_LEVEL_INFO, strformat(F(LOG_NAME_P101 "ERROR, MAC Address Invalid. [%s]"), deviceTemplate[1])); } - // Save the user's IP and MAC Address parameters into Custom Settings. - safe_strncpy(deviceTemplate[0], ipString, IP_BUFF_SIZE_P101); - safe_strncpy(deviceTemplate[1], macString, MAC_BUFF_SIZE_P101); - - if (errorStr.length() > 0) { // Send error messages (if any) to webform. + if (!errorStr.isEmpty()) { // Send error messages (if any) to webform. addHtmlError(errorStr); } // Save all the Task parameters. SaveCustomTaskSettings(event->TaskIndex, reinterpret_cast(&deviceTemplate), sizeof(deviceTemplate)); SET_UDP_PORT_P101 = getFormItemInt(F(FORM_PORT_P101)); - success = true; + success = true; break; } @@ -269,98 +242,71 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_WRITE: { - char ipString[IP_BUFF_SIZE_P101] = {0}; - char macString[MAC_BUFF_SIZE_P101] = {0}; - uint8_t parse_error = false; - String msgStr; String strings[2]; - String tmpString = string; - const String wolStr = F(LOG_NAME_P101); - // addLog(LOG_LEVEL_INFO, String(F("--> WOL taskIndex= ")) + String(event->TaskIndex)); // Debug + // addLog(LOG_LEVEL_INFO, concat(F("--> WOL taskIndex= "), event->TaskIndex)); // Debug - String cmd = parseString(tmpString, 1); + const String cmd = parseString(string, 1); // Warning, event->TaskIndex is invalid in PLUGIN_WRITE during controller ack calls. // So checking the Device Name needs special attention. // See https://github.com/letscontrolit/ESPEasy/issues/3317 if (validTaskIndex(event->TaskIndex) && - (cmd.equalsIgnoreCase(F(CMD_NAME_P101)) || + (equals(cmd, F(CMD_NAME_P101)) || cmd.equalsIgnoreCase(getTaskDeviceName(event->TaskIndex)))) { - - // Do not process WOL command if plugin disabled. This code is for errant situations which may never occur. - if (!Settings.TaskDeviceEnabled[event->TaskIndex]) { - // String ErrorStr = F("Plugin is Disabled, Command Ignored. "); - // addLog(LOG_LEVEL_INFO, wolStr + ErrorStr); - // SendStatus(event, ErrorStr); // Reply (echo) to sender. This will print message on browser. - break; - } - success = true; LoadCustomTaskSettings(event->TaskIndex, strings, 2, CUSTOMTASK_STR_SIZE_P101); - safe_strncpy(ipString, strings[0], IP_BUFF_SIZE_P101); - safe_strncpy(macString, strings[1], MAC_BUFF_SIZE_P101); - String paramMac = parseString(tmpString, 2); // MAC Address (optional) - String paramIp = parseString(tmpString, 3); // IP Address (optional) - String paramPort = parseString(tmpString, 4); // UDP Port (optional) + String paramMac = parseString(string, 2); // MAC Address (optional) + String paramIp = parseString(string, 3); // IP Address (optional) + String paramPort = parseString(string, 4); // UDP Port (optional) // Populate Parameters with default settings when missing from command line. - if (paramMac.isEmpty()) { // Missing from command line, use default setting. - paramMac = macString; + if (paramMac.isEmpty()) { // Missing from command line, use default setting. + paramMac = strings[1]; } if (paramIp.isEmpty()) { // Missing from command line, use default setting. - paramIp = ipString; + paramIp = strings[0]; } if (paramPort.isEmpty()) { - LoadTaskSettings(event->TaskIndex); - paramPort = GET_UDP_PORT_P101; // Get default Port from user settings. + LoadTaskSettings(event->TaskIndex); // FIXME Not sure if this is still needed... + paramPort = GET_UDP_PORT_P101; // Get default Port from user settings. } // Validate the MAC Address. if (!validateMac(paramMac)) { - parse_error = true; - msgStr = concat(wolStr, F("Error, MAC Addr Invalid [")); - msgStr += paramMac; - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + success = false; + addLogMove(LOG_LEVEL_INFO, strformat(F(LOG_NAME_P101 "Error, MAC Addr Invalid [%s]"), paramMac.c_str())); } // Validate IP Address. if (!validateIp(paramIp)) { - parse_error = true; - msgStr = concat(wolStr, F("Error, IP Addr Invalid [")); - msgStr += paramIp; - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + success = false; + addLogMove(LOG_LEVEL_INFO, strformat(F(LOG_NAME_P101 "Error, IP Addr Invalid [%s]"), paramIp.c_str())); } // Validate UDP Port. if (!validatePort(paramPort)) { - parse_error = true; - msgStr = concat(wolStr, F("Error, Port Invalid [")); - msgStr += paramPort; - msgStr += ']'; - addLogMove(LOG_LEVEL_INFO, msgStr); + success = false; + addLogMove(LOG_LEVEL_INFO, strformat(F(LOG_NAME_P101 "Error, Port Invalid [%s]"), paramPort.c_str())); } // If no errors we can send Magic Packet. - if (parse_error == true) { - msgStr = F("CMD Syntax Error"); - addLogMove(LOG_LEVEL_INFO, concat(wolStr, msgStr)); + if (!success) { + String msgStr = F("CMD Syntax Error"); + addLogMove(LOG_LEVEL_INFO, concat(F(LOG_NAME_P101), msgStr)); msgStr += F("
"); SendStatus(event, msgStr); // Reply (echo) to sender. This will print message on browser. } - else { // No parsing errors, Send Magic Packet (Wake Up the MAC). + else { // No parsing errors, Send Magic Packet (Wake Up the MAC). addLogMove(LOG_LEVEL_INFO, strformat( - F("%sMAC= %s, IP= %s, Port= %s"), - wolStr.c_str(), - paramMac.c_str(), - paramIp.c_str(), - paramPort.c_str())); + F(LOG_NAME_P101 "MAC= %s, IP= %s, Port= %s"), + paramMac.c_str(), + paramIp.c_str(), + paramPort.c_str())); // Send Magic Packet. if (WiFi.status() == WL_CONNECTED) { @@ -371,11 +317,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, concat(wolStr, F("Error, Magic Packet Failed (check parameters)"))); + addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, Magic Packet Failed (check parameters)")); } } else { - addLogMove(LOG_LEVEL_INFO, concat(wolStr, F("Error, WiFi Off-Line"))); + addLogMove(LOG_LEVEL_INFO, F(LOG_NAME_P101 "Error, WiFi Off-Line")); } } } @@ -390,7 +336,7 @@ boolean Plugin_101(uint8_t function, struct EventStruct *event, String& string) // Arg: task index // Returns: NAME_SAFE, NAME_MISSING, or NAME_USAFE. uint8_t safeName(taskIndex_t index) { - String devName = getTaskDeviceName(index); + String devName = getTaskDeviceName(index); if (devName.isEmpty()) { return NAME_MISSING; @@ -429,14 +375,13 @@ bool validateIp(const String& ipStr) { // Return true if MAC string appears legit. bool validateMac(const String& macStr) { uint8_t pos = 0; - char hexChar; if (macStr.length() != MAC_ADDR_SIZE_P101) { return false; } - for (uint8_t strPos = 0; strPos < MAC_ADDR_SIZE_P101; strPos++) { - uint8_t mod = strPos % 3; + for (uint8_t strPos = 0; strPos < MAC_ADDR_SIZE_P101; ++strPos) { + const uint8_t mod = strPos % 3; if (mod == 2) { if (macStr[strPos] == MAC_SEP_CHAR_P101) { // Must be a colon in the third position. @@ -447,9 +392,7 @@ bool validateMac(const String& macStr) { } } else { - hexChar = macStr[strPos]; - - if (!isHexadecimalDigit(hexChar)) { + if (!isHexadecimalDigit(macStr.charAt(strPos))) { return false; } } diff --git a/src/_P102_PZEM004Tv3.ino b/src/_P102_PZEM004Tv3.ino index f202b9974..726d19dd8 100644 --- a/src/_P102_PZEM004Tv3.ino +++ b/src/_P102_PZEM004Tv3.ino @@ -33,7 +33,7 @@ # define P102_QUERY1_DFLT 0 // Voltage (V) # define P102_QUERY2_DFLT 1 // Current (A) # define P102_QUERY3_DFLT 2 // Power (W) -# define P102_QUERY4_DFLT 3 // Energy (WH) +# define P102_QUERY4_DFLT 3 // Energy (kWH) # define P102_NR_OUTPUT_VALUES 4 # define P102_NR_OUTPUT_OPTIONS 6 # define P102_QUERY1_CONFIG_POS 3 @@ -374,7 +374,7 @@ const __FlashStringHelper* p102_getQueryString(uint8_t query) { case 0: return F("Voltage_V"); case 1: return F("Current_A"); case 2: return F("Power_W"); - case 3: return F("Energy_WH"); + case 3: return F("Energy_kWh"); case 4: return F("Power_Factor_cosphi"); case 5: return F("Frequency Hz"); } diff --git a/src/_P105_AHT.ino b/src/_P105_AHT.ino index 7c6606090..f0de4315b 100644 --- a/src/_P105_AHT.ino +++ b/src/_P105_AHT.ino @@ -1,216 +1,220 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P105 - -// ####################################################################################################### -// ######################## Plugin 105 AHT I2C Temperature and Humidity Sensor ########################## -// ####################################################################################################### -// data sheet AHT10: https://wiki.liutyi.info/display/ARDUINO/AHT10 -// device AHT10: http://www.aosong.com/en/products-40.html -// device and manual AHT20: http://www.aosong.com/en/products-32.html -// device and manual AHT21: http://www.aosong.com/en/products-60.html - -/* AHT10/15/20 - Temperature and Humidity - * - * (Comment copied from _P248_TempHumidity_AHT1x.ino) - * - * AHT1x I2C Address: 0x38, 0x39 - * the driver supports two I2c adresses but only one Sensor allowed. - * - * ATTENTION: The AHT10/15 Sensor is incompatible with other I2C devices on I2C bus. - * - * The Datasheet write: - * "Only a single AHT10 can be connected to the I2C bus and no other I2C - * devices can be connected". - * - * after lot of search and tests, now is confirmed that works only reliable with one sensor - * on I2C Bus - */ - -// History: -// 2021-08-01 tonhuisman: Plugin migrated from ESPEsyPluginPlayground repository -// Minor adjustments, changed castings to use static_cast(var) method, -// Added check for other I2C devoces configured on ESPEasy tasks to give a warning -// about I2C incmopatibility, for AHT10 device only -// 2021-03 sakinit: Initial plugin, added on ESPEasyPluginPlayground - -# include "src/PluginStructs/P105_data_struct.h" - -# define PLUGIN_105 -# define PLUGIN_ID_105 105 -# define PLUGIN_NAME_105 "Environment - AHT10/AHT2x" -# define PLUGIN_VALUENAME1_105 "Temperature" -# define PLUGIN_VALUENAME2_105 "Humidity" - - -boolean Plugin_105(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_105; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_105); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_105)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_105)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[2] = { 0x38, 0x39 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); - addFormNote(F("SDO Low=0x38, High=0x39. NB: Only available on AHT10 sensors.")); - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - if (static_cast(PCONFIG(1)) == AHTx_device_type::AHT10_DEVICE) { - bool hasOtherI2CDevices = false; - - for (taskIndex_t x = 0; validTaskIndex(x) && !hasOtherI2CDevices; x++) { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - - if (validDeviceIndex(DeviceIndex) - && (Settings.TaskDeviceDataFeed[x] == 0) - && ((Device[DeviceIndex].Type == DEVICE_TYPE_I2C) - # ifdef PLUGIN_USES_SERIAL // Has I2C Serial option - || (Device[DeviceIndex].Type == DEVICE_TYPE_SERIAL) - || (Device[DeviceIndex].Type == DEVICE_TYPE_SERIAL_PLUS1) - # endif // ifdef PLUGIN_USES_SERIAL - ) - ) { - hasOtherI2CDevices = true; - } - } - - if (hasOtherI2CDevices) { - addRowLabel(EMPTY_STRING, EMPTY_STRING); - addHtmlDiv(F("note warning"), - F("Attention: Sensor model AHT10 may cause I2C issues when combined with other I2C devices on the same bus!")); - } - } - { - const __FlashStringHelper *options[] = { F("AHT10"), F("AHT20"), F("AHT21") }; - const int indices[] = { static_cast(AHTx_device_type::AHT10_DEVICE), - static_cast(AHTx_device_type::AHT20_DEVICE), - static_cast(AHTx_device_type::AHT21_DEVICE) }; - addFormSelector(F("Sensor model"), F("ahttype"), 3, options, indices, PCONFIG(1), true); - addFormNote(F("Changing Sensor model will reload the page.")); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(1) = getFormItemInt(F("ahttype")); - - if (static_cast(PCONFIG(1)) != AHTx_device_type::AHT10_DEVICE) { - PCONFIG(0) = 0x38; // AHT20/AHT21 only support a single I2C address. - } else { - PCONFIG(0) = getFormItemInt(F("i2c_addr")); - } - success = true; - break; - } - - case PLUGIN_INIT: - { - success = initPluginTaskData( - event->TaskIndex, - new (std::nothrow) P105_data_struct(PCONFIG(0), static_cast(PCONFIG(1)))); - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - P105_data_struct *P105_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P105_data) { - if (P105_data->updateMeasurements(event->TaskIndex)) { - // Update was succesfull, schedule a read. - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - } - } - break; - } - - case PLUGIN_READ: - { - P105_data_struct *P105_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P105_data) { - if (P105_data->state != AHTx_state::AHTx_New_values) { - break; - } - P105_data->state = AHTx_state::AHTx_Values_read; - - UserVar.setFloat(event->TaskIndex, 0, P105_data->getTemperature()); - UserVar.setFloat(event->TaskIndex, 1, P105_data->getHumidity()); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(60); // Prevent re-allocation - log = P105_data->getDeviceName(); - log += F(" : Addr: 0x"); - log += String(PCONFIG(0), HEX); - addLogMove(LOG_LEVEL_INFO, log); - log = P105_data->getDeviceName(); - log += F(" : Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - log += F(" : Humidity: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - } - break; - } - } - return success; -} - -#endif // USES_P105 +#include "_Plugin_Helper.h" +#ifdef USES_P105 + +// ####################################################################################################### +// ######################## Plugin 105 AHT I2C Temperature and Humidity Sensor ########################## +// ####################################################################################################### +// data sheet AHT10: https://wiki.liutyi.info/display/ARDUINO/AHT10 +// device AHT10: http://www.aosong.com/en/products-40.html +// device and manual AHT20: http://www.aosong.com/en/products-32.html +// device and manual AHT21: http://www.aosong.com/en/products-60.html + +/* AHT10/15/20 - Temperature and Humidity + * + * (Comment copied from _P248_TempHumidity_AHT1x.ino) + * + * AHT1x I2C Address: 0x38, 0x39 + * the driver supports two I2c adresses but only one Sensor allowed. + * + * ATTENTION: The AHT10/15 Sensor is incompatible with other I2C devices on I2C bus. + * + * The Datasheet write: + * "Only a single AHT10 can be connected to the I2C bus and no other I2C + * devices can be connected". + * + * after lot of search and tests, now is confirmed that works only reliable with one sensor + * on I2C Bus + */ + +/** History: + * 2024-04-28 tonhuisman: Update plugin name and documentation as DHT20 and AM2301B actually contain an AHT20! + * DHT20: https://www.adafruit.com/product/5183 (Description) + * AM2301B: https://www.adafruit.com/product/5181 (Description) + * 2021-08-01 tonhuisman: Plugin migrated from ESPEsyPluginPlayground repository + * Minor adjustments, changed castings to use static_cast(var) method, + * Added check for other I2C devices configured on ESPEasy tasks to give a warning + * about I2C incompatibility, for AHT10/AHT15 device only + * 2021-03 sakinit: Initial plugin, added on ESPEasyPluginPlayground + */ + +# include "src/PluginStructs/P105_data_struct.h" + +# define PLUGIN_105 +# define PLUGIN_ID_105 105 +# define PLUGIN_NAME_105 "Environment - AHT1x/AHT2x/DHT20/AM2301B" +# define PLUGIN_VALUENAME1_105 "Temperature" +# define PLUGIN_VALUENAME2_105 "Humidity" + + +boolean Plugin_105(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_105; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_105); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_105)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_105)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[2] = { 0x38, 0x39 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, PCONFIG(0)); + addFormNote(F("SDO Low=0x38, High=0x39. NB: Only available on AHT1x sensors.")); + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = PCONFIG(0); + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + PCONFIG(1) = static_cast(AHTx_device_type::AHT20_DEVICE); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + if (static_cast(PCONFIG(1)) == AHTx_device_type::AHT10_DEVICE) { + bool hasOtherI2CDevices = false; + + for (taskIndex_t x = 0; validTaskIndex(x) && !hasOtherI2CDevices; ++x) { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + + if (validDeviceIndex(DeviceIndex) + && (Settings.TaskDeviceDataFeed[x] == 0) + && ((Device[DeviceIndex].Type == DEVICE_TYPE_I2C) + # ifdef PLUGIN_USES_SERIAL // Has I2C Serial option + || (Device[DeviceIndex].Type == DEVICE_TYPE_SERIAL) + || (Device[DeviceIndex].Type == DEVICE_TYPE_SERIAL_PLUS1) + # endif // ifdef PLUGIN_USES_SERIAL + ) + ) { + hasOtherI2CDevices = true; + } + } + + if (hasOtherI2CDevices) { + addRowLabel(EMPTY_STRING, EMPTY_STRING); + addHtmlDiv(F("note warning"), + F("Attention: Sensor model AHT1x may cause I2C issues when combined with other I2C devices on the same bus!")); + } + } + { + const __FlashStringHelper *options[] = { F("AHT1x"), F("AHT20"), F("AHT21") }; + const int indices[] = { static_cast(AHTx_device_type::AHT10_DEVICE), + static_cast(AHTx_device_type::AHT20_DEVICE), + static_cast(AHTx_device_type::AHT21_DEVICE) }; + addFormSelector(F("Sensor model"), F("ahttype"), 3, options, indices, PCONFIG(1), true); + addFormNote(F("Changing Sensor model will reload the page.")); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + PCONFIG(1) = getFormItemInt(F("ahttype")); + + if (static_cast(PCONFIG(1)) != AHTx_device_type::AHT10_DEVICE) { + PCONFIG(0) = 0x38; // AHT20/AHT21 only support a single I2C address. + } else { + PCONFIG(0) = getFormItemInt(F("i2c_addr")); + } + success = true; + break; + } + + case PLUGIN_INIT: + { + success = initPluginTaskData( + event->TaskIndex, + new (std::nothrow) P105_data_struct(PCONFIG(0), static_cast(PCONFIG(1)))); + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P105_data_struct *P105_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P105_data) { + if (P105_data->updateMeasurements(event->TaskIndex)) { + // Update was succesfull, schedule a read. + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + } + } + break; + } + + case PLUGIN_READ: + { + P105_data_struct *P105_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P105_data) { + if (P105_data->state != AHTx_state::AHTx_New_values) { + break; + } + P105_data->state = AHTx_state::AHTx_Values_read; + + UserVar.setFloat(event->TaskIndex, 0, P105_data->getTemperature()); + UserVar.setFloat(event->TaskIndex, 1, P105_data->getHumidity()); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("%s : Addr: 0x%02x"), P105_data->getDeviceName().c_str(), PCONFIG(0))); + addLogMove(LOG_LEVEL_INFO, + strformat(F("%s : Temperature: %s : Humidity: %s"), + P105_data->getDeviceName().c_str(), + formatUserVarNoCheck(event, 0).c_str(), + formatUserVarNoCheck(event, 1).c_str())); + } + success = true; + } + break; + } + } + return success; +} + +#endif // USES_P105 diff --git a/src/_P106_BME680.ino b/src/_P106_BME680.ino index 4ce6a7fc1..66836f659 100644 --- a/src/_P106_BME680.ino +++ b/src/_P106_BME680.ino @@ -77,10 +77,10 @@ boolean Plugin_106(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { 0x77, 0x76 }; + const uint8_t i2cAddressValues[] = { 0x76, 0x77 }; if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P106_I2C_ADDRESS); + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P106_I2C_ADDRESS, 0x77); addFormNote(F("SDO Low=0x76, High=0x77")); } else { success = intArrayContains(2, i2cAddressValues, event->Par1); @@ -97,6 +97,14 @@ boolean Plugin_106(uint8_t function, struct EventStruct *event, String& string) } # endif // if FEATURE_I2C_GET_ADDRESS + case PLUGIN_SET_DEFAULTS: + { + P106_I2C_ADDRESS = 0x77; // Default address + + success = true; + break; + } + case PLUGIN_WEBFORM_LOAD: { addFormNumericBox(F("Altitude"), F("elev"), P106_ALTITUDE); diff --git a/src/_P107_SI1145.ino b/src/_P107_SI1145.ino index f84eac576..0f7481411 100644 --- a/src/_P107_SI1145.ino +++ b/src/_P107_SI1145.ino @@ -1,117 +1,111 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P107 - -// ####################################################################################################### -// #################################### Plugin-107: SI1145 - UV index / IR / visible #################### -// ####################################################################################################### - -# include "src/PluginStructs/P107_data_struct.h" - -# define PLUGIN_107 -# define PLUGIN_ID_107 107 -# define PLUGIN_NAME_107 "UV - SI1145" -# define PLUGIN_VALUENAME1_107 "Visible" -# define PLUGIN_VALUENAME2_107 "Infra" -# define PLUGIN_VALUENAME3_107 "UV" - -boolean Plugin_107(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_107; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 3; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_107); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_107)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_107)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_107)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - { - success = (event->Par1 == 0x60); - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = 0x60; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P107_data_struct()); - P107_data_struct *P107_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - success = (nullptr != P107_data && P107_data->begin()); - break; - } - - case PLUGIN_READ: - { - P107_data_struct *P107_data = - static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P107_data) { - break; - } - - if (!P107_data->begin()) { - break; - } - delay(8); // Measurement Rate: 255 * 31.25uS = 8ms - - UserVar.setFloat(event->TaskIndex, 0, P107_data->uv.readVisible()); - UserVar.setFloat(event->TaskIndex, 1, P107_data->uv.readIR()); - UserVar.setFloat(event->TaskIndex, 2, P107_data->uv.readUV() / 100.0f); - - P107_data->uv.reset(); // Stop the sensor reading - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SI1145: Visible: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - log = F("SI1145: Infrared: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - log = F("SI1145: UV index: "); - log += formatUserVarNoCheck(event->TaskIndex, 2); - addLogMove(LOG_LEVEL_INFO, log); - } - success = true; - break; - } - } - return success; -} - -#endif // ifdef USES_P107 +#include "_Plugin_Helper.h" +#ifdef USES_P107 + +// ####################################################################################################### +// #################################### Plugin-107: SI1145 - UV index / IR / visible #################### +// ####################################################################################################### + +# include "src/PluginStructs/P107_data_struct.h" + +# define PLUGIN_107 +# define PLUGIN_ID_107 107 +# define PLUGIN_NAME_107 "UV - SI1145" +# define PLUGIN_VALUENAME1_107 "Visible" +# define PLUGIN_VALUENAME2_107 "Infra" +# define PLUGIN_VALUENAME3_107 "UV" + +boolean Plugin_107(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_107; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_107); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_107)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_107)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_107)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = (event->Par1 == 0x60); + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = 0x60; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P107_data_struct()); + P107_data_struct *P107_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P107_data && P107_data->begin()); + break; + } + + case PLUGIN_READ: + { + P107_data_struct *P107_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P107_data) { + break; + } + + if (!P107_data->begin()) { + break; + } + delay(8); // Measurement Rate: 255 * 31.25uS = 8ms + + UserVar.setFloat(event->TaskIndex, 0, P107_data->uv.readVisible()); + UserVar.setFloat(event->TaskIndex, 1, P107_data->uv.readIR()); + UserVar.setFloat(event->TaskIndex, 2, P107_data->uv.readUV() / 100.0f); + + P107_data->uv.reset(); // Stop the sensor reading + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: Visible: "), formatUserVarNoCheck(event, 0))); + addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: Infrared: "), formatUserVarNoCheck(event, 1))); + addLogMove(LOG_LEVEL_INFO, concat(F("SI1145: UV index: "), formatUserVarNoCheck(event, 2))); + } + success = true; + break; + } + } + return success; +} + +#endif // ifdef USES_P107 diff --git a/src/_P108_DDS238.ino b/src/_P108_DDS238.ino index 3b16b80ff..e2209716e 100644 --- a/src/_P108_DDS238.ino +++ b/src/_P108_DDS238.ino @@ -139,13 +139,7 @@ boolean Plugin_108(uint8_t function, struct EventStruct *event, String& string) addRowLabel(F("Checksum (pass/fail/nodata)")); uint32_t reads_pass, reads_crc_failed, reads_nodata; P108_data->modbus.getStatistics(reads_pass, reads_crc_failed, reads_nodata); - String chksumStats; - chksumStats = reads_pass; - chksumStats += '/'; - chksumStats += reads_crc_failed; - chksumStats += '/'; - chksumStats += reads_nodata; - addHtml(chksumStats); + addHtml(strformat(F("%d/%d/%d"), reads_pass, reads_crc_failed, reads_nodata)); addFormSubHeader(F("Logged Values")); p108_showValueLoadPage(P108_QUERY_Wh_imp, event); @@ -243,7 +237,7 @@ boolean Plugin_108(uint8_t function, struct EventStruct *event, String& string) if ((nullptr != P108_data) && P108_data->isInitialized()) { for (int i = 0; i < P108_NR_OUTPUT_VALUES; ++i) { - UserVar.setFloat(event->TaskIndex, i, p108_readValue(PCONFIG(i + P108_QUERY1_CONFIG_POS), event)); + UserVar.setFloat(event->TaskIndex, i, p108_readValue(PCONFIG(i + P108_QUERY1_CONFIG_POS), event)); delay(1); } diff --git a/src/_P110_VL53L0X.ino b/src/_P110_VL53L0X.ino index 58914d61b..0f45c94ac 100644 --- a/src/_P110_VL53L0X.ino +++ b/src/_P110_VL53L0X.ino @@ -1,160 +1,176 @@ -#ifdef USES_P110 - -// ####################################################################################################### -// ########################### Plugin 110 VL53L0X I2C Ranging LIDAR ################################# -// ####################################################################################################### -// ###################################### stefan@clumsy.ch ########################################## -// ####################################################################################################### - -// Changelog: -// 2022-06-22, tonhuisman: Remove delay() call from begin(), handle delay via PLUGIN_FIFTY_PER_SECOND -// Reformat source (uncrustify) -// 2021-04-05, tonhuisman: Removed check for VL53L1X as that is not compatible with this driver (Got its own plugin P113) -// 2021-02-06, tonhuisman: Refactored to use PluginStruct to enable multiple-instance use with an I2C Multiplexer -// 2021-01-07, tonhuisman: Moved from PluginPlayground (P133) to main repo (P110), fixed some issues - -// needs VL53L0X library from pololu https://github.com/pololu/vl53l0x-arduino - -#include "src/PluginStructs/P110_data_struct.h" - -#define PLUGIN_110 -#define PLUGIN_ID_110 110 -#define PLUGIN_NAME_110 "Distance - VL53L0X (200cm)" -#define PLUGIN_VALUENAME1_110 "Distance" - - -/////////////////////////// -// VL53L0X Command Codes // -/////////////////////////// - -boolean Plugin_110(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_110; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 1; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_110); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_110)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x29, 0x30 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2cAddr"), 2, i2cAddressValues, P110_I2C_ADDRESS); - #ifndef BUILD_NO_DEBUG - addFormNote(F("SDO Low=0x29, High=0x30")); - #endif // ifndef BUILD_NO_DEBUG - } else { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P110_I2C_ADDRESS; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - { - const __FlashStringHelper *optionsMode2[3] = { - F("Normal"), - F("Fast"), - F("Accurate") }; - const int optionValuesMode2[3] = { 80, 20, 320 }; - addFormSelector(F("Timing"), F("ptiming"), 3, optionsMode2, optionValuesMode2, P110_TIMING); - } - - { - const __FlashStringHelper *optionsMode3[2] = { - F("Normal"), - F("Long") }; - const int optionValuesMode3[2] = { 0, 1 }; - addFormSelector(F("Range"), F("prange"), 2, optionsMode3, optionValuesMode3, P110_RANGE); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P110_I2C_ADDRESS = getFormItemInt(F("i2cAddr")); - P110_TIMING = getFormItemInt(F("ptiming")); - P110_RANGE = getFormItemInt(F("prange")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P110_data_struct(P110_I2C_ADDRESS, P110_TIMING, P110_RANGE == 1)); - P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); - - success = (nullptr != P110_data) && P110_data->begin(); // Start the sensor - break; - } - case PLUGIN_READ: - { - P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P110_data) { - long dist = P110_data->readDistance(); - - success = P110_data->isReadSuccessful(); - - if (success) { - UserVar.setFloat(event->TaskIndex, 0, dist); // Value is classified as invalid when > 8190, so no conversion or 'split' needed - } - } - break; - } - - case PLUGIN_FIFTY_PER_SECOND: // Handle startup delay - { - P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P110_data) { - success = P110_data->plugin_fifty_per_second(); - } - break; - } - } - return success; -} - -#endif // ifdef USES_P110 +#ifdef USES_P110 + +// ####################################################################################################### +// ########################### Plugin 110 VL53L0X I2C Ranging LIDAR ################################# +// ####################################################################################################### +// ###################################### stefan@clumsy.ch ########################################## +// ####################################################################################################### + +/** Changelog: + * 2024-04-27 tonhuisman: Read sensor asynchronously to enable (the new default) trigger on changed value + * 2024-04-26 tonhuisman: Migrate 'Send event when value unchanged' and 'Trigger delta' settings from P113 (at last...) + * Add Direction value, -1 = closer, 0 = unchanged, 1 = further away + * 2022-06-22 tonhuisman: Remove delay() call from begin(), handle delay via PLUGIN_FIFTY_PER_SECOND + * Reformat source (uncrustify) + * 2021-04-05 tonhuisman: Removed check for VL53L1X as that is not compatible with this driver (Got its own plugin P113) + * 2021-02-06 tonhuisman: Refactored to use PluginStruct to enable multiple-instance use with an I2C Multiplexer + * 2021-01-07 tonhuisman: Moved from PluginPlayground (P133) to main repo (P110), fixed some issues + */ + +// needs VL53L0X library from pololu https://github.com/pololu/vl53l0x-arduino + +#include "src/PluginStructs/P110_data_struct.h" + +#define PLUGIN_110 +#define PLUGIN_ID_110 110 +#define PLUGIN_NAME_110 "Distance - VL53L0X (200cm)" +#define PLUGIN_VALUENAME1_110 "Distance" +#define PLUGIN_VALUENAME2_110 "Direction" + + +/////////////////////////// +// VL53L0X Command Codes // +/////////////////////////// + +boolean Plugin_110(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_110; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_110); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_110)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_110)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x29, 0x30 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2cAddr"), 2, i2cAddressValues, P110_I2C_ADDRESS); + #ifndef BUILD_NO_DEBUG + addFormNote(F("SDO Low=0x29, High=0x30")); + #endif // ifndef BUILD_NO_DEBUG + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + #if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P110_I2C_ADDRESS; + success = true; + break; + } + #endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *optionsMode2[3] = { + F("Normal"), + F("Fast"), + F("Accurate") }; + const int optionValuesMode2[3] = { 80, 20, 320 }; + addFormSelector(F("Timing"), F("ptiming"), 3, optionsMode2, optionValuesMode2, P110_TIMING); + } + + { + const __FlashStringHelper *optionsMode3[2] = { + F("Normal"), + F("Long") }; + const int optionValuesMode3[2] = { 0, 1 }; + addFormSelector(F("Range"), F("prange"), 2, optionsMode3, optionValuesMode3, P110_RANGE); + } + addFormCheckBox(F("Send event when value unchanged"), F("notchanged"), P110_SEND_ALWAYS == 1); + addFormNote(F("When checked, 'Trigger delta' setting is ignored!")); + + addFormNumericBox(F("Trigger delta"), F("delta"), P110_DELTA, 0, 100); + addUnit(F("0-100mm")); + #ifndef LIMIT_BUILD_SIZE + addFormNote(F("Minimal change in Distance to trigger an event.")); + #endif // ifndef LIMIT_BUILD_SIZE + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P110_I2C_ADDRESS = getFormItemInt(F("i2cAddr")); + P110_TIMING = getFormItemInt(F("ptiming")); + P110_RANGE = getFormItemInt(F("prange")); + P110_SEND_ALWAYS = isFormItemChecked(F("notchanged")) ? 1 : 0; + P110_DELTA = getFormItemInt(F("delta")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P110_data_struct(P110_I2C_ADDRESS, P110_TIMING, P110_RANGE == 1)); + P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P110_data) { + const uint32_t interval_ms = Settings.TaskDeviceTimer[event->TaskIndex] * 1000; + + // Clear the "previous" distance so there will be a new result when starting the task + UserVar.setFloat(event->TaskIndex, 3, -1); + success = P110_data->begin(interval_ms); // Start the sensor + } + break; + } + case PLUGIN_READ: + { + P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P110_data) { + success = P110_data->plugin_read(event); + } + break; + } + case PLUGIN_TEN_PER_SECOND: // Handle startup delay and sensor reading + { + P110_data_struct *P110_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P110_data) { + success = P110_data->check_reading_ready(event); + } + break; + } + } + return success; +} + +#endif // ifdef USES_P110 diff --git a/src/_P112_AS7265x.ino b/src/_P112_AS7265x.ino index dfc520d03..324827de0 100644 --- a/src/_P112_AS7265x.ino +++ b/src/_P112_AS7265x.ino @@ -144,7 +144,9 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) }; addFormSelector(F("Integration Time"), F("IntegrationTime"), 6, optionsMode2, optionValuesMode2, PCONFIG_LONG(1)); } + # ifndef BUILD_NO_DEBUG addFormNote(F("Raw Readings shall not reach the upper limit of 65535 (Sensor Saturation).")); + # endif // ifndef BUILD_NO_DEBUG addFormSubHeader(F("LED settings")); addFormCheckBox(F("Blue"), PCONFIG_LABEL(0), PCONFIG(0)); @@ -169,7 +171,9 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) addFormSelector(EMPTY_STRING, PCONFIG_LABEL(1), 4, optionsMode3, optionValuesMode3, PCONFIG(1)); } addHtml(F(" Current Limit")); + # ifndef BUILD_NO_DEBUG addFormNote(F("Activate Status LEDs only for debugging purpose.")); + # endif // ifndef BUILD_NO_DEBUG { // White LED has max forward current of 120mA @@ -239,12 +243,13 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) PCONFIG_LONG(0) = getFormItemInt(F("Gain")); PCONFIG_LONG(1) = getFormItemInt(F("IntegrationTime")); PCONFIG(0) = isFormItemChecked(PCONFIG_LABEL(0)); + for (int i = 1; i <= 4; ++i) { - PCONFIG(i) = getFormItemInt(PCONFIG_LABEL(i)); + PCONFIG(i) = getFormItemInt(PCONFIG_LABEL(i)); } - PCONFIG(5) = isFormItemChecked(PCONFIG_LABEL(5)); - PCONFIG(6) = isFormItemChecked(PCONFIG_LABEL(6)); - success = true; + PCONFIG(5) = isFormItemChecked(PCONFIG_LABEL(5)); + PCONFIG(6) = isFormItemChecked(PCONFIG_LABEL(6)); + success = true; break; } @@ -281,12 +286,12 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat( - F("AS7265X: AMS Device Type: 0x%X HW ver: 0x%X FW ver: %X.%X.%X"), - P112_data->sensor.getDeviceType(), - P112_data->sensor.getHardwareVersion(), - P112_data->sensor.getMajorFirmwareVersion(), - P112_data->sensor.getPatchFirmwareVersion(), - P112_data->sensor.getBuildFirmwareVersion())); + F("AS7265X: AMS Device Type: 0x%X HW ver: 0x%X FW ver: %X.%X.%X"), + P112_data->sensor.getDeviceType(), + P112_data->sensor.getHardwareVersion(), + P112_data->sensor.getMajorFirmwareVersion(), + P112_data->sensor.getPatchFirmwareVersion(), + P112_data->sensor.getBuildFirmwareVersion())); } success = true; @@ -369,10 +374,10 @@ boolean Plugin_112(uint8_t function, struct EventStruct *event, String& string) case 18: queueEvent(event->TaskIndex, 940, PCONFIG(6) ? P112_data->sensor.getCalibratedL() : P112_data->sensor.getL()); - P112_data->MeasurementStatus = 0; // FIXME Why is this only executed for case 18? + P112_data->MeasurementStatus = 0; // FIXME Why is this only executed for case 18? UserVar.setFloat(event->TaskIndex, 2, 0); - if (PCONFIG(0)) // Blue Status LED + if (PCONFIG(0)) // Blue Status LED { P112_data->sensor.enableIndicator(); } diff --git a/src/_P113_VL53L1X.ino b/src/_P113_VL53L1X.ino index 5a4355a39..bf91696f3 100644 --- a/src/_P113_VL53L1X.ino +++ b/src/_P113_VL53L1X.ino @@ -1,184 +1,193 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P113 - -// ####################################################################################################### -// ########################### Plugin 113 VL53L1X I2C Ranging LIDAR ################################# -// ####################################################################################################### - -/** Changelog: - * 2023-08-11, tonhuisman: Fix issue not surfacing before, that the library right-shifts the I2C address when that is set... - * Also use new/delete on sensor object (code improvement) - * Limit the selection list of I2C addresses to 1 item, as changing the I2C address of the sensor does not work as - * intended/expected - * 2021-04-06, tonhuisman: Remove Interval optional attribute to avoid system overload, cleanup source - * 2021-04-05, tonhuisman: Add VL53L1X Time of Flight sensor to main repo (similar to but not compatible with VL53L0X) - */ - -// needs SparkFun_VL53L1X library from https://github.com/sparkfun/SparkFun_VL53L1X_Arduino_Library - -# include "src/PluginStructs/P113_data_struct.h" - -# define PLUGIN_113 -# define PLUGIN_ID_113 113 -# define PLUGIN_NAME_113 "Distance - VL53L1X (400cm)" -# define PLUGIN_VALUENAME1_113 "Distance" -# define PLUGIN_VALUENAME2_113 "Ambient" - - -boolean Plugin_113(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_113; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_113); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_113)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_113)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - # define P113_ACTIVE_I2C_ADDRESSES 1 // Setting the address messes up the sensor, so disabled - const uint8_t i2cAddressValues[] = { 0x29, 0x30 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c"), P113_ACTIVE_I2C_ADDRESSES, i2cAddressValues, PCONFIG(0)); - } else { - success = intArrayContains(P113_ACTIVE_I2C_ADDRESSES, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = PCONFIG(0); - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_WEBFORM_LOAD: - { - { - const __FlashStringHelper *optionsMode2[] = { - F("100ms (Normal)"), - F("20ms (Fastest)"), - F("33ms (Fast)"), - F("50ms"), - F("200ms (Accurate)"), - F("500ms"), - }; - const int optionValuesMode2[] = { 100, 20, 33, 50, 200, 500 }; - addFormSelector(F("Timing"), F("timing"), 6, optionsMode2, optionValuesMode2, PCONFIG(1)); - } - - { - const __FlashStringHelper *optionsMode3[] = { - F("Normal (~130cm)"), - F("Long (~400cm)"), - }; - const int optionValuesMode3[2] = { 0, 1 }; - addFormSelector(F("Range"), F("range"), 2, optionsMode3, optionValuesMode3, PCONFIG(2)); - } - addFormCheckBox(F("Send event when value unchanged"), F("notchanged"), PCONFIG(3) == 1); - addFormNote(F("When checked, 'Trigger delta' setting is ignored!")); - - addFormNumericBox(F("Trigger delta"), F("delta"), PCONFIG(4), 0, 100); - addUnit(F("0-100mm")); - addFormNote(F("Minimal change in Distance to trigger an event.")); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - PCONFIG(0) = getFormItemInt(F("i2c")); - PCONFIG(1) = getFormItemInt(F("timing")); - PCONFIG(2) = getFormItemInt(F("range")); - PCONFIG(3) = isFormItemChecked(F("notchanged")) ? 1 : 0; - PCONFIG(4) = getFormItemInt(F("delta")); - - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P113_data_struct(PCONFIG(0), PCONFIG(1), PCONFIG(2) == 1)); - P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); - - success = (nullptr != P113_data) && P113_data->begin(); // Start the sensor - break; - } - - case PLUGIN_EXIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P113_data) { - uint16_t dist = P113_data->readDistance(); - uint16_t ambient = P113_data->readAmbient(); - bool triggered = (dist > UserVar[event->BaseVarIndex] + PCONFIG(4)) || (dist < UserVar[event->BaseVarIndex] - PCONFIG(4)); - - if (P113_data->isReadSuccessful() && (triggered || (PCONFIG(3) == 1)) && (dist != 0xFFFF)) { - UserVar.setFloat(event->TaskIndex, 0, dist); - UserVar.setFloat(event->TaskIndex, 1, ambient); - success = true; - } - } - break; - } - - case PLUGIN_FIFTY_PER_SECOND: - { - P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P113_data) { - if (P113_data->startRead()) { - if (P113_data->readAvailable() && (Settings.TaskDeviceTimer[event->TaskIndex] == 0)) { // Trigger as soon as there's a valid - // measurement and the time-out is set to 0 - Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); - } - } - } - break; - } - } - return success; -} - -#endif // USES_P113 +#include "_Plugin_Helper.h" +#ifdef USES_P113 + +// ####################################################################################################### +// ########################### Plugin 113 VL53L1X I2C Ranging LIDAR ################################# +// ####################################################################################################### + +/** Changelog: + * 2024-04-25 tonhuisman: Add Direction value (1/0/-1), code improvements + * 2023-08-11 tonhuisman: Fix issue not surfacing before, that the library right-shifts the I2C address when that is set... + * Also use new/delete on sensor object (code improvement) + * Limit the selection list of I2C addresses to 1 item, as changing the I2C address of the sensor does not work as + * intended/expected + * 2021-04-06 tonhuisman: Remove Interval optional attribute to avoid system overload, cleanup source + * 2021-04-05 tonhuisman: Add VL53L1X Time of Flight sensor to main repo (similar to but not compatible with VL53L0X) + */ + +// needs SparkFun_VL53L1X library from https://github.com/sparkfun/SparkFun_VL53L1X_Arduino_Library + +# include "src/PluginStructs/P113_data_struct.h" + +# define PLUGIN_113 +# define PLUGIN_ID_113 113 +# define PLUGIN_NAME_113 "Distance - VL53L1X (400cm)" +# define PLUGIN_VALUENAME1_113 "Distance" +# define PLUGIN_VALUENAME2_113 "Ambient" +# define PLUGIN_VALUENAME3_113 "Direction" + + +boolean Plugin_113(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_113; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_113); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_113)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_113)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_113)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + # define P113_ACTIVE_I2C_ADDRESSES 1 // Setting the address messes up the sensor, so disabled + const uint8_t i2cAddressValues[] = { 0x29, 0x30 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c"), P113_ACTIVE_I2C_ADDRESSES, i2cAddressValues, P113_I2C_ADDRESS); + } else { + success = intArrayContains(P113_ACTIVE_I2C_ADDRESSES, i2cAddressValues, event->Par1); + } + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P113_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *optionsMode2[] = { + F("100ms (Normal)"), + F("20ms (Fastest)"), + F("33ms (Fast)"), + F("50ms"), + F("200ms (Accurate)"), + F("500ms"), + }; + const int optionValuesMode2[] = { 100, 20, 33, 50, 200, 500 }; + addFormSelector(F("Timing"), F("timing"), 6, optionsMode2, optionValuesMode2, P113_TIMING); + } + + { + const __FlashStringHelper *optionsMode3[] = { + F("Normal (~130cm)"), + F("Long (~400cm)"), + }; + const int optionValuesMode3[2] = { 0, 1 }; + addFormSelector(F("Range"), F("range"), 2, optionsMode3, optionValuesMode3, P113_RANGE); + } + addFormCheckBox(F("Send event when value unchanged"), F("notchanged"), P113_SEND_ALWAYS == 1); + addFormNote(F("When checked, 'Trigger delta' setting is ignored!")); + + addFormNumericBox(F("Trigger delta"), F("delta"), P113_DELTA, 0, 100); + addUnit(F("0-100mm")); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Minimal change in Distance to trigger an event.")); + # endif // ifndef LIMIT_BUILD_SIZE + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P113_I2C_ADDRESS = getFormItemInt(F("i2c")); + P113_TIMING = getFormItemInt(F("timing")); + P113_RANGE = getFormItemInt(F("range")); + P113_SEND_ALWAYS = isFormItemChecked(F("notchanged")) ? 1 : 0; + P113_DELTA = getFormItemInt(F("delta")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P113_data_struct(P113_I2C_ADDRESS, P113_TIMING, P113_RANGE == 1)); + P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P113_data) && P113_data->begin(); // Start the sensor + break; + } + + case PLUGIN_EXIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P113_data) { + const uint16_t dist = P113_data->readDistance(); + const uint16_t ambient = P113_data->readAmbient(); + const uint16_t p_dist = UserVar.getFloat(event->TaskIndex, 0); + const int16_t direct = dist == p_dist ? 0 : (dist < p_dist ? -1 : 1); + const bool triggered = (dist > p_dist + P113_DELTA) || (dist < p_dist - P113_DELTA); + + if (P113_data->isReadSuccessful() && (triggered || (P113_SEND_ALWAYS == 1)) && (dist != 0xFFFF)) { + UserVar.setFloat(event->TaskIndex, 0, dist); + UserVar.setFloat(event->TaskIndex, 1, ambient); + UserVar.setFloat(event->TaskIndex, 2, direct); + success = true; + } + } + break; + } + + case PLUGIN_FIFTY_PER_SECOND: + { + P113_data_struct *P113_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P113_data) { + if (P113_data->startRead()) { + if (P113_data->readAvailable() && (Settings.TaskDeviceTimer[event->TaskIndex] == 0)) { // Trigger as soon as there's a valid + // measurement and the time-out is set to 0 + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + } + } + } + break; + } + } + return success; +} + +#endif // USES_P113 diff --git a/src/_P114_VEML6075.ino b/src/_P114_VEML6075.ino index ae41b8fd4..ef7c9b385 100644 --- a/src/_P114_VEML6075.ino +++ b/src/_P114_VEML6075.ino @@ -100,13 +100,11 @@ boolean Plugin_114(uint8_t function, struct EventStruct *event, String& string) } { - const __FlashStringHelper *optionsMode3[2]; - optionsMode3[0] = F("Normal Dynamic"); - optionsMode3[1] = F("High Dynamic"); - int optionValuesMode3[2]; - optionValuesMode3[0] = 0; - optionValuesMode3[1] = 1; - addFormSelector(F("Dynamic Setting"), F("hd"), 2, optionsMode3, optionValuesMode3, PCONFIG(2)); + const __FlashStringHelper *optionsMode3[] = { + F("Normal Dynamic"), + F("High Dynamic") } + ; + addFormSelector(F("Dynamic Setting"), F("hd"), 2, optionsMode3, nullptr, PCONFIG(2)); } success = true; @@ -148,25 +146,12 @@ boolean Plugin_114(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 2, UVIndex); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(130)) { - String log = F("VEML6075: Address: 0x"); - log += String(PCONFIG(0), HEX); - log += F(" / Integration Time: "); - log += PCONFIG(1); - log += F(" / Dynamic Mode: "); - log += PCONFIG(2); - log += F(" / divisor: "); - log += String(1 << (PCONFIG(1) - 1)); - log += F(" / UVA: "); - log += UserVar[event->BaseVarIndex]; - log += F(" / UVB: "); - log += UserVar[event->BaseVarIndex + 1]; - log += F(" / UVIndex: "); - log += UserVar[event->BaseVarIndex + 2]; - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat(F("VEML6075: Address: 0x%02x / Integration Time: %d / " + "Dynamic Mode: %d / divisor: %d / UVA: %.2f / UVB: %.2f / UVIndex: %.2f"), + PCONFIG(0), PCONFIG(1), PCONFIG(2), 1 << (PCONFIG(1) - 1), + UserVar[event->BaseVarIndex], + UserVar[event->BaseVarIndex + 1], + UserVar[event->BaseVarIndex + 2])); } success = true; diff --git a/src/_P115_MAX1704x_v2.ino b/src/_P115_MAX1704x_v2.ino index e7a6b3e78..8d93779c3 100644 --- a/src/_P115_MAX1704x_v2.ino +++ b/src/_P115_MAX1704x_v2.ino @@ -141,17 +141,8 @@ boolean Plugin_115(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 3, P115_data->changeRate); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(64); - log = F("MAX1704x : Voltage: "); - log += P115_data->voltage; - log += F(" SoC: "); - log += P115_data->soc; - log += F(" Alert: "); - log += P115_data->alert; - log += F(" Rate: "); - log += P115_data->changeRate; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("MAX1704x : Voltage: %.2f SoC: %.2f Alert: %d Rate: %.2f"), + P115_data->voltage, P115_data->soc, P115_data->alert, P115_data->changeRate)); } success = true; } @@ -165,7 +156,7 @@ boolean Plugin_115(uint8_t function, struct EventStruct *event, String& string) if ((nullptr != P115_data) && P115_data->initialized) { const String command = parseString(string, 1); - if ((equals(command, F("max1704xclearalert")))) + if (equals(command, F("max1704xclearalert"))) { P115_data->clearAlert(); success = true; @@ -190,9 +181,9 @@ boolean Plugin_115(uint8_t function, struct EventStruct *event, String& string) const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); if (validDeviceIndex(DeviceIndex)) { - String eventvalues = formatUserVarNoCheck(event, 0); // Voltage - eventvalues += ','; - eventvalues += formatUserVarNoCheck(event, 1); // State Of Charge + String eventvalues = strformat(F("%s,%s"), + formatUserVarNoCheck(event, 0).c_str(), // Voltage + formatUserVarNoCheck(event, 1).c_str()); // State Of Charge eventQueue.add(event->TaskIndex, F("AlertTriggered"), eventvalues); } } diff --git a/src/_P116_ST77xx.ino b/src/_P116_ST77xx.ino index ef010cd91..a966cc438 100644 --- a/src/_P116_ST77xx.ino +++ b/src/_P116_ST77xx.ino @@ -1,411 +1,446 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P116 - -// ####################################################################################################### -// ########################### Plugin 116: ST77xx TFT displays ########################################### -// ####################################################################################################### - - -// History: -// 2023-02-27 tonhuisman: Implement support for getting config values, see AdafruitGFX_Helper.h changelog for details -// 2022-07-06 tonhuisman: Add support for ST7735sv M5Stack StickC (Inverted colors) -// 2021-11-16 tonhuisman: P116: Change state from Development to Testing -// 2021-11-08 tonhuisman: Add support for function PLUGIN_GET_DISPLAY_PARAMETERS for retrieving the display parameters -// as implemented by FT6206 touchscreen plugin. Added ST77xx_type_toResolution -// 2021-11-06 tonhuisman: P116: Add support for ST7796s 320x480 displays -// Changed name of plugin to 'Display - ST77xx TFT' (was 'Display - ST7735/ST7789 TFT') -// 2021-08-16 tonhuisman: P116: Add default color settings -// 2021-08-16 tonhuisman: P116: Reorder some device configuration options, add backlight command (triggerCmd option) -// 2021-08-15 tonhuisman: P116: Make CursorX/CursorY coordinates available as Values (no events are generated!) -// P116: Use more features of AdafruitGFX_helper -// AdafruitGFX: Apply 'Text Print Mode' options -// 2021-08 tonhuisman: Refactor into AdafruitGFX_helper -// 2021-08 tonhuisman: Continue development, added new features, font scaling, display limits, extra text lines -// update to current ESPEasy state/style of development, make multi-instance possible -// 2020-08 tonhuisman: Adaptations for multiple ST77xx chips, ST7735s, ST7789vw (shelved temporarily) -// Added several features like display button, rotation -// 2020-04 WDS (Wolfdieter): initial plugin for ST7735, based on P012 - -# define PLUGIN_116 -# define PLUGIN_ID_116 116 -# define PLUGIN_NAME_116 "Display - ST77xx TFT" -# define PLUGIN_VALUENAME1_116 "CursorX" -# define PLUGIN_VALUENAME2_116 "CursorY" - -# include "src/PluginStructs/P116_data_struct.h" - -boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) -{ - bool success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_116; - Device[deviceCount].Type = DEVICE_TYPE_SPI3; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = false; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_116); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_116)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_116)); - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_output_optional(F("CS ")); - event->String2 = formatGpioName_output(F("DC")); - event->String3 = formatGpioName_output_optional(F("RES ")); - break; - } - - case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: - { - const char* separator = event->String1.c_str(); // contains the NewLine sequence - string = strformat( - F("CS: %s%sDC: %s%s RES: %s%sBtn: %s%sBckl: : %s"), - formatGpioLabel(PIN(0), false).c_str(), - separator, - formatGpioLabel(PIN(1), false).c_str(), - separator, - formatGpioLabel(PIN(2), false).c_str(), - separator, - formatGpioLabel(P116_CONFIG_BUTTON_PIN, false).c_str(), - separator, - formatGpioLabel(P116_CONFIG_BACKLIGHT_PIN, false).c_str()); - success = true; - break; - } - - case PLUGIN_SET_DEFAULTS: - { - # ifdef ESP32 - - if (Settings.InitSPI == 2) { // When using ESP32 H(ardware-)SPI - PIN(0) = P116_TFT_CS_HSPI; - } else { - PIN(0) = P116_TFT_CS; - } - # else // ifdef ESP32 - PIN(0) = P116_TFT_CS; - # endif // ifdef ESP32 - PIN(1) = P116_TFT_DC; - PIN(2) = P116_TFT_RST; - P116_CONFIG_BUTTON_PIN = -1; // No button connected - P116_CONFIG_BACKLIGHT_PIN = P116_BACKLIGHT_PIN; - P116_CONFIG_BACKLIGHT_PERCENT = 100; // Percentage backlight - - uint32_t lSettings = 0; - - // Truncate exceeding message - set4BitToUL(lSettings, P116_CONFIG_FLAG_MODE, static_cast(AdaGFXTextPrintMode::TruncateExceedingMessage)); - set4BitToUL(lSettings, P116_CONFIG_FLAG_FONTSCALE, 1); - set4BitToUL(lSettings, P116_CONFIG_FLAG_CMD_TRIGGER, 1); // Default trigger on st77xx - P116_CONFIG_FLAGS = lSettings; - - P116_CONFIG_COLORS = ADAGFX_WHITE | (ADAGFX_BLACK << 16); - - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - AdaGFXFormBacklight(F("backlight"), P116_CONFIG_BACKLIGHT_PIN, - F("backpercentage"), P116_CONFIG_BACKLIGHT_PERCENT); - - AdaGFXFormDisplayButton(F("button"), P116_CONFIG_BUTTON_PIN, - F("buttonInverse"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_INVERT_BUTTON), - F("timer"), P116_CONFIG_DISPLAY_TIMEOUT); - - { - const __FlashStringHelper *options4[] = { - ST77xx_type_toString(ST77xx_type_e::ST7735s_128x128), - ST77xx_type_toString(ST77xx_type_e::ST7735s_128x160), - ST77xx_type_toString(ST77xx_type_e::ST7735s_80x160), - ST77xx_type_toString(ST77xx_type_e::ST7735s_80x160_M5), - ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x320), - ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x240), - ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x280), - ST77xx_type_toString(ST77xx_type_e::ST7789vw_135x240), - ST77xx_type_toString(ST77xx_type_e::ST7796s_320x480) - }; - const int optionValues4[] = { - static_cast(ST77xx_type_e::ST7735s_128x128), - static_cast(ST77xx_type_e::ST7735s_128x160), - static_cast(ST77xx_type_e::ST7735s_80x160), - static_cast(ST77xx_type_e::ST7735s_80x160_M5), - static_cast(ST77xx_type_e::ST7789vw_240x320), - static_cast(ST77xx_type_e::ST7789vw_240x240), - static_cast(ST77xx_type_e::ST7789vw_240x280), - static_cast(ST77xx_type_e::ST7789vw_135x240), - static_cast(ST77xx_type_e::ST7796s_320x480) - }; - constexpr int optCount4 = sizeof(optionValues4) / sizeof(optionValues4[0]); - addFormSelector(F("TFT display model"), - F("type"), - optCount4, - options4, - optionValues4, - P116_CONFIG_FLAG_GET_TYPE); - } - - addFormSubHeader(F("Layout")); - - AdaGFXFormRotation(F("rotate"), P116_CONFIG_FLAG_GET_ROTATION); - - AdaGFXFormTextPrintMode(F("mode"), P116_CONFIG_FLAG_GET_MODE); - - AdaGFXFormFontScaling(F("fontscale"), P116_CONFIG_FLAG_GET_FONTSCALE); - - addFormCheckBox(F("Clear display on exit"), F("clearOnExit"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_CLEAR_ON_EXIT)); - - { - const __FlashStringHelper *commandTriggers[] = { // Be sure to use all options available in the enum (except MAX)! - P116_CommandTrigger_toString(P116_CommandTrigger::tft), - P116_CommandTrigger_toString(P116_CommandTrigger::st77xx), - P116_CommandTrigger_toString(P116_CommandTrigger::st7735), - P116_CommandTrigger_toString(P116_CommandTrigger::st7789), - P116_CommandTrigger_toString(P116_CommandTrigger::st7796) - }; - const int commandTriggerOptions[] = { - static_cast(P116_CommandTrigger::tft), - static_cast(P116_CommandTrigger::st77xx), - static_cast(P116_CommandTrigger::st7735), - static_cast(P116_CommandTrigger::st7789), - static_cast(P116_CommandTrigger::st7796) - }; - constexpr int cmdCount = sizeof(commandTriggerOptions) / sizeof(commandTriggerOptions[0]); - addFormSelector(F("Write Command trigger"), - F("commandtrigger"), - cmdCount, - commandTriggers, - commandTriggerOptions, - P116_CONFIG_FLAG_GET_CMD_TRIGGER); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Select the command that is used to handle commands for this display.")); - # endif // ifndef LIMIT_BUILD_SIZE - } - - // Inverted state! - addFormCheckBox(F("Wake display on receiving text"), F("NoDisplay"), !bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_NO_WAKE)); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("When checked, the display wakes up at receiving remote updates.")); - # endif // ifndef LIMIT_BUILD_SIZE - - AdaGFXFormTextColRowMode(F("colrow"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW) == 1); - - AdaGFXFormTextBackgroundFill(F("backfill"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_BACK_FILL) == 0); // Inverse - - addFormSubHeader(F("Content")); - - AdaGFXFormForeAndBackColors(F("foregroundcolor"), - P116_CONFIG_GET_COLOR_FOREGROUND, - F("backgroundcolor"), - P116_CONFIG_GET_COLOR_BACKGROUND); - { - String strings[P116_Nlines]; - LoadCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); - - uint16_t remain = DAT_TASKS_CUSTOM_SIZE; - - for (uint8_t varNr = 0; varNr < P116_Nlines; varNr++) { - addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P116_Nchars); - remain -= (strings[varNr].length() + 1); - } - addUnit(concat(F("Remaining: "), remain)); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P116_CONFIG_BUTTON_PIN = getFormItemInt(F("button")); - P116_CONFIG_DISPLAY_TIMEOUT = getFormItemInt(F("timer")); - P116_CONFIG_BACKLIGHT_PIN = getFormItemInt(F("backlight")); - P116_CONFIG_BACKLIGHT_PERCENT = getFormItemInt(F("backpercentage")); - - uint32_t lSettings = 0; - 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_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) - P116_CONFIG_FLAGS = lSettings; - - String color = webArg(F("foregroundcolor")); - uint16_t fgcolor = ADAGFX_WHITE; // Default to white when empty - - if (!color.isEmpty()) { - fgcolor = AdaGFXparseColor(color); // Reduce to rgb565 - } - color = webArg(F("backgroundcolor")); - uint16_t bgcolor = AdaGFXparseColor(color); - - P116_CONFIG_COLORS = fgcolor | (bgcolor << 16); // Store as a single setting - { - String strings[P116_Nlines]; - - for (uint8_t varNr = 0; varNr < P116_Nlines; varNr++) { - strings[varNr] = webArg(getPluginCustomArgName(varNr)); - } - - const String error = SaveCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); - - if (!error.isEmpty()) { - addHtmlError(error); - } - } - - success = true; - break; - } - - case PLUGIN_GET_DISPLAY_PARAMETERS: - { - uint16_t x, y; - ST77xx_type_toResolution(static_cast(P116_CONFIG_FLAG_GET_TYPE), x, y); - - event->Par1 = x; // X-resolution in pixels - event->Par2 = y; // Y-resolution in pixels - event->Par3 = P116_CONFIG_FLAG_GET_ROTATION; // Rotation (0..3: 0, 90, 180, 270 degrees) - event->Par4 = static_cast(AdaGFXColorDepth::FullColor); // Color depth - - success = true; - break; - } - - case PLUGIN_INIT: - { - if (Settings.InitSPI != 0) { - initPluginTaskData(event->TaskIndex, - new (std::nothrow) P116_data_struct(static_cast(P116_CONFIG_FLAG_GET_TYPE), - P116_CONFIG_FLAG_GET_ROTATION, - P116_CONFIG_FLAG_GET_FONTSCALE, - static_cast(P116_CONFIG_FLAG_GET_MODE), - P116_CONFIG_BACKLIGHT_PIN, - P116_CONFIG_BACKLIGHT_PERCENT, - P116_CONFIG_DISPLAY_TIMEOUT, - P116_CommandTrigger_toString(static_cast( - P116_CONFIG_FLAG_GET_CMD_TRIGGER)), - P116_CONFIG_GET_COLOR_FOREGROUND, - P116_CONFIG_GET_COLOR_BACKGROUND, - bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_BACK_FILL) == 0)); - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - success = (nullptr != P116_data) && P116_data->plugin_init(event); // Start the display - } else { - addLog(LOG_LEVEL_ERROR, F("ST77xx: SPI not enabled, init cancelled.")); - } - break; - } - - case PLUGIN_EXIT: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_exit(event); // Stop the display - } - break; - } - - // Check more often for debouncing the button, when enabled - case PLUGIN_FIFTY_PER_SECOND: - { - if (P116_CONFIG_BUTTON_PIN != -1) { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - P116_data->registerButtonState(digitalRead(P116_CONFIG_BUTTON_PIN), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_INVERT_BUTTON)); - success = true; - } - } - break; - } - - case PLUGIN_TEN_PER_SECOND: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_ten_per_second(event); // 10 per second actions - } - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_once_a_second(event); // Once a second actions - } - break; - } - - case PLUGIN_READ: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_read(event); // Read operation, redisplay the configured content - } - break; - } - - case PLUGIN_WRITE: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_write(event, string); // Write operation, handle commands, mostly delegated to AdafruitGFX_helper - } - break; - } - - # if ADAGFX_ENABLE_GET_CONFIG_VALUE - case PLUGIN_GET_CONFIG_VALUE: - { - P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P116_data) { - success = P116_data->plugin_get_config_value(event, string); // GetConfig operation, handle variables, fully delegated to - // AdafruitGFX_helper - } - break; - } - # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE - } - return success; -} - -#endif // USES_P116 +#include "_Plugin_Helper.h" + +#ifdef USES_P116 + +// ####################################################################################################### +// ########################### Plugin 116: ST77xx TFT displays ########################################### +// ####################################################################################################### + + +// History: +// 2024-05-04 tonhuisman: Add Default font selection setting, if AdafruitGFX_Helper fonts are included +// 2024-03-17 tonhuisman: Add support for another alternative initialization for ST7735 displays, as the display controller +// used on the LilyGO TTGO T-Display (16 MB) seems to be a ST7735, despite being documented as ST7789 +// By default (also) only enabled on ESP32 builds +// Disabled the ST7789 alternatives for now, as that's not verified on any hardware +// 2024-03-09 tonhuisman: Add support for alternative initialization sequences for ST7789 displays, like used on +// some LilyGO models like the TTGO T-Display (16 MB Flash), and possibly the T-Display S3 +// By default only enabled on ESP32 builds +// 2023-02-27 tonhuisman: Implement support for getting config values, see AdafruitGFX_Helper.h changelog for details +// 2022-07-06 tonhuisman: Add support for ST7735sv M5Stack StickC (Inverted colors) +// 2021-11-16 tonhuisman: P116: Change state from Development to Testing +// 2021-11-08 tonhuisman: Add support for function PLUGIN_GET_DISPLAY_PARAMETERS for retrieving the display parameters +// as implemented by FT6206 touchscreen plugin. Added ST77xx_type_toResolution +// 2021-11-06 tonhuisman: P116: Add support for ST7796s 320x480 displays +// Changed name of plugin to 'Display - ST77xx TFT' (was 'Display - ST7735/ST7789 TFT') +// 2021-08-16 tonhuisman: P116: Add default color settings +// 2021-08-16 tonhuisman: P116: Reorder some device configuration options, add backlight command (triggerCmd option) +// 2021-08-15 tonhuisman: P116: Make CursorX/CursorY coordinates available as Values (no events are generated!) +// P116: Use more features of AdafruitGFX_helper +// AdafruitGFX: Apply 'Text Print Mode' options +// 2021-08 tonhuisman: Refactor into AdafruitGFX_helper +// 2021-08 tonhuisman: Continue development, added new features, font scaling, display limits, extra text lines +// update to current ESPEasy state/style of development, make multi-instance possible +// 2020-08 tonhuisman: Adaptations for multiple ST77xx chips, ST7735s, ST7789vw (shelved temporarily) +// Added several features like display button, rotation +// 2020-04 WDS (Wolfdieter): initial plugin for ST7735, based on P012 + +# define PLUGIN_116 +# define PLUGIN_ID_116 116 +# define PLUGIN_NAME_116 "Display - ST77xx TFT" +# define PLUGIN_VALUENAME1_116 "CursorX" +# define PLUGIN_VALUENAME2_116 "CursorY" + +# include "src/PluginStructs/P116_data_struct.h" + +boolean Plugin_116(uint8_t function, struct EventStruct *event, String& string) +{ + bool success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_116; + Device[deviceCount].Type = DEVICE_TYPE_SPI3; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_NONE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_116); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_116)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_116)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_output_optional(F("CS ")); + event->String2 = formatGpioName_output(F("DC")); + event->String3 = formatGpioName_output_optional(F("RES ")); + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + const char *separator = event->String1.c_str(); // contains the NewLine sequence + string = strformat( + F("CS: %s%sDC: %s%s RES: %s%sBtn: %s%sBckl: : %s"), + formatGpioLabel(PIN(0), false).c_str(), + separator, + formatGpioLabel(PIN(1), false).c_str(), + separator, + formatGpioLabel(PIN(2), false).c_str(), + separator, + formatGpioLabel(P116_CONFIG_BUTTON_PIN, false).c_str(), + separator, + formatGpioLabel(P116_CONFIG_BACKLIGHT_PIN, false).c_str()); + success = true; + break; + } + + case PLUGIN_SET_DEFAULTS: + { + # ifdef ESP32 + + if (Settings.InitSPI == 2) { // When using ESP32 H(ardware-)SPI + PIN(0) = P116_TFT_CS_HSPI; + } else { + PIN(0) = P116_TFT_CS; + } + # else // ifdef ESP32 + PIN(0) = P116_TFT_CS; + # endif // ifdef ESP32 + PIN(1) = P116_TFT_DC; + PIN(2) = P116_TFT_RST; + P116_CONFIG_BUTTON_PIN = -1; // No button connected + P116_CONFIG_BACKLIGHT_PIN = P116_BACKLIGHT_PIN; + P116_CONFIG_BACKLIGHT_PERCENT = 100; // Percentage backlight + + uint32_t lSettings = 0; + + // Truncate exceeding message + set4BitToUL(lSettings, P116_CONFIG_FLAG_MODE, static_cast(AdaGFXTextPrintMode::TruncateExceedingMessage)); + set4BitToUL(lSettings, P116_CONFIG_FLAG_FONTSCALE, 1); + set4BitToUL(lSettings, P116_CONFIG_FLAG_CMD_TRIGGER, 1); // Default trigger on st77xx + P116_CONFIG_FLAGS = lSettings; + + P116_CONFIG_COLORS = ADAGFX_WHITE | (ADAGFX_BLACK << 16); + + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + AdaGFXFormBacklight(F("backlight"), P116_CONFIG_BACKLIGHT_PIN, + F("backpercentage"), P116_CONFIG_BACKLIGHT_PERCENT); + + AdaGFXFormDisplayButton(F("button"), P116_CONFIG_BUTTON_PIN, + F("buttonInverse"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_INVERT_BUTTON), + F("timer"), P116_CONFIG_DISPLAY_TIMEOUT); + + { + const __FlashStringHelper *options4[] = { + ST77xx_type_toString(ST77xx_type_e::ST7735s_128x128), + ST77xx_type_toString(ST77xx_type_e::ST7735s_128x160), + ST77xx_type_toString(ST77xx_type_e::ST7735s_80x160), + ST77xx_type_toString(ST77xx_type_e::ST7735s_80x160_M5), + # if P116_EXTRA_ST7735 + ST77xx_type_toString(ST77xx_type_e::ST7735s_135x240), + # endif // if P116_EXTRA_ST7735 + ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x320), + ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x240), + ST77xx_type_toString(ST77xx_type_e::ST7789vw_240x280), + ST77xx_type_toString(ST77xx_type_e::ST7789vw_135x240), + # if P116_EXTRA_ST7789 + ST77xx_type_toString(ST77xx_type_e::ST7789vw1_135x240), + ST77xx_type_toString(ST77xx_type_e::ST7789vw2_135x240), + ST77xx_type_toString(ST77xx_type_e::ST7789vw3_135x240), + # endif // if P116_EXTRA_ST7789 + ST77xx_type_toString(ST77xx_type_e::ST7796s_320x480) + }; + const int optionValues4[] = { + static_cast(ST77xx_type_e::ST7735s_128x128), + static_cast(ST77xx_type_e::ST7735s_128x160), + static_cast(ST77xx_type_e::ST7735s_80x160), + static_cast(ST77xx_type_e::ST7735s_80x160_M5), + # if P116_EXTRA_ST7735 + static_cast(ST77xx_type_e::ST7735s_135x240), + # endif // if P116_EXTRA_ST7735 + static_cast(ST77xx_type_e::ST7789vw_240x320), + static_cast(ST77xx_type_e::ST7789vw_240x240), + static_cast(ST77xx_type_e::ST7789vw_240x280), + static_cast(ST77xx_type_e::ST7789vw_135x240), + # if P116_EXTRA_ST7789 + static_cast(ST77xx_type_e::ST7789vw1_135x240), + static_cast(ST77xx_type_e::ST7789vw2_135x240), + static_cast(ST77xx_type_e::ST7789vw3_135x240), + # endif // if P116_EXTRA_ST7789 + static_cast(ST77xx_type_e::ST7796s_320x480) + }; + constexpr int optCount4 = NR_ELEMENTS(optionValues4); + addFormSelector(F("TFT display model"), + F("type"), + optCount4, + options4, + optionValues4, + P116_CONFIG_FLAG_GET_TYPE); + } + + addFormSubHeader(F("Layout")); + + AdaGFXFormRotation(F("rotate"), P116_CONFIG_FLAG_GET_ROTATION); + + AdaGFXFormTextPrintMode(F("mode"), P116_CONFIG_FLAG_GET_MODE); + + # if ADAGFX_FONTS_INCLUDED + AdaGFXFormDefaultFont(F("deffont"), P116_CONFIG_DEFAULT_FONT); + # endif // if ADAGFX_FONTS_INCLUDED + + AdaGFXFormFontScaling(F("fontscale"), P116_CONFIG_FLAG_GET_FONTSCALE); + + addFormCheckBox(F("Clear display on exit"), F("clearOnExit"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_CLEAR_ON_EXIT)); + + { + const __FlashStringHelper *commandTriggers[] = { // Be sure to use all options available in the enum (except MAX)! + P116_CommandTrigger_toString(P116_CommandTrigger::tft), + P116_CommandTrigger_toString(P116_CommandTrigger::st77xx), + P116_CommandTrigger_toString(P116_CommandTrigger::st7735), + P116_CommandTrigger_toString(P116_CommandTrigger::st7789), + P116_CommandTrigger_toString(P116_CommandTrigger::st7796) + }; + const int commandTriggerOptions[] = { + static_cast(P116_CommandTrigger::tft), + static_cast(P116_CommandTrigger::st77xx), + static_cast(P116_CommandTrigger::st7735), + static_cast(P116_CommandTrigger::st7789), + static_cast(P116_CommandTrigger::st7796) + }; + constexpr int cmdCount = NR_ELEMENTS(commandTriggerOptions); + addFormSelector(F("Write Command trigger"), + F("commandtrigger"), + cmdCount, + commandTriggers, + commandTriggerOptions, + P116_CONFIG_FLAG_GET_CMD_TRIGGER); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Select the command that is used to handle commands for this display.")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + // Inverted state! + addFormCheckBox(F("Wake display on receiving text"), F("NoDisplay"), !bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_NO_WAKE)); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("When checked, the display wakes up at receiving remote updates.")); + # endif // ifndef LIMIT_BUILD_SIZE + + AdaGFXFormTextColRowMode(F("colrow"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW) == 1); + + AdaGFXFormTextBackgroundFill(F("backfill"), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_BACK_FILL) == 0); // Inverse + + addFormSubHeader(F("Content")); + + AdaGFXFormForeAndBackColors(F("foregroundcolor"), + P116_CONFIG_GET_COLOR_FOREGROUND, + F("backgroundcolor"), + P116_CONFIG_GET_COLOR_BACKGROUND); + { + String strings[P116_Nlines]; + LoadCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); + + uint16_t remain = DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE; + + for (uint8_t varNr = 0; varNr < P116_Nlines; ++varNr) { + addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P116_Nchars); + remain -= (strings[varNr].length() + 1); + } + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P116_CONFIG_BUTTON_PIN = getFormItemInt(F("button")); + P116_CONFIG_DISPLAY_TIMEOUT = getFormItemInt(F("timer")); + P116_CONFIG_BACKLIGHT_PIN = getFormItemInt(F("backlight")); + P116_CONFIG_BACKLIGHT_PERCENT = getFormItemInt(F("backpercentage")); + # if ADAGFX_FONTS_INCLUDED + P116_CONFIG_DEFAULT_FONT = getFormItemInt(F("deffont")); + # endif // if ADAGFX_FONTS_INCLUDED + + uint32_t lSettings = 0; + 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_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) + P116_CONFIG_FLAGS = lSettings; + + String color = webArg(F("foregroundcolor")); + uint16_t fgcolor = ADAGFX_WHITE; // Default to white when empty + + if (!color.isEmpty()) { + fgcolor = AdaGFXparseColor(color); // Reduce to rgb565 + } + color = webArg(F("backgroundcolor")); + uint16_t bgcolor = AdaGFXparseColor(color); + + P116_CONFIG_COLORS = fgcolor | (bgcolor << 16); // Store as a single setting + { + String strings[P116_Nlines]; + + for (uint8_t varNr = 0; varNr < P116_Nlines; ++varNr) { + strings[varNr] = webArg(getPluginCustomArgName(varNr)); + } + + const String error = SaveCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); + + if (!error.isEmpty()) { + addHtmlError(error); + } + } + + success = true; + break; + } + + case PLUGIN_GET_DISPLAY_PARAMETERS: + { + uint16_t x, y; + ST77xx_type_toResolution(static_cast(P116_CONFIG_FLAG_GET_TYPE), x, y); + + event->Par1 = x; // X-resolution in pixels + event->Par2 = y; // Y-resolution in pixels + event->Par3 = P116_CONFIG_FLAG_GET_ROTATION; // Rotation (0..3: 0, 90, 180, 270 degrees) + event->Par4 = static_cast(AdaGFXColorDepth::FullColor); // Color depth + + success = true; + break; + } + + case PLUGIN_INIT: + { + if (Settings.InitSPI != 0) { + initPluginTaskData(event->TaskIndex, + new (std::nothrow) P116_data_struct(static_cast(P116_CONFIG_FLAG_GET_TYPE), + P116_CONFIG_FLAG_GET_ROTATION, + P116_CONFIG_FLAG_GET_FONTSCALE, + static_cast(P116_CONFIG_FLAG_GET_MODE), + P116_CONFIG_BACKLIGHT_PIN, + P116_CONFIG_BACKLIGHT_PERCENT, + P116_CONFIG_DISPLAY_TIMEOUT, + P116_CommandTrigger_toString(static_cast( + P116_CONFIG_FLAG_GET_CMD_TRIGGER)), + P116_CONFIG_GET_COLOR_FOREGROUND, + P116_CONFIG_GET_COLOR_BACKGROUND, + bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_BACK_FILL) == 0 + # if ADAGFX_FONTS_INCLUDED + , + P116_CONFIG_DEFAULT_FONT + # endif // if ADAGFX_FONTS_INCLUDED + )); + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P116_data) && P116_data->plugin_init(event); // Start the display + } else { + addLog(LOG_LEVEL_ERROR, F("ST77xx: SPI not enabled, init cancelled.")); + } + break; + } + + case PLUGIN_EXIT: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_exit(event); // Stop the display + } + break; + } + + // Check more often for debouncing the button, when enabled + case PLUGIN_FIFTY_PER_SECOND: + { + if (P116_CONFIG_BUTTON_PIN != -1) { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + P116_data->registerButtonState(digitalRead(P116_CONFIG_BUTTON_PIN), bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_INVERT_BUTTON)); + success = true; + } + } + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_ten_per_second(event); // 10 per second actions + } + break; + } + + case PLUGIN_ONCE_A_SECOND: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_once_a_second(event); // Once a second actions + } + break; + } + + case PLUGIN_READ: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_read(event); // Read operation, redisplay the configured content + } + break; + } + + case PLUGIN_WRITE: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_write(event, string); // Write operation, handle commands, mostly delegated to AdafruitGFX_helper + } + break; + } + + # if ADAGFX_ENABLE_GET_CONFIG_VALUE + case PLUGIN_GET_CONFIG_VALUE: + { + P116_data_struct *P116_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P116_data) { + success = P116_data->plugin_get_config_value(event, string); // GetConfig operation, handle variables, fully delegated to + // AdafruitGFX_helper + } + break; + } + # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE + } + return success; +} + +#endif // USES_P116 diff --git a/src/_P117_SCD30.ino b/src/_P117_SCD30.ino index 7c7a0961d..be3576352 100644 --- a/src/_P117_SCD30.ino +++ b/src/_P117_SCD30.ino @@ -202,52 +202,42 @@ boolean Plugin_117(uint8_t function, struct EventStruct *event, String& string) if (nullptr == P117_data) { return success; } - String command = parseString(string, 1); - uint16_t value = 0; - String log; - float temp; + const String command = parseString(string, 1); + uint16_t value = 0; + String log; + float temp; if (equals(command, F("scdgetabc"))) { P117_data->getCalibrationType(&value); - log += F("ABC: "); - log += value; + log += concat(F("ABC: "), value); success = true; } else if (equals(command, F("scdgetalt"))) { P117_data->getAltitudeCompensation(&value); - log += F("Altitude: "); - log += value; + log += concat(F("Altitude: "), value); success = true; } else if (equals(command, F("scdgettmp"))) { P117_data->getTemperatureOffset(&temp); - log += F("Temp offset: "); - log += toString(temp, 2); + log += concat(F("Temp offset: "), toString(temp, 2)); success = true; } else if (equals(command, F("scdsetcalibration")) && (event->Par1 >= 0) && (event->Par1 <= 1)) { P117_data->setCalibrationMode(event->Par1 == 1); P117_AUTO_CALIBRATION = event->Par1; // Update device configuration - log += F("Calibration: "); - log += event->Par1 == 1 ? F("auto") : F("manual"); + log += concat(F("Calibration: "), event->Par1 == 1 ? F("auto") : F("manual")); success = true; } else if (equals(command, F("scdsetfrc")) && (event->Par1 >= 400) && (event->Par1 <= 2000)) { int res = P117_data->setForcedRecalibrationFactor(event->Par1); - log += F("SCD30 Forced calibration: "); - log += event->Par1; - log += F(", result: "); - log += res; + log += strformat(F("SCD30 Forced calibration: %d, result: %d"), event->Par1, res); success = true; } else if (equals(command, F("scdgetinterval"))) { P117_data->getMeasurementInterval(&value); - log += F("Interval: "); - log += value; + log += concat(F("Interval: "), value); success = true; } else if (equals(command, F("scdsetinterval")) && (event->Par1 >= 2) && (event->Par1 <= 1800)) { int res = P117_data->setMeasurementInterval(event->Par1); P117_MEASURE_INTERVAL = event->Par1; // Update device configuration - log += F("SCD30 Measurement Interval: "); - log += event->Par1; - log += F(", result: "); - log += res; - success = true; + log += strformat(F("SCD30 Measurement Interval: %d, result: %d"), + event->Par1, res); + success = true; } if (success) { diff --git a/src/_P118_Itho.ino b/src/_P118_Itho.ino index 478d5abb3..51185e0e9 100644 --- a/src/_P118_Itho.ino +++ b/src/_P118_Itho.ino @@ -162,11 +162,10 @@ boolean Plugin_118(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = F("GDO2: "); - string += formatGpioLabel(P118_IRQPIN, false); - string += event->String1; - string += F("CSN: "); - string += formatGpioLabel(P118_CSPIN, false); + string = strformat(F("GDO2: %s%sCSN: %s"), + formatGpioLabel(P118_IRQPIN, false).c_str(), + event->String1.c_str(), + formatGpioLabel(P118_CSPIN, false).c_str()); success = true; break; } @@ -198,8 +197,10 @@ boolean Plugin_118(uint8_t function, struct EventStruct *event, String& string) P118_data_struct *P118_data = static_cast(getPluginTaskData(event->TaskIndex)); success = (nullptr != P118_data) && P118_data->plugin_init(event); + # ifndef BUILD_NO_DEBUG } else { addLog(LOG_LEVEL_ERROR, F("ITHO: CS pin not correctly configured, plugin can not start!")); + # endif // ifndef BUILD_NO_DEBUG } break; @@ -283,8 +284,15 @@ boolean Plugin_118(uint8_t function, struct EventStruct *event, String& string) addFormNumericBox(F("Device ID byte 1"), F("pdevid1"), P118_CONFIG_DEVID1, 0, 255); addFormNumericBox(F("Device ID byte 2"), F("pdevid2"), P118_CONFIG_DEVID2, 0, 255); addFormNumericBox(F("Device ID byte 3"), F("pdevid3"), P118_CONFIG_DEVID3, 0, 255); - addFormNote(F("Device ID of your ESP, should not be the same as your neighbours ;-). " - "Defaults to 10,87,81 which corresponds to the old Itho library")); + addFormNote(F("Device ID of your ESP" + # ifndef BUILD_NO_DEBUG + ", should not be the same as your neighbours ;-)" + # endif // ifndef BUILD_NO_DEBUG + ". Defaults to 10,87,81" + # ifndef BUILD_NO_DEBUG + " which corresponds to the old Itho library" + # endif // ifndef BUILD_NO_DEBUG + )); # if P118_FEATURE_ORCON addFormNote(F("For Orcon: This is the destination ID a.k.a. the ID of the Ventilation unit.")); diff --git a/src/_P120_ADXL345_Accelerometer.ino b/src/_P120_ADXL345_Accelerometer.ino index 3ed31e99d..220165618 100644 --- a/src/_P120_ADXL345_Accelerometer.ino +++ b/src/_P120_ADXL345_Accelerometer.ino @@ -89,10 +89,10 @@ boolean Plugin_120(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { 0x53, 0x1D }; + const uint8_t i2cAddressValues[] = { 0x1D, 0x53 }; if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P120_I2C_ADDR); + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P120_I2C_ADDR, 0x53); addFormNote(F("AD0 Low=0x53, High=0x1D")); } else { success = intArrayContains(2, i2cAddressValues, event->Par1); diff --git a/src/_P121_HMC5883L.ino b/src/_P121_HMC5883L.ino index 63e41beb0..f8efc70c4 100644 --- a/src/_P121_HMC5883L.ino +++ b/src/_P121_HMC5883L.ino @@ -99,7 +99,8 @@ boolean Plugin_121(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_LOAD: { addFormFloatNumberBox(F("Declination Angle"), F("pdecl"), PCONFIG_FLOAT(0), -180.0f, 180.0f, 2, 0.01f); - PCONFIG_FLOAT(1) = PCONFIG_FLOAT(0) * M_PI / 180.0f; // convert from degree to radian + # define M_PI_180 0.01745329251994329577f // M_PI / 180.0f + PCONFIG_FLOAT(1) = PCONFIG_FLOAT(0) * M_PI_180; // M_PI / 180.0f; // convert from degree to radian addUnit(F("degree")); success = true; break; @@ -137,23 +138,22 @@ boolean Plugin_121(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 1, s_event.magnetic.y); UserVar.setFloat(event->TaskIndex, 2, s_event.magnetic.z); - float heading = atan2(s_event.magnetic.y, s_event.magnetic.x); + double heading = atan2(s_event.magnetic.y, s_event.magnetic.x); - const float decl = PCONFIG_FLOAT(1); + const double decl = PCONFIG_FLOAT(1); - if (decl != 0) { + if (!essentiallyZero(decl)) { heading += decl; } - if (heading < 0) { - heading += 2.0f * PI; + if (definitelyLessThan(heading, 0)) { + heading += TWO_PI; + } else + if (definitelyGreaterThan(heading, TWO_PI)) { + heading -= TWO_PI; } - if (heading > 2.0f * PI) { - heading -= 2.0f * PI; - } - - UserVar.setFloat(event->TaskIndex, 3, heading * 180.0f / M_PI); + UserVar.setFloat(event->TaskIndex, 3, heading * M_PI_180); success = true; // Assume we want to send out values to controllers } diff --git a/src/_P122_SHT2x.ino b/src/_P122_SHT2x.ino index 1ccf17e7f..800d025e7 100644 --- a/src/_P122_SHT2x.ino +++ b/src/_P122_SHT2x.ino @@ -1,259 +1,254 @@ -#include "_Plugin_Helper.h" -#ifdef USES_P122 - -// ####################################################################################################### -// ######################## Plugin 122 SHT2x I2C Temperature Humidity Sensor ############################ -// ####################################################################################################### -// 26-03-2023 Flashmark creation based upon https://github.com/RobTillaart/SHT2x - -# include "src/PluginStructs/P122_data_struct.h" - -# define PLUGIN_122 -# define PLUGIN_ID_122 122 // plugin id -# define PLUGIN_NAME_122 "Environment - SHT2x" // What will be dislpayed in the selection list -# define PLUGIN_VALUENAME1_122 "Temperature" // variable output of the plugin. The label is in quotation marks -# define PLUGIN_VALUENAME2_122 "Humidity" // multiple outputs are supported - -// PIN/port configuration is stored in the following: -// CONFIG_PIN1 - The first GPIO pin selected within the task -// CONFIG_PIN2 - The second GPIO pin selected within the task -// CONFIG_PIN3 - The third GPIO pin selected within the task -// CONFIG_PORT - The port in case the device has multiple in/out pins -// -// Custom configuration is stored in the following: -// PCONFIG(x) -// x can be between 1 - 8 and can store values between -32767 - 32768 (16 bit) -// -// N.B. these are aliases for a longer less readable amount of code. See _Plugin_Helper.h -// -// PCONFIG_LABEL(x) is a function to generate a unique label used as HTML id to be able to match -// returned values when saving a configuration. - -// Make accessing specific parameters more readable in the code -// #define Pxxx_OUTPUT_TYPE_INDEX 2 -# define P122_I2C_ADDRESS PCONFIG(0) -# define P122_I2C_ADDRESS_LABEL PCONFIG_LABEL(0) -# define P122_RESOLUTION PCONFIG(1) -# define P122_RESOLUTION_LABEL PCONFIG_LABEL(1) - -// A plugin has to implement the following function - -boolean Plugin_122(uint8_t function, struct EventStruct *event, String& string) -{ - // function: reason the plugin was called - // event: ??add description here?? - // string: ??add description here?? - - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - // This case defines the device characteristics - Device[++deviceCount].Number = PLUGIN_ID_122; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 2; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].I2CNoDeviceCheck = true; - - // Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - Device[deviceCount].OutputDataType = Output_Data_type_t::Default; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - // return the device name - string = F(PLUGIN_NAME_122); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - // called when the user opens the module configuration page - // it allows to add a new row for each output variable of the plugin - // For plugins able to choose output types, see P026_Sysinfo.ino. - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_122)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_122)); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - // Set a default config here, which will be called when a plugin is assigned to a task. - P122_I2C_ADDRESS = P122_I2C_ADDRESS_AD0_0; - P122_RESOLUTION = P122_RESOLUTION_14T_12RH; - success = true; - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P122_I2C_ADDRESS; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { P122_I2C_ADDRESS_AD0_0, P122_I2C_ADDRESS_AD0_1 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) - { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P122_I2C_ADDRESS); - addFormNote(F("ADO Low=0x40, High=0x41")); - } - else - { - success = intArrayContains(2, i2cAddressValues, event->Par1); - } - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - // this case defines what should be displayed on the web form, when this plugin is selected - // The user's selection will be stored in - // PCONFIG(x) (custom configuration) - - # define P122_RESOLUTION_OPTIONS 4 - - const __FlashStringHelper *options[] = { - F("Temp 14 bits / RH 12 bits"), - F("Temp 13 bits / RH 10 bits"), - F("Temp 12 bits / RH 8 bits"), - F("Temp 11 bits / RH 11 bits"), - }; - const int optionValues[] = { - P122_RESOLUTION_14T_12RH, - P122_RESOLUTION_13T_10RH, - P122_RESOLUTION_12T_08RH, - P122_RESOLUTION_11T_11RH, - }; - addFormSelector(F("Resolution"), P122_RESOLUTION_LABEL, P122_RESOLUTION_OPTIONS, options, optionValues, P122_RESOLUTION); - -# ifndef LIMIT_BUILD_SIZE - P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (P122_data != nullptr) - { - uint32_t eida; - uint32_t eidb; - uint8_t firmware; - P122_data->getEID(eida, eidb, firmware); - String txt = F("CHIP ID:"); - txt += formatToHex(eida); - txt += ','; - txt += formatToHex(eidb); - txt += F(" firmware="); - txt += String(firmware); -# ifdef PLUGIN_122_DEBUG - txt += F(" userReg= "); - txt += formatToHex(P122_data->getUserReg()); -# endif // ifdef PLUGIN_122_DEBUG - addFormNote(txt); - } -# endif // ifndef LIMIT_BUILD_SIZE - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - // this case defines the code to be executed when the form is submitted - // the plugin settings should be saved to PCONFIG(x) - // ping configuration should be read from CONFIG_PIN1 and stored - - // after the form has been saved successfuly, set success and break - P122_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); - P122_RESOLUTION = getFormItemInt(P122_RESOLUTION_LABEL); - success = true; - break; - } - - case PLUGIN_INIT: - { - // this case defines code to be executed when the plugin is initialised - initPluginTaskData(event->TaskIndex, new (std::nothrow) P122_data_struct()); - P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (P122_data != nullptr) - { - P122_data->setupDevice(P122_I2C_ADDRESS, P122_RESOLUTION); - P122_data->reset(); - success = true; - } - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - UserVar.setFloat(event->TaskIndex, 2, NAN); - break; - } - - case PLUGIN_READ: - { - // code to be executed to read data - // It is executed according to the delay configured on the device configuration page, only once - P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P122_data) - { - if (P122_data->inError()) - { - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - addLog(LOG_LEVEL_ERROR, F("SHT2x: in Error!")); - } - else if (P122_data->newValues()) - { - UserVar.setFloat(event->TaskIndex, 0, P122_data->getTemperature()); - UserVar.setFloat(event->TaskIndex, 1, P122_data->getHumidity()); - P122_data->startMeasurements(); // getting ready for another read cycle - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) - { - String log = F("P122: Temperature: "); - log += UserVar[event->BaseVarIndex + 0]; - log += F(" Humidity: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_INFO, log); - } - success = true; - break; - } - - case PLUGIN_ONCE_A_SECOND: - { - // code to be executed once a second. Tasks which do not require fast response can be added here - success = true; - } - - case PLUGIN_TEN_PER_SECOND: - { - // code to be executed 10 times per second. Tasks which require fast response can be added here - // be careful on what is added here. Heavy processing will result in slowing the module down! - P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P122_data) - { - P122_data->update(); // SHT2x FSM evaluation - } - success = true; - } - } // switch - return success; -} // function - -#endif //USES_P122 +#include "_Plugin_Helper.h" +#ifdef USES_P122 + +// ####################################################################################################### +// ######################## Plugin 122 SHT2x I2C Temperature Humidity Sensor ############################ +// ####################################################################################################### +// 26-03-2023 Flashmark creation based upon https://github.com/RobTillaart/SHT2x + +# include "src/PluginStructs/P122_data_struct.h" + +# define PLUGIN_122 +# define PLUGIN_ID_122 122 // plugin id +# define PLUGIN_NAME_122 "Environment - SHT2x" // What will be dislpayed in the selection list +# define PLUGIN_VALUENAME1_122 "Temperature" // variable output of the plugin. The label is in quotation marks +# define PLUGIN_VALUENAME2_122 "Humidity" // multiple outputs are supported + +// PIN/port configuration is stored in the following: +// CONFIG_PIN1 - The first GPIO pin selected within the task +// CONFIG_PIN2 - The second GPIO pin selected within the task +// CONFIG_PIN3 - The third GPIO pin selected within the task +// CONFIG_PORT - The port in case the device has multiple in/out pins +// +// Custom configuration is stored in the following: +// PCONFIG(x) +// x can be between 1 - 8 and can store values between -32767 - 32768 (16 bit) +// +// N.B. these are aliases for a longer less readable amount of code. See _Plugin_Helper.h +// +// PCONFIG_LABEL(x) is a function to generate a unique label used as HTML id to be able to match +// returned values when saving a configuration. + +// Make accessing specific parameters more readable in the code +// #define Pxxx_OUTPUT_TYPE_INDEX 2 +# define P122_I2C_ADDRESS PCONFIG(0) +# define P122_I2C_ADDRESS_LABEL PCONFIG_LABEL(0) +# define P122_RESOLUTION PCONFIG(1) +# define P122_RESOLUTION_LABEL PCONFIG_LABEL(1) + +// A plugin has to implement the following function + +boolean Plugin_122(uint8_t function, struct EventStruct *event, String& string) +{ + // function: reason the plugin was called + // event: ??add description here?? + // string: ??add description here?? + + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + // This case defines the device characteristics + Device[++deviceCount].Number = PLUGIN_ID_122; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_HUM; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].I2CNoDeviceCheck = true; + + // Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Default; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + // return the device name + string = F(PLUGIN_NAME_122); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + // called when the user opens the module configuration page + // it allows to add a new row for each output variable of the plugin + // For plugins able to choose output types, see P026_Sysinfo.ino. + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_122)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_122)); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + // Set a default config here, which will be called when a plugin is assigned to a task. + P122_I2C_ADDRESS = P122_I2C_ADDRESS_AD0_0; + P122_RESOLUTION = P122_RESOLUTION_14T_12RH; + success = true; + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P122_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { P122_I2C_ADDRESS_AD0_0, P122_I2C_ADDRESS_AD0_1 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) + { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P122_I2C_ADDRESS); + addFormNote(F("ADO Low=0x40, High=0x41")); + } + else + { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + // this case defines what should be displayed on the web form, when this plugin is selected + // The user's selection will be stored in + // PCONFIG(x) (custom configuration) + + # define P122_RESOLUTION_OPTIONS 4 + + const __FlashStringHelper *options[] = { + F("Temp 14 bits / RH 12 bits"), + F("Temp 13 bits / RH 10 bits"), + F("Temp 12 bits / RH 8 bits"), + F("Temp 11 bits / RH 11 bits"), + }; + const int optionValues[] = { + P122_RESOLUTION_14T_12RH, + P122_RESOLUTION_13T_10RH, + P122_RESOLUTION_12T_08RH, + P122_RESOLUTION_11T_11RH, + }; + addFormSelector(F("Resolution"), P122_RESOLUTION_LABEL, P122_RESOLUTION_OPTIONS, options, optionValues, P122_RESOLUTION); + +# ifndef LIMIT_BUILD_SIZE + P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (P122_data != nullptr) + { + uint32_t eida; + uint32_t eidb; + uint8_t firmware; + P122_data->getEID(eida, eidb, firmware); + String txt = F("CHIP ID:"); + txt += formatToHex(eida); + txt += ','; + txt += formatToHex(eidb); + txt += F(" firmware="); + txt += String(firmware); +# ifdef PLUGIN_122_DEBUG + txt += F(" userReg= "); + txt += formatToHex(P122_data->getUserReg()); +# endif // ifdef PLUGIN_122_DEBUG + addFormNote(txt); + } +# endif // ifndef LIMIT_BUILD_SIZE + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + // this case defines the code to be executed when the form is submitted + // the plugin settings should be saved to PCONFIG(x) + // ping configuration should be read from CONFIG_PIN1 and stored + + // after the form has been saved successfuly, set success and break + P122_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + P122_RESOLUTION = getFormItemInt(P122_RESOLUTION_LABEL); + success = true; + break; + } + + case PLUGIN_INIT: + { + // this case defines code to be executed when the plugin is initialised + initPluginTaskData(event->TaskIndex, new (std::nothrow) P122_data_struct()); + P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (P122_data != nullptr) + { + P122_data->setupDevice(P122_I2C_ADDRESS, P122_RESOLUTION); + P122_data->reset(); + success = true; + } + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + UserVar.setFloat(event->TaskIndex, 2, NAN); + break; + } + + case PLUGIN_READ: + { + // code to be executed to read data + // It is executed according to the delay configured on the device configuration page, only once + P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P122_data) + { + if (P122_data->inError()) + { + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + addLog(LOG_LEVEL_ERROR, F("SHT2x: in Error!")); + } + else if (P122_data->newValues()) + { + UserVar.setFloat(event->TaskIndex, 0, P122_data->getTemperature()); + UserVar.setFloat(event->TaskIndex, 1, P122_data->getHumidity()); + P122_data->startMeasurements(); // getting ready for another read cycle + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) + { + addLog(LOG_LEVEL_INFO, + strformat(F("P122: Temperature: %s Humidity: %s"), + formatUserVarNoCheck(event, 0).c_str(), + formatUserVarNoCheck(event, 1).c_str() + )); + } + success = true; + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + // code to be executed 10 times per second. Tasks which require fast response can be added here + // be careful on what is added here. Heavy processing will result in slowing the module down! + P122_data_struct *P122_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P122_data) + { + P122_data->update(); // SHT2x FSM evaluation + } + success = true; + break; + } + } // switch + return success; +} // function + +#endif //USES_P122 diff --git a/src/_P123_I2CTouch.ino b/src/_P123_I2CTouch.ino new file mode 100644 index 000000000..3dab764c9 --- /dev/null +++ b/src/_P123_I2CTouch.ino @@ -0,0 +1,347 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P123 + +// ####################################################################################################### +// ###################################### Plugin 123: I2C Touchscreens ################################### +// ####################################################################################################### + +/** + * Changelog: + * 2024-06-10 tonhuisman: Add support for CHSC5816, as found in https://github.com/lewisxhe/SensorLib by Lewis He (added to bb_captouch) + * 2024-06-02 tonhuisman: Renamed to I2C Touchscreens + * Refactor to use modified bb_captouch library https://github.com/bitbank2/bb_captouch to add support for + * FT62x6 and GT911 (tested), CST820, CST226 and AXS15231 (untested) + * The library supports auto-configure, but that only checks if an I2C device is available at certain addresses + * so that's not really reliable. Added methods to override the used touchscreen with the known device + * Only single-point support for now. + * For the auto-configure option the I2C device check is disabled for this plugin. + * 2024-03-21 tonhuisman: Refactor increment/decrement to next/prevButtonGroup/Page functions, to align with ESPEasy_TouchHandler + * 2023-12-31 tonhuisman: Code optimizations + * 2023-10-01 tonhuisman: Re-implement (fix) switching of X/Y/Z vs X/Y output values using PLUGIN_GET_DEVICEVALUECOUNT, store (also) in task + * settings for speed + * Implement PLUGIN_I2C_GET_ADDRESS function + * 2023-08-15 tonhuisman: Implement Extended CustomTaskSettings + * 2022-12-04 tonhuisman: Remove [Testing] tag from plugin name + * 2022-09-26 tonhuisman: Add nullptr checks, improved log/string handling + * 2022-08-15 tonhuisman: Add Swipe and Slider support (to TouchHandler) + * 2022-08-15 tonhuisman: UI improvement, settings table uses alternate color per 2 rows, code improvements + * 2022-06-10 tonhuisman: Remove p123_ prefixes on Settings variables + * 2022-06-06 tonhuisman: Move PLUGIN_WRITE handling mostly to ESPEasy_TouchHandler (only rot and flip subcommands remain) + * Move PLUGIN_GET_CONFIG_VALUE handling to ESPEasy_TouchHandler + * 2022-05-29 tonhuisman: Extend enable,disable subcommands to support a list of objects + * 2022-05-28 tonhuisman: Add incpage and decpage subcommands that + and - 10 to the current buttongroup + * 2022-05-26 tonhuisman: Add touch,updatebutton command + * 2022-05-23 tonhuisman: Refactor touch settings and button emulation into ESPEasy_TouchHandler class for reuse in P099 + * 2022-05-02 tonhuisman: Small updates and improvements + * 2022-04-30 tonhuisman: Add support for AdaGFX btn subcommand use and (local) button groups + * Start preparations for refactoring touch objects into separate helper class + * 2022-04-25 tonhuisman: Code cleanup, initialize object event -2 for disabled objects, -1 for enabled objects + * Add on and off subcommands to switch a touchbutton object. Generate init-event for enable + * disable, on and off states when init events option enabled + * 2022-04-24 tonhuisman: Code improvements, increased button response speed + * 2022-04-24 tonhuisman: Add event arguments for OnOff button objects, fix addLog statements, minor improvements + * 2022-04-23 tonhuisman: Rename struct TS_Point in FT6206 library to FT_Point to avoid conflict with XPT2048 library (P099) + * 2021-11-07 tonhuisman: Initial plugin, based on _P099_XPT2046_Touchscreen.ino plugin and Adafruit FT6206 Library + */ + +/** + * Commands supported: + * ------------------- + * touch,rot,<0..3> : Set rotation to 0(0), 90(1), 180(2), 270(3) degrees + * touch,flip,<0|1> : Set rotation normal(0) or flipped by 180 degrees(1) + * + * Other commands: see ESPEasy_TouchHandler.h + */ + +# define PLUGIN_123 +# define PLUGIN_ID_123 123 +# define PLUGIN_NAME_123 "Touch - I2C Touchscreens" +# define PLUGIN_VALUENAME1_123 "X" +# define PLUGIN_VALUENAME2_123 "Y" +# define PLUGIN_VALUENAME3_123 "Z" + +# include "src/PluginStructs/P123_data_struct.h" + + +boolean Plugin_123(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_123; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TRIPLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = false; + Device[deviceCount].TimerOption = false; + Device[deviceCount].ExitTaskBeforeSave = false; + Device[deviceCount].I2CNoDeviceCheck = true; + success = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_123); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_123)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_123)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_123)); + success = true; + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + success = P123_data_struct::plugin_i2c_has_address(event->Par1); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P123_CONFIG_DISPLAY_TASK = event->TaskIndex; // Preselect current task to avoid pointing to Task 1 by default + P123_CONFIG_THRESHOLD = P123_TS_THRESHOLD; + P123_CONFIG_ROTATION = P123_TS_ROTATION; + P123_CONFIG_X_RES = P123_TS_X_RES; + P123_CONFIG_Y_RES = P123_TS_Y_RES; + P123_I2C_ADDRESS = 0x38; // Former default was FT62x6 + P123_SET_TOUCH_TYPE(static_cast(P123_TouchType_e::FT62x6)); + P123_INTERRUPTPIN = -1; // No interrupt + P123_RESETPIN = -1; // No reset + + success = true; + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P123_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = P123_CONFIG_VTYPE; + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + # ifdef PLUGIN_123_DEBUG + addLogMove(LOG_LEVEL_INFO, F("P123 PLUGIN_WEBFORM_LOAD")); + # endif // ifdef PLUGIN_123_DEBUG + { + addRowLabel(F("Display task")); + addTaskSelect(F("dsptask"), P123_CONFIG_DISPLAY_TASK); + # ifndef P123_LIMIT_BUILD_SIZE + addFormNote(F("Screen Width, Heigth, Rotation & Color-depth will be fetched from the Display task if possible.")); + # endif // ifndef P123_LIMIT_BUILD_SIZE + } + + uint16_t width_ = P123_CONFIG_X_RES; + uint16_t height_ = P123_CONFIG_Y_RES; + uint16_t rotation_ = P123_CONFIG_ROTATION; + uint16_t colorDepth_ = P123_COLOR_DEPTH; + + if (P123_CONFIG_DISPLAY_TASK != P123_CONFIG_DISPLAY_PREV) { // Changed since last saved? + getPluginDisplayParametersFromTaskIndex(P123_CONFIG_DISPLAY_TASK, width_, height_, rotation_, colorDepth_); + } + P123_COLOR_DEPTH = colorDepth_; + + if (width_ == 0) { + width_ = P123_TS_X_RES; // default value + } + addFormNumericBox(F("Screen Width (px) (x)"), F("xres"), width_, 1, 65535); + + + if (height_ == 0) { + height_ = P123_TS_Y_RES; // default value + } + addFormNumericBox(F("Screen Height (px) (y)"), F("yres"), height_, 1, 65535); + + AdaGFXFormRotation(F("rotate"), rotation_); + + AdaGFXFormColorDepth(F("colordepth"), P123_COLOR_DEPTH, (colorDepth_ == 0)); + + addFormNumericBox(F("Touch minimum pressure"), F("threshold"), P123_CONFIG_THRESHOLD, 0, 255); + addUnit(F("Only used for FT62x6")); + + { + P123_data_struct *P123_data = static_cast(getPluginTaskData(event->TaskIndex)); + bool deleteP123_data = false; + { + const __FlashStringHelper *touchTypes[] = { + toString(P123_TouchType_e::FT62x6), + toString(P123_TouchType_e::GT911_1), + toString(P123_TouchType_e::GT911_2), + toString(P123_TouchType_e::CST820), + toString(P123_TouchType_e::CST226), + toString(P123_TouchType_e::AXS15231), + toString(P123_TouchType_e::CHSC5816), + toString(P123_TouchType_e::Automatic), + }; + const int touchTypeOptions[] = { + static_cast(P123_TouchType_e::FT62x6), + static_cast(P123_TouchType_e::GT911_1), + static_cast(P123_TouchType_e::GT911_2), + static_cast(P123_TouchType_e::CST820), + static_cast(P123_TouchType_e::CST226), + static_cast(P123_TouchType_e::AXS15231), + static_cast(P123_TouchType_e::CHSC5816), + static_cast(P123_TouchType_e::Automatic), + }; + addFormSelector(F("Touchscreen type (address)"), + F("ttype"), + NR_ELEMENTS(touchTypeOptions), + touchTypes, + touchTypeOptions, + P123_GET_TOUCH_TYPE); + + if (nullptr != P123_data) { + addUnit(concat(F("Detected: "), toString(P123_data->getTouchType()))); + } + } + addFormPinSelect(PinSelectPurpose::Generic_bidir, F("Interrupt pin"), F("taskdevicepin1"), P123_INTERRUPTPIN); + addFormPinSelect(PinSelectPurpose::Generic_bidir, F("Reset pin"), F("taskdevicepin2"), P123_RESETPIN); + addFormNote(F("Interrupt and Reset pins are optional. Interrupt is only used by GT911.")); + + if (nullptr == P123_data) { + P123_data = new (std::nothrow) P123_data_struct(static_cast(P123_GET_TOUCH_TYPE)); + deleteP123_data = true; + } + + if (nullptr != P123_data) { + P123_data->plugin_webform_load(event); + + if (deleteP123_data) { + delete P123_data; + } + } + } + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + # ifdef PLUGIN_123_DEBUG + addLogMove(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")); + P123_CONFIG_DISPLAY_TASK = getFormItemInt(F("dsptask")); + P123_CONFIG_ROTATION = getFormItemInt(F("rotate")); + P123_CONFIG_X_RES = getFormItemInt(F("xres")); + P123_CONFIG_Y_RES = getFormItemInt(F("yres")); + P123_SET_TOUCH_TYPE(getFormItemInt(F("ttype"))); + P123_I2C_ADDRESS = P123_data_struct::plugin_i2c_address(static_cast(P123_GET_TOUCH_TYPE)); + + // taskdevicepin1/taskdevicepin2 are saved automatically + + const int colorDepth = getFormItemInt(F("colordepth"), -1); + + if (colorDepth != -1) { + P123_COLOR_DEPTH = colorDepth; + } + + { + P123_data_struct *P123_data = nullptr; // static_cast(getPluginTaskData(event->TaskIndex)); + bool deleteP123_data = false; + + if (nullptr == P123_data) { + P123_data = new (std::nothrow) P123_data_struct(static_cast(P123_GET_TOUCH_TYPE)); + deleteP123_data = true; + } + + if (nullptr != P123_data) { + success = P123_data->plugin_webform_save(event); + + if (deleteP123_data) { + delete P123_data; + } + } + } + + break; + } + + case PLUGIN_INIT: + { + # ifdef PLUGIN_123_DEBUG + addLogMove(LOG_LEVEL_INFO, F("P123 PLUGIN_INIT")); + # endif // ifdef PLUGIN_123_DEBUG + + if (0 == P123_I2C_ADDRESS) { + P123_I2C_ADDRESS = 0x38; // Former default was FT62x6 + P123_SET_TOUCH_TYPE(static_cast(P123_TouchType_e::FT62x6)); + } + initPluginTaskData(event->TaskIndex, new (std::nothrow) P123_data_struct(static_cast(P123_GET_TOUCH_TYPE))); + P123_data_struct *P123_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P123_data) { + return success; + } + + success = true; + + if (!P123_data->init(event)) { + clearPluginTaskData(event->TaskIndex); + success = false; + } + break; + } + + // case PLUGIN_READ: // Not implemented on purpose, *only* send out events/values when device is touched, and configured to send events + + case PLUGIN_WRITE: + { + P123_data_struct *P123_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P123_data) { + success = P123_data->plugin_write(event, string); + } + + break; + } + + case PLUGIN_FIFTY_PER_SECOND: // Increased response + { + P123_data_struct *P123_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P123_data) { + success = P123_data->plugin_fifty_per_second(event); + } + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P123_data_struct *P123_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P123_data) { + success = P123_data->plugin_get_config_value(event, string); + } + break; + } + } // switch(function) + return success; +} // Plugin_123 + +#endif // USES_P123 diff --git a/src/_P124_MultiRelay.ino b/src/_P124_MultiRelay.ino index 035065afe..15a329d1d 100644 --- a/src/_P124_MultiRelay.ino +++ b/src/_P124_MultiRelay.ino @@ -88,8 +88,8 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) addFormSelectorI2C(F("i2caddress"), 8, i2cAddressValues, P124_CONFIG_I2C_ADDRESS); addFormCheckBox(F("Change I2C address of board"), F("change_i2c"), false); - addFormNote( - F("Change of address will be stored in the board and retained until changed again. See documentation for change-procedure.")); + addFormNote(F("Change of address will be stored in the board and retained until changed again. " + "See documentation for change-procedure.")); } else { success = intArrayContains(8, i2cAddressValues, event->Par1); } @@ -117,7 +117,6 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) addFormSelector_YesNo(F("Initialize relays on startup"), getPluginCustomArgName(P124_FLAGS_INIT_RELAYS), bitRead(P124_CONFIG_FLAGS, P124_FLAGS_INIT_RELAYS) ? 1 : 0, true); - String label; if (bitRead(P124_CONFIG_FLAGS, P124_FLAGS_INIT_RELAYS)) { addFormCheckBox(F("Apply initial state always"), @@ -125,10 +124,8 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) bitRead(P124_CONFIG_FLAGS, P124_FLAGS_INIT_ALWAYS)); addFormNote(F("Disabled: Applied once per restart, Enabled: Applied on every plugin start, like on Submit of this page")); - for (int i = 0; i < P124_CONFIG_RELAY_COUNT; i++) { - label = F("Relay "); - label += i + 1; - label += F(" initial state (on/off)"); + for (int i = 0; i < P124_CONFIG_RELAY_COUNT; ++i) { + const String label = strformat(F("Relay %d initial state (on/off)"), i + 1); addFormCheckBox(label, getPluginCustomArgName(i), bitRead(P124_CONFIG_FLAGS, i)); } } @@ -138,10 +135,8 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) bitRead(P124_CONFIG_FLAGS, P124_FLAGS_EXIT_RELAYS) ? 1 : 0, true); if (bitRead(P124_CONFIG_FLAGS, P124_FLAGS_EXIT_RELAYS)) { - for (int i = 0; i < P124_CONFIG_RELAY_COUNT; i++) { - label = F("Relay "); - label += i + 1; - label += F(" exit-state (on/off)"); + for (int i = 0; i < P124_CONFIG_RELAY_COUNT; ++i) { + const String label = strformat(F("Relay %d exit-state (on/off)"), i + 1); addFormCheckBox(label, getPluginCustomArgName(i + P124_FLAGS_EXIT_OFFSET), bitRead(P124_CONFIG_FLAGS, i + P124_FLAGS_EXIT_OFFSET)); } addFormNote(F("ATTENTION: These Relay states will be set when the task is enabled and the settings are saved!")); @@ -166,7 +161,7 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) bitWrite(lSettings, P124_FLAGS_LOOP_GET, isFormItemChecked(getPluginCustomArgName(P124_FLAGS_LOOP_GET))); if (lSettings != 0) { - for (int i = 0; i < P124_CONFIG_RELAY_COUNT; i++) { // INIT and EXIT states + for (int i = 0; i < P124_CONFIG_RELAY_COUNT; ++i) { // INIT and EXIT states bitWrite(lSettings, i, isFormItemChecked(getPluginCustomArgName(i))); bitWrite(lSettings, i + P124_FLAGS_EXIT_OFFSET, isFormItemChecked(getPluginCustomArgName(i + P124_FLAGS_EXIT_OFFSET))); } @@ -206,7 +201,7 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) (!bitRead(P124_InitializedRelays, event->TaskIndex) || bitRead(P124_CONFIG_FLAGS, P124_FLAGS_INIT_ALWAYS))) { P124_data->channelCtrl(get8BitFromUL(P124_CONFIG_FLAGS, P124_FLAGS_INIT_OFFSET)); // Set relays state - UserVar.setFloat(event->TaskIndex, 0, P124_data->getChannelState()); // Get relays state + UserVar.setFloat(event->TaskIndex, 0, P124_data->getChannelState()); // Get relays state bitSet(P124_InitializedRelays, event->TaskIndex); // Update initialization status } P124_data->setLoopState(bitRead(P124_CONFIG_FLAGS, P124_FLAGS_LOOP_GET)); // Loop state @@ -225,7 +220,7 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) if (nullptr != P124_data) { if (P124_data->isInitialized()) { P124_data->channelCtrl(get8BitFromUL(P124_CONFIG_FLAGS, P124_FLAGS_EXIT_OFFSET)); // Set relays state - UserVar.setFloat(event->TaskIndex, 0, P124_data->getChannelState()); // Get relays state + UserVar.setFloat(event->TaskIndex, 0, P124_data->getChannelState()); // Get relays state } addLog(LOG_LEVEL_INFO, F("MultiRelay: Object still alive.")); } @@ -241,8 +236,8 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) UserVar.setFloat(event->TaskIndex, 0, P124_data->getChannelState()); // Get relays state if (P124_data->isLoopEnabled()) { - uint8_t chan = P124_data->getNextLoop(); - uint8_t data = P124_data->getChannelState() & (1 << (chan - 1)); + const uint8_t chan = P124_data->getNextLoop(); + const uint8_t data = P124_data->getChannelState() & (1 << (chan - 1)); UserVar.setFloat(event->TaskIndex, 1, chan); UserVar.setFloat(event->TaskIndex, 2, data ? 1 : 0); } @@ -256,13 +251,10 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) P124_data_struct *P124_data = static_cast(getPluginTaskData(event->TaskIndex)); if ((nullptr != P124_data) && P124_data->isInitialized()) { - uint8_t varNr = 3; // VARS_PER_TASK; - String label = F("Relay state "); - label += P124_CONFIG_RELAY_COUNT; - label += F(".."); - label += 1; - String state = F("0b "); - uint32_t val = UserVar[event->BaseVarIndex]; + uint8_t varNr = 3; // VARS_PER_TASK; + const String label = strformat(F("Relay state %d..1"), P124_CONFIG_RELAY_COUNT); + String state = F("0b "); + uint32_t val = UserVar[event->BaseVarIndex]; val &= 0xff; val |= (0x1 << P124_CONFIG_RELAY_COUNT); state += ull2String(val, 2); @@ -292,13 +284,7 @@ boolean Plugin_124(uint8_t function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_INFO, string); if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Par1..3:"); - log += event->Par1; - log += ','; - log += event->Par2; - log += ','; - log += event->Par3; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("Par1..3:%d,%d,%d"), event->Par1, event->Par2, event->Par3)); } # endif // ifdef P124_DEBUG_LOG diff --git a/src/_P126_74HC595.ino b/src/_P126_74HC595.ino index c478f1998..5a16874db 100644 --- a/src/_P126_74HC595.ino +++ b/src/_P126_74HC595.ino @@ -1,353 +1,349 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P126 - -// ####################################################################################################### -// ################################ Plugin 126 Shift registers 74HC595 ############################### -// ####################################################################################################### - -/** Changelog: - * 2022-02-27 tonhuisman: Rename plugin title to Output - Shift registers (74HC595) - * 2022-02-25 tonhuisman: Again rename commands, now using separate prefix shiftout and the rest of the previous command as subcommand. - * 2022-02-24 tonhuisman: Further update changing 74hc commands to 74hc595. - * Allow selecting value output decimal + hex/bin, decimal only or hex/bin only. - * Adjust Values display label order to show State_4_1 instead of State_1_4, same order as byte values. - * 2022-02-23 tonhuisman: Rename commands using prefix 74hc595 to distinguish from plugin P129 74hc165 using similar commands. - * 2022-01-22 tonhuisman: ShiftRegister74HC595_NonTemplate library: Add setSize method, cleanup constructor - * Setting: Restore register-buffer state from RTC values after warm boot (or crash...) - * NB:!!! Only restores up to 4 * VARS_PER_TASK (16) chip values, starting at the configured Offset for display !!! - * When enabled, changing the offset will reset the values content to 0. - * Code improvements and optimizations - * Add command 74hc595SetChipCount for changing the number of chips at runtime. Does not restart the plugin. - * Hide regular Values display if plugin is active, only custom Hex/Bin states. Show periods in Hex state (too). - * Hide Formula and Decimals for Values. Correct Sensor_VType setting. - * Output both Decimal and Hex or Bin (depending on setting) in generated event. Don't use Single event option, as - * that won't allow handling all 8 values (yet). - * 2022-01-20 tonhuisman: Fix some bugs, optimize code, now actually supports 255 chips = 2048 pins - * Hex Values display now in uppercase for readability - * 2022-01-19 tonhuisman: Add 74hc595SetOffset and 74hxSetHexBin commands - * 2022-01-18 tonhuisman: Improve parsing for 74hc595setall with chipnumber (1..chipCount) and data width (1..4) options - * 2022-01-17 tonhuisman: Extend to max. 255 chips, add offset for display values, add 74hc595SetAllNoUpdate command - * Rename Value names - * 2022-01-16 tonhuisman: Refactor ShiftRegister74HC595 to ShiftRegister74HC595_NonTemplate to enable runtime sizing - * Add commands, implement PLUGIN_WEBFORM_SHOW_VALUES, testing and improving - * 2022-01-15 tonhuisman: Implement command handling - * 2021-11-17 tonhuisman: Initial plugin development. Based on a Forum request: https://www.letscontrolit.com/forum/viewtopic.php?f=5&t=8751 - */ - -/** Commands: - * ShiftOut,Set,,<0|1> : Set a single pin on or off, and update. - * ShiftOut,SetNoUpdate,,<0|1> : Set a single pin on or off. Use ShiftOut,Update to set outputs. - * ShiftOut,Update : Update all pin states to the registers. - * ShiftOut,SetAll,[chip:][width:]... : Set a range of chips with values, default 32 bit values (width 4). - * ShiftOut,SetAllNoUpdate,[chip:][width:] : Ditto, without immediate update. Use ShiftOut,Update to set outputs. - * ShiftOut,SetAllLow : Set all register outputs to 0/low. - * ShiftOut,SetAllHigh : Set all register outputs to 1/high. - * ShiftOut,SetChipCount, : Set the number of chips, without restarting the plugin. Range 1..P126_MAX_CHIP_COUNT. - * ShiftOut,SetOffset, : Set the chip offset for display. Will reflect in device configuration, but not saved. - * ShiftOut,SetHexBin,<0|1> : Turn off/on the Hex or Bin Values display, reflected in device configuration, not saved. - */ - -# define PLUGIN_126 -# define PLUGIN_ID_126 126 -# define PLUGIN_NAME_126 "Output - Shift registers (74HC595)" -# define PLUGIN_VALUENAME1_126 "State_A" -# define PLUGIN_VALUENAME2_126 "State_B" -# define PLUGIN_VALUENAME3_126 "State_C" -# define PLUGIN_VALUENAME4_126 "State_D" - -# include "./src/PluginStructs/P126_data_struct.h" - -// TODO tonhuisman: ? Move to StringConverter ? though it is a bit specific, can also be used by P129 -String P126_ul2stringFixed(uint32_t value, uint8_t base) { - uint64_t val = static_cast(value); - - val &= 0x0ffffffff; // Keep 32 bits - val |= 0x100000000; // Set bit just left of 32 bits so we will see the leading zeroes - String valStr = ull2String(val, base); - - valStr.remove(0, 1); // Delete leading 1 we added - valStr.toUpperCase(); // uppercase hex for readability - return valStr; -} - -boolean Plugin_126(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_126; - Device[deviceCount].Type = DEVICE_TYPE_TRIPLE; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = false; - Device[deviceCount].ValueCount = - # if P126_MAX_CHIP_COUNT <= 4 - 1 - # elif P126_MAX_CHIP_COUNT <= 8 - 2 - # elif P126_MAX_CHIP_COUNT <= 12 - 3 - # else // if P126_MAX_CHIP_COUNT <= 4 - 4 - # endif // if P126_MAX_CHIP_COUNT <= 4 - ; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].TimerOptional = true; - - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_126); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_126)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_126)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_126)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_126)); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P126_CONFIG_DATA_PIN = -1; - P126_CONFIG_CLOCK_PIN = -1; - P126_CONFIG_LATCH_PIN = -1; - ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // No decimals needed - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_output(F("Data pin (DS)")); - event->String2 = formatGpioName_output(F("Clock pin (SH_CP)")); - event->String3 = formatGpioName_output(F("Latch pin (ST_CP)")); - break; - } - case PLUGIN_WEBFORM_LOAD: - { - addFormSubHeader(F("Device configuration")); - - addFormNumericBox(F("Number of chips (Q7' → DS)"), - F("chips"), - P126_CONFIG_CHIP_COUNT, - 1, // Minimum is 1 chip - P126_MAX_CHIP_COUNT); // Max chip count - String unit = F("Daisychained 1.."); - unit += P126_MAX_CHIP_COUNT; - addUnit(unit); - - addFormNumericBox(F("Offset for display"), - F("offset"), - P126_CONFIG_SHOW_OFFSET, - 0, - P126_MAX_SHOW_OFFSET); - addUnit(F("Multiple of 4")); - - # ifdef P126_SHOW_VALUES - addFormCheckBox(F("Values display (Off=Hex/On=Bin)"), F("valdisplay"), P126_CONFIG_FLAGS_GET_VALUES_DISPLAY == 1); - # endif // ifdef P126_SHOW_VALUES - - const __FlashStringHelper *outputOptions[] = { - F("Decimal & hex/bin"), - F("Decimal only"), - F("Hex/bin only") }; - const int outputValues[] = { P126_OUTPUT_BOTH, P126_OUTPUT_DEC_ONLY, P126_OUTPUT_HEXBIN }; - addFormSelector(F("Output selection"), F("output"), 3, outputOptions, outputValues, P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION); - - addFormCheckBox(F("Restore Values on warm boot"), F("valrestore"), P126_CONFIG_FLAGS_GET_VALUES_RESTORE); - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - uint8_t previousOffset = P126_CONFIG_SHOW_OFFSET; - P126_CONFIG_CHIP_COUNT = getFormItemInt(F("chips")); - P126_CONFIG_SHOW_OFFSET = getFormItemInt(F("offset")); - - if (P126_CONFIG_SHOW_OFFSET >= P126_CONFIG_CHIP_COUNT) { - P126_CONFIG_SHOW_OFFSET = 0; - } - P126_CONFIG_SHOW_OFFSET -= (P126_CONFIG_SHOW_OFFSET % 4); - - if ((P126_CONFIG_CHIP_COUNT > 4) && - (P126_CONFIG_SHOW_OFFSET > P126_CONFIG_CHIP_COUNT - 4) && - (P126_CONFIG_CHIP_COUNT < P126_MAX_SHOW_OFFSET)) { - P126_CONFIG_SHOW_OFFSET -= 4; - } - - uint32_t lSettings = 0u; - - # ifdef P126_SHOW_VALUES - - if (isFormItemChecked(F("valdisplay"))) { bitSet(lSettings, P126_FLAGS_VALUES_DISPLAY); } - # endif // ifdef P126_SHOW_VALUES - - if (!isFormItemChecked(F("valrestore"))) { bitSet(lSettings, P126_FLAGS_VALUES_RESTORE); } // Inverted setting! - set4BitToUL(lSettings, P126_FLAGS_OUTPUT_SELECTION, getFormItemInt(F("output"))); - - P126_CONFIG_FLAGS = lSettings; - - // Reset State_A..D values when changing the offset - if ((previousOffset != P126_CONFIG_SHOW_OFFSET) && P126_CONFIG_FLAGS_GET_VALUES_RESTORE) { - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) { - UserVar.setUint32(event->TaskIndex, varNr, 0u); - } - # ifdef P126_DEBUG_LOG - addLog(LOG_LEVEL_INFO, F("74HC595: 'Offset for display' changed: state values reset.")); - # endif // ifdef P126_DEBUG_LOG - } - - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P126_data_struct(P126_CONFIG_DATA_PIN, - P126_CONFIG_CLOCK_PIN, - P126_CONFIG_LATCH_PIN, - P126_CONFIG_CHIP_COUNT)); - P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P126_data) && P126_data->isInitialized()) { - success = P126_data->plugin_init(event); // Optionally restore State_A..State_D values from RTC (on warm-boot only!) - } - - if (!success) { - addLog(LOG_LEVEL_ERROR, F("74HC595: Initialization error!")); - # ifdef P126_DEBUG_LOG - } else { - addLog(LOG_LEVEL_INFO, F("74HC595: Initialized.")); - # endif // ifdef P126_DEBUG_LOG - } - - break; - } - - case PLUGIN_EXIT: - { - success = true; - break; - } - - case PLUGIN_READ: - { - P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P126_data) { - success = P126_data->plugin_read(event); // Get state - } - - - break; - } - - case PLUGIN_FORMAT_USERVAR: - { - string.clear(); - - if ((P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) || - (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_DEC_ONLY)) { - string += ull2String(UserVar.getUint32(event->TaskIndex, event->idx)); - } - - if (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) { - string += ','; - } - - if ((P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) || - (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_HEXBIN)) { - string += '0'; - string += (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? 'b' : 'x'); - string += P126_ul2stringFixed(UserVar.getUint32(event->TaskIndex, event->idx), - # ifdef P126_SHOW_VALUES - (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : - # endif // ifdef P126_SHOW_VALUES - HEX - # ifdef P126_SHOW_VALUES - ) - # endif // ifdef P126_SHOW_VALUES - ); - } - success = true; - break; - } - - # ifdef P126_SHOW_VALUES - case PLUGIN_WEBFORM_SHOW_VALUES: - { - String state, label; - state.reserve(40); - String abcd = F("ABCDEFGH"); // In case anyone dares to extend - // VARS_PER_TASK to 8... - const uint16_t endCheck = P126_CONFIG_CHIP_COUNT + (P126_CONFIG_CHIP_COUNT == 255 ? 3 : 4); // 4(.0) = nr of bytes in an uint32_t. - const uint16_t maxVar = min(static_cast(VARS_PER_TASK), static_cast(ceil(P126_CONFIG_CHIP_COUNT / 4.0))); - uint8_t dotInsert; - uint8_t dotOffset; - - for (uint16_t varNr = 0; varNr < maxVar; varNr++) { - if (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY) { - label = F("Bin"); - state = F("0b"); - dotInsert = 10; - dotOffset = 9; - } else { - label = F("Hex"); - state = F("0x"); - dotInsert = 4; - dotOffset = 3; - } - label += F(" State_"); - label += abcd.substring(varNr, varNr + 1); - label += ' '; - - label += min(255, P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 4); // Limited to max 255 chips - label += '_'; - label += (P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 1); // 4 = nr of bytes in an uint32_t. - - if ((P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 4) <= endCheck) { // Only show if still in range - state += P126_ul2stringFixed(UserVar.getUint32(event->TaskIndex, varNr), P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : HEX); - - for (uint8_t i = 0; i < 3; i++, dotInsert += dotOffset) { // Insert readability separators - state = state.substring(0, dotInsert) + '.' + state.substring(dotInsert); - } - pluginWebformShowValue(event->TaskIndex, VARS_PER_TASK + varNr, label, state, true); - } - } - success = true; // Don't show the default value data - break; - } - # endif // ifdef P126_SHOW_VALUES - case PLUGIN_WRITE: - { - P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P126_data) { - success = P126_data->plugin_write(event, string); - } - - break; - } - } - return success; -} - -#endif // ifdef USES_P126 +#include "_Plugin_Helper.h" + +#ifdef USES_P126 + +// ####################################################################################################### +// ################################ Plugin 126 Shift registers 74HC595 ############################### +// ####################################################################################################### + +/** Changelog: + * 2022-02-27 tonhuisman: Rename plugin title to Output - Shift registers (74HC595) + * 2022-02-25 tonhuisman: Again rename commands, now using separate prefix shiftout and the rest of the previous command as subcommand. + * 2022-02-24 tonhuisman: Further update changing 74hc commands to 74hc595. + * Allow selecting value output decimal + hex/bin, decimal only or hex/bin only. + * Adjust Values display label order to show State_4_1 instead of State_1_4, same order as byte values. + * 2022-02-23 tonhuisman: Rename commands using prefix 74hc595 to distinguish from plugin P129 74hc165 using similar commands. + * 2022-01-22 tonhuisman: ShiftRegister74HC595_NonTemplate library: Add setSize method, cleanup constructor + * Setting: Restore register-buffer state from RTC values after warm boot (or crash...) + * NB:!!! Only restores up to 4 * VARS_PER_TASK (16) chip values, starting at the configured Offset for display !!! + * When enabled, changing the offset will reset the values content to 0. + * Code improvements and optimizations + * Add command 74hc595SetChipCount for changing the number of chips at runtime. Does not restart the plugin. + * Hide regular Values display if plugin is active, only custom Hex/Bin states. Show periods in Hex state (too). + * Hide Formula and Decimals for Values. Correct Sensor_VType setting. + * Output both Decimal and Hex or Bin (depending on setting) in generated event. Don't use Single event option, as + * that won't allow handling all 8 values (yet). + * 2022-01-20 tonhuisman: Fix some bugs, optimize code, now actually supports 255 chips = 2048 pins + * Hex Values display now in uppercase for readability + * 2022-01-19 tonhuisman: Add 74hc595SetOffset and 74hxSetHexBin commands + * 2022-01-18 tonhuisman: Improve parsing for 74hc595setall with chipnumber (1..chipCount) and data width (1..4) options + * 2022-01-17 tonhuisman: Extend to max. 255 chips, add offset for display values, add 74hc595SetAllNoUpdate command + * Rename Value names + * 2022-01-16 tonhuisman: Refactor ShiftRegister74HC595 to ShiftRegister74HC595_NonTemplate to enable runtime sizing + * Add commands, implement PLUGIN_WEBFORM_SHOW_VALUES, testing and improving + * 2022-01-15 tonhuisman: Implement command handling + * 2021-11-17 tonhuisman: Initial plugin development. Based on a Forum request: https://www.letscontrolit.com/forum/viewtopic.php?f=5&t=8751 + */ + +/** Commands: + * ShiftOut,Set,,<0|1> : Set a single pin on or off, and update. + * ShiftOut,SetNoUpdate,,<0|1> : Set a single pin on or off. Use ShiftOut,Update to set outputs. + * ShiftOut,Update : Update all pin states to the registers. + * ShiftOut,SetAll,[chip:][width:]... : Set a range of chips with values, default 32 bit values (width 4). + * ShiftOut,SetAllNoUpdate,[chip:][width:] : Ditto, without immediate update. Use ShiftOut,Update to set outputs. + * ShiftOut,SetAllLow : Set all register outputs to 0/low. + * ShiftOut,SetAllHigh : Set all register outputs to 1/high. + * ShiftOut,SetChipCount, : Set the number of chips, without restarting the plugin. Range 1..P126_MAX_CHIP_COUNT. + * ShiftOut,SetOffset, : Set the chip offset for display. Will reflect in device configuration, but not saved. + * ShiftOut,SetHexBin,<0|1> : Turn off/on the Hex or Bin Values display, reflected in device configuration, not saved. + */ + +# define PLUGIN_126 +# define PLUGIN_ID_126 126 +# define PLUGIN_NAME_126 "Output - Shift registers (74HC595)" +# define PLUGIN_VALUENAME1_126 "State_A" +# define PLUGIN_VALUENAME2_126 "State_B" +# define PLUGIN_VALUENAME3_126 "State_C" +# define PLUGIN_VALUENAME4_126 "State_D" + +# include "./src/PluginStructs/P126_data_struct.h" + +// TODO tonhuisman: ? Move to StringConverter ? though it is a bit specific, can also be used by P129 +String P126_ul2stringFixed(uint32_t value, uint8_t base) { + uint64_t val = static_cast(value); + + val &= 0x0ffffffff; // Keep 32 bits + val |= 0x100000000; // Set bit just left of 32 bits so we will see the leading zeroes + String valStr = ull2String(val, base); + + valStr.remove(0, 1); // Delete leading 1 we added + valStr.toUpperCase(); // uppercase hex for readability + return valStr; +} + +boolean Plugin_126(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_126; + Device[deviceCount].Type = DEVICE_TYPE_TRIPLE; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = false; + Device[deviceCount].ValueCount = + # if P126_MAX_CHIP_COUNT <= 4 + 1 + # elif P126_MAX_CHIP_COUNT <= 8 + 2 + # elif P126_MAX_CHIP_COUNT <= 12 + 3 + # else // if P126_MAX_CHIP_COUNT <= 4 + 4 + # endif // if P126_MAX_CHIP_COUNT <= 4 + ; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].HasFormatUserVar = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_126); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_126)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_126)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_126)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_126)); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P126_CONFIG_DATA_PIN = -1; + P126_CONFIG_CLOCK_PIN = -1; + P126_CONFIG_LATCH_PIN = -1; + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // No decimals needed + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_output(F("Data pin (DS)")); + event->String2 = formatGpioName_output(F("Clock pin (SH_CP)")); + event->String3 = formatGpioName_output(F("Latch pin (ST_CP)")); + break; + } + case PLUGIN_WEBFORM_LOAD: + { + addFormSubHeader(F("Device configuration")); + + addFormNumericBox(F("Number of chips (Q7' → DS)"), + F("chips"), + P126_CONFIG_CHIP_COUNT, + 1, // Minimum is 1 chip + P126_MAX_CHIP_COUNT); // Max chip count + addUnit(concat(F("Daisychained 1.."), P126_MAX_CHIP_COUNT)); + + addFormNumericBox(F("Offset for display"), + F("offset"), + P126_CONFIG_SHOW_OFFSET, + 0, + P126_MAX_SHOW_OFFSET); + addUnit(F("Multiple of 4")); + + # ifdef P126_SHOW_VALUES + addFormCheckBox(F("Values display (Off=Hex/On=Bin)"), F("valdisplay"), P126_CONFIG_FLAGS_GET_VALUES_DISPLAY == 1); + # endif // ifdef P126_SHOW_VALUES + + const __FlashStringHelper *outputOptions[] = { + F("Decimal & hex/bin"), + F("Decimal only"), + F("Hex/bin only") }; + const int outputValues[] = { P126_OUTPUT_BOTH, P126_OUTPUT_DEC_ONLY, P126_OUTPUT_HEXBIN }; + addFormSelector(F("Output selection"), F("output"), 3, outputOptions, outputValues, P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION); + + addFormCheckBox(F("Restore Values on warm boot"), F("valrestore"), P126_CONFIG_FLAGS_GET_VALUES_RESTORE); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + uint8_t previousOffset = P126_CONFIG_SHOW_OFFSET; + P126_CONFIG_CHIP_COUNT = getFormItemInt(F("chips")); + P126_CONFIG_SHOW_OFFSET = getFormItemInt(F("offset")); + + if (P126_CONFIG_SHOW_OFFSET >= P126_CONFIG_CHIP_COUNT) { + P126_CONFIG_SHOW_OFFSET = 0; + } + P126_CONFIG_SHOW_OFFSET -= (P126_CONFIG_SHOW_OFFSET % 4); + + if ((P126_CONFIG_CHIP_COUNT > 4) && + (P126_CONFIG_SHOW_OFFSET > P126_CONFIG_CHIP_COUNT - 4) && + (P126_CONFIG_CHIP_COUNT < P126_MAX_SHOW_OFFSET)) { + P126_CONFIG_SHOW_OFFSET -= 4; + } + + uint32_t lSettings = 0u; + + # ifdef P126_SHOW_VALUES + + if (isFormItemChecked(F("valdisplay"))) { bitSet(lSettings, P126_FLAGS_VALUES_DISPLAY); } + # endif // ifdef P126_SHOW_VALUES + + if (!isFormItemChecked(F("valrestore"))) { bitSet(lSettings, P126_FLAGS_VALUES_RESTORE); } // Inverted setting! + set4BitToUL(lSettings, P126_FLAGS_OUTPUT_SELECTION, getFormItemInt(F("output"))); + + P126_CONFIG_FLAGS = lSettings; + + // Reset State_A..D values when changing the offset + if ((previousOffset != P126_CONFIG_SHOW_OFFSET) && P126_CONFIG_FLAGS_GET_VALUES_RESTORE) { + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { + UserVar.setUint32(event->TaskIndex, varNr, 0u); + } + # ifdef P126_DEBUG_LOG + addLog(LOG_LEVEL_INFO, F("74HC595: 'Offset for display' changed: state values reset.")); + # endif // ifdef P126_DEBUG_LOG + } + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P126_data_struct(P126_CONFIG_DATA_PIN, + P126_CONFIG_CLOCK_PIN, + P126_CONFIG_LATCH_PIN, + P126_CONFIG_CHIP_COUNT)); + P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P126_data) && P126_data->isInitialized()) { + success = P126_data->plugin_init(event); // Optionally restore State_A..State_D values from RTC (on warm-boot only!) + } + + if (!success) { + addLog(LOG_LEVEL_ERROR, F("74HC595: Initialization error!")); + # ifdef P126_DEBUG_LOG + } else { + addLog(LOG_LEVEL_INFO, F("74HC595: Initialized.")); + # endif // ifdef P126_DEBUG_LOG + } + + break; + } + + case PLUGIN_EXIT: + { + success = true; + break; + } + + case PLUGIN_READ: + { + P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P126_data) { + success = P126_data->plugin_read(event); // Get state + } + + break; + } + + case PLUGIN_FORMAT_USERVAR: + { + string.clear(); + + if ((P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) || + (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_DEC_ONLY)) { + string += ull2String(UserVar.getUint32(event->TaskIndex, event->idx)); + } + + if (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) { + string += ','; + } + + if ((P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_BOTH) || + (P126_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P126_OUTPUT_HEXBIN)) { + string += '0'; + string += (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? 'b' : 'x'); + string += P126_ul2stringFixed(UserVar.getUint32(event->TaskIndex, event->idx), + # ifdef P126_SHOW_VALUES + (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : + # endif // ifdef P126_SHOW_VALUES + HEX + # ifdef P126_SHOW_VALUES + ) + # endif // ifdef P126_SHOW_VALUES + ); + } + success = true; + break; + } + + # ifdef P126_SHOW_VALUES + case PLUGIN_WEBFORM_SHOW_VALUES: + { + String state, label; + state.reserve(40); + const String abcd = F("ABCDEFGH"); // In case anyone dares to extend + // VARS_PER_TASK to 8... + const uint16_t endCheck = P126_CONFIG_CHIP_COUNT + (P126_CONFIG_CHIP_COUNT == 255 ? 3 : 4); // 4(.0) = nr of bytes in an uint32_t. + const uint16_t maxVar = min(static_cast(VARS_PER_TASK), static_cast(ceil(P126_CONFIG_CHIP_COUNT / 4.0))); + uint8_t dotInsert; + uint8_t dotOffset; + + for (uint16_t varNr = 0; varNr < maxVar; ++varNr) { + if (P126_CONFIG_FLAGS_GET_VALUES_DISPLAY) { + label = F("Bin"); + state = F("0b"); + dotInsert = 10; + dotOffset = 9; + } else { + label = F("Hex"); + state = F("0x"); + dotInsert = 4; + dotOffset = 3; + } + label += strformat(F(" State_%s "), abcd.substring(varNr, varNr + 1).c_str()); + + label += min(255, P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 4); // Limited to max 255 chips + label += '_'; + label += (P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 1); // 4 = nr of bytes in an uint32_t. + + if ((P126_CONFIG_SHOW_OFFSET + (4 * varNr) + 4) <= endCheck) { // Only show if still in range + state += P126_ul2stringFixed(UserVar.getUint32(event->TaskIndex, varNr), P126_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : HEX); + + for (uint8_t i = 0; i < 3; ++i, dotInsert += dotOffset) { // Insert readability separators + state = state.substring(0, dotInsert) + '.' + state.substring(dotInsert); + } + pluginWebformShowValue(event->TaskIndex, VARS_PER_TASK + varNr, label, state, true); + } + } + success = true; // Don't show the default value data + break; + } + # endif // ifdef P126_SHOW_VALUES + case PLUGIN_WRITE: + { + P126_data_struct *P126_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P126_data) { + success = P126_data->plugin_write(event, string); + } + + break; + } + } + return success; +} + +#endif // ifdef USES_P126 diff --git a/src/_P127_CDM7160.ino b/src/_P127_CDM7160.ino index c91282216..d9fe0d5fc 100644 --- a/src/_P127_CDM7160.ino +++ b/src/_P127_CDM7160.ino @@ -65,10 +65,10 @@ boolean Plugin_127(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_I2C_HAS_ADDRESS: case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: { - const uint8_t i2cAddressValues[] = { CDM7160_ADDR, CDM7160_ADDR_0 }; + const uint8_t i2cAddressValues[] = { CDM7160_ADDR_0, CDM7160_ADDR }; if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P127_CONFIG_I2C_ADDRESS); + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P127_CONFIG_I2C_ADDRESS, CDM7160_ADDR); # ifndef LIMIT_BUILD_SIZE addFormNote(F("CAD0 High/open=0x69, Low=0x68")); # endif // ifndef LIMIT_BUILD_SIZE @@ -89,6 +89,7 @@ boolean Plugin_127(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_SET_DEFAULTS: { + P127_CONFIG_I2C_ADDRESS = CDM7160_ADDR; ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // No decimals needed break; } @@ -149,15 +150,11 @@ boolean Plugin_127(uint8_t function, struct EventStruct *event, String& string) } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("CDM7160: Address: 0x"); - log += String(P127_CONFIG_I2C_ADDRESS, HEX); - log += F(": CO2 ppm: "); - log += UserVar[event->BaseVarIndex]; - log += F(", alt: "); - log += P127_data->getAltitude(); - log += F(", comp: "); - log += P127_data->getCompensation(); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("CDM7160: Address: 0x%02x: CO2 ppm: %d, alt: %d, comp: %d"), + P127_CONFIG_I2C_ADDRESS, + UserVar[event->BaseVarIndex], + P127_data->getAltitude(), + P127_data->getCompensation())); } break; } diff --git a/src/_P129_74HC165.ino b/src/_P129_74HC165.ino index 0159b7666..063be7cfb 100644 --- a/src/_P129_74HC165.ino +++ b/src/_P129_74HC165.ino @@ -1,452 +1,448 @@ -#include "_Plugin_Helper.h" - -#ifdef USES_P129 - -// ####################################################################################################### -// ################################ Plugin 129 74HC165 Shiftregisters ############################### -// ####################################################################################################### - -/** Changelog: - * 2023-01-04 tonhuisman: Use DIRECT_pin GPIO functions for faster GPIO handling (mostly on ESP32), string optimization - * 2022-08-05 tonhuisman: Fix issue with reading 8th bit of each byte (found during HW testing) - * Reduce number of Values to match the selected number of chips/4. Small UI improvements. - * Enable pin is no longer required, as it is not available or required on some boards. - * 2022-07-30 tonhuisman: Remove Testing tag from plugin name. - * 2022-06-12 tonhuisman: Optimizations and small fixes. Implement use of PCONFIG_ULONG() - * 2022-02-25 tonhuisman: Rename command to ShiftIn,,... - * 2022-02-23 tonhuisman: Add command handling. - * 2022-02-22 tonhuisman: Compare results and generate events. - * 2022-02-21 tonhuisman: Add output selection dec + hex/bin, dec or hex/bin. - * 2022-02-20 tonhuisman: Initial plugin development. - * Based on a Forum request: https://www.letscontrolit.com/forum/viewtopic.php?p=57072&hilit=74hc165#p57072 - */ - -/** Commands: - * These commands only change configuration settings, but do not save them. They can be saved using the 'save' command. - * - * ShiftIn,PinEvent,,<0|1> : Set the event enable state for pin (1..128), max. up to configured chips * 8. - * ShiftIn,ChipEvent,,<0|1> : Set the event enable state for an entire chip (1..16), max. up to configured chips. - * ShiftIn,SetChipCount, : Set the number of chips, up to 16 (P129_MAX_CHIP_COUNT). - * ShiftIn,SampleFrequency,<0|1> : Set the sample frequency, 0 = 10x/sec, 1 = 50x/sec. - * ShiftIn,EventPerPin,<0|1> : Set events per pin off or on. - */ - -# define PLUGIN_129 -# define PLUGIN_ID_129 129 -# define PLUGIN_NAME_129 "Input - Shift registers (74HC165)" -# define PLUGIN_VALUENAME1_129 "State_A" -# define PLUGIN_VALUENAME2_129 "State_B" -# define PLUGIN_VALUENAME3_129 "State_C" -# define PLUGIN_VALUENAME4_129 "State_D" - -# include "./src/PluginStructs/P129_data_struct.h" - -// TODO tonhuisman: ? Move to StringConverter ? though it is a bit specific, can also be used by P126 -String P129_ul2stringFixed(uint32_t value, uint8_t base) { - // Set bit just left of 32 bits so we will see the leading zeroes - const uint64_t val = static_cast(value) | 0x100000000ull; - - String valStr = ull2String(val, base).substring(1); // Delete leading 1 we added - if (base == HEX) { - valStr.toUpperCase(); // uppercase hex for readability - } - return valStr; -} - -boolean Plugin_129(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_129; - Device[deviceCount].Type = DEVICE_TYPE_TRIPLE; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = false; - Device[deviceCount].DecimalsOnly = false; - Device[deviceCount].ValueCount = - # if P129_MAX_CHIP_COUNT <= 4 - 1 - # elif P129_MAX_CHIP_COUNT <= 8 - 2 - # elif P129_MAX_CHIP_COUNT <= 12 - 3 - # else // if P129_MAX_CHIP_COUNT > 12 - 4 - # endif // if P129_MAX_CHIP_COUNT <= 4 - ; - Device[deviceCount].SendDataOption = true; // No use in sending the Values to a controller - Device[deviceCount].TimerOption = true; // Used to update the Devices page - Device[deviceCount].TimerOptional = true; - - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_129); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_129)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_129)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_129)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_129)); - break; - } - - case PLUGIN_SET_DEFAULTS: - { - P129_CONFIG_CHIP_COUNT = 1; // Minimum is 1 chip - P129_CONFIG_DATA_PIN = -1; - P129_CONFIG_CLOCK_PIN = -1; - P129_CONFIG_ENABLE_PIN = -1; - P129_CONFIG_LOAD_PIN = -1; - ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // No decimals needed - ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // No decimals needed - break; - } - - case PLUGIN_GET_DEVICEGPIONAMES: - { - event->String1 = formatGpioName_input(F("Data (Q7)")); - event->String2 = formatGpioName_output(F("Clock (CP)")); - event->String3 = formatGpioName_output(F("Enable (EN) (opt.)")); - break; - } - - case PLUGIN_GET_DEVICEVALUECOUNT: - { - event->Par1 = min(static_cast(VARS_PER_TASK), - static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f))); - success = true; - break; - } - - case PLUGIN_GET_DEVICEVTYPE: - { - event->sensorType = static_cast( - min(static_cast(VARS_PER_TASK), - static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f)))); - event->idx = 0; - success = true; - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - addFormPinSelect(PinSelectPurpose::Generic_output, - formatGpioName_output(F("Load (PL)")), - F("load_pin"), - P129_CONFIG_LOAD_PIN); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("GPIO pins for Data, Clock and Load must be configured to correctly initialize the plugin.")); - # endif // ifndef LIMIT_BUILD_SIZE - - addFormSubHeader(F("Device configuration")); - - { - String chipCount[P129_MAX_CHIP_COUNT]; - int chipOption[P129_MAX_CHIP_COUNT]; - - for (uint8_t i = 0; i < P129_MAX_CHIP_COUNT; i++) { - chipCount[i] = String(i + 1); - chipOption[i] = i + 1; - } - addFormSelector(F("Number of chips (Q7 → DS)"), - F("chipcnt"), - P129_MAX_CHIP_COUNT, - chipCount, - chipOption, - P129_CONFIG_CHIP_COUNT, - true); - addUnit(concat(F("Daisychained 1.."), P129_MAX_CHIP_COUNT)); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Changing the number of chips will reload the page and update the Event configuration.")); - # endif // ifndef LIMIT_BUILD_SIZE - } - - const __FlashStringHelper *frequencyOptions[] = { - F("10/sec (100 msec)"), - F("50/sec (20 msec)") }; - const int frequencyValues[] = { P129_FREQUENCY_10, P129_FREQUENCY_50 }; - addFormSelector(F("Sample frequency"), F("frequency"), 2, frequencyOptions, frequencyValues, P129_CONFIG_FLAGS_GET_READ_FREQUENCY); - - addFormSubHeader(F("Display and output")); - - # ifdef P129_SHOW_VALUES - addFormCheckBox(F("Values display (Off=Hex/On=Bin)"), F("valuesdisplay"), P129_CONFIG_FLAGS_GET_VALUES_DISPLAY == 1); - # endif // ifdef P129_SHOW_VALUES - - const __FlashStringHelper *outputOptions[] = { - F("Decimal & hex/bin"), - F("Decimal only"), - F("Hex/bin only") }; - const int outputValues[] = { P129_OUTPUT_BOTH, P129_OUTPUT_DEC_ONLY, P129_OUTPUT_HEXBIN }; - addFormSelector(F("Output selection"), F("outputsel"), 3, outputOptions, outputValues, P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION); - - addFormCheckBox(F("Separate events per pin"), F("separate_events"), P129_CONFIG_FLAGS_GET_SEPARATE_EVENTS == 1); - - addFormSubHeader(F("Event configuration")); - - { - addRowLabel(F("Enable change-event for")); - html_table(EMPTY_STRING); // Sub-table - html_table_header(F("Chip # "), 70); - html_table_header(F("Port:"), 70); - html_table_header(F("D7"), 30); - html_table_header(F("D6"), 30); - html_table_header(F("D5"), 30); - html_table_header(F("D4"), 30); - html_table_header(F("D3"), 30); - html_table_header(F("D2"), 30); - html_table_header(F("D1"), 30); - html_table_header(F("D0"), 30); - - uint64_t bits = 0; - uint8_t off = 0; - - for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; i++) { - if (i % 4 == 0) { - bits = PCONFIG_ULONG(i / 4) & 0x0ffffffff; - off = 0; - # ifndef P129_DEBUG_LOG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("74HC165 Reading from: "); - log += (i / 4); - log += F(", bits: "); - log += P129_ul2stringFixed(bits, BIN); - addLog(LOG_LEVEL_INFO, log); - } - # endif // ifndef P129_DEBUG_LOG - } - html_TR(); - addHtml(F("")); - addHtmlInt(i + 1); - html_TD(); - - for (uint8_t j = 0; j < 8; j++) { - html_TD(); - # if FEATURE_TOOLTIPS - const String toolTip = strformat( - F("Chip %d port D %d, pin %d"), - (i + 1), - (7 - j), - i * 8 + (8 - j)); - # endif // if FEATURE_TOOLTIPS - addCheckBox(getPluginCustomArgName((i * 8 + (7 - j)) + 1), bitRead(bits, off * 8 + (7 - j)) == 1 - # if FEATURE_TOOLTIPS - , false // = not Disabled - , toolTip - # endif // if FEATURE_TOOLTIPS - ); - } - off++; - } - html_end_table(); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P129_CONFIG_LOAD_PIN = getFormItemInt(F("load_pin")); - P129_CONFIG_CHIP_COUNT = getFormItemInt(F("chipcnt")); - - uint32_t lSettings = 0u; - - # ifdef P129_SHOW_VALUES - - if (isFormItemChecked(F("valuesdisplay"))) { bitSet(lSettings, P129_FLAGS_VALUES_DISPLAY); } - - if (isFormItemChecked(F("separate_events"))) { bitSet(lSettings, P129_FLAGS_SEPARATE_EVENTS); } - # endif // ifdef P129_SHOW_VALUES - - if (getFormItemInt(F("frequency"))) { bitSet(lSettings, P129_FLAGS_READ_FREQUENCY); } - set4BitToUL(lSettings, P129_FLAGS_OUTPUT_SELECTION, getFormItemInt(F("outputsel"))); - - P129_CONFIG_FLAGS = lSettings & 0xFFFF; - - uint64_t bits = 0; - uint8_t off = 0; - - for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; i++) { - if (i % 4 == 0) { - bits = 0; - off = 0; - } - - for (uint8_t j = 0; j < 8; j++) { - bitWriteULL(bits, static_cast(off * 8 + (7 - j)), isFormItemChecked(getPluginCustomArgName((i * 8 + (7 - j)) + 1))); // -V629 - } - PCONFIG_ULONG(i / 4) = bits; - - # ifndef P129_DEBUG_LOG - - if (loglevelActiveFor(LOG_LEVEL_INFO) && ((i % 4 == 3) || (i == P129_CONFIG_CHIP_COUNT))) { - String log = F("74HC165 Writing to: "); - log += (i / 4); - log += F(", offset: "); - log += (off * 8); - log += F(", bits: "); - log += P129_ul2stringFixed(bits, BIN); - addLog(LOG_LEVEL_INFO, log); - } - # endif // ifndef P129_DEBUG_LOG - off++; - } - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P129_data_struct(P129_CONFIG_DATA_PIN, - P129_CONFIG_CLOCK_PIN, - P129_CONFIG_ENABLE_PIN, - P129_CONFIG_LOAD_PIN, - P129_CONFIG_CHIP_COUNT)); - P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if ((nullptr != P129_data) && P129_data->isInitialized()) { - success = P129_data->plugin_init(event); - } - - if (!success) { - addLog(LOG_LEVEL_ERROR, F("74HC165: Initialization error!")); - # ifdef P129_DEBUG_LOG - } else { - addLog(LOG_LEVEL_INFO, F("74HC165: Initialized.")); - # endif // ifdef P129_DEBUG_LOG - } - - break; - } - - case PLUGIN_READ: - { - P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P129_data) { - success = P129_data->plugin_read(event); // Get state - } - - break; - } - - case PLUGIN_TEN_PER_SECOND: - case PLUGIN_FIFTY_PER_SECOND: - { - if (((function == PLUGIN_TEN_PER_SECOND) && (P129_CONFIG_FLAGS_GET_READ_FREQUENCY == P129_FREQUENCY_10)) || - ((function == PLUGIN_FIFTY_PER_SECOND) && (P129_CONFIG_FLAGS_GET_READ_FREQUENCY == P129_FREQUENCY_50))) { - P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P129_data) { - success = P129_data->plugin_readData(event); - } - } - - break; - } - case PLUGIN_FORMAT_USERVAR: - { - string.clear(); - - if ((P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) || - (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_DEC_ONLY)) { - string += String(UserVar.getUint32(event->TaskIndex, event->idx)); - } - - if (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) { - string += ','; - } - - if ((P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) || - (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_HEXBIN)) { - string += '0'; - string += (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? 'b' : 'x'); - string += P129_ul2stringFixed(UserVar.getUint32(event->TaskIndex, event->idx), - # ifdef P129_SHOW_VALUES - (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : - # endif // ifdef P129_SHOW_VALUES - HEX - # ifdef P129_SHOW_VALUES - ) - # endif // ifdef P129_SHOW_VALUES - ); - } - success = true; - break; - } - - # ifdef P129_SHOW_VALUES - case PLUGIN_WEBFORM_SHOW_VALUES: - { - String state, label; - state.reserve(40); - String abcd = F("ABCDEFGH"); // In case anyone dares to extend VARS_PER_TASK to 8... - const uint16_t endCheck = P129_CONFIG_CHIP_COUNT + 4; // 4(.0) = nr of bytes in an uint32_t. - const uint16_t maxVar = min(static_cast(VARS_PER_TASK), static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f))); - uint8_t dotInsert; - uint8_t dotOffset; - - for (uint16_t varNr = 0; varNr < maxVar; varNr++) { - if (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY) { - label = F("Bin"); - state = F("0b"); - dotInsert = 10; - dotOffset = 9; - } else { - label = F("Hex"); - state = F("0x"); - dotInsert = 4; - dotOffset = 3; - } - label += F(" State_"); - label += abcd.substring(varNr, varNr + 1); - label += ' '; - - label += min(255, P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 4); // Limited to max 255 chips - label += '_'; - label += (P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 1); // 4 = nr of bytes in an uint32_t. - - if ((P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 4) <= endCheck) { // Only show if still in range - state += P129_ul2stringFixed(UserVar.getUint32(event->TaskIndex, varNr), P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : HEX); - - for (uint8_t i = 0; i < 3; i++, dotInsert += dotOffset) { // Insert readability separators - state = state.substring(0, dotInsert) + '.' + state.substring(dotInsert); - } - pluginWebformShowValue(event->TaskIndex, VARS_PER_TASK + varNr, label, state, true); - } - } - success = true; // Don't show the default value data - break; - } - # endif // ifdef P129_SHOW_VALUES - case PLUGIN_WRITE: - { - P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P129_data) { - success = P129_data->plugin_write(event, string); - } - - break; - } - } - return success; -} - -#endif // ifdef USES_P129 +#include "_Plugin_Helper.h" + +#ifdef USES_P129 + +// ####################################################################################################### +// ################################ Plugin 129 74HC165 Shiftregisters ############################### +// ####################################################################################################### + +/** Changelog: + * 2023-01-04 tonhuisman: Use DIRECT_pin GPIO functions for faster GPIO handling (mostly on ESP32), string optimization + * 2022-08-05 tonhuisman: Fix issue with reading 8th bit of each byte (found during HW testing) + * Reduce number of Values to match the selected number of chips/4. Small UI improvements. + * Enable pin is no longer required, as it is not available or required on some boards. + * 2022-07-30 tonhuisman: Remove Testing tag from plugin name. + * 2022-06-12 tonhuisman: Optimizations and small fixes. Implement use of PCONFIG_ULONG() + * 2022-02-25 tonhuisman: Rename command to ShiftIn,,... + * 2022-02-23 tonhuisman: Add command handling. + * 2022-02-22 tonhuisman: Compare results and generate events. + * 2022-02-21 tonhuisman: Add output selection dec + hex/bin, dec or hex/bin. + * 2022-02-20 tonhuisman: Initial plugin development. + * Based on a Forum request: https://www.letscontrolit.com/forum/viewtopic.php?p=57072&hilit=74hc165#p57072 + */ + +/** Commands: + * These commands only change configuration settings, but do not save them. They can be saved using the 'save' command. + * + * ShiftIn,PinEvent,,<0|1> : Set the event enable state for pin (1..128), max. up to configured chips * 8. + * ShiftIn,ChipEvent,,<0|1> : Set the event enable state for an entire chip (1..16), max. up to configured chips. + * ShiftIn,SetChipCount, : Set the number of chips, up to 16 (P129_MAX_CHIP_COUNT). + * ShiftIn,SampleFrequency,<0|1> : Set the sample frequency, 0 = 10x/sec, 1 = 50x/sec. + * ShiftIn,EventPerPin,<0|1> : Set events per pin off or on. + */ + +# define PLUGIN_129 +# define PLUGIN_ID_129 129 +# define PLUGIN_NAME_129 "Input - Shift registers (74HC165)" +# define PLUGIN_VALUENAME1_129 "State_A" +# define PLUGIN_VALUENAME2_129 "State_B" +# define PLUGIN_VALUENAME3_129 "State_C" +# define PLUGIN_VALUENAME4_129 "State_D" + +# include "./src/PluginStructs/P129_data_struct.h" + +// TODO tonhuisman: ? Move to StringConverter ? though it is a bit specific, can also be used by P126 +String P129_ul2stringFixed(uint32_t value, uint8_t base) { + // Set bit just left of 32 bits so we will see the leading zeroes + const uint64_t val = static_cast(value) | 0x100000000ull; + + String valStr = ull2String(val, base).substring(1); // Delete leading 1 we added + + if (base == HEX) { + valStr.toUpperCase(); // uppercase hex for readability + } + return valStr; +} + +boolean Plugin_129(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_129; + Device[deviceCount].Type = DEVICE_TYPE_TRIPLE; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = false; + Device[deviceCount].DecimalsOnly = false; + Device[deviceCount].ValueCount = + # if P129_MAX_CHIP_COUNT <= 4 + 1 + # elif P129_MAX_CHIP_COUNT <= 8 + 2 + # elif P129_MAX_CHIP_COUNT <= 12 + 3 + # else // if P129_MAX_CHIP_COUNT > 12 + 4 + # endif // if P129_MAX_CHIP_COUNT <= 4 + ; + Device[deviceCount].SendDataOption = true; // No use in sending the Values to a controller + Device[deviceCount].TimerOption = true; // Used to update the Devices page + Device[deviceCount].TimerOptional = true; + Device[deviceCount].HasFormatUserVar = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_129); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_129)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_129)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_129)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_129)); + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P129_CONFIG_CHIP_COUNT = 1; // Minimum is 1 chip + P129_CONFIG_DATA_PIN = -1; + P129_CONFIG_CLOCK_PIN = -1; + P129_CONFIG_ENABLE_PIN = -1; + P129_CONFIG_LOAD_PIN = -1; + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // No decimals needed + ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // No decimals needed + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_input(F("Data (Q7)")); + event->String2 = formatGpioName_output(F("Clock (CP)")); + event->String3 = formatGpioName_output(F("Enable (EN) (opt.)")); + break; + } + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + event->Par1 = min(static_cast(VARS_PER_TASK), + static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f))); + success = true; + break; + } + + case PLUGIN_GET_DEVICEVTYPE: + { + event->sensorType = static_cast( + min(static_cast(VARS_PER_TASK), + static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f)))); + event->idx = 0; + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormPinSelect(PinSelectPurpose::Generic_output, + formatGpioName_output(F("Load (PL)")), + F("load_pin"), + P129_CONFIG_LOAD_PIN); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("GPIO pins for Data, Clock and Load must be configured to correctly initialize the plugin.")); + # endif // ifndef LIMIT_BUILD_SIZE + + addFormSubHeader(F("Device configuration")); + + { + String chipCount[P129_MAX_CHIP_COUNT]; + int chipOption[P129_MAX_CHIP_COUNT]; + + for (uint8_t i = 0; i < P129_MAX_CHIP_COUNT; ++i) { + chipCount[i] = i + 1; + chipOption[i] = i + 1; + } + addFormSelector(F("Number of chips (Q7 → DS)"), + F("chipcnt"), + P129_MAX_CHIP_COUNT, + chipCount, + chipOption, + P129_CONFIG_CHIP_COUNT, + true); + addUnit(concat(F("Daisychained 1.."), P129_MAX_CHIP_COUNT)); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Changing the number of chips will reload the page and update the Event configuration.")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + const __FlashStringHelper *frequencyOptions[] = { + F("10/sec (100 msec)"), + F("50/sec (20 msec)") }; + const int frequencyValues[] = { P129_FREQUENCY_10, P129_FREQUENCY_50 }; + addFormSelector(F("Sample frequency"), F("frequency"), 2, frequencyOptions, frequencyValues, P129_CONFIG_FLAGS_GET_READ_FREQUENCY); + + addFormSubHeader(F("Display and output")); + + # ifdef P129_SHOW_VALUES + addFormCheckBox(F("Values display (Off=Hex/On=Bin)"), F("valuesdisplay"), P129_CONFIG_FLAGS_GET_VALUES_DISPLAY == 1); + # endif // ifdef P129_SHOW_VALUES + + const __FlashStringHelper *outputOptions[] = { + F("Decimal & hex/bin"), + F("Decimal only"), + F("Hex/bin only") }; + const int outputValues[] = { P129_OUTPUT_BOTH, P129_OUTPUT_DEC_ONLY, P129_OUTPUT_HEXBIN }; + addFormSelector(F("Output selection"), F("outputsel"), 3, outputOptions, outputValues, P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION); + + addFormCheckBox(F("Separate events per pin"), F("separate_events"), P129_CONFIG_FLAGS_GET_SEPARATE_EVENTS == 1); + + addFormSubHeader(F("Event configuration")); + + { + addRowLabel(F("Enable change-event for")); + html_table(EMPTY_STRING); // Sub-table + html_table_header(F("Chip # "), 70); + html_table_header(F("Port:"), 70); + html_table_header(F("D7"), 30); + html_table_header(F("D6"), 30); + html_table_header(F("D5"), 30); + html_table_header(F("D4"), 30); + html_table_header(F("D3"), 30); + html_table_header(F("D2"), 30); + html_table_header(F("D1"), 30); + html_table_header(F("D0"), 30); + + uint64_t bits = 0; + uint8_t off = 0; + + for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; ++i) { + if (i % 4 == 0) { + bits = PCONFIG_ULONG(i / 4) & 0x0ffffffff; + off = 0; + # ifndef P129_DEBUG_LOG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("74HC165 Reading from: %d, bits: %s"), i / 4, P129_ul2stringFixed(bits, BIN).c_str())); + } + # endif // ifndef P129_DEBUG_LOG + } + html_TR(); + addHtml(F("")); + addHtmlInt(i + 1); + html_TD(); + + for (uint8_t j = 0; j < 8; ++j) { + html_TD(); + # if FEATURE_TOOLTIPS + const String toolTip = strformat( + F("Chip %d port D %d, pin %d"), + (i + 1), + (7 - j), + i * 8 + (8 - j)); + # endif // if FEATURE_TOOLTIPS + addCheckBox(getPluginCustomArgName((i * 8 + (7 - j)) + 1), bitRead(bits, off * 8 + (7 - j)) == 1 + # if FEATURE_TOOLTIPS + , false // = not Disabled + , toolTip + # endif // if FEATURE_TOOLTIPS + ); + } + off++; + } + html_end_table(); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P129_CONFIG_LOAD_PIN = getFormItemInt(F("load_pin")); + P129_CONFIG_CHIP_COUNT = getFormItemInt(F("chipcnt")); + + uint32_t lSettings = 0u; + + # ifdef P129_SHOW_VALUES + + if (isFormItemChecked(F("valuesdisplay"))) { bitSet(lSettings, P129_FLAGS_VALUES_DISPLAY); } + + if (isFormItemChecked(F("separate_events"))) { bitSet(lSettings, P129_FLAGS_SEPARATE_EVENTS); } + # endif // ifdef P129_SHOW_VALUES + + if (getFormItemInt(F("frequency"))) { bitSet(lSettings, P129_FLAGS_READ_FREQUENCY); } + set4BitToUL(lSettings, P129_FLAGS_OUTPUT_SELECTION, getFormItemInt(F("outputsel"))); + + P129_CONFIG_FLAGS = lSettings & 0xFFFF; + + uint64_t bits = 0; + uint8_t off = 0; + + for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; ++i) { + if (i % 4 == 0) { + bits = 0; + off = 0; + } + + for (uint8_t j = 0; j < 8; ++j) { + bitWriteULL(bits, static_cast(off * 8 + (7 - j)), isFormItemChecked(getPluginCustomArgName((i * 8 + (7 - j)) + 1))); // -V629 + } + PCONFIG_ULONG(i / 4) = bits; + + # ifndef P129_DEBUG_LOG + + if (loglevelActiveFor(LOG_LEVEL_INFO) && ((i % 4 == 3) || (i == P129_CONFIG_CHIP_COUNT))) { + String log = F("74HC165 Writing to: "); + log += (i / 4); + log += F(", offset: "); + log += (off * 8); + log += F(", bits: "); + log += P129_ul2stringFixed(bits, BIN); + addLog(LOG_LEVEL_INFO, log); + } + # endif // ifndef P129_DEBUG_LOG + off++; + } + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P129_data_struct(P129_CONFIG_DATA_PIN, + P129_CONFIG_CLOCK_PIN, + P129_CONFIG_ENABLE_PIN, + P129_CONFIG_LOAD_PIN, + P129_CONFIG_CHIP_COUNT)); + P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((nullptr != P129_data) && P129_data->isInitialized()) { + success = P129_data->plugin_init(event); + } + + if (!success) { + addLog(LOG_LEVEL_ERROR, F("74HC165: Initialization error!")); + # ifdef P129_DEBUG_LOG + } else { + addLog(LOG_LEVEL_INFO, F("74HC165: Initialized.")); + # endif // ifdef P129_DEBUG_LOG + } + + break; + } + + case PLUGIN_READ: + { + P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P129_data) { + success = P129_data->plugin_read(event); // Get state + } + + break; + } + + case PLUGIN_TEN_PER_SECOND: + case PLUGIN_FIFTY_PER_SECOND: + { + if (((function == PLUGIN_TEN_PER_SECOND) && (P129_CONFIG_FLAGS_GET_READ_FREQUENCY == P129_FREQUENCY_10)) || + ((function == PLUGIN_FIFTY_PER_SECOND) && (P129_CONFIG_FLAGS_GET_READ_FREQUENCY == P129_FREQUENCY_50))) { + P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P129_data) { + success = P129_data->plugin_readData(event); + } + } + + break; + } + case PLUGIN_FORMAT_USERVAR: + { + string.clear(); + + if ((P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) || + (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_DEC_ONLY)) { + string += String(UserVar.getUint32(event->TaskIndex, event->idx)); + } + + if (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) { + string += ','; + } + + if ((P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_BOTH) || + (P129_CONFIG_FLAGS_GET_OUTPUT_SELECTION == P129_OUTPUT_HEXBIN)) { + string += '0'; + string += (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? 'b' : 'x'); + string += P129_ul2stringFixed(UserVar.getUint32(event->TaskIndex, event->idx), + # ifdef P129_SHOW_VALUES + (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : + # endif // ifdef P129_SHOW_VALUES + HEX + # ifdef P129_SHOW_VALUES + ) + # endif // ifdef P129_SHOW_VALUES + ); + } + success = true; + break; + } + + # ifdef P129_SHOW_VALUES + case PLUGIN_WEBFORM_SHOW_VALUES: + { + String state, label; + state.reserve(40); + const String abcd = F("ABCDEFGH"); // In case anyone dares to extend VARS_PER_TASK to 8... + const uint16_t endCheck = P129_CONFIG_CHIP_COUNT + 4; // 4(.0) = nr of bytes in an uint32_t. + const uint16_t maxVar = min(static_cast(VARS_PER_TASK), static_cast(ceil(P129_CONFIG_CHIP_COUNT / 4.0f))); + uint8_t dotInsert; + uint8_t dotOffset; + + for (uint16_t varNr = 0; varNr < maxVar; ++varNr) { + if (P129_CONFIG_FLAGS_GET_VALUES_DISPLAY) { + label = F("Bin"); + state = F("0b"); + dotInsert = 10; + dotOffset = 9; + } else { + label = F("Hex"); + state = F("0x"); + dotInsert = 4; + dotOffset = 3; + } + label += strformat(F(" State_%s "), abcd.substring(varNr, varNr + 1).c_str()); + + label += min(255, P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 4); // Limited to max 255 chips + label += '_'; + label += (P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 1); // 4 = nr of bytes in an uint32_t. + + if ((P129_CONFIG_SHOW_OFFSET + (4 * varNr) + 4) <= endCheck) { // Only show if still in range + state += P129_ul2stringFixed(UserVar.getUint32(event->TaskIndex, varNr), P129_CONFIG_FLAGS_GET_VALUES_DISPLAY ? BIN : HEX); + + for (uint8_t i = 0; i < 3; ++i, dotInsert += dotOffset) { // Insert readability separators + state = state.substring(0, dotInsert) + '.' + state.substring(dotInsert); + } + pluginWebformShowValue(event->TaskIndex, VARS_PER_TASK + varNr, label, state, true); + } + } + success = true; // Don't show the default value data + break; + } + # endif // ifdef P129_SHOW_VALUES + case PLUGIN_WRITE: + { + P129_data_struct *P129_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P129_data) { + success = P129_data->plugin_write(event, string); + } + + break; + } + } + return success; +} + +#endif // ifdef USES_P129 diff --git a/src/_P131_NeoPixelMatrix.ino b/src/_P131_NeoPixelMatrix.ino index 17535107d..f9e77e0fd 100644 --- a/src/_P131_NeoPixelMatrix.ino +++ b/src/_P131_NeoPixelMatrix.ino @@ -7,6 +7,7 @@ // ####################################################################################################### /** Changelog: + * 2024-04-17 tonhuisman: Add selection of a default font to use. * 2023-10-03 tonhuisman: Optimizate alignment of settings struct, exclude some logging if BUILD_NO_DEBUG is defined * 2023-02-27 tonhuisman: Implement support for getting config values, see AdafruitGFX_Helper.h changelog for details * 2022-07-30 tonhuisman: Add commands to set scroll-options (settext, setscroll, setstep, setspeed, setempty, setright) @@ -158,6 +159,8 @@ boolean Plugin_131(uint8_t function, struct EventStruct *event, String& string) addFormNumericBox(F("Maximum allowed brightness"), F("maxbright"), P131_CONFIG_FLAG_GET_MAXBRIGHT, 1, 255); addUnit(F("1..255")); + AdaGFXFormDefaultFont(F("deffont"), P131_CONFIG_DEFAULT_FONT); + AdaGFXFormFontScaling(F("fontscale"), P131_CONFIG_FLAG_GET_FONTSCALE, 4); # ifdef P131_SHOW_SPLASH @@ -198,7 +201,7 @@ boolean Plugin_131(uint8_t function, struct EventStruct *event, String& string) String strings[P131_Nlines]; LoadCustomTaskSettings(event->TaskIndex, strings, P131_Nlines, 0); - uint16_t remain = DAT_TASKS_CUSTOM_SIZE; + uint16_t remain = DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE; addFormSubHeader(F("Lines")); addRowLabel(F("Lines")); @@ -288,6 +291,7 @@ boolean Plugin_131(uint8_t function, struct EventStruct *event, String& string) P131_CONFIG_MATRIX_HEIGHT = getFormItemInt(F("mxheight")); P131_CONFIG_TILE_WIDTH = getFormItemInt(F("tlwidth")); P131_CONFIG_TILE_HEIGHT = getFormItemInt(F("tlheight")); + P131_CONFIG_DEFAULT_FONT = getFormItemInt(F("deffont")); // Bits are already in the correct order/configuration to be passed on to the constructor // Matrix bits @@ -382,7 +386,8 @@ boolean Plugin_131(uint8_t function, struct EventStruct *event, String& string) P131_CONFIG_FLAG_GET_BRIGHTNESS, P131_CONFIG_FLAG_GET_MAXBRIGHT, P131_CONFIG_GET_COLOR_FOREGROUND, - P131_CONFIG_GET_COLOR_BACKGROUND)); + P131_CONFIG_GET_COLOR_BACKGROUND, + P131_CONFIG_DEFAULT_FONT)); P131_data_struct *P131_data = static_cast(getPluginTaskData(event->TaskIndex)); success = (nullptr != P131_data) && P131_data->plugin_init(event); // Start the display diff --git a/src/_P132_INA3221.ino b/src/_P132_INA3221.ino index 98e9cfc80..43aaf8c08 100644 --- a/src/_P132_INA3221.ino +++ b/src/_P132_INA3221.ino @@ -1,273 +1,270 @@ -#ifdef USES_P132 - -// ####################################################################################################### -// ######################### Plugin 132: INA3221 DC Voltage/Current sensor ############################### -// ####################################################################################################### - -/** - * Changelog: - * 2022-04-23, tonhuisman: Add separate settings for Conversion rate Voltage and Current - * 2022-04-21, tonhuisman: Move source into PluginStructs - * 2022-04-20, tonhuisman: Add averaging of samples and conversion rate settings - * 2022-04-19, tonhuisman: Adapt to general ESPEasy coding standards - **/ - -// Initial development: ## 25 jan 2021 Fred van Duin #### - -#include "_Plugin_Helper.h" - -#define PLUGIN_132 -#define PLUGIN_ID_132 132 -#define PLUGIN_NAME_132 "Energy (DC) - INA3221" -#define PLUGIN_VALUENAME1_132 "Value1" -#define PLUGIN_VALUENAME2_132 "Value2" -#define PLUGIN_VALUENAME3_132 "Value3" -#define PLUGIN_VALUENAME4_132 "Value4" - -#include "./src/PluginStructs/P132_data_struct.h" - -boolean Plugin_132(uint8_t function, struct EventStruct *event, String& string) -{ - boolean success = false; - - switch (function) - { - case PLUGIN_DEVICE_ADD: - { - Device[++deviceCount].Number = PLUGIN_ID_132; - Device[deviceCount].Type = DEVICE_TYPE_I2C; - Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; - Device[deviceCount].Ports = 0; - Device[deviceCount].PullUpOption = false; - Device[deviceCount].InverseLogicOption = false; - Device[deviceCount].FormulaOption = true; - Device[deviceCount].ValueCount = 4; - Device[deviceCount].SendDataOption = true; - Device[deviceCount].TimerOption = true; - Device[deviceCount].GlobalSyncOption = true; - Device[deviceCount].PluginStats = true; - break; - } - - case PLUGIN_GET_DEVICENAME: - { - string = F(PLUGIN_NAME_132); - break; - } - - case PLUGIN_GET_DEVICEVALUENAMES: - { - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_132)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_132)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_132)); - strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_132)); - break; - } - - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - { - const uint8_t i2cAddressValues[] = { 0x40, 0x41, 0x42, 0x43 }; - - if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), 4, i2cAddressValues, P132_I2C_ADDR); - addFormNote(F("A0 connected to: GND= 0x40, VCC= 0x41, SDA= 0x42, SCL= 0x43")); - } else { - success = intArrayContains(4, i2cAddressValues, event->Par1); - } - break; - } - - # if FEATURE_I2C_GET_ADDRESS - case PLUGIN_I2C_GET_ADDRESS: - { - event->Par1 = P132_I2C_ADDR; - success = true; - break; - } - # endif // if FEATURE_I2C_GET_ADDRESS - - case PLUGIN_SET_DEFAULTS: - { - P132_VALUE_1 = 0; // Configure randomly - P132_VALUE_2 = 1; - P132_VALUE_3 = 2; - P132_VALUE_4 = 3; - uint32_t lSettings = 0; - set3BitToUL(lSettings, P132_FLAG_AVERAGE, 0x00); - set3BitToUL(lSettings, P132_FLAG_CONVERSION_B, 0x04); // Voltage - set3BitToUL(lSettings, P132_FLAG_CONVERSION_S, 0x04); // Current - P132_CONFIG_FLAGS = lSettings; - break; - } - - case PLUGIN_WEBFORM_LOAD: - { - #define INA3221_var_OPTION 6 - { - const __FlashStringHelper *varOptions[] = { - F("Current channel 1"), - F("Voltage channel 1"), - F("Current channel 2"), - F("Voltage channel 2"), - F("Current channel 3"), - F("Voltage channel 3") - }; - - for (uint8_t r = 0; r < VARS_PER_TASK; r++) { - addFormSelector(concat(F("Power value "), r + 1), - getPluginCustomArgName(r), INA3221_var_OPTION, varOptions, NULL, PCONFIG(P132_CONFIG_BASE + r)); - } - } - - - addFormSubHeader(F("Hardware")); - - #define INA3221_shunt_OPTION 3 - { - const __FlashStringHelper *varshuntptions[] = { - F("0.1 ohm"), - F("0.01 ohm"), - F("0.005 ohm"), - }; - const int shuntvalue[] = { 1, 10, 20 }; - addFormSelector(F("Shunt resistor"), F("shunt"), INA3221_shunt_OPTION, varshuntptions, shuntvalue, P132_SHUNT); - addFormNote(F("Select as is installed on the board.")); - } - - addFormSubHeader(F("Measurement")); - - #define INA3221_average_OPTION 8 - { - const __FlashStringHelper *averagingSamples[] = { - F("1 (default)"), - F("4"), - F("16"), - F("64"), - F("128"), - F("256"), - F("512"), - F("1024"), - }; - const int averageValue[] = { 0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111 }; - addFormSelector(F("Averaging samples"), - F("average"), - INA3221_average_OPTION, - averagingSamples, - averageValue, - P132_GET_AVERAGE); - addFormNote(F("Samples > 16 then min. Interval: 64= 4, 128= 7, 256= 14, 512= 26, 1024= 52 seconds!")); - } - - #define INA3221_conversion_OPTION 8 - { - const __FlashStringHelper *conversionRates[] = { - F("140 µsec"), - F("204 µsec"), - F("332 µsec"), - F("588 µsec"), - F("1.1 msec (default)"), - F("2.116 msec"), - F("4.156 msec"), - F("8.244 msec"), - }; - - // 140us 204us 332us 588us 1.1ms 2.1ms 4.1ms 8.2ms - const int conversionValues[] = { 0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111 }; - addFormSelector(F("Conversion rate Voltage"), - F("conv_v"), - INA3221_conversion_OPTION, - conversionRates, - conversionValues, - P132_GET_CONVERSION_B); - - addFormSelector(F("Conversion rate Current"), - F("conv_c"), - INA3221_conversion_OPTION, - conversionRates, - conversionValues, - P132_GET_CONVERSION_S); - } - - success = true; - break; - } - - case PLUGIN_WEBFORM_SAVE: - { - P132_I2C_ADDR = getFormItemInt(F("i2c_addr")); - - for (uint8_t r = 0; r < VARS_PER_TASK; r++) { - PCONFIG(P132_CONFIG_BASE + r) = getFormItemIntCustomArgName(r); - } - P132_SHUNT = getFormItemInt(F("shunt")); - - uint32_t lSettings = 0; - set3BitToUL(lSettings, P132_FLAG_AVERAGE, getFormItemInt(F("average"))); - set3BitToUL(lSettings, P132_FLAG_CONVERSION_B, getFormItemInt(F("conv_v"))); - set3BitToUL(lSettings, P132_FLAG_CONVERSION_S, getFormItemInt(F("conv_c"))); - P132_CONFIG_FLAGS = lSettings; - - success = true; - break; - } - - case PLUGIN_INIT: - { - initPluginTaskData(event->TaskIndex, new (std::nothrow) P132_data_struct(event)); - P132_data_struct *P132_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr != P132_data) { - P132_data->setCalibration_INA3221(event); - success = true; - } - - break; - } - - case PLUGIN_READ: - { - P132_data_struct *P132_data = static_cast(getPluginTaskData(event->TaskIndex)); - - if (nullptr == P132_data) { - return success; - } - - uint8_t reg; - - for (uint8_t r = 0; r < VARS_PER_TASK; r++) { - // VALUES 1..4 - reg = static_cast(PCONFIG(P132_CONFIG_BASE + r) + 1); - - if ((reg == 2) || (reg == 4) || (reg == 6)) { - UserVar.setFloat(event->TaskIndex, r, P132_data->getBusVoltage_V(reg) - + (P132_data->getShuntVoltage_mV(reg - 1) / 1000.0f)); - } else { - UserVar.setFloat(event->TaskIndex, r, (P132_data->getShuntVoltage_mV(reg) / 100.0f) * P132_SHUNT); - } - } - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("INA3221: Values: "); - log += UserVar[event->BaseVarIndex]; - log += '/'; - log += UserVar[event->BaseVarIndex + 1]; - log += '/'; - log += UserVar[event->BaseVarIndex + 2]; - log += '/'; - log += UserVar[event->BaseVarIndex + 3]; - addLog(LOG_LEVEL_INFO, log); - } - #endif // ifndef BUILD_NO_DEBUG - - success = true; - break; - } - } - - return success; -} - -#endif // USES_P132 +#ifdef USES_P132 + +// ####################################################################################################### +// ######################### Plugin 132: INA3221 DC Voltage/Current sensor ############################### +// ####################################################################################################### + +/** + * Changelog: + * 2022-04-23, tonhuisman: Add separate settings for Conversion rate Voltage and Current + * 2022-04-21, tonhuisman: Move source into PluginStructs + * 2022-04-20, tonhuisman: Add averaging of samples and conversion rate settings + * 2022-04-19, tonhuisman: Adapt to general ESPEasy coding standards + **/ + +// Initial development: ## 25 jan 2021 Fred van Duin #### + +#include "_Plugin_Helper.h" + +#define PLUGIN_132 +#define PLUGIN_ID_132 132 +#define PLUGIN_NAME_132 "Energy (DC) - INA3221" +#define PLUGIN_VALUENAME1_132 "Value1" +#define PLUGIN_VALUENAME2_132 "Value2" +#define PLUGIN_VALUENAME3_132 "Value3" +#define PLUGIN_VALUENAME4_132 "Value4" + +#include "./src/PluginStructs/P132_data_struct.h" + +boolean Plugin_132(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_132; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_132); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_132)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_132)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_132)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_132)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x40, 0x41, 0x42, 0x43 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), NR_ELEMENTS(i2cAddressValues), i2cAddressValues, P132_I2C_ADDR); + addFormNote(F("A0 connected to: GND= 0x40, VCC= 0x41, SDA= 0x42, SCL= 0x43")); + } else { + success = intArrayContains(NR_ELEMENTS(i2cAddressValues), i2cAddressValues, event->Par1); + } + break; + } + + #if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P132_I2C_ADDR; + success = true; + break; + } + #endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P132_VALUE_1 = 0; // Configure randomly + P132_VALUE_2 = 1; + P132_VALUE_3 = 2; + P132_VALUE_4 = 3; + uint32_t lSettings = 0; + set3BitToUL(lSettings, P132_FLAG_AVERAGE, 0x00); + set3BitToUL(lSettings, P132_FLAG_CONVERSION_B, 0x04); // Voltage + set3BitToUL(lSettings, P132_FLAG_CONVERSION_S, 0x04); // Current + P132_CONFIG_FLAGS = lSettings; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + #define INA3221_var_OPTION 6 + { + const __FlashStringHelper *varOptions[] = { + F("Current channel 1"), + F("Voltage channel 1"), + F("Current channel 2"), + F("Voltage channel 2"), + F("Current channel 3"), + F("Voltage channel 3") + }; + + for (uint8_t r = 0; r < VARS_PER_TASK; ++r) { + addFormSelector(concat(F("Power value "), r + 1), + getPluginCustomArgName(r), INA3221_var_OPTION, varOptions, NULL, PCONFIG(P132_CONFIG_BASE + r)); + } + } + + + addFormSubHeader(F("Hardware")); + + #define INA3221_shunt_OPTION 3 + { + const __FlashStringHelper *varshuntptions[] = { + F("0.1 ohm"), + F("0.01 ohm"), + F("0.005 ohm"), + }; + const int shuntvalue[] = { 1, 10, 20 }; + addFormSelector(F("Shunt resistor"), F("shunt"), INA3221_shunt_OPTION, varshuntptions, shuntvalue, P132_SHUNT); + addFormNote(F("Select as is installed on the board.")); + } + + addFormSubHeader(F("Measurement")); + + #define INA3221_average_OPTION 8 + { + const __FlashStringHelper *averagingSamples[] = { + F("1 (default)"), + F("4"), + F("16"), + F("64"), + F("128"), + F("256"), + F("512"), + F("1024"), + }; + const int averageValue[] = { 0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111 }; + addFormSelector(F("Averaging samples"), + F("average"), + INA3221_average_OPTION, + averagingSamples, + averageValue, + P132_GET_AVERAGE); + addFormNote(F("Samples > 16 then min. Interval: 64= 4, 128= 7, 256= 14, 512= 26, 1024= 52 seconds!")); + } + + #define INA3221_conversion_OPTION 8 + { + const __FlashStringHelper *conversionRates[] = { + F("140 µsec"), + F("204 µsec"), + F("332 µsec"), + F("588 µsec"), + F("1.1 msec (default)"), + F("2.116 msec"), + F("4.156 msec"), + F("8.244 msec"), + }; + + // 140us 204us 332us 588us 1.1ms 2.1ms 4.1ms 8.2ms + const int conversionValues[] = { 0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111 }; + addFormSelector(F("Conversion rate Voltage"), + F("conv_v"), + INA3221_conversion_OPTION, + conversionRates, + conversionValues, + P132_GET_CONVERSION_B); + + addFormSelector(F("Conversion rate Current"), + F("conv_c"), + INA3221_conversion_OPTION, + conversionRates, + conversionValues, + P132_GET_CONVERSION_S); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P132_I2C_ADDR = getFormItemInt(F("i2c_addr")); + + for (uint8_t r = 0; r < VARS_PER_TASK; ++r) { + PCONFIG(P132_CONFIG_BASE + r) = getFormItemIntCustomArgName(r); + } + P132_SHUNT = getFormItemInt(F("shunt")); + + uint32_t lSettings = 0; + set3BitToUL(lSettings, P132_FLAG_AVERAGE, getFormItemInt(F("average"))); + set3BitToUL(lSettings, P132_FLAG_CONVERSION_B, getFormItemInt(F("conv_v"))); + set3BitToUL(lSettings, P132_FLAG_CONVERSION_S, getFormItemInt(F("conv_c"))); + P132_CONFIG_FLAGS = lSettings; + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P132_data_struct(event)); + P132_data_struct *P132_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P132_data) { + P132_data->setCalibration_INA3221(event); + success = true; + } + + break; + } + + case PLUGIN_READ: + { + P132_data_struct *P132_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P132_data) { + return success; + } + + uint8_t reg; + + for (uint8_t r = 0; r < VARS_PER_TASK; ++r) { + // VALUES 1..4 + reg = static_cast(PCONFIG(P132_CONFIG_BASE + r) + 1); + + if ((reg == 2) || (reg == 4) || (reg == 6)) { + UserVar.setFloat(event->TaskIndex, r, + P132_data->getBusVoltage_V(reg) + + (P132_data->getShuntVoltage_mV(reg - 1) / 1000.0f)); + } else { + UserVar.setFloat(event->TaskIndex, r, (P132_data->getShuntVoltage_mV(reg) / 100.0f) * P132_SHUNT); + } + } + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("INA3221: Values: %.2f/%.2f/%.2f/%.2f"), + UserVar[event->BaseVarIndex], + UserVar[event->BaseVarIndex + 1], + UserVar[event->BaseVarIndex + 2], + UserVar[event->BaseVarIndex + 3])); + } + #endif // ifndef BUILD_NO_DEBUG + + success = true; + break; + } + } + + return success; +} + +#endif // USES_P132 diff --git a/src/_P135_SCD4x.ino b/src/_P135_SCD4x.ino index 7146006f9..f6d4abf29 100644 --- a/src/_P135_SCD4x.ino +++ b/src/_P135_SCD4x.ino @@ -6,6 +6,7 @@ // ####################################################################################################### /** + * 2024-04-27 tonhuisman: Fix bug that sensor settings can only be retrieved if measuring is stopped * 2023-11-23 tonhuisman: Add Device flag for I2CMax100kHz as this sensor won't work at 400 kHz * 2022-08-28 tonhuisman: Include 'CO2' in plugin name, to be in line with other CO2 plugins * 2022-08-24 tonhuisman: Removed [TESTING] tag diff --git a/src/_P137_AXP192.ino b/src/_P137_AXP192.ino index 23a5c6a00..142f9da14 100644 --- a/src/_P137_AXP192.ino +++ b/src/_P137_AXP192.ino @@ -143,14 +143,14 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) break; } - # if FEATURE_I2C_GET_ADDRESS + # if FEATURE_I2C_GET_ADDRESS case PLUGIN_I2C_GET_ADDRESS: { event->Par1 = I2C_AXP192_DEFAULT_ADDRESS; success = true; break; } - # endif // if FEATURE_I2C_GET_ADDRESS + # endif // if FEATURE_I2C_GET_ADDRESS case PLUGIN_SET_DEFAULTS: { @@ -196,12 +196,10 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) addFormNote(F("Page will reload when selection is changed.")); } - if (static_cast(P137_CURRENT_PREDEFINED) != P137_PredefinedDevices_e::Unselected) { - String note; - note.reserve(55); - note += F("Last selected: "); - note += toString(static_cast(P137_CURRENT_PREDEFINED)); - addFormNote(note); + const P137_PredefinedDevices_e current_ = static_cast(P137_CURRENT_PREDEFINED); + + if (current_ != P137_PredefinedDevices_e::Unselected) { + addFormNote(concat(F("Last selected: "), toString(current_))); } } const __FlashStringHelper *notConnected = F("N/C - Unused"); @@ -260,14 +258,14 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) static_cast(P137_GPIOBootState_e::PWM), }; const String bootStateAttributes[] = { - F(""), - F(""), - F(""), + EMPTY_STRING, + EMPTY_STRING, + EMPTY_STRING, F("disabled"), F("disabled"), }; - for (int i = 0; i < 5; i++) { // GPIO0..4 + for (int i = 0; i < 5; ++i) { // GPIO0..4 const String id = concat(F("pgpio"), i); addRowLabel(concat(F("Initial state GPIO"), i)); addSelector(id, sizeof(bootStateValues) / sizeof(int), @@ -336,7 +334,7 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) static_cast(P137_valueOptions_e::DCDC3), }; - for (uint8_t i = 0; i < P137_NR_OUTPUT_VALUES; i++) { + for (uint8_t i = 0; i < P137_NR_OUTPUT_VALUES; ++i) { sensorTypeHelper_loadOutputSelector(event, P137_CONFIG_BASE + i, i, @@ -351,7 +349,7 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SAVE: { - for (uint8_t i = 0; i < P137_NR_OUTPUT_VALUES; i++) { + for (uint8_t i = 0; i < P137_NR_OUTPUT_VALUES; ++i) { sensorTypeHelper_saveOutputSelector(event, P137_CONFIG_BASE + i, i, toString(static_cast(PCONFIG(P137_CONFIG_BASE + i)), false)); } @@ -362,13 +360,13 @@ boolean Plugin_137(uint8_t function, struct EventStruct *event, String& string) P137_valueToSetting(getFormItemInt(F("pldo3")), P137_CONST_MAX_LDO); P137_REG_LDOIO = P137_valueToSetting(getFormItemInt(F("ldoiovolt")), P137_CONST_MAX_LDOIO); - for (int i = 0; i < 5; i++) { // GPIO0..4 + for (int i = 0; i < 5; ++i) { // GPIO0..4 P137_SET_GPIO_FLAGS(i, getFormItemInt(concat(F("pgpio"), i))); } P137_CONFIG_DECIMALS = getFormItemInt(F("decimals")); P137_CONFIG_PREDEFINED = getFormItemInt(F("predef")); - P137_CONFIG_DISABLEBITS = getFormItemInt(F("pbits"), static_cast(P137_CONFIG_DISABLEBITS)); // Keep previous value if not found + P137_CONFIG_DISABLEBITS = getFormItemInt(F("pbits"), P137_CONFIG_DISABLEBITS); // Keep previous value if not found success = true; break; diff --git a/src/_P141_PCD8544_Nokia5110.ino b/src/_P141_PCD8544_Nokia5110.ino index 3e1e05b51..2fc509bf7 100644 --- a/src/_P141_PCD8544_Nokia5110.ino +++ b/src/_P141_PCD8544_Nokia5110.ino @@ -147,7 +147,7 @@ boolean Plugin_141(uint8_t function, struct EventStruct *event, String& string) }; addFormSelector(F("Write Command trigger"), F("pcmdtrigger"), - sizeof(commandTriggerOptions) / sizeof(int), + NR_ELEMENTS(commandTriggerOptions), commandTriggers, commandTriggerOptions, P141_CONFIG_FLAG_GET_CMD_TRIGGER); @@ -175,7 +175,7 @@ boolean Plugin_141(uint8_t function, struct EventStruct *event, String& string) uint16_t remain = P141_Nlines * (P141_Nchars + 1); // DAT_TASKS_CUSTOM_SIZE; # endif // ifndef LIMIT_BUILD_SIZE - for (uint8_t varNr = 0; varNr < P141_Nlines; varNr++) { + for (uint8_t varNr = 0; varNr < P141_Nlines; ++varNr) { addFormTextBox(concat(F("Line "), varNr + 1), getPluginCustomArgName(varNr), strings[varNr], P141_Nchars); # ifndef LIMIT_BUILD_SIZE remain -= (strings[varNr].length() + 1); @@ -219,7 +219,7 @@ boolean Plugin_141(uint8_t function, struct EventStruct *event, String& string) String strings[P141_Nlines]; String error; - for (uint8_t varNr = 0; varNr < P141_Nlines; varNr++) { + for (uint8_t varNr = 0; varNr < P141_Nlines; ++varNr) { strings[varNr] = web_server.arg(getPluginCustomArgName(varNr)); } diff --git a/src/_P143_I2C_Rotary.ino b/src/_P143_I2C_Rotary.ino index a4f69c188..6da3d1a04 100644 --- a/src/_P143_I2C_Rotary.ino +++ b/src/_P143_I2C_Rotary.ino @@ -135,11 +135,8 @@ boolean Plugin_143(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: { - string = F("Encoder: "); - string += toString(static_cast(P143_ENCODER_TYPE)); - string += F(" ("); - string += formatToHex(P143_I2C_ADDR); - string += ')'; + string = concat(F("Encoder: "), toString(static_cast(P143_ENCODER_TYPE))); + string += strformat(F(" (%s)"), formatToHex(P143_I2C_ADDR).c_str()); success = true; break; } @@ -200,7 +197,7 @@ boolean Plugin_143(uint8_t function, struct EventStruct *event, String& string) # endif // if P143_FEATURE_INCLUDE_M5STACK { { - addRowLabel(F("Neopixel 1 initial color")); + addRowLabel(strformat(F("Neopixel %d initial color"), 1)); addHtml(F("")); // remove padding to align vertically with other inputs html_TD(F("padding:0")); addHtml('R'); @@ -216,7 +213,7 @@ boolean Plugin_143(uint8_t function, struct EventStruct *event, String& string) # if P143_FEATURE_INCLUDE_M5STACK if (device == P143_DeviceType_e::M5StackEncoder) { - addRowLabel(F("Neopixel 2 initial color")); + addRowLabel(strformat(F("Neopixel %d initial color"), 2)); addHtml(F("
")); // remove padding to align vertically with other inputs html_TD(F("padding:0")); addHtml('R'); diff --git a/src/_P144_Vindriktning.ino b/src/_P144_Vindriktning.ino index b7ed0e51b..ff0e1a7e5 100644 --- a/src/_P144_Vindriktning.ino +++ b/src/_P144_Vindriktning.ino @@ -155,9 +155,9 @@ boolean Plugin_144(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_INIT: { // this case defines code to be executed when the plugin is initialised - int8_t rxPin = serialHelper_getRxPin(event); - int8_t txPin = serialHelper_getTxPin(event); - ESPEasySerialPort portType = serialHelper_getSerialType(event); + const int8_t rxPin = serialHelper_getRxPin(event); + const int8_t txPin = serialHelper_getTxPin(event); + const ESPEasySerialPort portType = serialHelper_getSerialType(event); // Create the P144_data_struct object that will do all the sensor interaction initPluginTaskData(event->TaskIndex, new (std::nothrow) P144_data_struct()); @@ -183,9 +183,7 @@ boolean Plugin_144(uint8_t function, struct EventStruct *event, String& string) #ifdef PLUGIN_144_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("P144 : READ "); - log += UserVar[event->BaseVarIndex]; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("P144 : READ "), UserVar[event->BaseVarIndex])); } #endif } diff --git a/src/_P145_MQxxx.ino b/src/_P145_MQxxx.ino index 8aa4d319d..80d52671c 100644 --- a/src/_P145_MQxxx.ino +++ b/src/_P145_MQxxx.ino @@ -41,13 +41,13 @@ // // Analog input is sampled 10/second and averaged with the same oversampling algoritm of P002_ADC // Note: ESP8266 uses hard coded A0 as analog input. -// ESP32 provides the standard ESPeasy serial configuration +// ESP32 provides the standard ESPeasy Analog input GPIO configuration // // Conversion algorithm: // Sensor resistance is logaritmic to the concentration of a gas. Sensors are not specific to a single // gas. Key is the ratio between the measured Rs and a reference resistor Rzero (Rs/Rzero). // For each specific gas a somewhat linear relation between gas concentration and the ratio (Rs/Rzero) is -// show on a log-log chart. Various algorithms are described in above mentioned literature. +// shown on a log-log chart. Various algorithms are described in above mentioned literature. // This plugin supports 3, almost similar, algorithms to make it easier to copy a parameter set from the // internet for a specific sensor/gas combination. // @@ -73,7 +73,7 @@ #define PLUGIN_145 #define PLUGIN_ID_145 145 // plugin id -#define PLUGIN_NAME_145 "Gases - MQxxx (MQ135 CO2, MQ3 Alcohol) [TESTING]" // "Plugin Name" is what will be dislpayed in the selection list +#define PLUGIN_NAME_145 "Gases - MQxxx (MQ135 CO2, MQ3 Alcohol)" // "Plugin Name" is what will be displayed in the selection list #define PLUGIN_VALUENAME1_145 "level" // variable output of the plugin. The label is in quotation marks #define PLUGIN_145_DEBUG false // set to true for extra log info in the debug @@ -171,24 +171,22 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) // Setup the web form for the task case PLUGIN_WEBFORM_LOAD: { - bool compensate = P145_PCONFIG_FLAGS & 0x0001; // Compensation enable flag - bool calibrate = (P145_PCONFIG_FLAGS >> 1) & 0x0001; // Calibreation enable flag - bool lowvcc = (P145_PCONFIG_FLAGS >> 2) & 0x0001; // Low voltage power supply indicator + const bool compensate = P145_PCONFIG_FLAGS & 0x0001; // Compensation enable flag + const bool calibrate = (P145_PCONFIG_FLAGS >> 1) & 0x0001; // Calibration enable flag + const bool lowvcc = (P145_PCONFIG_FLAGS >> 2) & 0x0001; // Low voltage power supply indicator // FormSelector with all predefined "Sensor - Gas" options String options[P145_MAXTYPES] = {}; - int optionValues[P145_MAXTYPES] = {}; int x = P145_data_struct::getNbrOfTypes(); if (x > P145_MAXTYPES) { x = P145_MAXTYPES; // Clip to prevent array boundary out of range access } - for (int i=0; igetCalibrationValue(); - if (calVal > 0.0) + if (definitelyGreaterThan(calVal, 0.0f)) { - addFormNote(String(F("Current measurement suggests Rzero= ")) + String(calVal)); + addFormNote(concat(F("Current measurement suggests Rzero= "), calVal)); } } addFormCheckBox(F("Low sensor supply voltage"), F(P145_GUID_LOWVCC), lowvcc); @@ -259,9 +257,9 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) P145_PCONFIG_RLOAD = getFormItemFloat(F(P145_GUID_RLOAD)); P145_PCONFIG_RZERO = getFormItemFloat(F(P145_GUID_RZERO)); P145_PCONFIG_REF = getFormItemFloat(F(P145_GUID_RREFLEVEL)); - bool compensate = (getFormItemInt(F(P145_GUID_COMP)) == 1); - bool calibrate = isFormItemChecked(F(P145_GUID_CAL)); - bool lowvcc = isFormItemChecked(F(P145_GUID_LOWVCC)); + const bool compensate = (getFormItemInt(F(P145_GUID_COMP)) == 1); + const bool calibrate = isFormItemChecked(F(P145_GUID_CAL)); + const bool lowvcc = isFormItemChecked(F(P145_GUID_LOWVCC)); P145_PCONFIG_FLAGS = compensate + (calibrate << 1) + (lowvcc << 2); P145_PCONFIG_TEMP_TASK = getFormItemInt(F(P145_GUID_TEMP_T)); P145_PCONFIG_TEMP_VAL = getFormItemInt(F(P145_GUID_TEMP_V)); @@ -322,12 +320,12 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) { float temperature = 20.0f; // A reasonable value in case temperature source task is invalid float humidity = 60.0f; // A reasonable value in case humidity source task is invalid - bool compensate = P145_PCONFIG_FLAGS & 0x0001; + const bool compensate = P145_PCONFIG_FLAGS & 0x0001; if (compensate && validTaskIndex(P145_PCONFIG_TEMP_TASK) && validTaskIndex(P145_PCONFIG_HUM_TASK)) { // we're checking a var from another task, so calculate that basevar - temperature = UserVar[P145_PCONFIG_TEMP_TASK * VARS_PER_TASK + P145_PCONFIG_TEMP_VAL]; // in degrees C - humidity = UserVar[P145_PCONFIG_HUM_TASK * VARS_PER_TASK + P145_PCONFIG_HUM_VAL]; // in % relative + temperature = UserVar.getFloat(P145_PCONFIG_TEMP_TASK, P145_PCONFIG_TEMP_VAL); // in degrees C + humidity = UserVar.getFloat(P145_PCONFIG_HUM_TASK, P145_PCONFIG_HUM_VAL); // in % relative } UserVar.setFloat(event->TaskIndex, 0, P145_data->readValue(temperature, humidity)); success = true; @@ -341,7 +339,7 @@ boolean Plugin_145(byte function, struct EventStruct *event, String& string) P145_data_struct *P145_data = static_cast(getPluginTaskData(event->TaskIndex)); if (P145_data != nullptr) { - if ((P145_PCONFIG_FLAGS >> 1) & 0x0001) // Calibration fleag + if ((P145_PCONFIG_FLAGS >> 1) & 0x0001) // Calibration flag { // Update Rzero in case of autocalibration // TODO is there an event to signal the plugin code that the value has been updated to prevent polling? diff --git a/src/_P147_SGP4x.ino b/src/_P147_SGP4x.ino index ad1460df3..14ff0d588 100644 --- a/src/_P147_SGP4x.ino +++ b/src/_P147_SGP4x.ino @@ -122,11 +122,15 @@ boolean Plugin_147(uint8_t function, struct EventStruct *event, String& string) static_cast(P147_sensor_e::SGP41), }; addFormSelector(F("Sensor model"), F("ptype"), 2, sensorTypes, sensorTypeOptions, P147_SENSOR_TYPE, true); + # ifndef BUILD_NO_DEBUG addFormNote(F("Page will reload on change.")); + # endif // ifndef BUILD_NO_DEBUG } addFormSelector_YesNo(F("Use Compensation"), F("comp"), P147_GET_USE_COMPENSATION, true); + # ifndef BUILD_NO_DEBUG addFormNote(F("Page will reload on change.")); + # endif // ifndef BUILD_NO_DEBUG if (P147_GET_USE_COMPENSATION) { addRowLabel(F("Temperature Task")); diff --git a/src/_P150_TMP117.ino b/src/_P150_TMP117.ino index 3cb0f3edb..b4e65a8f4 100644 --- a/src/_P150_TMP117.ino +++ b/src/_P150_TMP117.ino @@ -120,7 +120,9 @@ boolean Plugin_150(uint8_t function, struct EventStruct *event, String& string) { addFormNumericBox(F("Temperature offset"), F("offset"), P150_TEMPERATURE_OFFSET); addUnit(F("x 0.1C")); + # ifndef BUILD_NO_DEBUG addFormNote(F("Offset in units of 0.1 degree Celsius!")); + # endif // ifndef BUILD_NO_DEBUG { const __FlashStringHelper *averagingCaptions[] = { @@ -148,7 +150,9 @@ boolean Plugin_150(uint8_t function, struct EventStruct *event, String& string) P150_CONVERSION_ONE_SHOT, }; addFormSelector(F("Conversion mode"), F("conv"), 2, conversionCaptions, conversionOptions, P150_GET_CONF_CONVERSION_MODE, true); + # ifndef BUILD_NO_DEBUG addFormNote(F("Changing this setting will save and reload this page.")); + # endif // ifndef BUILD_NO_DEBUG } if (P150_GET_CONF_CONVERSION_MODE == P150_CONVERSION_CONTINUOUS) { @@ -178,7 +182,9 @@ boolean Plugin_150(uint8_t function, struct EventStruct *event, String& string) addFormSubHeader(F("Output")); addFormSelector_YesNo(F("Enable 'Raw' value"), F("raw"), P150_GET_OPT_ENABLE_RAW ? 1 : 0, true); + # ifndef BUILD_NO_DEBUG addFormNote(F("Changing this setting will save and reload this page.")); + # endif // ifndef BUILD_NO_DEBUG addFormCheckBox(F("Log measured values (INFO)"), F("log"), P150_GET_OPT_ENABLE_LOG); diff --git a/src/_P151_Honeywell_pressure.ino b/src/_P151_Honeywell_pressure.ino index 5d6c3acb0..8c0d68f0d 100644 --- a/src/_P151_Honeywell_pressure.ino +++ b/src/_P151_Honeywell_pressure.ino @@ -84,6 +84,15 @@ boolean Plugin_151(uint8_t function, struct EventStruct *event, String& string) break; } + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P151_I2C_ADDR; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + case PLUGIN_SET_DEFAULTS: { P151_I2C_ADDR = 0x28; diff --git a/src/_P153_SHT4x.ino b/src/_P153_SHT4x.ino index d49cbd2cd..f7e3b19e9 100644 --- a/src/_P153_SHT4x.ino +++ b/src/_P153_SHT4x.ino @@ -92,7 +92,9 @@ boolean Plugin_153(uint8_t function, struct EventStruct *event, String& string) if (PLUGIN_WEBFORM_SHOW_I2C_PARAMS == function) { addFormSelectorI2C(F("i2c_addr"), 3, i2cAddressValues, P153_I2C_ADDRESS); + # ifndef BUILD_NO_DEBUG addFormNote(F("Chip type determines address: SHT-4x-Axxx = 0x44, SHT-4x-Bxxx = 0x45, SHT-4x-Cxxx = 0x46")); + # endif // ifndef BUILD_NO_DEBUG } else { success = intArrayContains(3, i2cAddressValues, event->Par1); } diff --git a/src/_P154_BMP3xx.ino b/src/_P154_BMP3xx.ino index 294719d64..5334dc12f 100644 --- a/src/_P154_BMP3xx.ino +++ b/src/_P154_BMP3xx.ino @@ -2,14 +2,14 @@ #ifdef USES_P154 // ####################################################################################################### -// #################################### Plugin-154: Environment - BMP3xx ############################### +// ################################## Plugin-154: Environment - BMP3xx I2C ############################# // ####################################################################################################### # include "src/PluginStructs/P154_data_struct.h" # define PLUGIN_154 # define PLUGIN_ID_154 154 -# define PLUGIN_NAME_154 "Environment - BMP3xx" +# define PLUGIN_NAME_154 "Environment - BMP3xx (I2C)" # define PLUGIN_VALUENAME1_154 "Temperature" # define PLUGIN_VALUENAME2_154 "Pressure" @@ -56,7 +56,7 @@ boolean Plugin_154(uint8_t function, struct EventStruct *event, String& string) constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { - addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P154_I2C_ADDR); + addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P154_I2C_ADDR, 0x77); addFormNote(F("SDO Low=0x76, High=0x77")); } else { success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); diff --git a/src/_P159_LD2410.ino b/src/_P159_LD2410.ino index 793eba241..6193ab594 100644 --- a/src/_P159_LD2410.ino +++ b/src/_P159_LD2410.ino @@ -241,15 +241,15 @@ boolean Plugin_159(uint8_t function, struct EventStruct *event, String& string) } case PLUGIN_INIT: { - int8_t rxPin = serialHelper_getRxPin(event); - int8_t txPin = serialHelper_getTxPin(event); - ESPEasySerialPort portType = serialHelper_getSerialType(event); + const int8_t rxPin = serialHelper_getRxPin(event); + const int8_t txPin = serialHelper_getTxPin(event); + const ESPEasySerialPort portType = serialHelper_getSerialType(event); // Create the P159_data_struct object that will do all the sensor interaction success = initPluginTaskData(event->TaskIndex, new (std::nothrow) P159_data_struct(portType, - rxPin, - txPin, - P159_GET_ENGINEERING_MODE == 1)); + rxPin, + txPin, + P159_GET_ENGINEERING_MODE == 1)); addLog(LOG_LEVEL_INFO, concat(F("P159 : INIT, success: "), success ? 1 : 0)); break; diff --git a/src/_P162_MCP42xxx.ino b/src/_P162_MCP42xxx.ino new file mode 100644 index 000000000..fd8576699 --- /dev/null +++ b/src/_P162_MCP42xxx.ino @@ -0,0 +1,146 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P162 + +// ####################################################################################################### +// ################################ Plugin-162: MCP42xxx/MCP41xxx Digipot ################################ +// ####################################################################################################### + +/** Changelog: + * 2024-04-16 tonhuisman: Add Send values on change option, so Interval can be set to 0, and the data will be sent when changed + * 2024-04-10 tonhuisman: Initial version. Support for Digipot MCP42xxx (dual channel) and MCP41xxx (single channel). + * No support for daisy-chaining (MCP42xxx can do that, but not implemented) + * RST and SHDN pins are not available on all boards, so should be set to none when not available. + * 2024-04-08 tonhuisman: Start plugin development + */ + +# define PLUGIN_162 +# define PLUGIN_ID_162 162 +# define PLUGIN_NAME_162 "Output - MCP42xxx Digipot" +# define PLUGIN_VALUENAME1_162 "W0" +# define PLUGIN_VALUENAME2_162 "W1" + +# include "src/PluginStructs/P162_data_struct.h" + +boolean Plugin_162(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_162; + Device[deviceCount].Type = DEVICE_TYPE_SPI3; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_162); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_162)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_162)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: // define 'GPIO 1st' name in webserver + { + event->String1 = formatGpioName_output(F("CS PIN")); // P162_CS_PIN + event->String2 = formatGpioName_output_optional(F("RST PIN ")); // P162_RST_PIN + event->String3 = formatGpioName_output_optional(F("SHDN PIN ")); // P162_SHD_PIN + break; + } + + case PLUGIN_SET_DEFAULTS: + { + P162_INIT_W0 = P162_RESET_VALUE; + P162_INIT_W1 = P162_RESET_VALUE; + P162_SHUTDOWN_VALUE = -1; + + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 0; + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("Initial value W0"), F("iw0"), P162_INIT_W0, 0, 255); + addUnit(F("0..255")); + addFormCheckBox(F("Initial shutdown W0"), F("sw0"), P162_SHUTDOWN_W0); + + addFormSeparator(2); + + addFormNumericBox(F("Initial value W1"), F("iw1"), P162_INIT_W1, 0, 255); + addUnit(F("0..255")); + addFormCheckBox(F("Initial shutdown W1"), F("sw1"), P162_SHUTDOWN_W0); + + addFormSeparator(2); + + addFormNumericBox(F("Value at Shutdown"), F("shd"), P162_SHUTDOWN_VALUE, -1, 256); + addUnit(F("-1..256")); + + addFormCheckBox(F("Send values on change"), F("chg"), P162_CHANGED_EVENTS); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P162_INIT_W0 = getFormItemInt(F("iw0")); + P162_INIT_W1 = getFormItemInt(F("iw1")); + P162_SHUTDOWN_W0 = isFormItemChecked(F("sw0")); + P162_SHUTDOWN_W1 = isFormItemChecked(F("sw1")); + P162_SHUTDOWN_VALUE = getFormItemInt(F("shd")); + P162_CHANGED_EVENTS = isFormItemChecked(F("chg")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P162_data_struct(P162_CS_PIN, P162_RST_PIN, P162_SHD_PIN)); + P162_data_struct *P162_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P162_data) { + success = P162_data->plugin_init(event); + } + + break; + } + + case PLUGIN_READ: + { + success = true; + break; + } + + case PLUGIN_WRITE: + { + P162_data_struct *P162_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P162_data) { + success = P162_data->plugin_write(event, string); + } + + break; + } + } + return success; +} + +#endif // USES_P162 diff --git a/src/_P164_gases_ens160.ino b/src/_P164_gases_ens160.ino new file mode 100644 index 000000000..9537971a0 --- /dev/null +++ b/src/_P164_gases_ens160.ino @@ -0,0 +1,153 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P164 + +// ####################################################################################################### +// #################################### Plugin-164: Gases - ENS16x (tvoc,eco2) ########################### +// ####################################################################################################### +// P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// For documentation of the ENS160 hardware device see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// PLugin code: +// 2023 By flashmark +// ####################################################################################################### + +# include "src/PluginStructs/P164_data_struct.h" + +# define PLUGIN_164 +# define PLUGIN_ID_164 164 +# define PLUGIN_NAME_164 "Gases - ENS16x" +# define PLUGIN_VALUENAME1_164 "TVOC" +# define PLUGIN_VALUENAME2_164 "eCO2" + +boolean Plugin_164(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_164; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_164); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_164)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_164)); + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { P164_ENS160_I2CADDR_0, P164_ENS160_I2CADDR_1 }; + constexpr int nrAddressOptions = NR_ELEMENTS(i2cAddressValues); + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) { + addFormSelectorI2C(F("i2c_addr"), nrAddressOptions, i2cAddressValues, P164_PCONFIG_I2C_ADDR); + addFormNote(F("ADDR Low=0x52, High=0x53")); + } else { + success = intArrayContains(nrAddressOptions, i2cAddressValues, event->Par1); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P164_PCONFIG_I2C_ADDR; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P164_PCONFIG_I2C_ADDR = P164_ENS160_I2CADDR_1; + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P164_data_struct(event)); + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P164_data && P164_data->begin()); + break; + } + + case PLUGIN_READ: + { + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P164_data) { + addLogMove(LOG_LEVEL_ERROR, F("P164: plugin_read NULLPTR")); + break; + } + + float temperature = 20.0f; // A reasonable value in case temperature source task is invalid + float humidity = 50.0f; // A reasonable value in case humidity source task is invalid + float tvoc = 0.0f; // tvoc value to be retrieved from device + float eco2 = 0.0f; // eCO2 value to be retrieved from device + + if (validTaskIndex(P164_PCONFIG_TEMP_TASK) && validTaskIndex(P164_PCONFIG_HUM_TASK)) + { + // we're checking a value from other tasks + temperature = UserVar.getFloat(P164_PCONFIG_TEMP_TASK, P164_PCONFIG_TEMP_VAL); // in degrees C + humidity = UserVar.getFloat(P164_PCONFIG_HUM_TASK, P164_PCONFIG_HUM_VAL); // in % relative + } + success = P164_data->read(tvoc, eco2, temperature, humidity); + UserVar.setFloat(event->TaskIndex, 0, tvoc); + UserVar.setFloat(event->TaskIndex, 1, eco2); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + success = P164_data_struct::webformLoad(event); + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P164_data_struct::webformSave(event); + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + P164_data_struct *P164_data = + static_cast(getPluginTaskData(event->TaskIndex)); + if (nullptr == P164_data) { + break; + } + success = P164_data->tenPerSecond(event); + break; + } + } + return success; +} + +#endif // ifdef USES_P164 diff --git a/src/_P166_GP8403.ino b/src/_P166_GP8403.ino new file mode 100644 index 000000000..4b8d7040d --- /dev/null +++ b/src/_P166_GP8403.ino @@ -0,0 +1,273 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P166 + +// ####################################################################################################### +// ######################### Plugin 166: Output - GP8403 Dual channel DAC 0-10V ########################## +// ####################################################################################################### + +/** Changelog: + * 2024-01-29 tonhuisman: Fix bug that changed Initial output values are not applied until a reset/power cycle. + * Disable development-log at Settings Save + * 2024-01-28 tonhuisman: Add option to restore output values on warm boot (default enabled, using unused 4th value for state) + * Add command to apply initial value(s) per channel + * Some code refactoring + * 2024-01-26 tonhuisman: Make 0x5F the default I2C address, as that's how the hardware is configured by default + * 2024-01-26 tonhuisman: Generate PLUGIN_READ when changing output + * 2024-01-25 tonhuisman: Add I2C enabled check on PLUGIN_INIT + * 2024-01-24 tonhuisman: Add PLUGIN_GET_CONFIG_VALUE support + * 2024-01-23 tonhuisman: Add initial value per channel, add some logging, refactoring + * 2024-01-22 tonhuisman: Add named presets (not case-sensitive) and command handling + * 2024-01-21 tonhuisman: Start plugin for GP8403 DAC 0-10V (12 bit, 2 channels) based on DFRobot_GP8403 library modified for ESPEasy + * (Newest changes on top) + **/ + +/** Commands: + * = output (channel) 0, 1 or 2 (both) + * gp8403,volt,, : Set the voltage in V (0..5.0/0..10.0) value to channel + * gp8403,mvolt,, : Set the voltage in mV (0..5000/0..10000) value to channel + * gp8403,range,<5|10> : Set the range to 5V or 10V (both channels) + * gp8403,preset,, : Set the voltage from preset to channel + * gp8403,init, : Set the initial voltage to channel + */ + +/** Get Config values: + * [#preset] : The configured preset value (range checked) + * [#initial0] : The configured initial output 0 value + * [#initial1] : The configured initial output 1 value + * [#range] : The configured range setting 5 or 10 + */ + +# define PLUGIN_166 +# define PLUGIN_ID_166 166 +# define PLUGIN_NAME_166 "Output - GP8403 Dual-channel DAC 0-10V" +# define PLUGIN_VALUENAME1_166 "Output0" +# define PLUGIN_VALUENAME2_166 "Output1" + +# include "./src/PluginStructs/P166_data_struct.h" + +boolean Plugin_166(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_166; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_DUAL; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; // FIXME: Is this useful? + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_166); + + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_166)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_166)); + + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F }; + + if (PLUGIN_WEBFORM_SHOW_I2C_PARAMS == function) { + addFormSelectorI2C(F("i2c_addr"), 8, i2cAddressValues, P166_I2C_ADDRESS, 0x5F); // Mark 0x5F as default + } else { + success = intArrayContains(8, i2cAddressValues, event->Par1); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P166_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P166_I2C_ADDRESS = 0x5F; // Hardware comes configured at this address + P166_MAX_VOLTAGE = static_cast(DFRobot_GP8403::eOutPutRange_t::eOutputRange10V); + P166_RESTORE_VALUES = 1; // Enabled by default + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *configurations[] = { + F("0-5V"), + F("0-10V"), + }; + const int configurationOptions[] = { + static_cast(DFRobot_GP8403::eOutPutRange_t::eOutputRange5V), + static_cast(DFRobot_GP8403::eOutPutRange_t::eOutputRange10V), + }; + addFormSelector(F("Output range"), + F("range"), + sizeof(configurationOptions) / sizeof(configurationOptions[0]), + configurations, + configurationOptions, + P166_MAX_VOLTAGE); + } + + addFormCheckBox(F("Restore output on warm boot"), F("prstr"), P166_RESTORE_VALUES == 1); + + addFormFloatNumberBox(F("Initial value output 0"), F("prch0"), P166_PRESET_OUTPUT(0), 0.0f, 10.0f, 3); + addFormFloatNumberBox(F("Initial value output 1"), F("prch1"), P166_PRESET_OUTPUT(1), 0.0f, 10.0f, 3); + + addFormSubHeader(F("Preset values")); + + String presets[P166_PresetEntries]{}; + + LoadCustomTaskSettings(event->TaskIndex, presets, P166_PresetEntries, 0); + + addRowLabel(F("Preset value")); + + html_table(EMPTY_STRING); + html_table_header(F("#"), 50); + html_table_header(F("Name"), 200); + html_table_header(F("Voltage (V)"), 120); + int i = 0; + int j = 0; + + while ((!presets[i].isEmpty() || j < 5) && i < P166_PresetEntries) { + html_TR(); + html_TD(F("text-align:center")); + addHtmlInt(i + 1); + html_TD(); + addTextBox(getPluginCustomArgName((i * 10) + 0), parseStringKeepCase(presets[i], 1), 16); + html_TD(); + float value{}; + validFloatFromString(parseStringKeepCase(presets[i], 2), value); + addFloatNumberBox(getPluginCustomArgName((i * 10) + 1), value, 0.0f, 10.0f, 3); + + if (presets[i].isEmpty()) { + ++j; + } + ++i; + } + html_end_table(); + addFormNote(strformat(F("Max. presets: %d. Submit page to add more entries."), P166_PresetEntries)); + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P166_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + P166_MAX_VOLTAGE = getFormItemInt(F("range")); + P166_RESTORE_VALUES = isFormItemChecked(F("prstr")) ? 1 : 0; + P166_PRESET_OUTPUT(0) = getFormItemFloat(F("prch0")); + P166_PRESET_OUTPUT(1) = getFormItemFloat(F("prch1")); + + UserVar.setFloat(event->TaskIndex, 3, 0.0f); // Reset state flag so Initial values will be applied + + String presets[P166_PresetEntries]{}; + + int i = 0; + int j = 0; + + while (i < P166_PresetEntries) { + String entry = webArg(getPluginCustomArgName((i * 10) + 0)); + entry.trim(); + + if (!entry.isEmpty()) { + const float value = getFormItemFloat(getPluginCustomArgName((i * 10) + 1)); + presets[j] = strformat(F("%s,%.6g"), wrapWithQuotesIfContainsParameterSeparatorChar(entry).c_str(), value); + + // addLog(LOG_LEVEL_INFO, strformat(F("Saving %d: [%s]"), j + 1, presets[j].c_str())); + ++j; + } + ++i; + } + const String error = SaveCustomTaskSettings(event->TaskIndex, presets, P166_PresetEntries, 0); + + if (!error.isEmpty()) { + addHtmlError(error); + } + + success = true; + break; + } + + case PLUGIN_INIT: + { + if (Settings.isI2CEnabled()) { + initPluginTaskData(event->TaskIndex, + new (std::nothrow) P166_data_struct(P166_I2C_ADDRESS, + static_cast(P166_MAX_VOLTAGE))); + P166_data_struct *P166_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P166_data) && P166_data->init(event); + } else { + addLog(LOG_LEVEL_ERROR, F("GP8403: I2C not enabled, init cancelled.")); + } + + break; + } + + case PLUGIN_READ: + { + P166_data_struct *P166_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P166_data) { + success = P166_data->plugin_read(event); + } + + break; + } + + case PLUGIN_WRITE: + { + P166_data_struct *P166_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P166_data) { + success = P166_data->plugin_write(event, string); + } + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P166_data_struct *P166_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P166_data) { + success = P166_data->plugin_get_config_value(event, string); + } + + break; + } + } + return success; +} + +#endif // USES_P166 diff --git a/src/_P167_Vindstyrka.ino b/src/_P167_Vindstyrka.ino new file mode 100644 index 000000000..70cdf8db6 --- /dev/null +++ b/src/_P167_Vindstyrka.ino @@ -0,0 +1,384 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P167 + +// ####################################################################################################### +// ######################## Plugin 167 IKEA Vindstyrka I2C Sensor (SEN5x) ############################ +// ####################################################################################################### + +/** Changelog: + * 2024-05-05 tonhuisman: Add subcommand sen5x,techlog,<1|0> to enable/disable Technical logging option. 0 = Off, any other value is on + * 2024-04-20 tonhuisman: Replace dewpoint calculation by standard calculation, fix issue with status bits, reduce strings + * Remove unneeded code and variables, move most defines to P167_data_struct.h + * Implement Get Config Value to retrieve all available values from a single instance + * Implement multi-instance use (using an I2C multiplexer, as the address isn't configurable) + * Use enum classes where applicable + * Keeping the FSM in place + * 2024-04-19 tonhuisman: Source formatting using Uncrustify (ESPEasy standard) and string handling modifications + * 2023-06-19 AndiBaciu creation based upon https://github.com/RobTillaart/SHT2x + */ + +# include "./src/PluginStructs/P167_data_struct.h" + +# define PLUGIN_167 +# define PLUGIN_ID_167 167 // plugin id +# define PLUGIN_NAME_167 "Environment - Sensirion SEN5x (IKEA Vindstyrka)" // What will be dislpayed in the selection list + + +boolean Plugin_167(uint8_t function, struct EventStruct *event, String& string) { + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + // This case defines the device characteristics + Device[++deviceCount].Number = PLUGIN_ID_167; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].I2CNoDeviceCheck = true; + Device[deviceCount].I2CMax100kHz = true; // SEN5x only supports up to 100 kHz + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + Device[deviceCount].OutputDataType = Output_Data_type_t::Simple; + break; + } + + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_167); + break; + } + + + case PLUGIN_GET_DEVICEVALUECOUNT: + { + if (P167_I2C_ADDRESS_DFLT == PCONFIG(0)) { + PCONFIG(P167_SENSOR_TYPE_INDEX) = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_QUAD); + } + event->Par1 = P167_NR_OUTPUT_VALUES; + success = true; + break; + } + + + case PLUGIN_GET_DEVICEVTYPE: + { + if (P167_I2C_ADDRESS_DFLT == PCONFIG(0)) { + PCONFIG(P167_SENSOR_TYPE_INDEX) = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_QUAD); + } + event->sensorType = static_cast(PCONFIG(P167_SENSOR_TYPE_INDEX)); + event->idx = P167_SENSOR_TYPE_INDEX; + success = true; + break; + } + + + case PLUGIN_GET_DEVICEVALUENAMES: + { + if (P167_I2C_ADDRESS_DFLT == PCONFIG(0)) { + PCONFIG(P167_SENSOR_TYPE_INDEX) = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_QUAD); + } + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (i < P167_NR_OUTPUT_VALUES) { + uint8_t choice = PCONFIG(i + P167_QUERY1_CONFIG_POS); + safe_strncpy(ExtraTaskSettings.TaskDeviceValueNames[i], P167_getQueryValueString(choice), + sizeof(ExtraTaskSettings.TaskDeviceValueNames[i])); + } else { + ZERO_FILL(ExtraTaskSettings.TaskDeviceValueNames[i]); + } + } + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + P167_MODEL = P167_MODEL_DFLT; + P167_QUERY1 = P167_QUERY1_DFLT; + P167_QUERY2 = P167_QUERY2_DFLT; + P167_QUERY3 = P167_QUERY3_DFLT; + P167_QUERY4 = P167_QUERY4_DFLT; + P167_MON_SCL_PIN = P167_MON_SCL_PIN_DFLT; + PCONFIG(P167_SENSOR_TYPE_INDEX) = static_cast(Sensor_VType::SENSOR_TYPE_QUAD); + + success = true; + break; + } + + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P167_I2C_ADDRESS_DFLT; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + + case PLUGIN_I2C_HAS_ADDRESS: + + { + success = P167_I2C_ADDRESS_DFLT == event->Par1; + break; + } + + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + // if (P167_SEN_FIRST == event->TaskIndex) { // If first SEN, serial config available + if (P167_MODEL == 0) { + string = strformat(F("MonPin SCL: %s"), formatGpioLabel(P167_MON_SCL_PIN, false).c_str()); + } + + // } + success = true; + break; + } + + + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + { + if (P167_I2C_ADDRESS_DFLT == PCONFIG(0)) { + PCONFIG(P167_SENSOR_TYPE_INDEX) = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_QUAD); + } + const __FlashStringHelper *options[P167_NR_OUTPUT_OPTIONS]; + + for (int i = 0; i < P167_NR_OUTPUT_OPTIONS; ++i) { + options[i] = P167_getQueryString(i); + } + + for (uint8_t i = 0; i < P167_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P167_QUERY1_CONFIG_POS; + sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P167_NR_OUTPUT_OPTIONS, options); + } + addFormNote(F("NOx is available ONLY on Sensirion SEN55 model")); + break; + } + + + case PLUGIN_WEBFORM_LOAD: + { + const __FlashStringHelper *options_model[] = { + toString(P167_model::Vindstyrka), + toString(P167_model::SEN54), + toString(P167_model::SEN55), + }; + const int options_model_value[] = { + P167_MODEL_VINDSTYRKA, + P167_MODEL_SEN54, + P167_MODEL_SEN55, + }; + constexpr uint8_t optCount = NR_ELEMENTS(options_model_value); + + addFormSelector(F("Model Type"), P167_MODEL_LABEL, optCount, + options_model, options_model_value, P167_MODEL, true); + addFormNote(F("Changing the Model Type will reload the page.")); + + if (P167_MODEL == P167_MODEL_VINDSTYRKA) { + addFormPinSelect(PinSelectPurpose::Generic_input, F("MonPin SCL"), F("taskdevicepin3"), P167_MON_SCL_PIN); + addFormNote(F("Pin for monitoring I2C communication between Vindstyrka controller and SEN54. " + "(Only when Model Type: IKEA Vindstyrka is selected.)")); + } + + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (Plugin_167_SEN != nullptr) { + addRowLabel(F("Device info")); + String prodname; + String sernum; + uint8_t firmware; + Plugin_167_SEN->getEID(prodname, sernum, firmware); + addHtml(strformat(F("ProdName: %s, Serial Number: %s, Firmware: %d"), + prodname.c_str(), sernum.c_str(), firmware)); + + addRowLabel(F("Device status")); + addHtml(strformat(F("Speed warning: %d, Auto Cleaning: %d, GAS Error: %d, " + "RHT Error: %d, LASER Error: %d, FAN Error: %d"), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_speed), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_autoclean), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_gas), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_rht), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_laser), + Plugin_167_SEN->getStatusInfo(P167_statusinfo::sensor_fan) + )); + + addRowLabel(F("Check (pass/fail/errCode)")); + addHtml(strformat(F("%d/%d/%d"), + Plugin_167_SEN->getSuccCount(), + Plugin_167_SEN->getErrCount(), + Plugin_167_SEN->getErrCode() + )); + } + + addFormCheckBox(F("Technical logging"), P167_ENABLE_LOG_LABEL, P167_ENABLE_LOG); + success = true; + break; + } + + + case PLUGIN_WEBFORM_SAVE: + { + // Save output selector parameters. + for (uint8_t i = 0; i < P167_NR_OUTPUT_VALUES; ++i) { + const uint8_t pconfigIndex = i + P167_QUERY1_CONFIG_POS; + const uint8_t choice = PCONFIG(pconfigIndex); + sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, P167_getQueryValueString(choice)); + } + P167_MODEL = getFormItemInt(P167_MODEL_LABEL); + P167_ENABLE_LOG = isFormItemChecked(P167_ENABLE_LOG_LABEL); + + if (P167_MODEL == P167_MODEL_VINDSTYRKA) { + P167_MON_SCL_PIN = getFormItemInt(F("taskdevicepin3")); + } else { + P167_MON_SCL_PIN = -1; // None + } + + + success = true; + break; + } + + + case PLUGIN_INIT: + { + if (P167_I2C_ADDRESS_DFLT == PCONFIG(0)) { + PCONFIG(P167_SENSOR_TYPE_INDEX) = getValueCountFromSensorType(Sensor_VType::SENSOR_TYPE_QUAD); + } + initPluginTaskData(event->TaskIndex, new (std::nothrow) P167_data_struct()); + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (Plugin_167_SEN != nullptr) { + Plugin_167_SEN->setupModel(static_cast(P167_MODEL)); + Plugin_167_SEN->setupDevice(P167_I2C_ADDRESS_DFLT); + Plugin_167_SEN->setLogging(P167_ENABLE_LOG); + + if (P167_MODEL == P167_MODEL_VINDSTYRKA) { + Plugin_167_SEN->setupMonPin(P167_MON_SCL_PIN); + } + success = Plugin_167_SEN->reset(); + } + + for (taskVarIndex_t v = 0; v < P167_NR_OUTPUT_VALUES; ++v) { + UserVar.setFloat(event->TaskIndex, v, NAN); + } + + break; + } + + + case PLUGIN_EXIT: + { + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if ((Plugin_167_SEN != nullptr) && (P167_MODEL == P167_MODEL_VINDSTYRKA)) { + Plugin_167_SEN->disableInterrupt_monpin(); + } + + success = true; + break; + } + + + case PLUGIN_READ: + { + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != Plugin_167_SEN) { + if (Plugin_167_SEN->inError()) { + for (taskVarIndex_t v = 0; v < P167_NR_OUTPUT_VALUES; ++v) { + UserVar.setFloat(event->TaskIndex, v, NAN); + } + addLog(LOG_LEVEL_ERROR, F("Vindstyrka / SEN5X: in Error!")); + } else { + // if (event->TaskIndex == P167_SEN_FIRST) { + Plugin_167_SEN->startMeasurements(); // getting ready for another read cycle + // } + + for (taskVarIndex_t v = 0; v < P167_NR_OUTPUT_VALUES; ++v) { + UserVar.setFloat(event->TaskIndex, v, Plugin_167_SEN->getRequestedValue(PCONFIG(P167_QUERY1_CONFIG_POS + v))); + } + success = true; + } + } + + break; + } + + + case PLUGIN_FIFTY_PER_SECOND: + { + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != Plugin_167_SEN) { + Plugin_167_SEN->monitorSCL(); // Vindstryka / SEN5X FSM evaluation + Plugin_167_SEN->update(); + } + + // } + success = true; + } + + case PLUGIN_GET_CONFIG_VALUE: + + { + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != Plugin_167_SEN) { + for (uint8_t v = 0; v < P167_VALUE_COUNT && !success; ++v) { + if (string.equalsIgnoreCase(P167_getQueryValueString(v))) { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("SEN5x: Get Config Value: %s: %.2f"), + string.c_str(), Plugin_167_SEN->getRequestedValue(v))); + } + # endif // ifndef BUILD_NO_DEBUG + string = toString(Plugin_167_SEN->getRequestedValue(v)); + success = true; + break; + } + } + } + break; + } + + + case PLUGIN_WRITE: + { + P167_data_struct *Plugin_167_SEN = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != Plugin_167_SEN) { + const String cmd = parseString(string, 1); + + if (equals(cmd, F("sen5x"))) { + const String subcmd = parseString(string, 2); + + if (equals(subcmd, F("startclean"))) { + Plugin_167_SEN->startCleaning(); + success = true; + } else + if (equals(subcmd, F("techlog"))) { + P167_ENABLE_LOG = event->Par2 == 0 ? 0 : 1; + Plugin_167_SEN->setLogging(P167_ENABLE_LOG); + success = true; + } + } + } + break; + } + } // switch + + return success; +} // Plugin_167 + +#endif // USES_P167 diff --git a/src/_P168_VEML6030_7700.ino b/src/_P168_VEML6030_7700.ino new file mode 100644 index 000000000..f58d1c436 --- /dev/null +++ b/src/_P168_VEML6030_7700.ino @@ -0,0 +1,247 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P168 + +// ####################################################################################################### +// ####################### Plugin 168: Light/Lux - VEML6030/VEML7700 I2C Light sensor #################### +// ####################################################################################################### + +/** + * 2024-06-21 tonhuisman: Fix support for VEML6030, using by default the alternate I2C address, by modifying the VEML7700 library + * 2024-05-18 tonhuisman: Implement AutoLux feature, and Get Config Value for automatically determined gain and integration + * 2024-05-16 tonhuisman: Start plugin for VEML6030/VEML7700 I2C Light sensor, using a slightly adjusted Adafruit library: + * https://github.com/adafruit/Adafruit_VEML7700 + **/ + +# define PLUGIN_168 +# define PLUGIN_ID_168 168 +# define PLUGIN_NAME_168 "Light/Lux - VEML6030/VEML7700" +# define PLUGIN_VALUENAME1_168 "Lux" +# define PLUGIN_VALUENAME2_168 "White" +# define PLUGIN_VALUENAME3_168 "Raw" + +# include "./src/PluginStructs/P168_data_struct.h" + +boolean Plugin_168(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_168; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_SINGLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 3; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_168); + + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_168)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_168)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_168)); + + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x10, 0x48 }; + + if (PLUGIN_WEBFORM_SHOW_I2C_PARAMS == function) { + addFormSelectorI2C(F("i2c_addr"), 2, i2cAddressValues, P168_I2C_ADDRESS); + # ifndef BUILD_NO_DEBUG + addFormNote(F("Address 0x48 only supported by VEML6030, ADDR -> VCC")); + # endif // ifndef BUILD_NO_DEBUG + } else { + success = intArrayContains(2, i2cAddressValues, event->Par1); + } + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P168_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P168_READLUX_MODE = VEML_LUX_AUTO; + P168_PSM_MODE = static_cast(P168_power_save_mode_e::Disabled); + + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 0; // White + ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // Raw + + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + { + const __FlashStringHelper *readMethod[] = { + F("Normal"), + F("Corrected"), + F("Auto"), + F("Normal (no wait)"), + F("Corrected (no wait)"), + }; + const int readMethodOptions[] = { + VEML_LUX_NORMAL, + VEML_LUX_CORRECTED, + VEML_LUX_AUTO, + VEML_LUX_NORMAL_NOWAIT, + VEML_LUX_CORRECTED_NOWAIT, + }; + addFormSelector(F("Lux Read-method"), + F("rmth"), + NR_ELEMENTS(readMethodOptions), + readMethod, + readMethodOptions, + P168_READLUX_MODE); + addFormNote(F("For 'Auto' Read-method, the Gain factor and Integration time settings are ignored.")); + } + { + const __FlashStringHelper *alsGain[] = { + F("x1"), + F("x2"), + F("x(1/8)"), + F("x(1/4)"), + }; + const int alsGainOptions[] = { + 0b00, + 0b01, + 0b10, + 0b11, + }; + addFormSelector(F("Gain factor"), + F("gain"), + NR_ELEMENTS(alsGainOptions), + alsGain, + alsGainOptions, + P168_ALS_GAIN); + } + { + const __FlashStringHelper *alsIntegration[] = { + F("25 ms"), + F("50 ms"), + F("100 ms"), + F("200 ms"), + F("400 ms"), + F("800 ms"), + }; + const int alsIntegrationOptions[] = { + 0b1100, + 0b1000, + 0b0000, + 0b0001, + 0b0010, + 0b0011, + }; + addFormSelector(F("Integration time"), + F("int"), + NR_ELEMENTS(alsIntegrationOptions), + alsIntegration, + alsIntegrationOptions, + P168_ALS_INTEGRATION); + } + addFormSeparator(2); + { + const __FlashStringHelper *psmMode[] = { + F("Disabled"), + F("Mode 1"), + F("Mode 2"), + F("Mode 3"), + F("Mode 4"), + }; + const int psmModeOptions[] = { + static_cast(P168_power_save_mode_e::Disabled), + static_cast(P168_power_save_mode_e::Mode1), + static_cast(P168_power_save_mode_e::Mode2), + static_cast(P168_power_save_mode_e::Mode3), + static_cast(P168_power_save_mode_e::Mode4), + }; + addFormSelector(F("Power Save Mode"), + F("psm"), + NR_ELEMENTS(psmModeOptions), + psmMode, + psmModeOptions, + P168_PSM_MODE); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P168_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + P168_READLUX_MODE = getFormItemInt(F("rmth")); + P168_ALS_GAIN = getFormItemInt(F("gain")); + P168_ALS_INTEGRATION = getFormItemInt(F("int")); + P168_PSM_MODE = getFormItemInt(F("psm")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P168_data_struct(P168_ALS_GAIN, + P168_ALS_INTEGRATION, + P168_PSM_MODE, + P168_READLUX_MODE)); + P168_data_struct *P168_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P168_data) && P168_data->init(event); + + break; + } + + case PLUGIN_READ: + { + P168_data_struct *P168_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P168_data) { + success = P168_data->plugin_read(event); + } + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P168_data_struct *P168_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P168_data) { + success = P168_data->plugin_get_config_value(event, string); + } + + break; + } + } + return success; +} + +#endif // USES_P168 diff --git a/src/_P169_AS3935_LightningDetector.ino b/src/_P169_AS3935_LightningDetector.ino new file mode 100644 index 000000000..8f1f8692b --- /dev/null +++ b/src/_P169_AS3935_LightningDetector.ino @@ -0,0 +1,284 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P169 + +// ####################################################################################################### +// ######################## Plugin 169 AS3935 Lightning Detector I2C ################################## +// ####################################################################################################### + +# include "./src/PluginStructs/P169_data_struct.h" + +# define PLUGIN_169 +# define PLUGIN_ID_169 169 +# define PLUGIN_NAME_169 "Environment - AS3935 Lightning Detector" +# define PLUGIN_VALUENAME1_169 "DistanceNear" +# define PLUGIN_VALUENAME2_169 "DistanceFar" +# define PLUGIN_VALUENAME3_169 "Lightning" +# define PLUGIN_VALUENAME4_169 "Total" + + +boolean Plugin_169(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_169; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TRIPLE; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 4; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].I2CNoDeviceCheck = true; // Sensor may sometimes not respond immediately + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_169); + break; + } + + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[2], PSTR(PLUGIN_VALUENAME3_169)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[3], PSTR(PLUGIN_VALUENAME4_169)); + break; + } + + + case PLUGIN_SET_DEFAULTS: + { + // Set a default config here, which will be called when a plugin is assigned to a task. + P169_I2C_ADDRESS = P169_I2C_ADDRESS_DFLT; + P169_LIGHTNING_THRESHOLD = AS3935MI::AS3935_MNL_1; + P169_AFE_GAIN_LOW = AS3935MI::AS3935_OUTDOORS; + P169_AFE_GAIN_HIGH = AS3935MI::AS3935_OUTDOORS; + P169_SET_MASK_DISTURBANCE(false); + P169_SET_SEND_ONLY_ON_LIGHTNING(true); + P169_SET_TOLERANT_CALIBRATION_RANGE(true); + + ExtraTaskSettings.TaskDeviceValueDecimals[0] = 1; // Distance Near + ExtraTaskSettings.TaskDeviceValueDecimals[1] = 1; // Distance Far + ExtraTaskSettings.TaskDeviceValueDecimals[2] = 0; // Lightning count since last PLUGIN_READ + ExtraTaskSettings.TaskDeviceValueDecimals[3] = 0; // Total lightning count + success = true; + break; + } + + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P169_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + { + const uint8_t i2cAddressValues[] = { 0x01, 0x02, 0x03 }; + + if (function == PLUGIN_WEBFORM_SHOW_I2C_PARAMS) + { + // addFormSelectorI2C(P169_I2C_ADDRESS_LABEL, 3, i2cAddressValues, P169_I2C_ADDRESS); + addFormSelectorI2C(F("i2c_addr"), NR_ELEMENTS(i2cAddressValues), i2cAddressValues, P169_I2C_ADDRESS); + addFormNote(F("Addr: 0-0-0-0-0-A1-A0. Both A0 & A1 low is not valid.")); + } + else + { + success = intArrayContains(NR_ELEMENTS(i2cAddressValues), i2cAddressValues, event->Par1); + } + break; + } + + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + { + string = concat(F("IRQ: "), formatGpioLabel(P169_IRQ_PIN, false)); + success = true; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormPinSelect( + PinSelectPurpose::Generic_input, + formatGpioName_input(F("IRQ")), + F(P169_IRQ_PIN_LABEL), + P169_IRQ_PIN); + + { + const __FlashStringHelper *options[] = { F("1"), F("5"), F("9"), F("16") }; + const int optionValues[] = { + AS3935MI::AS3935_MNL_1, + AS3935MI::AS3935_MNL_5, + AS3935MI::AS3935_MNL_9, + AS3935MI::AS3935_MNL_16 }; + addFormSelector(F("Lightning Threshold"), + P169_LIGHTNING_THRESHOLD_LABEL, + NR_ELEMENTS(optionValues), + options, + optionValues, + P169_LIGHTNING_THRESHOLD); + addFormNote(F("Minimum number of lightning strikes in the last 15 minutes")); + } + { + const __FlashStringHelper *options[] = { + F("0.30x"), + F("0.40x"), + F("0.55x"), + F("0.74x"), + F("1.00x (Outdoor)"), + F("1.35x"), + F("1.83x"), + F("2.47x"), + F("3.34x (Indoor)") + }; + const int optionValues[] = { + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18 + }; + addFormSelector(F("AFE Gain Min"), P169_AFE_GAIN_LOW_LABEL, NR_ELEMENTS(optionValues), options, optionValues, P169_AFE_GAIN_LOW); + addFormSelector(F("AFE Gain Max"), P169_AFE_GAIN_HIGH_LABEL, NR_ELEMENTS(optionValues), options, optionValues, P169_AFE_GAIN_HIGH); + addFormNote(F("Lower and upper limit for the Analog Frond-End auto gain to use.")); + } + + addFormCheckBox(F("Ignore Disturbance"), F(P169_MASK_DISTURBANCE_LABEL), P169_GET_MASK_DISTURBANCE); + addFormCheckBox(F("Tolerate out-of-range calibration"), F(P169_TOLERANT_CALIBRATION_RANGE_LABEL), P169_GET_TOLERANT_CALIBRATION_RANGE); + addFormNote(F("When checked, allow for more than 3.5% deviation for the 500 kHz LCO resonance frequency")); + addFormCheckBox(F("Slow LCO Calibration"), F(P169_SLOW_LCO_CALIBRATION_LABEL), P169_GET_SLOW_LCO_CALIBRATION); + addFormNote(F("Slow Calibration may improve accuracy of measured resonance frequency")); + addFormCheckBox(F("Send Only On Lightning"), F(P169_SEND_ONLY_ON_LIGHTNING_LABEL), P169_GET_SEND_ONLY_ON_LIGHTNING); + addFormNote(F("Only send to controller when lightning detected since last taskrun")); + + P169_data_struct *P169_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (P169_data != nullptr) { + P169_data->html_show_sensor_info(event); + } + + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P169_I2C_ADDRESS = getFormItemInt(F("i2c_addr")); + + /* + P169_NOISE = getFormItemInt(P169_NOISE_LABEL); + P169_WATCHDOG = getFormItemInt(P169_WATCHDOG_LABEL); + P169_SPIKE_REJECTION = getFormItemInt(P169_SPIKE_REJECTION_LABEL); + */ + P169_LIGHTNING_THRESHOLD = getFormItemInt(P169_LIGHTNING_THRESHOLD_LABEL); + const int gain_low = getFormItemInt(P169_AFE_GAIN_LOW_LABEL); + const int gain_high = getFormItemInt(P169_AFE_GAIN_HIGH_LABEL); + P169_AFE_GAIN_LOW = gain_low; + P169_AFE_GAIN_HIGH = gain_high; + + if (gain_low > gain_high) { + P169_AFE_GAIN_LOW = gain_high; + P169_AFE_GAIN_HIGH = gain_low; + } + P169_SET_MASK_DISTURBANCE(isFormItemChecked(F(P169_MASK_DISTURBANCE_LABEL))); + P169_SET_SEND_ONLY_ON_LIGHTNING(isFormItemChecked(F(P169_SEND_ONLY_ON_LIGHTNING_LABEL))); + P169_SET_TOLERANT_CALIBRATION_RANGE(isFormItemChecked(F(P169_TOLERANT_CALIBRATION_RANGE_LABEL))); + P169_SET_SLOW_LCO_CALIBRATION(isFormItemChecked(F(P169_SLOW_LCO_CALIBRATION_LABEL))); + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P169_data_struct(event)); + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_init(event); + } + + break; + } + + case PLUGIN_READ: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + if (P169_data->getAndClearLightningCount() > 0) { + success = true; + } else { + UserVar.setFloat(event->TaskIndex, 0, -1.0f); + UserVar.setFloat(event->TaskIndex, 1, -1.0f); + UserVar.setFloat(event->TaskIndex, 2, 0.0f); + P169_data->clearStatistics(); + + if (!P169_GET_SEND_ONLY_ON_LIGHTNING) { + success = true; + } + } + } + + break; + } + + case PLUGIN_TEN_PER_SECOND: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + if (P169_data->loop(event)) {} + success = true; + } + break; + } + + case PLUGIN_WRITE: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_write(event, string); + } + + break; + } + + case PLUGIN_GET_CONFIG_VALUE: + { + P169_data_struct *P169_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P169_data) { + success = P169_data->plugin_get_config_value(event, string); + } + + break; + } + } + + return success; +} // function + +#endif // USES_P169 diff --git a/src/_P170_Waterlevel.ino b/src/_P170_Waterlevel.ino new file mode 100644 index 000000000..ff35455d6 --- /dev/null +++ b/src/_P170_Waterlevel.ino @@ -0,0 +1,143 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P170 + +// ####################################################################################################### +// ############################### Plugin 170: Input - I2C Liquid level sensor ########################### +// ####################################################################################################### + +/** + * 2024-05-25 tonhuisman: Add optional logging at info level with received data + * 2024-05-20 tonhuisman: Add low- and high-level trigger checks (0 = disabled), and trigger-once option with auto-reset + * Trigger is checked at Interval setting, or once per second if Interval = 0. + * Add sensitivity setting, to compensate for different liquids. + * 2024-05-19 tonhuisman: Start plugin for Seeed studio I2C Liquid level sensor + * Using direct I2C communication + **/ + +# define PLUGIN_170 +# define PLUGIN_ID_170 170 +# define PLUGIN_NAME_170 "Input - I2C Liquid level sensor" +# define PLUGIN_VALUENAME1_170 "Level" +# define PLUGIN_VALUENAME2_170 "Steps" + +# include "./src/PluginStructs/P170_data_struct.h" + +boolean Plugin_170(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_170; + Device[deviceCount].Type = DEVICE_TYPE_I2C; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_QUAD; + Device[deviceCount].Ports = 0; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].TimerOptional = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_170); + + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_170)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_170)); + + break; + } + + case PLUGIN_I2C_HAS_ADDRESS: + { + const uint8_t i2cAddressValues[] = { P170_I2C_ADDRESS, P170_I2C_ADDRESS_HIGH }; + + success = intArrayContains(2, i2cAddressValues, event->Par1); + + break; + } + + # if FEATURE_I2C_GET_ADDRESS + case PLUGIN_I2C_GET_ADDRESS: + { + event->Par1 = P170_I2C_ADDRESS; + success = true; + break; + } + # endif // if FEATURE_I2C_GET_ADDRESS + + case PLUGIN_SET_DEFAULTS: + { + P170_STEP_ACTIVE_LEVEL = P170_STEP_ACTIVE_LEVEL_DEF; + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + addFormNumericBox(F("Sensitivity"), F("sens"), P170_STEP_ACTIVE_LEVEL, 50, 254); + addUnit(F("50..254")); + + addFormSubHeader(F("Events")); + + addFormNumericBox(F("Trigger on Low level"), F("low"), P170_TRIGGER_LOW_LEVEL, 0, 100); + addUnit(F("0..100mm")); + addFormNumericBox(F("Trigger on High level"), F("high"), P170_TRIGGER_HIGH_LEVEL, 0, 100); + addUnit(F("0..100mm")); + addFormNote(F("Trigger level 0 = Disabled, step size: " P170_MM_PER_STEP_STR "mm, (rounded down)")); + addFormCheckBox(F("Trigger only once"), F("once"), P170_TRIGGER_ONCE); + addFormNote(F("Auto-reset when level is correct again, separate for Low and High level.")); + + addFormCheckBox(F("Log signal level"), F("log"), P170_ENABLE_LOG); + success = true; + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + P170_STEP_ACTIVE_LEVEL = getFormItemInt(F("sens")); + P170_TRIGGER_LOW_LEVEL = (getFormItemInt(F("low")) / P170_MM_PER_STEP) * P170_MM_PER_STEP; + P170_TRIGGER_HIGH_LEVEL = (getFormItemInt(F("high")) / P170_MM_PER_STEP) * P170_MM_PER_STEP; + P170_TRIGGER_ONCE = isFormItemChecked(F("once")); + P170_ENABLE_LOG = isFormItemChecked(F("log")); + + success = true; + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P170_data_struct(P170_STEP_ACTIVE_LEVEL, P170_ENABLE_LOG)); + P170_data_struct *P170_data = static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P170_data) && P170_data->init(event); + + break; + } + + case PLUGIN_READ: + { + P170_data_struct *P170_data = static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P170_data) { + success = P170_data->plugin_read(event); + } + + break; + } + } + return success; +} + +#endif // USES_P170 diff --git a/src/_P172_BMP3xx_SPI.ino b/src/_P172_BMP3xx_SPI.ino new file mode 100644 index 000000000..e6c3442ee --- /dev/null +++ b/src/_P172_BMP3xx_SPI.ino @@ -0,0 +1,104 @@ +#include "_Plugin_Helper.h" +#ifdef USES_P172 + +// ####################################################################################################### +// ################################## Plugin-172: Environment - BMP3xx SPI ############################# +// ####################################################################################################### + +/** + * 2024-06-30 tonhuisman: Start SPI plugin, based on P154, re-using most code (dependency checked in define_plugin_sets.h) + */ + +# include "src/PluginStructs/P154_data_struct.h" + +# define PLUGIN_172 +# define PLUGIN_ID_172 172 +# define PLUGIN_NAME_172 "Environment - BMP3xx (SPI)" +# define PLUGIN_VALUENAME1_172 "Temperature" +# define PLUGIN_VALUENAME2_172 "Pressure" + +boolean Plugin_172(uint8_t function, struct EventStruct *event, String& string) +{ + boolean success = false; + + switch (function) + { + case PLUGIN_DEVICE_ADD: + { + Device[++deviceCount].Number = PLUGIN_ID_172; + Device[deviceCount].Type = DEVICE_TYPE_SPI; + Device[deviceCount].VType = Sensor_VType::SENSOR_TYPE_TEMP_BARO; + Device[deviceCount].Ports = 0; + Device[deviceCount].PullUpOption = false; + Device[deviceCount].InverseLogicOption = false; + Device[deviceCount].FormulaOption = true; + Device[deviceCount].ValueCount = 2; + Device[deviceCount].SendDataOption = true; + Device[deviceCount].TimerOption = true; + Device[deviceCount].GlobalSyncOption = true; + Device[deviceCount].PluginStats = true; + break; + } + + case PLUGIN_GET_DEVICENAME: + { + string = F(PLUGIN_NAME_172); + break; + } + + case PLUGIN_GET_DEVICEVALUENAMES: + { + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[0], PSTR(PLUGIN_VALUENAME1_172)); + strcpy_P(ExtraTaskSettings.TaskDeviceValueNames[1], PSTR(PLUGIN_VALUENAME2_172)); + break; + } + + case PLUGIN_GET_DEVICEGPIONAMES: + { + event->String1 = formatGpioName_output(F("CS")); + break; + } + + case PLUGIN_INIT: + { + initPluginTaskData(event->TaskIndex, new (std::nothrow) P154_data_struct(event)); + P154_data_struct *P154_P172_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + success = (nullptr != P154_P172_data && P154_P172_data->begin(false)); + break; + } + + case PLUGIN_READ: + { + P154_data_struct *P154_P172_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr == P154_P172_data) { + break; + } + + float temp, pressure{}; + + success = P154_P172_data->read(temp, pressure); + UserVar.setFloat(event->TaskIndex, 0, temp); + UserVar.setFloat(event->TaskIndex, 1, pressure); + break; + } + + case PLUGIN_WEBFORM_LOAD: + { + success = P154_data_struct::webformLoad(event, false); + break; + } + + case PLUGIN_WEBFORM_SAVE: + { + success = P154_data_struct::webformSave(event); + break; + } + } + return success; +} + +#endif // ifdef USES_P172 diff --git a/src/_Plugin_Helper.cpp b/src/_Plugin_Helper.cpp index fedde4567..5e49b5512 100644 --- a/src/_Plugin_Helper.cpp +++ b/src/_Plugin_Helper.cpp @@ -1,208 +1,208 @@ -#include "_Plugin_Helper.h" - -#include "ESPEasy_common.h" - -#include "src/CustomBuild/ESPEasyLimits.h" -#include "src/DataStructs/PluginTaskData_base.h" -#include "src/DataStructs/SettingsStruct.h" -#include "src/DataStructs/TimingStats.h" -#include "src/Globals/Cache.h" -#include "src/Globals/Plugins.h" -#include "src/Globals/Settings.h" -#include "src/Helpers/Misc.h" -#include "src/Helpers/StringParser.h" - - -PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {}; - - -String PCONFIG_LABEL(int n) { - if (n < PLUGIN_CONFIGVAR_MAX) { - return concat(F("pconf_"), n); - } - return F("error"); -} - -void resetPluginTaskData() { - for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { - Plugin_task_data[i] = nullptr; - } -} - -void clearPluginTaskData(taskIndex_t taskIndex) { - if (validTaskIndex(taskIndex)) { - if (Plugin_task_data[taskIndex] != nullptr) { - delete Plugin_task_data[taskIndex]; - Plugin_task_data[taskIndex] = nullptr; - } - } -} - -bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) { - if (!validTaskIndex(taskIndex)) { - if (data != nullptr) { - delete data; - } - return false; - } - - // 2nd heap may have been active to allocate the PluginTaskData, but here we need to keep the default heap active - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - - clearPluginTaskData(taskIndex); - - if (data != nullptr) { - if (Settings.TaskDeviceEnabled[taskIndex]) { - Plugin_task_data[taskIndex] = data; - Plugin_task_data[taskIndex]->_taskdata_pluginID = Settings.getPluginID_for_task(taskIndex); - - #if FEATURE_PLUGIN_STATS - const uint8_t valueCount = getValueCountForTask(taskIndex); - for (size_t i = 0; i < valueCount; ++i) { - if (Cache.enabledPluginStats(taskIndex, i)) { - Plugin_task_data[taskIndex]->initPluginStats(i); - } - } - #endif - #if FEATURE_PLUGIN_FILTER - // TODO TD-er: Implement init - - #endif - - } else { - delete data; - } - } - return getPluginTaskData(taskIndex) != nullptr; -} - -PluginTaskData_base* getPluginTaskData(taskIndex_t taskIndex) { - if (pluginTaskData_initialized(taskIndex)) { - - if (!Plugin_task_data[taskIndex]->baseClassOnly()) { - return Plugin_task_data[taskIndex]; - } - } - return nullptr; -} - -PluginTaskData_base* getPluginTaskDataBaseClassOnly(taskIndex_t taskIndex) { - if (pluginTaskData_initialized(taskIndex)) { - return Plugin_task_data[taskIndex]; - } - return nullptr; -} - - -bool pluginTaskData_initialized(taskIndex_t taskIndex) { - if (!validTaskIndex(taskIndex)) { - return false; - } - return Plugin_task_data[taskIndex] != nullptr && - (Plugin_task_data[taskIndex]->_taskdata_pluginID == Settings.getPluginID_for_task(taskIndex)); -} - -String getPluginCustomArgName(int varNr) { - return getPluginCustomArgName(F("pc_arg"), varNr); -} - -String getPluginCustomArgName(const __FlashStringHelper * label, int varNr) { - return concat(label, varNr + 1); -} - -int getFormItemIntCustomArgName(int varNr) { - return getFormItemInt(getPluginCustomArgName(varNr)); -} - -// Helper function to create formatted custom values for display in the devices overview page. -// When called from PLUGIN_WEBFORM_SHOW_VALUES, the last item should add a traling div_br class -// if the regular values should also be displayed. -// The call to PLUGIN_WEBFORM_SHOW_VALUES should only return success = true when no regular values should be displayed -// Note that the varNr of the custom values should not conflict with the existing variable numbers (e.g. start at VARS_PER_TASK) -void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper * label, const String& value, bool addTrailingBreak) { - pluginWebformShowValue(taskIndex, varNr, String(label), value, addTrailingBreak); -} - -void pluginWebformShowValue(taskIndex_t taskIndex, - uint8_t varNr, - const String& label, - const String& value, - bool addTrailingBreak) { - if (varNr > 0) { - addHtmlDiv(F("div_br")); - } - String postfix(taskIndex); - postfix += '_'; - postfix += varNr; - - pluginWebformShowValue( - label, concat(F("valuename_"), postfix), - value, concat(F("value_"), postfix), - addTrailingBreak); -} - -void pluginWebformShowValue(const String& valName, const String& value, bool addBR) { - pluginWebformShowValue(valName, EMPTY_STRING, value, EMPTY_STRING, addBR); -} - -void pluginWebformShowValue(const String& valName, const String& valName_id, const String& value, const String& value_id, bool addBR) { - String valName_tmp(valName); - - if (!valName_tmp.endsWith(F(":"))) { - valName_tmp += ':'; - } - addHtmlDiv(F("div_l"), valName_tmp, valName_id); - addHtmlDiv(F("div_r"), value, value_id); - - if (addBR) { - addHtmlDiv(F("div_br")); - } -} - -bool pluginOptionalTaskIndexArgumentMatch(taskIndex_t taskIndex, const String& string, uint8_t paramNr) { - if (!validTaskIndex(taskIndex)) { - return false; - } - const taskIndex_t found_taskIndex = parseCommandArgumentTaskIndex(string, paramNr); - - if (!validTaskIndex(found_taskIndex)) { - // Optional parameter not present - return true; - } - return found_taskIndex == taskIndex; -} - -bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex, - const __FlashStringHelper * newline, - String& description) -{ - struct EventStruct TempEvent(taskIndex); - TempEvent.String1 = newline; - return PluginCall(PLUGIN_WEBFORM_SHOW_GPIO_DESCR, &TempEvent, description); -} - -int getValueCountForTask(taskIndex_t taskIndex) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - PluginCall(PLUGIN_GET_DEVICEVALUECOUNT, &TempEvent, dummy); - return TempEvent.Par1; -} - -int checkDeviceVTypeForTask(struct EventStruct *event) { - // TD-er: Do not use event->getSensorType() here - if (event->sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET) { - if (validTaskIndex(event->TaskIndex)) { - String dummy; - - event->idx = -1; - if (PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy)) { - return event->idx; // pconfig_index - } - } - } - return -1; -} +#include "_Plugin_Helper.h" + +#include "ESPEasy_common.h" + +#include "src/CustomBuild/ESPEasyLimits.h" +#include "src/DataStructs/PluginTaskData_base.h" +#include "src/DataStructs/SettingsStruct.h" +#include "src/DataStructs/TimingStats.h" +#include "src/Globals/Cache.h" +#include "src/Globals/Plugins.h" +#include "src/Globals/Settings.h" +#include "src/Helpers/Misc.h" +#include "src/Helpers/StringParser.h" + + +PluginTaskData_base *Plugin_task_data[TASKS_MAX] = {}; + + +String PCONFIG_LABEL(int n) { + if (n < PLUGIN_CONFIGVAR_MAX) { + return concat(F("pconf_"), n); + } + return F("error"); +} + +void resetPluginTaskData() { + for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { + Plugin_task_data[i] = nullptr; + } +} + +void clearPluginTaskData(taskIndex_t taskIndex) { + if (validTaskIndex(taskIndex)) { + if (Plugin_task_data[taskIndex] != nullptr) { + delete Plugin_task_data[taskIndex]; + Plugin_task_data[taskIndex] = nullptr; + } + } +} + +bool initPluginTaskData(taskIndex_t taskIndex, PluginTaskData_base *data) { + if (!validTaskIndex(taskIndex)) { + if (data != nullptr) { + delete data; + } + return false; + } + + // 2nd heap may have been active to allocate the PluginTaskData, but here we need to keep the default heap active + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + + clearPluginTaskData(taskIndex); + + if (data != nullptr) { + if (Settings.TaskDeviceEnabled[taskIndex]) { + Plugin_task_data[taskIndex] = data; + Plugin_task_data[taskIndex]->_taskdata_pluginID = Settings.getPluginID_for_task(taskIndex); + + #if FEATURE_PLUGIN_STATS + 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); + } + } + #endif + #if FEATURE_PLUGIN_FILTER + // TODO TD-er: Implement init + + #endif + + } else { + delete data; + } + } + return getPluginTaskData(taskIndex) != nullptr; +} + +PluginTaskData_base* getPluginTaskData(taskIndex_t taskIndex) { + if (pluginTaskData_initialized(taskIndex)) { + + if (!Plugin_task_data[taskIndex]->baseClassOnly()) { + return Plugin_task_data[taskIndex]; + } + } + return nullptr; +} + +PluginTaskData_base* getPluginTaskDataBaseClassOnly(taskIndex_t taskIndex) { + if (pluginTaskData_initialized(taskIndex)) { + return Plugin_task_data[taskIndex]; + } + return nullptr; +} + + +bool pluginTaskData_initialized(taskIndex_t taskIndex) { + if (!validTaskIndex(taskIndex)) { + return false; + } + return Plugin_task_data[taskIndex] != nullptr && + (Plugin_task_data[taskIndex]->_taskdata_pluginID == Settings.getPluginID_for_task(taskIndex)); +} + +String getPluginCustomArgName(int varNr) { + return getPluginCustomArgName(F("pc_arg"), varNr); +} + +String getPluginCustomArgName(const __FlashStringHelper * label, int varNr) { + return concat(label, varNr + 1); +} + +int getFormItemIntCustomArgName(int varNr) { + return getFormItemInt(getPluginCustomArgName(varNr)); +} + +// Helper function to create formatted custom values for display in the devices overview page. +// When called from PLUGIN_WEBFORM_SHOW_VALUES, the last item should add a traling div_br class +// if the regular values should also be displayed. +// The call to PLUGIN_WEBFORM_SHOW_VALUES should only return success = true when no regular values should be displayed +// Note that the varNr of the custom values should not conflict with the existing variable numbers (e.g. start at VARS_PER_TASK) +void pluginWebformShowValue(taskIndex_t taskIndex, uint8_t varNr, const __FlashStringHelper * label, const String& value, bool addTrailingBreak) { + pluginWebformShowValue(taskIndex, varNr, String(label), value, addTrailingBreak); +} + +void pluginWebformShowValue(taskIndex_t taskIndex, + uint8_t varNr, + const String& label, + const String& value, + bool addTrailingBreak) { + if (varNr > 0) { + addHtmlDiv(F("div_br")); + } + String postfix(taskIndex); + postfix += '_'; + postfix += varNr; + + pluginWebformShowValue( + label, concat(F("valuename_"), postfix), + value, concat(F("value_"), postfix), + addTrailingBreak); +} + +void pluginWebformShowValue(const String& valName, const String& value, bool addBR) { + pluginWebformShowValue(valName, EMPTY_STRING, value, EMPTY_STRING, addBR); +} + +void pluginWebformShowValue(const String& valName, const String& valName_id, const String& value, const String& value_id, bool addBR) { + String valName_tmp(valName); + + if (!valName_tmp.endsWith(F(":"))) { + valName_tmp += ':'; + } + addHtmlDiv(F("div_l"), valName_tmp, valName_id); + addHtmlDiv(F("div_r"), value, value_id); + + if (addBR) { + addHtmlDiv(F("div_br")); + } +} + +bool pluginOptionalTaskIndexArgumentMatch(taskIndex_t taskIndex, const String& string, uint8_t paramNr) { + if (!validTaskIndex(taskIndex)) { + return false; + } + const taskIndex_t found_taskIndex = parseCommandArgumentTaskIndex(string, paramNr); + + if (!validTaskIndex(found_taskIndex)) { + // Optional parameter not present + return true; + } + return found_taskIndex == taskIndex; +} + +bool pluginWebformShowGPIOdescription(taskIndex_t taskIndex, + const __FlashStringHelper * newline, + String& description) +{ + struct EventStruct TempEvent(taskIndex); + TempEvent.String1 = newline; + return PluginCall(PLUGIN_WEBFORM_SHOW_GPIO_DESCR, &TempEvent, description); +} + +int getValueCountForTask(taskIndex_t taskIndex) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + PluginCall(PLUGIN_GET_DEVICEVALUECOUNT, &TempEvent, dummy); + return TempEvent.Par1; +} + +int checkDeviceVTypeForTask(struct EventStruct *event) { + // TD-er: Do not use event->getSensorType() here + if (event->sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET) { + if (validTaskIndex(event->TaskIndex)) { + String dummy; + + event->idx = -1; + if (PluginCall(PLUGIN_GET_DEVICEVTYPE, event, dummy)) { + return event->idx; // pconfig_index + } + } + } + return -1; +} diff --git a/src/_Pxxx_PluginTemplate.ino b/src/_Pxxx_PluginTemplate.ino index a91af78c0..b72a7b9c6 100644 --- a/src/_Pxxx_PluginTemplate.ino +++ b/src/_Pxxx_PluginTemplate.ino @@ -1,16 +1,22 @@ /* This file is a template for Plugins */ -/* References: - https://www.letscontrolit.com/wiki/index.php/ESPEasyDevelopment - https://www.letscontrolit.com/wiki/index.php/ESPEasyDevelopmentGuidelines +/* + This guide shows the setup of VSCode and some required and a few optional extensions for development on ESPEasy: + https://espeasy.readthedocs.io/en/latest/Participate/PlatformIO.html + + We even have a starter guide for development on ESPEasy with all steps from beginning to end including writing the documentation: + https://espeasy.readthedocs.io/en/latest/Participate/PlatformIO.html#starter-guide-for-local-development-on-espeasy + + Other References: + https://www.letscontrolit.com/wiki/index.php/ESPEasyDevelopment (No longer updated) + https://www.letscontrolit.com/wiki/index.php/ESPEasyDevelopmentGuidelines (No longer updated) https://github.com/letscontrolit/ESPEasyPluginPlayground - https://diyprojects.io/esp-easy-develop-plugins/ A Plugin should have an ID. The official plugin list is available here: https://www.letscontrolit.com/wiki/index.php/Official_plugin_list The plugin playground is available here: https://github.com/letscontrolit/ESPEasyPluginPlayground - Use the next available ID. The maximum number of Plugins is defined in ESPEasy-Globals.h (PLUGIN_MAX) + Request a new PluginID via this Github issue: https://github.com/letscontrolit/ESPEasy/issues/3839 The Plugin filename should be of the form "_Pxxx_name.ino", where: xxx is the ID @@ -28,12 +34,12 @@ - set plugin status to DEVELOPMENT and distribute to other users for testing - after sufficient usage and possible code correction, set plugin status to TESTING and perform testing with more users - finally, plugin will be accepted in project, then the TESTING tag can be removed. - - along with the plugin source code, prepare a wiki page containing: + - along with the plugin source code, prepare the Read The Docs documentation (included in the repository) containing: - instructions on how to make the necessary configuration - instructions on commands (if any) - examples: plugin usage, command usage,... - when a plugin is removed (deleted), make sure you free any memory it uses. Use PLUGIN_EXIT for that - - if your plugin creates log entries, prefix your entries with your plugin id: "[Pxxx] my plugin did this" + - if your plugin creates log entries, prefix your entries with your plugin id: "Pxxx : my plugin did this" - if your plugin takes input from user and/or accepts/sends http commands, make sure you properly handle non-alphanumeric characters correctly - After ESP boots, all devices can send data instantly. If your plugin is for a sensor which sends data, ensure it doesn't need a delay @@ -186,7 +192,7 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) // The position in the config parameters used in this example is PCONFIG(Pxxx_OUTPUT_TYPE_INDEX) // Must match the one used in case PLUGIN_GET_DEVICEVALUECOUNT (best to use a define for it) // IDX is used here to mark the PCONFIG position used to store the Device VType. - // see P026_Sysinfo.ino for more examples. + // see _P026_Sysinfo.ino for more examples. event->idx = Pxxx_OUTPUT_TYPE_INDEX; event->sensorType = static_cast(PCONFIG(event->idx)); success = true; @@ -219,7 +225,9 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) // # if FEATURE_I2C_GET_ADDRESS // case PLUGIN_I2C_GET_ADDRESS: // { - // event->Par1 = 0x77; // or: = PCONFIG(0); + // // Called to show the configured I2C address on the Devices page + // + // event->Par1 = 0x77; // or: = Pxxx_I2C_ADDR // success = true; // break; // } @@ -254,11 +262,15 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) // For strings, always use the F() macro, which stores the string in flash, not in memory. - // String dropdown[5] = { F("option1"), F("option2"), F("option3"), F("option4")}; - // addFormSelector(string, F("drop-down menu"), F("plugin_xxx_displtype"), 4, dropdown, nullptr, PCONFIG(0)); + // const __FlashStringHelper dropdownList[] = { F("option1"), F("option2"), F("option3"), F("option4")}; + // const int dropdownOptions[] = { 1, 2, 3, 4 }; + // constexpr int dropdownCount = NR_ELEMENTS(dropdownOptions); + // addFormSelector(string, F("drop-down menu"), F("dsptype"), dropdownCount, dropdownList, dropdownOptions, PCONFIG(0)); - // number selection (min-value - max-value) - addFormNumericBox(string, F("description"), F("plugin_xxx_description"), PCONFIG(1), min - value, max - value); + // number selection (min_value - max_value) + addFormNumericBox(string, F("description"), F("desc"), PCONFIG(1), min_value, max_value); + + // If custom tasksettings need to be loaded and displayed, this is the place to add that // after the form has been loaded, set success and break success = true; @@ -269,7 +281,9 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) { // this case defines the code to be executed when the form is submitted // the plugin settings should be saved to PCONFIG(x) - // ping configuration should be read from CONFIG_PIN1 and stored + // PCONFIG(0) = getFormItemInt(F("dsptype")); + // pin configuration will be read from CONFIG_PIN1 and stored + // If custom tasksettings need to be stored, then here is the place to add that // after the form has been saved successfuly, set success and break success = true; @@ -317,7 +331,7 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) } else { // do non-specific subcommand } - success = true; // set to true only if plugin has executed a command successfully + success = true; // set to true **only** if plugin has executed a command/subcommand successfully } break; @@ -325,7 +339,7 @@ boolean Plugin_xxx(uint8_t function, struct EventStruct *event, String& string) case PLUGIN_EXIT: { - // perform cleanup tasks here. For example, free memory + // perform cleanup tasks here. For example, free memory, shut down/clear a display break; } diff --git a/src/src/Commands/Blynk.cpp b/src/src/Commands/Blynk.cpp index 4187f0326..dda35d5a5 100644 --- a/src/src/Commands/Blynk.cpp +++ b/src/src/Commands/Blynk.cpp @@ -1,192 +1,192 @@ -#include "../Commands/Blynk.h" - -#ifdef USES_C012 - -#include "../Commands/Common.h" -#include "../DataStructs/ESPEasy_EventStruct.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/_CPlugin_Helper.h" - -#include "../../ESPEasy_fdwdecl.h" - - -controllerIndex_t firstEnabledBlynk_ControllerIndex() { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); - - if (validProtocolIndex(ProtocolIndex)) { - const cpluginID_t number = getCPluginID_from_ProtocolIndex(ProtocolIndex); - - if ((number == 12) && Settings.ControllerEnabled[i]) { - return i; - } - } - } - return INVALID_CONTROLLER_INDEX; -} - -const __FlashStringHelper * Command_Blynk_Get(struct EventStruct *event, const char *Line) -{ - controllerIndex_t first_enabled_blynk_controller = firstEnabledBlynk_ControllerIndex(); - - if (!validControllerIndex(first_enabled_blynk_controller)) { - return F("Controller not enabled"); - } else { - // FIXME TD-er: This one is not using parseString* function - String strLine = Line; - strLine = strLine.substring(9); - int index = strLine.indexOf(','); - - if (index > 0) - { - int index = strLine.lastIndexOf(','); - String blynkcommand = strLine.substring(index + 1); - float value = 0; - - if (Blynk_get(blynkcommand, first_enabled_blynk_controller, &value)) - { - UserVar.setFloat((event->Par1 - 1), event->Par2 - 1, value); - } - else { - return F("Error getting data"); - } - } - else - { - if (!Blynk_get(strLine, first_enabled_blynk_controller, nullptr)) - { - return F("Error getting data"); - } - } - } - return return_command_success_flashstr(); -} - -bool Blynk_get(const String& command, controllerIndex_t controllerIndex, float *data) -{ - bool MustCheckReply = false; - String hostname, pass; - unsigned int ClientTimeout = 0; - WiFiClient client; - - { - // Place ControllerSettings in its own scope, as it is quite big. - MakeControllerSettings(ControllerSettings); //-V522 - if (!AllocatedControllerSettings()) { - addLog(LOG_LEVEL_ERROR, F("Blynk : Cannot run GET, out of RAM")); - return false; - } - - LoadControllerSettings(controllerIndex, *ControllerSettings); - MustCheckReply = ControllerSettings->MustCheckReply; - hostname = ControllerSettings->getHost(); - pass = getControllerPass(controllerIndex, *ControllerSettings); - ClientTimeout = ControllerSettings->ClientTimeout; - - if (pass.isEmpty()) { - addLog(LOG_LEVEL_ERROR, F("Blynk : No password set")); - return false; - } - - if (!try_connect_host(/* CPLUGIN_ID_012 */ 12, client, *ControllerSettings)) { - return false; - } - } - - // We now create a URI for the request - { - // Place this stack allocated array in its own scope, as it is quite big. - char request[300] = { 0 }; - sprintf_P(request, - PSTR("GET /%s/%s HTTP/1.1\r\n Host: %s \r\n Connection: close\r\n\r\n"), - pass.c_str(), - command.c_str(), - hostname.c_str()); -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, request); -#endif - client.print(request); - } - bool success = !MustCheckReply; - - if (MustCheckReply || data) { - unsigned long timer = millis() + ClientTimeout; - - while (!client_available(client) && !timeOutReached(timer)) { - delay(1); - } - - #ifndef BUILD_NO_DEBUG - char log[80] = { 0 }; - #endif - timer = millis() + 1500; - - // Read all the lines of the reply from server and log them - while (client_available(client) && !success && !timeOutReached(timer)) { - String line; - safeReadStringUntil(client, line, '\n'); - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, line); - #endif - - // success ? - if (equals(line.substring(0, 15), F("HTTP/1.1 200 OK"))) { - #ifndef BUILD_NO_DEBUG - strcpy_P(log, PSTR("HTTP : Success")); - #endif - - if (!data) { success = true; } - } - #ifndef BUILD_NO_DEBUG - else if (equals(line.substring(0, 24), F("HTTP/1.1 400 Bad Request"))) { - strcpy_P(log, PSTR("HTTP : Unauthorized")); - } - else if (equals(line.substring(0, 25), F("HTTP/1.1 401 Unauthorized"))) { - strcpy_P(log, PSTR("HTTP : Unauthorized")); - } - addLog(LOG_LEVEL_DEBUG, log); - #endif - - // data only - if (data && line.startsWith("[")) - { - String strValue = line; - uint8_t pos = strValue.indexOf('"', 2); - strValue = strValue.substring(2, pos); - strValue.trim(); - *data = 0.0f; - validFloatFromString(strValue, *data); - success = true; - - char value_char[5] = { 0 }; - strValue.toCharArray(value_char, 5); - #ifndef BUILD_NO_DEBUG - sprintf_P(log, PSTR("Blynk get - %s => %s"), command.c_str(), value_char); - addLog(LOG_LEVEL_DEBUG, log); - #endif - } - delay(0); - } - } - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("HTTP : closing connection (012)")); - #endif - - client.flush(); - client.stop(); - - // important - backgroundtasks - free mem - unsigned long timer = millis() + 10; - - while (!timeOutReached(timer)) { - backgroundtasks(); - } - - return success; -} - -#endif // ifdef USES_C012 +#include "../Commands/Blynk.h" + +#ifdef USES_C012 + +#include "../Commands/Common.h" +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/_CPlugin_Helper.h" + +#include "../../ESPEasy_fdwdecl.h" + + +controllerIndex_t firstEnabledBlynk_ControllerIndex() { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(i); + + if (validProtocolIndex(ProtocolIndex)) { + const cpluginID_t number = getCPluginID_from_ProtocolIndex(ProtocolIndex); + + if ((number == 12) && Settings.ControllerEnabled[i]) { + return i; + } + } + } + return INVALID_CONTROLLER_INDEX; +} + +const __FlashStringHelper * Command_Blynk_Get(struct EventStruct *event, const char *Line) +{ + controllerIndex_t first_enabled_blynk_controller = firstEnabledBlynk_ControllerIndex(); + + if (!validControllerIndex(first_enabled_blynk_controller)) { + return F("Controller not enabled"); + } else { + // FIXME TD-er: This one is not using parseString* function + String strLine = Line; + strLine = strLine.substring(9); + int index = strLine.indexOf(','); + + if (index > 0) + { + int index = strLine.lastIndexOf(','); + String blynkcommand = strLine.substring(index + 1); + float value = 0; + + if (Blynk_get(blynkcommand, first_enabled_blynk_controller, &value)) + { + UserVar.setFloat((event->Par1 - 1), event->Par2 - 1, value); + } + else { + return F("Error getting data"); + } + } + else + { + if (!Blynk_get(strLine, first_enabled_blynk_controller, nullptr)) + { + return F("Error getting data"); + } + } + } + return return_command_success_flashstr(); +} + +bool Blynk_get(const String& command, controllerIndex_t controllerIndex, float *data) +{ + bool MustCheckReply = false; + String hostname, pass; + unsigned int ClientTimeout = 0; + WiFiClient client; + + { + // Place ControllerSettings in its own scope, as it is quite big. + MakeControllerSettings(ControllerSettings); //-V522 + if (!AllocatedControllerSettings()) { + addLog(LOG_LEVEL_ERROR, F("Blynk : Cannot run GET, out of RAM")); + return false; + } + + LoadControllerSettings(controllerIndex, *ControllerSettings); + MustCheckReply = ControllerSettings->MustCheckReply; + hostname = ControllerSettings->getHost(); + pass = getControllerPass(controllerIndex, *ControllerSettings); + ClientTimeout = ControllerSettings->ClientTimeout; + + if (pass.isEmpty()) { + addLog(LOG_LEVEL_ERROR, F("Blynk : No password set")); + return false; + } + + if (!try_connect_host(/* CPLUGIN_ID_012 */ 12, client, *ControllerSettings)) { + return false; + } + } + + // We now create a URI for the request + { + // Place this stack allocated array in its own scope, as it is quite big. + char request[300] = { 0 }; + sprintf_P(request, + PSTR("GET /%s/%s HTTP/1.1\r\n Host: %s \r\n Connection: close\r\n\r\n"), + pass.c_str(), + command.c_str(), + hostname.c_str()); +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, request); +#endif + client.print(request); + } + bool success = !MustCheckReply; + + if (MustCheckReply || data) { + unsigned long timer = millis() + ClientTimeout; + + while (!client_available(client) && !timeOutReached(timer)) { + delay(1); + } + + #ifndef BUILD_NO_DEBUG + char log[80] = { 0 }; + #endif + timer = millis() + 1500; + + // Read all the lines of the reply from server and log them + while (client_available(client) && !success && !timeOutReached(timer)) { + String line; + safeReadStringUntil(client, line, '\n'); + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, line); + #endif + + // success ? + if (equals(line.substring(0, 15), F("HTTP/1.1 200 OK"))) { + #ifndef BUILD_NO_DEBUG + strcpy_P(log, PSTR("HTTP : Success")); + #endif + + if (!data) { success = true; } + } + #ifndef BUILD_NO_DEBUG + else if (equals(line.substring(0, 24), F("HTTP/1.1 400 Bad Request"))) { + strcpy_P(log, PSTR("HTTP : Unauthorized")); + } + else if (equals(line.substring(0, 25), F("HTTP/1.1 401 Unauthorized"))) { + strcpy_P(log, PSTR("HTTP : Unauthorized")); + } + addLog(LOG_LEVEL_DEBUG, log); + #endif + + // data only + if (data && line.startsWith("[")) + { + String strValue = line; + uint8_t pos = strValue.indexOf('"', 2); + strValue = strValue.substring(2, pos); + strValue.trim(); + *data = 0.0f; + validFloatFromString(strValue, *data); + success = true; + + char value_char[5] = { 0 }; + strValue.toCharArray(value_char, 5); + #ifndef BUILD_NO_DEBUG + sprintf_P(log, PSTR("Blynk get - %s => %s"), command.c_str(), value_char); + addLog(LOG_LEVEL_DEBUG, log); + #endif + } + delay(0); + } + } + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("HTTP : closing connection (012)")); + #endif + + client.flush(); + client.stop(); + + // important - backgroundtasks - free mem + unsigned long timer = millis() + 10; + + while (!timeOutReached(timer)) { + backgroundtasks(); + } + + return success; +} + +#endif // ifdef USES_C012 diff --git a/src/src/Commands/Common.cpp b/src/src/Commands/Common.cpp index 64fa7bb64..0eb96ac9f 100644 --- a/src/src/Commands/Common.cpp +++ b/src/src/Commands/Common.cpp @@ -1,238 +1,237 @@ -#include "../Commands/Common.h" - -#include -#include - -#include "../../ESPEasy_common.h" - - -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/Serial.h" - -#include "../Helpers/Networking.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" - - -// 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_boolean_result_flashstr(bool success) -{ - return success ? return_command_success_flashstr() : return_command_failed_flashstr(); -} - - -String return_command_success() -{ - return return_command_success_flashstr(); -} - -String return_command_failed() -{ - return return_command_failed_flashstr(); -} - -const __FlashStringHelper * return_incorrect_nr_arguments() { return F("Too many arguments, try using quotes!"); } -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 String& result) -{ - serialPrintln(); - serialPrintln(result); - - if (event->Source == EventValueSource::Enum::VALUE_SOURCE_SERIAL) { - return return_command_success(); - } - return result; -} - - -const __FlashStringHelper * return_see_serial(struct EventStruct *event) -{ - return (event->Source == EventValueSource::Enum::VALUE_SOURCE_SERIAL) - ? return_command_success_flashstr() - : F("Output sent to serial"); -} - - -String Command_GetORSetIP(struct EventStruct *event, - const __FlashStringHelper * targetDescription, - const char *Line, - uint8_t *IP, - const IPAddress & dhcpIP, - int arg) -{ - bool hasArgument = false; - { - // Check if command is valid. Leave in separate scope to delete the TmpStr1 - String TmpStr1; - - if (GetArgv(Line, TmpStr1, arg + 1)) { - hasArgument = true; - - if (!str2ip(TmpStr1, IP)) { - return return_result(event, concat(F("Invalid parameter: "), TmpStr1)); - } - } - } - - if (!hasArgument) { - String result = targetDescription; - - if (useStaticIP()) { - result += formatIP(IP); - } else { - result += formatIP(dhcpIP); - result += F("(DHCP)"); - } - return return_result(event, result); - } - return return_command_success(); -} - -String Command_GetORSetString(struct EventStruct *event, - const __FlashStringHelper * targetDescription, - const char *Line, - char *target, - size_t len, - int arg - ) -{ - bool hasArgument = false; - { - // Check if command is valid. Leave in separate scope to delete the TmpStr1 - String TmpStr1; - - if (GetArgv(Line, TmpStr1, arg + 1)) { - hasArgument = true; - - if (TmpStr1.length() > len) { - String result = concat(targetDescription, F(" is too large. max size is ")); - result += len; - return return_result(event, result); - } - safe_strncpy(target, TmpStr1, len); - } - } - - if (hasArgument) { - String result = targetDescription; - result += target; - return return_result(event, result); - } - return return_command_success(); -} - -String Command_GetORSetBool(struct EventStruct *event, - const __FlashStringHelper * targetDescription, - const char *Line, - bool *value, - int arg) -{ - bool hasArgument = false; - { - // Check if command is valid. Leave in separate scope to delete the TmpStr1 - String TmpStr1; - - if (GetArgv(Line, TmpStr1, arg + 1)) { - hasArgument = true; - TmpStr1.toLowerCase(); - - int32_t tmp_int = 0; - if (validIntFromString(TmpStr1, tmp_int)) { - *value = tmp_int > 0; - } - else if (TmpStr1.isEmpty()) {} // Empty string not always handled nicely by strcmp_P - else if (strcmp_P(PSTR("on"), TmpStr1.c_str()) == 0) { *value = true; } - else if (strcmp_P(PSTR("true"), TmpStr1.c_str()) == 0) { *value = true; } - else if (strcmp_P(PSTR("off"), TmpStr1.c_str()) == 0) { *value = false; } - else if (strcmp_P(PSTR("false"), TmpStr1.c_str()) == 0) { *value = false; } - } - } - - if (hasArgument) { - return return_result(event, concat(targetDescription, boolToString(*value))); - } - return return_command_success(); -} - -#if FEATURE_ETHERNET -String Command_GetORSetETH(struct EventStruct *event, - const __FlashStringHelper * targetDescription, - const __FlashStringHelper * valueToString, - const char *Line, - uint8_t *value, - int arg) -{ - bool hasArgument = false; - { - // Check if command is valid. Leave in separate scope to delete the TmpStr1 - String TmpStr1; - - if (GetArgv(Line, TmpStr1, arg + 1)) { - hasArgument = true; - TmpStr1.toLowerCase(); - - int32_t tmp_int = 0; - if (validIntFromString(TmpStr1, tmp_int)) { - *value = static_cast(tmp_int); - } - - // FIXME TD-er: This should not be in a generic function, but rather pre-processed in the command itself - - - // WiFi/Eth mode - else if (equals(TmpStr1, F("wifi"))) { *value = 0; } - else if (equals(TmpStr1, F("ethernet"))) { *value = 1; } - - // ETH clockMode - else if (TmpStr1.startsWith(F("ext"))) { *value = 0; } - else if (TmpStr1.indexOf(F("gpio0")) != -1) { *value = 1; } - else if (TmpStr1.indexOf(F("gpio16")) != -1) { *value = 2; } - else if (TmpStr1.indexOf(F("gpio17")) != -1) { *value = 3; } - } - } - - String result = targetDescription; - if (hasArgument) { - result += *value; - } else { - result += valueToString; - } - return return_result(event, result); -} -#endif - -String Command_GetORSetInt8_t(struct EventStruct *event, - const __FlashStringHelper * targetDescription, - const char *Line, - int8_t *value, - int arg) -{ - bool hasArgument = false; - { - // Check if command is valid. Leave in separate scope to delete the TmpStr1 - String TmpStr1; - - if (GetArgv(Line, TmpStr1, arg + 1)) { - hasArgument = true; - TmpStr1.toLowerCase(); - - int32_t tmp_int = 0; - if (validIntFromString(TmpStr1, tmp_int)) { - *value = static_cast(tmp_int); - } - } - } - - if (hasArgument) { - String result = targetDescription; - result += *value; - return return_result(event, result); - } - return return_command_success(); -} +#include "../Commands/Common.h" + +#include +#include + +#include "../../ESPEasy_common.h" + + +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/Serial.h" + +#include "../Helpers/Networking.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" + + +// 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_boolean_result_flashstr(bool success) +{ + return success ? return_command_success_flashstr() : return_command_failed_flashstr(); +} + + +String return_command_success() +{ + return return_command_success_flashstr(); +} + +String return_command_failed() +{ + return return_command_failed_flashstr(); +} + +const __FlashStringHelper * return_incorrect_nr_arguments() { return F("Too many arguments, try using quotes!"); } +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 String& result) +{ + serialPrintln(); + serialPrintln(result); + + if (event->Source == EventValueSource::Enum::VALUE_SOURCE_SERIAL) { + return return_command_success(); + } + return result; +} + + +const __FlashStringHelper * return_see_serial(struct EventStruct *event) +{ + return (event->Source == EventValueSource::Enum::VALUE_SOURCE_SERIAL) + ? return_command_success_flashstr() + : F("Output sent to serial"); +} + + +String Command_GetORSetIP(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const char *Line, + uint8_t *IP, + const IPAddress & dhcpIP, + int arg) +{ + bool hasArgument = false; + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + hasArgument = true; + + if (!str2ip(TmpStr1, IP)) { + return return_result(event, concat(F("Invalid parameter: "), TmpStr1)); + } + } + } + + if (!hasArgument) { + String result = targetDescription; + + if (useStaticIP()) { + result += formatIP(IP); + } else { + result += formatIP(dhcpIP); + result += F("(DHCP)"); + } + return return_result(event, result); + } + return return_command_success(); +} + +String Command_GetORSetString(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const char *Line, + char *target, + size_t len, + int arg + ) +{ + bool hasArgument = false; + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + hasArgument = true; + + if (TmpStr1.length() > len) { + String result = concat(targetDescription, F(" is too large. max size is ")); + result += len; + return return_result(event, result); + } + safe_strncpy(target, TmpStr1, len); + } + } + + if (hasArgument) { + String result = targetDescription; + result += target; + return return_result(event, result); + } + return return_command_success(); +} + +String Command_GetORSetBool(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const char *Line, + bool *value, + int arg) +{ + bool hasArgument = false; + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + hasArgument = true; + TmpStr1.toLowerCase(); + + int32_t tmp_int = 0; + if (validIntFromString(TmpStr1, tmp_int)) { + *value = tmp_int > 0; + } + else if (equals(TmpStr1, F("on"))) { *value = true; } + else if (equals(TmpStr1, F("true"))) { *value = true; } + else if (equals(TmpStr1, F("off"))) { *value = false; } + else if (equals(TmpStr1, F("false"))) { *value = false; } + } + } + + if (hasArgument) { + return return_result(event, concat(targetDescription, boolToString(*value))); + } + return return_command_success(); +} + +#if FEATURE_ETHERNET +String Command_GetORSetETH(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const __FlashStringHelper * valueToString, + const char *Line, + uint8_t *value, + int arg) +{ + bool hasArgument = false; + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + hasArgument = true; + TmpStr1.toLowerCase(); + + int32_t tmp_int = 0; + if (validIntFromString(TmpStr1, tmp_int)) { + *value = static_cast(tmp_int); + } + + // FIXME TD-er: This should not be in a generic function, but rather pre-processed in the command itself + + + // WiFi/Eth mode + else if (equals(TmpStr1, F("wifi"))) { *value = 0; } + else if (equals(TmpStr1, F("ethernet"))) { *value = 1; } + + // ETH clockMode + else if (TmpStr1.startsWith(F("ext"))) { *value = 0; } + else if (TmpStr1.indexOf(F("gpio0")) != -1) { *value = 1; } + else if (TmpStr1.indexOf(F("gpio16")) != -1) { *value = 2; } + else if (TmpStr1.indexOf(F("gpio17")) != -1) { *value = 3; } + } + } + + String result = targetDescription; + if (hasArgument) { + result += *value; + } else { + result += valueToString; + } + return return_result(event, result); +} +#endif + +String Command_GetORSetInt8_t(struct EventStruct *event, + const __FlashStringHelper * targetDescription, + const char *Line, + int8_t *value, + int arg) +{ + bool hasArgument = false; + { + // Check if command is valid. Leave in separate scope to delete the TmpStr1 + String TmpStr1; + + if (GetArgv(Line, TmpStr1, arg + 1)) { + hasArgument = true; + TmpStr1.toLowerCase(); + + int32_t tmp_int = 0; + if (validIntFromString(TmpStr1, tmp_int)) { + *value = static_cast(tmp_int); + } + } + } + + if (hasArgument) { + String result = targetDescription; + result += *value; + return return_result(event, result); + } + return return_command_success(); +} diff --git a/src/src/Commands/Diagnostic.cpp b/src/src/Commands/Diagnostic.cpp index 8ead051dc..98caceacf 100644 --- a/src/src/Commands/Diagnostic.cpp +++ b/src/src/Commands/Diagnostic.cpp @@ -179,26 +179,17 @@ const __FlashStringHelper * Command_JSONPortStatus(struct EventStruct *event, co void createLogPortStatus(std::map::iterator it) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log += F("PortStatus detail: Port="); - log += getPortFromKey(it->first); - log += F(" State="); - log += it->second.getValue(); - log += F(" Output="); - log += it->second.output; - log += F(" Mode="); - log += it->second.mode; - log += F(" Task="); - log += it->second.task; - log += F(" Monitor="); - log += it->second.monitor; - log += F(" Command="); - log += it->second.command; - log += F(" Init="); - log += it->second.init; - log += F(" PreviousTask="); - log += it->second.previousTask; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat( + F("PortStatus detail: Port=%u State=%d Output=%d Mode=%u Task=%u Monitor=%u Command=%d Init=%d PreviousTask=%d"), + getPortFromKey(it->first), + it->second.getValue(), + it->second.output, + it->second.mode, + it->second.task, + it->second.monitor, + it->second.command, + it->second.init, + it->second.previousTask)); } } diff --git a/src/src/Commands/ExecuteCommand.cpp b/src/src/Commands/ExecuteCommand.cpp index 31487ab21..045d9e543 100644 --- a/src/src/Commands/ExecuteCommand.cpp +++ b/src/src/Commands/ExecuteCommand.cpp @@ -12,36 +12,152 @@ #include "../Helpers/StringConverter.h" #include "../Helpers/StringParser.h" +ExecuteCommandArgs::ExecuteCommandArgs(EventValueSource::Enum source, + const char *Line) : + _taskIndex(INVALID_TASK_INDEX), + _source(source), + _Line(Line), + _tryPlugin(false), + _tryInternal(false), + _tryRemoteConfig(false) {} + +ExecuteCommandArgs::ExecuteCommandArgs(EventValueSource::Enum source, + const String & Line) : + _taskIndex(INVALID_TASK_INDEX), + _source(source), + _Line(Line), + _tryPlugin(false), + _tryInternal(false), + _tryRemoteConfig(false) {} + +ExecuteCommandArgs::ExecuteCommandArgs(EventValueSource::Enum source, + String && Line) : + _taskIndex(INVALID_TASK_INDEX), + _source(source), + _Line(Line), + _tryPlugin(false), + _tryInternal(false), + _tryRemoteConfig(false) {} + + +ExecuteCommandArgs::ExecuteCommandArgs( + taskIndex_t taskIndex, + EventValueSource::Enum source, + const char *Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig) : + _taskIndex(taskIndex), + _source(source), + _Line(Line), + _tryPlugin(tryPlugin), + _tryInternal(tryInternal), + _tryRemoteConfig(tryRemoteConfig) {} + +ExecuteCommandArgs::ExecuteCommandArgs( + taskIndex_t taskIndex, + EventValueSource::Enum source, + const String & Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig) : + _taskIndex(taskIndex), + _source(source), + _Line(Line), + _tryPlugin(tryPlugin), + _tryInternal(tryInternal), + _tryRemoteConfig(tryRemoteConfig) {} + +ExecuteCommandArgs::ExecuteCommandArgs( + taskIndex_t taskIndex, + EventValueSource::Enum source, + String && Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig) : + _taskIndex(taskIndex), + _source(source), + _Line(std::move(Line)), + _tryPlugin(tryPlugin), + _tryInternal(tryInternal), + _tryRemoteConfig(tryRemoteConfig) {} + + +std::list ExecuteCommand_queue; + +bool processExecuteCommandQueue() +{ + bool res = false; + + if (!ExecuteCommand_queue.empty()) { + auto it = ExecuteCommand_queue.front(); + + res = ExecuteCommand(std::move(it), false); + ExecuteCommand_queue.pop_front(); + } + return res; +} + // Execute command which may be plugin or internal commands -bool ExecuteCommand_all(EventValueSource::Enum source, const char *Line) +bool ExecuteCommand_all(ExecuteCommandArgs&& args, + bool addToQueue) { - return ExecuteCommand(INVALID_TASK_INDEX, source, Line, true, true, false); + args._tryPlugin = true; + args._tryInternal = true; + args._tryRemoteConfig = false; + return ExecuteCommand(std::move(args), addToQueue); } -bool ExecuteCommand_all_config(EventValueSource::Enum source, const char *Line) +bool ExecuteCommand_all_config(ExecuteCommandArgs&& args, + bool addToQueue) { - return ExecuteCommand(INVALID_TASK_INDEX, source, Line, true, true, true); + args._tryPlugin = true; + args._tryInternal = true; + args._tryRemoteConfig = true; + return ExecuteCommand(std::move(args), addToQueue); } -bool ExecuteCommand_plugin_config(EventValueSource::Enum source, const char *Line) +bool ExecuteCommand_plugin_config(ExecuteCommandArgs&& args, + bool addToQueue) { - return ExecuteCommand(INVALID_TASK_INDEX, source, Line, true, false, true); + args._tryPlugin = true; + args._tryInternal = false; + args._tryRemoteConfig = true; + return ExecuteCommand(std::move(args), addToQueue); } - -bool ExecuteCommand_internal(EventValueSource::Enum source, const char *Line) +bool ExecuteCommand_internal(ExecuteCommandArgs&& args, + bool addToQueue) { - return ExecuteCommand(INVALID_TASK_INDEX, source, Line, false, true, false); + args._tryPlugin = false; + args._tryInternal = true; + args._tryRemoteConfig = false; + return ExecuteCommand(std::move(args), addToQueue); } - bool ExecuteCommand(taskIndex_t taskIndex, EventValueSource::Enum source, const char *Line, bool tryPlugin, bool tryInternal, - bool tryRemoteConfig) + bool tryRemoteConfig, + bool addToQueue) { + ExecuteCommandArgs args(taskIndex, + source, + Line, + tryPlugin, + tryInternal, + tryRemoteConfig); + return ExecuteCommand(std::move(args), addToQueue); +} + +bool ExecuteCommand(ExecuteCommandArgs&& args, bool addToQueue) +{ + if (addToQueue) { + ExecuteCommand_queue.emplace_back(std::move(args)); + return false; // What should be returned here? + } #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("ExecuteCommand")); #endif // ifndef BUILD_NO_RAM_TRACKER @@ -50,29 +166,36 @@ bool ExecuteCommand(taskIndex_t taskIndex, // We first try internal commands, which should not have a taskIndex set. struct EventStruct TempEvent; - if (!GetArgv(Line, cmd, 1)) { + if (!GetArgv(args._Line.c_str(), cmd, 1)) { SendStatus(&TempEvent, return_command_failed()); return false; } - if (tryInternal) { + if (args._tryInternal) { // Small optimization for events, which happen frequently // FIXME TD-er: Make quick check to see if a command is an internal command, so we don't need to try all if (cmd.equalsIgnoreCase(F("event"))) { - tryPlugin = false; - tryRemoteConfig = false; + args._tryPlugin = false; + args._tryRemoteConfig = false; } } - TempEvent.Source = source; + TempEvent.Source = args._source; - String action(Line); - action = parseTemplate(action); // parseTemplate before executing the command + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("Command: "), cmd)); + addLog(LOG_LEVEL_DEBUG, args._Line); // for debug purposes add the whole line. + } + #endif + + args._Line = parseTemplate(args._Line); // parseTemplate before executing the command // Split the arguments into Par1...5 of the event. // Do not split it in executeInternalCommand, since that one will be called from the scheduler with pre-set events. // FIXME TD-er: Why call this for all commands? The CalculateParam function is quite heavy. - parseCommandString(&TempEvent, action); + parseCommandString(&TempEvent, args._Line); // FIXME TD-er: This part seems a bit strange. // It can't schedule a call to PLUGIN_WRITE. @@ -82,8 +205,6 @@ bool ExecuteCommand(taskIndex_t taskIndex, #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, concat(F("Command: "), cmd)); - addLog(LOG_LEVEL_DEBUG, Line); // for debug purposes add the whole line. addLogMove(LOG_LEVEL_DEBUG, strformat( F("Par1: %d Par2: %d Par3: %d Par4: %d Par5: %d"), TempEvent.Par1, @@ -95,8 +216,8 @@ bool ExecuteCommand(taskIndex_t taskIndex, #endif // ifndef BUILD_NO_DEBUG - if (tryInternal) { - InternalCommands internalCommands(cmd.c_str(), &TempEvent, action.c_str()); + if (args._tryInternal) { + InternalCommands internalCommands(cmd.c_str(), &TempEvent, args._Line.c_str()); bool handled = internalCommands.executeInternalCommand(); const command_case_data& data = internalCommands.getData(); @@ -115,23 +236,23 @@ bool ExecuteCommand(taskIndex_t taskIndex, // When trying a task command, set the task index, even if it is not a valid task index. // For example commands from elsewhere may not have a proper task index. - TempEvent.setTaskIndex(taskIndex); + TempEvent.setTaskIndex(args._taskIndex); checkDeviceVTypeForTask(&TempEvent); - if (tryPlugin) { + if (args._tryPlugin) { // Use a tmp string to call PLUGIN_WRITE, since PluginCall may inadvertenly // alter the string. - String tmpAction(action); + String tmpAction(args._Line); bool handled = PluginCall(PLUGIN_WRITE, &TempEvent, tmpAction); // if (handled) addLog(LOG_LEVEL_INFO, F("PLUGIN_WRITE accepted")); #ifndef BUILD_NO_DEBUG - if (!tmpAction.equals(action)) { + if (!tmpAction.equals(args._Line)) { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { String log = F("PLUGIN_WRITE altered the string: "); - log += action; + log += args._Line; log += F(" to: "); log += tmpAction; addLogMove(LOG_LEVEL_ERROR, log); @@ -152,8 +273,8 @@ bool ExecuteCommand(taskIndex_t taskIndex, } } - if (tryRemoteConfig) { - if (remoteConfig(&TempEvent, action)) { + if (args._tryRemoteConfig) { + if (remoteConfig(&TempEvent, args._Line)) { SendStatus(&TempEvent, return_command_success()); // addLog(LOG_LEVEL_INFO, F("remoteConfig accepted")); @@ -161,9 +282,9 @@ bool ExecuteCommand(taskIndex_t taskIndex, return true; } } - const String errorUnknown = concat(F("Command unknown: "), action); - addLog(LOG_LEVEL_INFO, errorUnknown); + String errorUnknown = concat(F("Command unknown: "), args._Line); SendStatus(&TempEvent, errorUnknown); + addLogMove(LOG_LEVEL_INFO, errorUnknown); delay(0); return false; } diff --git a/src/src/Commands/ExecuteCommand.h b/src/src/Commands/ExecuteCommand.h index 1a3f7a29f..509eb4817 100644 --- a/src/src/Commands/ExecuteCommand.h +++ b/src/src/Commands/ExecuteCommand.h @@ -6,20 +6,65 @@ #include "../DataTypes/EventValueSource.h" #include "../DataTypes/TaskIndex.h" +#include + +struct ExecuteCommandArgs { + ExecuteCommandArgs(EventValueSource::Enum source, + const char *Line); + + ExecuteCommandArgs(EventValueSource::Enum source, + const String & Line); + + ExecuteCommandArgs(EventValueSource::Enum source, + String && Line); + + + ExecuteCommandArgs(taskIndex_t taskIndex, + EventValueSource::Enum source, + const char *Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig); + + ExecuteCommandArgs(taskIndex_t taskIndex, + EventValueSource::Enum source, + const String & Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig); + + ExecuteCommandArgs(taskIndex_t taskIndex, + EventValueSource::Enum source, + String && Line, + bool tryPlugin, + bool tryInternal, + bool tryRemoteConfig); + + taskIndex_t _taskIndex = INVALID_TASK_INDEX; + EventValueSource::Enum _source = EventValueSource::Enum::VALUE_SOURCE_NOT_SET; + String _Line; + bool _tryPlugin = false; + bool _tryInternal = false; + bool _tryRemoteConfig = false; +}; + +extern std::list ExecuteCommand_queue; + +bool processExecuteCommandQueue(); // Execute command which may be plugin or internal commands -bool ExecuteCommand_all(EventValueSource::Enum source, - const char *Line); +bool ExecuteCommand_all(ExecuteCommandArgs&& args, + bool addToQueue = false); -bool ExecuteCommand_all_config(EventValueSource::Enum source, - const char *Line); +bool ExecuteCommand_all_config(ExecuteCommandArgs&& args, + bool addToQueue = false); -bool ExecuteCommand_plugin_config(EventValueSource::Enum source, - const char *Line); +bool ExecuteCommand_plugin_config(ExecuteCommandArgs&& args, + bool addToQueue = false); -bool ExecuteCommand_internal(EventValueSource::Enum source, - const char *Line); +bool ExecuteCommand_internal(ExecuteCommandArgs&& args, + bool addToQueue = false); bool ExecuteCommand(taskIndex_t taskIndex, @@ -27,7 +72,10 @@ bool ExecuteCommand(taskIndex_t taskIndex, const char *Line, bool tryPlugin, bool tryInternal, - bool tryRemoteConfig); + bool tryRemoteConfig, + bool addToQueue = false); + +bool ExecuteCommand(ExecuteCommandArgs&& args, bool addToQueue); #endif // ifndef COMMANDS_EXECUTECOMMAND_H diff --git a/src/src/Commands/InternalCommands.cpp b/src/src/Commands/InternalCommands.cpp index 2c56a5bda..5a41a7499 100644 --- a/src/src/Commands/InternalCommands.cpp +++ b/src/src/Commands/InternalCommands.cpp @@ -1,478 +1,488 @@ -#include "../Commands/InternalCommands.h" - -#include "../../ESPEasy_common.h" - -#include "../../_Plugin_Helper.h" -#include "../Globals/Settings.h" - -#if FEATURE_BLYNK -# include "../Commands/Blynk.h" -# include "../Commands/Blynk_c015.h" -#endif // if FEATURE_BLYNK - -#include "../Commands/Common.h" -#include "../Commands/Controller.h" -#include "../Commands/Diagnostic.h" -#include "../Commands/GPIO.h" -#include "../Commands/HTTP.h" -#include "../Commands/InternalCommands_decoder.h" -#include "../Commands/i2c.h" - -#if FEATURE_MQTT -# include "../Commands/MQTT.h" -#endif // if FEATURE_MQTT - -#include "../Commands/Networks.h" -#if FEATURE_NOTIFIER -# include "../Commands/Notifications.h" -#endif // if FEATURE_NOTIFIER -#include "../Commands/Provisioning.h" -#include "../Commands/RTC.h" -#include "../Commands/Rules.h" -#include "../Commands/SDCARD.h" -#include "../Commands/Settings.h" -#if FEATURE_SERVO -# include "../Commands/Servo.h" -#endif // if FEATURE_SERVO -#include "../Commands/System.h" -#include "../Commands/Tasks.h" -#include "../Commands/Time.h" -#include "../Commands/Timer.h" -#include "../Commands/UPD.h" -#include "../Commands/wd.h" -#include "../Commands/WiFi.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - - -bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { - if (nrArguments < 0) { return true; } - - // 0 arguments means argument on pos1 is valid (the command) and argpos 2 should not be there. - if (HasArgv(Line.c_str(), nrArguments + 2)) { - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - - if (log.reserve(128)) { - log += F("Too many arguments: cmd="); - log += cmd; - - if (nrArguments < 1) { - log += Line; - } else { - // Check for one more argument than allowed, since we apparently have one. - bool done = false; - int i = 1; - - while (!done) { - String parameter; - - if (i == nrArguments) { - parameter = tolerantParseStringKeepCase(Line, i + 1); - } else { - parameter = parseStringKeepCase(Line, i + 1); - } - done = parameter.isEmpty(); - - if (!done) { - if (i <= nrArguments) { - if (Settings.TolerantLastArgParse() && (i == nrArguments)) { - log += F(" (fixed)"); - } - log += F(" Arg"); - } else { - log += F(" ExtraArg"); - } - log += i; - log += '='; - log += parameter; - } - ++i; - } - } - log += F(" lineLength="); - log += Line.length(); - addLogMove(LOG_LEVEL_ERROR, log); - } - addLogMove(LOG_LEVEL_ERROR, strformat(F("Line: _%s_"), Line.c_str())); - - addLogMove(LOG_LEVEL_ERROR, concat(Settings.TolerantLastArgParse() ? - F("Command executed, but may fail.") : F("Command not executed!"), - F(" See: https://github.com/letscontrolit/ESPEasy/issues/2724"))); - } - #endif // ifndef BUILD_NO_DEBUG - - if (Settings.TolerantLastArgParse()) { - return true; - } - return false; - } - return true; -} - -bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { - if (EventValueSource::partOfGroup(source, group)) { - return true; - } - addLog(LOG_LEVEL_ERROR, return_incorrect_source()); - return false; -} - -command_case_data::command_case_data(const char *cmd, struct EventStruct *event, const char *line) : - cmd(cmd), event(event), line(line) -{ - cmd_lc = cmd; - cmd_lc.toLowerCase(); -} - -InternalCommands::InternalCommands(const char *cmd, struct EventStruct *event, const char *line) - : _data(cmd, event, line) {} - - -// Wrapper to reduce generated code by macro -bool InternalCommands::do_command_case_all(command_function_fs pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); -} - -bool InternalCommands::do_command_case_all(command_function pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); -} - -// Wrapper to reduce generated code by macro -bool InternalCommands::do_command_case_all_restricted(command_function_fs pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); -} - -bool InternalCommands::do_command_case_all_restricted(command_function pFunc, - int nrArguments) -{ - return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); -} - -bool do_command_case_check(command_case_data & data, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - // The data struct is re-used on each attempt to process an internal command. - // Re-initialize the only two members that may have been altered by a previous call. - data.retval = false; - data.status = String(); - - if (!checkSourceFlags(data.event->Source, group)) { - data.status = return_incorrect_source(); - return false; - } - - // 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 - // e.g. ESP_Easy/Bathroom_pir_env/GPIO/14 with data 0 or 1 - // This only allows for 2 parameters, but some commands need more arguments (default to "0") - const bool mustCheckNrArguments = data.event->Source != EventValueSource::Enum::VALUE_SOURCE_MQTT; - - if (mustCheckNrArguments) { - if (!checkNrArguments(data.cmd, data.line, nrArguments)) { - data.status = return_incorrect_nr_arguments(); - - // data.retval = false; - return true; // Command is handled - } - } - data.retval = true; // Mark the command should be executed. - return true; // Command is handled -} - -bool InternalCommands::do_command_case(command_case_data & data, - command_function_fs pFunc, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - if (do_command_case_check(data, nrArguments, group)) { - // It has been handled, check if we need to execute it. - // FIXME TD-er: Must change command function signature to use const String& - START_TIMER; - data.status = pFunc(data.event, data.line.c_str()); - STOP_TIMER(COMMAND_EXEC_INTERNAL); - return true; - } - return false; -} - -bool InternalCommands::do_command_case(command_case_data & data, - command_function pFunc, - int nrArguments, - EventValueSourceGroup::Enum group) -{ - if (do_command_case_check(data, nrArguments, group)) { - // It has been handled, check if we need to execute it. - // FIXME TD-er: Must change command function signature to use const String& - START_TIMER; - data.status = pFunc(data.event, data.line.c_str()); - STOP_TIMER(COMMAND_EXEC_INTERNAL); - return true; - } - return false; -} - -bool InternalCommands::executeInternalCommand() -{ - // Simple macro to match command to function call. - - // EventValueSourceGroup::Enum::ALL - #define COMMAND_CASE_A(C, NARGS) \ - do_command_case_all(&C, NARGS); break; - - // EventValueSourceGroup::Enum::RESTRICTED - #define COMMAND_CASE_R(C, NARGS) \ - do_command_case_all_restricted(&C, NARGS); break; - - - const ESPEasy_cmd_e cmd = match_ESPEasy_internal_command(_data.cmd_lc); - - _data.retval = false; - - if (cmd == ESPEasy_cmd_e::NotMatched) { - return false; - } - - // FIXME TD-er: Should we execute command when number of arguments is wrong? - - // FIXME TD-er: must determine nr arguments where NARGS is set to -1 - switch (cmd) { - case ESPEasy_cmd_e::accessinfo: COMMAND_CASE_A(Command_AccessInfo_Ls, 0); // Network Command - case ESPEasy_cmd_e::asyncevent: COMMAND_CASE_A(Command_Rules_Async_Events, -1); // Rule.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::background: COMMAND_CASE_R(Command_Background, 1); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - case ESPEasy_cmd_e::blynkget: COMMAND_CASE_A(Command_Blynk_Get, -1); -#endif // ifdef USES_C012 -#ifdef USES_C015 - case ESPEasy_cmd_e::blynkset: COMMAND_CASE_R(Command_Blynk_Set, -1); -#endif // ifdef USES_C015 - case ESPEasy_cmd_e::build: COMMAND_CASE_A(Command_Settings_Build, 1); // Settings.h - case ESPEasy_cmd_e::clearaccessblock: COMMAND_CASE_R(Command_AccessInfo_Clear, 0); // Network Command - case ESPEasy_cmd_e::clearpassword: COMMAND_CASE_R(Command_Settings_Password_Clear, 1); // Settings.h - case ESPEasy_cmd_e::clearrtcram: COMMAND_CASE_R(Command_RTC_Clear, 0); // RTC.h -#ifdef ESP8266 - case ESPEasy_cmd_e::clearsdkwifi: COMMAND_CASE_R(Command_System_Erase_SDK_WiFiconfig, 0); // System.h - case ESPEasy_cmd_e::clearwifirfcal: COMMAND_CASE_R(Command_System_Erase_RFcal, 0); // System.h -#endif // ifdef ESP8266 - case ESPEasy_cmd_e::config: COMMAND_CASE_R(Command_Task_RemoteConfig, -1); // Tasks.h - 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 - case ESPEasy_cmd_e::debug: COMMAND_CASE_R(Command_Debug, 1); // Diagnostic.h - 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 -#if FEATURE_PLUGIN_PRIORITY - case ESPEasy_cmd_e::disableprioritytask: COMMAND_CASE_R(Command_PriorityTask_Disable, 1); // Tasks.h -#endif // if FEATURE_PLUGIN_PRIORITY - 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 - case ESPEasy_cmd_e::ethdns: COMMAND_CASE_R(Command_ETH_DNS, 1); // Network Command - 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 - case ESPEasy_cmd_e::erasesdkwifi: COMMAND_CASE_R(Command_WiFi_Erase, 0); // WiFi.h - 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::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 - case ESPEasy_cmd_e::hiddenssid: COMMAND_CASE_R(Command_Wifi_HiddenSSID, 1); // wifi.h - 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 -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h - 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 - 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::longpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 5); // GPIO.h - case ESPEasy_cmd_e::longpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 5); // GPIO.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::logportstatus: COMMAND_CASE_A(Command_logPortStatus, 0); // Diagnostic.h - case ESPEasy_cmd_e::lowmem: COMMAND_CASE_A(Command_Lowmem, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_P009 - case ESPEasy_cmd_e::mcpgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h - case ESPEasy_cmd_e::mcpgpiorange: COMMAND_CASE_A(Command_GPIO_McpGPIORange, -1); // Gpio.h - case ESPEasy_cmd_e::mcpgpiopattern: COMMAND_CASE_A(Command_GPIO_McpGPIOPattern, -1); // Gpio.h - case ESPEasy_cmd_e::mcpgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h - case ESPEasy_cmd_e::mcplongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h - case ESPEasy_cmd_e::mcplongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h - case ESPEasy_cmd_e::mcpmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h - case ESPEasy_cmd_e::mcpmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h - case ESPEasy_cmd_e::mcppulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#endif // ifdef USES_P009 - case ESPEasy_cmd_e::monitor: COMMAND_CASE_A(Command_GPIO_Monitor, 2); // GPIO.h - case ESPEasy_cmd_e::monitorrange: COMMAND_CASE_A(Command_GPIO_MonitorRange, 3); // GPIO.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::malloc: COMMAND_CASE_A(Command_Malloc, 1); // Diagnostic.h - 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::nosleep: COMMAND_CASE_R(Command_System_NoSleep, 1); // System.h -#if FEATURE_NOTIFIER - case ESPEasy_cmd_e::notify: COMMAND_CASE_R(Command_Notifications_Notify, 2); // Notifications.h -#endif // if FEATURE_NOTIFIER - case ESPEasy_cmd_e::ntphost: COMMAND_CASE_R(Command_NTPHost, 1); // Time.h -#ifdef USES_P019 - case ESPEasy_cmd_e::pcfgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h - case ESPEasy_cmd_e::pcfgpiorange: COMMAND_CASE_A(Command_GPIO_PcfGPIORange, -1); // Gpio.h - case ESPEasy_cmd_e::pcfgpiopattern: COMMAND_CASE_A(Command_GPIO_PcfGPIOPattern, -1); // Gpio.h - case ESPEasy_cmd_e::pcfgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h - case ESPEasy_cmd_e::pcflongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h - case ESPEasy_cmd_e::pcflongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h - case ESPEasy_cmd_e::pcfmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h - case ESPEasy_cmd_e::pcfmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h ************ - case ESPEasy_cmd_e::pcfpulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#endif // ifdef USES_P019 - 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 -#endif // if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - case ESPEasy_cmd_e::provision: COMMAND_CASE_A(Command_Provisioning_Dispatcher, -1); // Provisioning.h -# ifdef PLUGIN_BUILD_MAX_ESP32 - - // FIXME DEPRECATED: Fallback for temporary backward compatibility - case ESPEasy_cmd_e::provisionconfig: COMMAND_CASE_A(Command_Provisioning_ConfigFallback, 0); // Provisioning.h - case ESPEasy_cmd_e::provisionsecurity: COMMAND_CASE_A(Command_Provisioning_SecurityFallback, 0); // Provisioning.h -# if FEATURE_NOTIFIER - case ESPEasy_cmd_e::provisionnotification: COMMAND_CASE_A(Command_Provisioning_NotificationFallback, 0); // Provisioning.h -# endif // if FEATURE_NOTIFIER - case ESPEasy_cmd_e::provisionprovision: COMMAND_CASE_A(Command_Provisioning_ProvisionFallback, 0); // Provisioning.h - case ESPEasy_cmd_e::provisionrules: COMMAND_CASE_A(Command_Provisioning_RulesFallback, 1); // Provisioning.h - case ESPEasy_cmd_e::provisionfirmware: COMMAND_CASE_A(Command_Provisioning_FirmwareFallback, 1); // Provisioning.h -# endif // ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // if FEATURE_CUSTOM_PROVISIONING - case ESPEasy_cmd_e::pulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h -#if FEATURE_MQTT - case ESPEasy_cmd_e::publish: COMMAND_CASE_A(Command_MQTT_Publish, -1); // MQTT.h -#endif // if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - case ESPEasy_cmd_e::puttohttp: COMMAND_CASE_A(Command_HTTP_PutToHTTP, -1); // HTTP.h -#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 - case ESPEasy_cmd_e::reset: COMMAND_CASE_R(Command_Settings_Reset, 0); // Settings.h - case ESPEasy_cmd_e::resetflashwritecounter: COMMAND_CASE_A(Command_RTC_resetFlashWriteCounter, 0); // RTC.h - case ESPEasy_cmd_e::restart: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h - case ESPEasy_cmd_e::rtttl: COMMAND_CASE_A(Command_GPIO_RTTTL, -1); // GPIO.h - case ESPEasy_cmd_e::rules: COMMAND_CASE_A(Command_Rules_UseRules, 1); // Rule.h - case ESPEasy_cmd_e::save: COMMAND_CASE_R(Command_Settings_Save, 0); // Settings.h - case ESPEasy_cmd_e::scheduletaskrun: COMMAND_CASE_A(Command_ScheduleTask_Run, 2); // Tasks.h - -#if FEATURE_SD - case ESPEasy_cmd_e::sdcard: COMMAND_CASE_R(Command_SD_LS, 0); // SDCARDS.h - case ESPEasy_cmd_e::sdremove: COMMAND_CASE_R(Command_SD_Remove, 1); // SDCARDS.h -#endif // if FEATURE_SD - -#if FEATURE_ESPEASY_P2P - - // FIXME TD-er: These send commands, can we determine the nr of arguments? - case ESPEasy_cmd_e::sendto: COMMAND_CASE_A(Command_UPD_SendTo, 2); // UDP.h -#endif // if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - case ESPEasy_cmd_e::sendtohttp: COMMAND_CASE_A(Command_HTTP_SendToHTTP, 3); // HTTP.h -#endif // FEATURE_SEND_TO_HTTP - case ESPEasy_cmd_e::sendtoudp: COMMAND_CASE_A(Command_UDP_SendToUPD, 3); // UDP.h -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::serialfloat: COMMAND_CASE_R(Command_SerialFloat, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::settings: COMMAND_CASE_R(Command_Settings_Print, 0); // Settings.h -#if FEATURE_SERVO - case ESPEasy_cmd_e::servo: COMMAND_CASE_A(Command_Servo, 3); // Servo.h -#endif // if FEATURE_SERVO - - case ESPEasy_cmd_e::status: COMMAND_CASE_A(Command_GPIO_Status, 2); // GPIO.h - case ESPEasy_cmd_e::subnet: COMMAND_CASE_R(Command_Subnet, 1); // Network Command -#if FEATURE_MQTT - case ESPEasy_cmd_e::subscribe: COMMAND_CASE_A(Command_MQTT_Subscribe, 1); // MQTT.h -#endif // if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::sysload: COMMAND_CASE_A(Command_SysLoad, 0); // Diagnostic.h -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - case ESPEasy_cmd_e::taskclear: COMMAND_CASE_R(Command_Task_Clear, 1); // Tasks.h - case ESPEasy_cmd_e::taskclearall: COMMAND_CASE_R(Command_Task_ClearAll, 0); // Tasks.h - case ESPEasy_cmd_e::taskdisable: COMMAND_CASE_R(Command_Task_Disable, 1); // Tasks.h - case ESPEasy_cmd_e::taskenable: COMMAND_CASE_R(Command_Task_Enable, 1); // Tasks.h - case ESPEasy_cmd_e::taskrun: COMMAND_CASE_A(Command_Task_Run, 1); // Tasks.h - case ESPEasy_cmd_e::taskrunat: COMMAND_CASE_A(Command_Task_Run, 2); // Tasks.h - case ESPEasy_cmd_e::taskvalueset: COMMAND_CASE_A(Command_Task_ValueSet, 3); // Tasks.h - case ESPEasy_cmd_e::taskvaluetoggle: COMMAND_CASE_A(Command_Task_ValueToggle, 2); // Tasks.h - case ESPEasy_cmd_e::taskvaluesetandrun: COMMAND_CASE_A(Command_Task_ValueSetAndRun, 3); // Tasks.h - case ESPEasy_cmd_e::timerpause: COMMAND_CASE_A(Command_Timer_Pause, 1); // Timers.h - case ESPEasy_cmd_e::timerresume: COMMAND_CASE_A(Command_Timer_Resume, 1); // Timers.h - case ESPEasy_cmd_e::timerset: COMMAND_CASE_A(Command_Timer_Set, 2); // Timers.h - case ESPEasy_cmd_e::timerset_ms: COMMAND_CASE_A(Command_Timer_Set_ms, 2); // Timers.h - case ESPEasy_cmd_e::timezone: COMMAND_CASE_R(Command_TimeZone, 1); // Time.h - case ESPEasy_cmd_e::tone: COMMAND_CASE_A(Command_GPIO_Tone, 3); // GPIO.h - case ESPEasy_cmd_e::udpport: COMMAND_CASE_R(Command_UDP_Port, 1); // UDP.h -#if FEATURE_ESPEASY_P2P - case ESPEasy_cmd_e::udptest: COMMAND_CASE_R(Command_UDP_Test, 2); // UDP.h -#endif // if FEATURE_ESPEASY_P2P - case ESPEasy_cmd_e::unit: COMMAND_CASE_R(Command_Settings_Unit, 1); // Settings.h - case ESPEasy_cmd_e::unmonitor: COMMAND_CASE_A(Command_GPIO_UnMonitor, 2); // GPIO.h - case ESPEasy_cmd_e::unmonitorrange: COMMAND_CASE_A(Command_GPIO_UnMonitorRange, 3); // GPIO.h - case ESPEasy_cmd_e::usentp: COMMAND_CASE_R(Command_useNTP, 1); // Time.h -#ifndef LIMIT_BUILD_SIZE - 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 - - 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 - case ESPEasy_cmd_e::wifidisconnect: COMMAND_CASE_A(Command_Wifi_Disconnect, 0); // WiFi.h - 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 - 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 - - - case ESPEasy_cmd_e::NotMatched: - return false; - - // Do not add default: here - // The compiler will then warn when a command is not included - } - - #undef COMMAND_CASE_R - #undef COMMAND_CASE_A - return _data.retval; -} +#include "../Commands/InternalCommands.h" + +#include "../../ESPEasy_common.h" + +#include "../../_Plugin_Helper.h" +#include "../Globals/Settings.h" + +#if FEATURE_BLYNK +# include "../Commands/Blynk.h" +# include "../Commands/Blynk_c015.h" +#endif // if FEATURE_BLYNK + +#include "../Commands/Common.h" +#include "../Commands/Controller.h" +#include "../Commands/Diagnostic.h" +#include "../Commands/GPIO.h" +#include "../Commands/HTTP.h" +#include "../Commands/InternalCommands_decoder.h" +#include "../Commands/i2c.h" + +#if FEATURE_MQTT +# include "../Commands/MQTT.h" +#endif // if FEATURE_MQTT + +#include "../Commands/Networks.h" +#if FEATURE_NOTIFIER +# include "../Commands/Notifications.h" +#endif // if FEATURE_NOTIFIER +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#include "../Commands/OneWire.h" +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#include "../Commands/Provisioning.h" +#include "../Commands/RTC.h" +#include "../Commands/Rules.h" +#include "../Commands/SDCARD.h" +#include "../Commands/Settings.h" +#if FEATURE_SERVO +# include "../Commands/Servo.h" +#endif // if FEATURE_SERVO +#include "../Commands/System.h" +#include "../Commands/Tasks.h" +#include "../Commands/Time.h" +#include "../Commands/Timer.h" +#include "../Commands/UPD.h" +#include "../Commands/wd.h" +#include "../Commands/WiFi.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" + +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + + +bool checkNrArguments(const char *cmd, const String& Line, int nrArguments) { + if (nrArguments < 0) { return true; } + + // 0 arguments means argument on pos1 is valid (the command) and argpos 2 should not be there. + if (HasArgv(Line.c_str(), nrArguments + 2)) { + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log; + + if (log.reserve(128)) { + log += F("Too many arguments: cmd="); + log += cmd; + + if (nrArguments < 1) { + log += Line; + } else { + // Check for one more argument than allowed, since we apparently have one. + bool done = false; + int i = 1; + + while (!done) { + String parameter; + + if (i == nrArguments) { + parameter = tolerantParseStringKeepCase(Line, i + 1); + } else { + parameter = parseStringKeepCase(Line, i + 1); + } + done = parameter.isEmpty(); + + if (!done) { + if (i <= nrArguments) { + if (Settings.TolerantLastArgParse() && (i == nrArguments)) { + log += F(" (fixed)"); + } + log += F(" Arg"); + } else { + log += F(" ExtraArg"); + } + log += i; + log += '='; + log += parameter; + } + ++i; + } + } + log += F(" lineLength="); + log += Line.length(); + addLogMove(LOG_LEVEL_ERROR, log); + } + addLogMove(LOG_LEVEL_ERROR, strformat(F("Line: _%s_"), Line.c_str())); + + addLogMove(LOG_LEVEL_ERROR, concat(Settings.TolerantLastArgParse() ? + F("Command executed, but may fail.") : F("Command not executed!"), + F(" See: https://github.com/letscontrolit/ESPEasy/issues/2724"))); + } + #endif // ifndef BUILD_NO_DEBUG + + if (Settings.TolerantLastArgParse()) { + return true; + } + return false; + } + return true; +} + +bool checkSourceFlags(EventValueSource::Enum source, EventValueSourceGroup::Enum group) { + if (EventValueSource::partOfGroup(source, group)) { + return true; + } + addLog(LOG_LEVEL_ERROR, return_incorrect_source()); + return false; +} + +command_case_data::command_case_data(const char *cmd, struct EventStruct *event, const char *line) : + cmd(cmd), event(event), line(line) +{ + cmd_lc = cmd; + cmd_lc.toLowerCase(); +} + +InternalCommands::InternalCommands(const char *cmd, struct EventStruct *event, const char *line) + : _data(cmd, event, line) {} + + +// Wrapper to reduce generated code by macro +bool InternalCommands::do_command_case_all(command_function_fs pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); +} + +bool InternalCommands::do_command_case_all(command_function pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::ALL); +} + +// Wrapper to reduce generated code by macro +bool InternalCommands::do_command_case_all_restricted(command_function_fs pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); +} + +bool InternalCommands::do_command_case_all_restricted(command_function pFunc, + int nrArguments) +{ + return do_command_case(_data, pFunc, nrArguments, EventValueSourceGroup::Enum::RESTRICTED); +} + +bool do_command_case_check(command_case_data & data, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + // The data struct is re-used on each attempt to process an internal command. + // Re-initialize the only two members that may have been altered by a previous call. + data.retval = false; + data.status = String(); + + if (!checkSourceFlags(data.event->Source, group)) { + data.status = return_incorrect_source(); + return false; + } + + // 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 + // e.g. ESP_Easy/Bathroom_pir_env/GPIO/14 with data 0 or 1 + // This only allows for 2 parameters, but some commands need more arguments (default to "0") + const bool mustCheckNrArguments = data.event->Source != EventValueSource::Enum::VALUE_SOURCE_MQTT; + + if (mustCheckNrArguments) { + if (!checkNrArguments(data.cmd, data.line, nrArguments)) { + data.status = return_incorrect_nr_arguments(); + + // data.retval = false; + return true; // Command is handled + } + } + data.retval = true; // Mark the command should be executed. + return true; // Command is handled +} + +bool InternalCommands::do_command_case(command_case_data & data, + command_function_fs pFunc, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + if (do_command_case_check(data, nrArguments, group)) { + // It has been handled, check if we need to execute it. + // FIXME TD-er: Must change command function signature to use const String& + START_TIMER; + data.status = pFunc(data.event, data.line.c_str()); + STOP_TIMER(COMMAND_EXEC_INTERNAL); + return true; + } + return false; +} + +bool InternalCommands::do_command_case(command_case_data & data, + command_function pFunc, + int nrArguments, + EventValueSourceGroup::Enum group) +{ + if (do_command_case_check(data, nrArguments, group)) { + // It has been handled, check if we need to execute it. + // FIXME TD-er: Must change command function signature to use const String& + START_TIMER; + data.status = pFunc(data.event, data.line.c_str()); + STOP_TIMER(COMMAND_EXEC_INTERNAL); + return true; + } + return false; +} + +bool InternalCommands::executeInternalCommand() +{ + // Simple macro to match command to function call. + + // EventValueSourceGroup::Enum::ALL + #define COMMAND_CASE_A(C, NARGS) \ + do_command_case_all(&C, NARGS); break; + + // EventValueSourceGroup::Enum::RESTRICTED + #define COMMAND_CASE_R(C, NARGS) \ + do_command_case_all_restricted(&C, NARGS); break; + + + const ESPEasy_cmd_e cmd = match_ESPEasy_internal_command(_data.cmd_lc); + + _data.retval = false; + + if (cmd == ESPEasy_cmd_e::NotMatched) { + return false; + } + + // FIXME TD-er: Should we execute command when number of arguments is wrong? + + // FIXME TD-er: must determine nr arguments where NARGS is set to -1 + switch (cmd) { + case ESPEasy_cmd_e::accessinfo: COMMAND_CASE_A(Command_AccessInfo_Ls, 0); // Network Command + case ESPEasy_cmd_e::asyncevent: COMMAND_CASE_A(Command_Rules_Async_Events, -1); // Rule.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::background: COMMAND_CASE_R(Command_Background, 1); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + case ESPEasy_cmd_e::blynkget: COMMAND_CASE_A(Command_Blynk_Get, -1); +#endif // ifdef USES_C012 +#ifdef USES_C015 + case ESPEasy_cmd_e::blynkset: COMMAND_CASE_R(Command_Blynk_Set, -1); +#endif // ifdef USES_C015 + case ESPEasy_cmd_e::build: COMMAND_CASE_A(Command_Settings_Build, 1); // Settings.h + case ESPEasy_cmd_e::clearaccessblock: COMMAND_CASE_R(Command_AccessInfo_Clear, 0); // Network Command + case ESPEasy_cmd_e::clearpassword: COMMAND_CASE_R(Command_Settings_Password_Clear, 1); // Settings.h + case ESPEasy_cmd_e::clearrtcram: COMMAND_CASE_R(Command_RTC_Clear, 0); // RTC.h +#ifdef ESP8266 + case ESPEasy_cmd_e::clearsdkwifi: COMMAND_CASE_R(Command_System_Erase_SDK_WiFiconfig, 0); // System.h + case ESPEasy_cmd_e::clearwifirfcal: COMMAND_CASE_R(Command_System_Erase_RFcal, 0); // System.h +#endif // ifdef ESP8266 + case ESPEasy_cmd_e::config: COMMAND_CASE_R(Command_Task_RemoteConfig, -1); // Tasks.h + 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 + case ESPEasy_cmd_e::debug: COMMAND_CASE_R(Command_Debug, 1); // Diagnostic.h + 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 +#if FEATURE_PLUGIN_PRIORITY + case ESPEasy_cmd_e::disableprioritytask: COMMAND_CASE_R(Command_PriorityTask_Disable, 1); // Tasks.h +#endif // if FEATURE_PLUGIN_PRIORITY + 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 + case ESPEasy_cmd_e::ethdns: COMMAND_CASE_R(Command_ETH_DNS, 1); // Network Command + 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 + case ESPEasy_cmd_e::erasesdkwifi: COMMAND_CASE_R(Command_WiFi_Erase, 0); // WiFi.h + 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::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 + case ESPEasy_cmd_e::hiddenssid: COMMAND_CASE_R(Command_Wifi_HiddenSSID, 1); // wifi.h + 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 +#if FEATURE_USE_IPV6 + case ESPEasy_cmd_e::ip6: COMMAND_CASE_A(Command_show_all_IP6, 0); // Network Command +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::jsonportstatus: COMMAND_CASE_A(Command_JSONPortStatus, -1); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::let: COMMAND_CASE_A(Command_Rules_Let, 2); // Rules.h + 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 + 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::longpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 5); // GPIO.h + case ESPEasy_cmd_e::longpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 5); // GPIO.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::logportstatus: COMMAND_CASE_A(Command_logPortStatus, 0); // Diagnostic.h + case ESPEasy_cmd_e::lowmem: COMMAND_CASE_A(Command_Lowmem, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_P009 + case ESPEasy_cmd_e::mcpgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h + case ESPEasy_cmd_e::mcpgpiorange: COMMAND_CASE_A(Command_GPIO_McpGPIORange, -1); // Gpio.h + case ESPEasy_cmd_e::mcpgpiopattern: COMMAND_CASE_A(Command_GPIO_McpGPIOPattern, -1); // Gpio.h + case ESPEasy_cmd_e::mcpgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h + case ESPEasy_cmd_e::mcplongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h + case ESPEasy_cmd_e::mcplongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h + case ESPEasy_cmd_e::mcpmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h + case ESPEasy_cmd_e::mcpmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h + case ESPEasy_cmd_e::mcppulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#endif // ifdef USES_P009 + case ESPEasy_cmd_e::monitor: COMMAND_CASE_A(Command_GPIO_Monitor, 2); // GPIO.h + case ESPEasy_cmd_e::monitorrange: COMMAND_CASE_A(Command_GPIO_MonitorRange, 3); // GPIO.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::malloc: COMMAND_CASE_A(Command_Malloc, 1); // Diagnostic.h + 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::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 +#endif // if FEATURE_NOTIFIER + case ESPEasy_cmd_e::ntphost: COMMAND_CASE_R(Command_NTPHost, 1); // Time.h +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + case ESPEasy_cmd_e::owscan: COMMAND_CASE_R(Command_OneWire_Owscan, -1); // OneWire.h +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +#ifdef USES_P019 + case ESPEasy_cmd_e::pcfgpio: COMMAND_CASE_A(Command_GPIO, 2); // Gpio.h + case ESPEasy_cmd_e::pcfgpiorange: COMMAND_CASE_A(Command_GPIO_PcfGPIORange, -1); // Gpio.h + case ESPEasy_cmd_e::pcfgpiopattern: COMMAND_CASE_A(Command_GPIO_PcfGPIOPattern, -1); // Gpio.h + case ESPEasy_cmd_e::pcfgpiotoggle: COMMAND_CASE_A(Command_GPIO_Toggle, 1); // Gpio.h + case ESPEasy_cmd_e::pcflongpulse: COMMAND_CASE_A(Command_GPIO_LongPulse, 3); // GPIO.h + case ESPEasy_cmd_e::pcflongpulse_ms: COMMAND_CASE_A(Command_GPIO_LongPulse_Ms, 3); // GPIO.h + case ESPEasy_cmd_e::pcfmode: COMMAND_CASE_A(Command_GPIO_Mode, 2); // Gpio.h + case ESPEasy_cmd_e::pcfmoderange: COMMAND_CASE_A(Command_GPIO_ModeRange, 3); // Gpio.h ************ + case ESPEasy_cmd_e::pcfpulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#endif // ifdef USES_P019 + 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 +#endif // if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + case ESPEasy_cmd_e::provision: COMMAND_CASE_A(Command_Provisioning_Dispatcher, -1); // Provisioning.h +# ifdef PLUGIN_BUILD_MAX_ESP32 + + // FIXME DEPRECATED: Fallback for temporary backward compatibility + case ESPEasy_cmd_e::provisionconfig: COMMAND_CASE_A(Command_Provisioning_ConfigFallback, 0); // Provisioning.h + case ESPEasy_cmd_e::provisionsecurity: COMMAND_CASE_A(Command_Provisioning_SecurityFallback, 0); // Provisioning.h +# if FEATURE_NOTIFIER + case ESPEasy_cmd_e::provisionnotification: COMMAND_CASE_A(Command_Provisioning_NotificationFallback, 0); // Provisioning.h +# endif // if FEATURE_NOTIFIER + case ESPEasy_cmd_e::provisionprovision: COMMAND_CASE_A(Command_Provisioning_ProvisionFallback, 0); // Provisioning.h + case ESPEasy_cmd_e::provisionrules: COMMAND_CASE_A(Command_Provisioning_RulesFallback, 1); // Provisioning.h + case ESPEasy_cmd_e::provisionfirmware: COMMAND_CASE_A(Command_Provisioning_FirmwareFallback, 1); // Provisioning.h +# endif // ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // if FEATURE_CUSTOM_PROVISIONING + case ESPEasy_cmd_e::pulse: COMMAND_CASE_A(Command_GPIO_Pulse, 3); // GPIO.h +#if FEATURE_MQTT + case ESPEasy_cmd_e::publish: COMMAND_CASE_A(Command_MQTT_Publish, -1); // MQTT.h + case ESPEasy_cmd_e::publishr: COMMAND_CASE_A(Command_MQTT_PublishR, -1); // MQTT.h +#endif // if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + case ESPEasy_cmd_e::puttohttp: COMMAND_CASE_A(Command_HTTP_PutToHTTP, -1); // HTTP.h +#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 + case ESPEasy_cmd_e::reset: COMMAND_CASE_R(Command_Settings_Reset, 0); // Settings.h + case ESPEasy_cmd_e::resetflashwritecounter: COMMAND_CASE_A(Command_RTC_resetFlashWriteCounter, 0); // RTC.h + case ESPEasy_cmd_e::restart: COMMAND_CASE_A(Command_System_Reboot, 0); // System.h + case ESPEasy_cmd_e::rtttl: COMMAND_CASE_A(Command_GPIO_RTTTL, -1); // GPIO.h + case ESPEasy_cmd_e::rules: COMMAND_CASE_A(Command_Rules_UseRules, 1); // Rule.h + case ESPEasy_cmd_e::save: COMMAND_CASE_R(Command_Settings_Save, 0); // Settings.h + case ESPEasy_cmd_e::scheduletaskrun: COMMAND_CASE_A(Command_ScheduleTask_Run, 2); // Tasks.h + +#if FEATURE_SD + case ESPEasy_cmd_e::sdcard: COMMAND_CASE_R(Command_SD_LS, 0); // SDCARDS.h + case ESPEasy_cmd_e::sdremove: COMMAND_CASE_R(Command_SD_Remove, 1); // SDCARDS.h +#endif // if FEATURE_SD + +#if FEATURE_ESPEASY_P2P + + // FIXME TD-er: These send commands, can we determine the nr of arguments? + case ESPEasy_cmd_e::sendto: COMMAND_CASE_A(Command_UPD_SendTo, 2); // UDP.h +#endif // if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + case ESPEasy_cmd_e::sendtohttp: COMMAND_CASE_A(Command_HTTP_SendToHTTP, 3); // HTTP.h +#endif // FEATURE_SEND_TO_HTTP + case ESPEasy_cmd_e::sendtoudp: COMMAND_CASE_A(Command_UDP_SendToUPD, 3); // UDP.h +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::serialfloat: COMMAND_CASE_R(Command_SerialFloat, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::settings: COMMAND_CASE_R(Command_Settings_Print, 0); // Settings.h +#if FEATURE_SERVO + case ESPEasy_cmd_e::servo: COMMAND_CASE_A(Command_Servo, 3); // Servo.h +#endif // if FEATURE_SERVO + + case ESPEasy_cmd_e::status: COMMAND_CASE_A(Command_GPIO_Status, 2); // GPIO.h + case ESPEasy_cmd_e::subnet: COMMAND_CASE_R(Command_Subnet, 1); // Network Command +#if FEATURE_MQTT + case ESPEasy_cmd_e::subscribe: COMMAND_CASE_A(Command_MQTT_Subscribe, 1); // MQTT.h +#endif // if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::sysload: COMMAND_CASE_A(Command_SysLoad, 0); // Diagnostic.h +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + case ESPEasy_cmd_e::taskclear: COMMAND_CASE_R(Command_Task_Clear, 1); // Tasks.h + case ESPEasy_cmd_e::taskclearall: COMMAND_CASE_R(Command_Task_ClearAll, 0); // Tasks.h + case ESPEasy_cmd_e::taskdisable: COMMAND_CASE_R(Command_Task_Disable, 1); // Tasks.h + case ESPEasy_cmd_e::taskenable: COMMAND_CASE_R(Command_Task_Enable, 1); // Tasks.h + case ESPEasy_cmd_e::taskrun: COMMAND_CASE_A(Command_Task_Run, 1); // Tasks.h + case ESPEasy_cmd_e::taskrunat: COMMAND_CASE_A(Command_Task_Run, 2); // Tasks.h + case ESPEasy_cmd_e::taskvalueset: COMMAND_CASE_A(Command_Task_ValueSet, 3); // Tasks.h + case ESPEasy_cmd_e::taskvaluetoggle: COMMAND_CASE_A(Command_Task_ValueToggle, 2); // Tasks.h + case ESPEasy_cmd_e::taskvaluesetandrun: COMMAND_CASE_A(Command_Task_ValueSetAndRun, 3); // Tasks.h + case ESPEasy_cmd_e::timerpause: COMMAND_CASE_A(Command_Timer_Pause, 1); // Timers.h + case ESPEasy_cmd_e::timerresume: COMMAND_CASE_A(Command_Timer_Resume, 1); // Timers.h + case ESPEasy_cmd_e::timerset: COMMAND_CASE_A(Command_Timer_Set, 2); // Timers.h + case ESPEasy_cmd_e::timerset_ms: COMMAND_CASE_A(Command_Timer_Set_ms, 2); // Timers.h + case ESPEasy_cmd_e::timezone: COMMAND_CASE_R(Command_TimeZone, 1); // Time.h + case ESPEasy_cmd_e::tone: COMMAND_CASE_A(Command_GPIO_Tone, 3); // GPIO.h + case ESPEasy_cmd_e::udpport: COMMAND_CASE_R(Command_UDP_Port, 1); // UDP.h +#if FEATURE_ESPEASY_P2P + case ESPEasy_cmd_e::udptest: COMMAND_CASE_R(Command_UDP_Test, 2); // UDP.h +#endif // if FEATURE_ESPEASY_P2P + case ESPEasy_cmd_e::unit: COMMAND_CASE_R(Command_Settings_Unit, 1); // Settings.h + case ESPEasy_cmd_e::unmonitor: COMMAND_CASE_A(Command_GPIO_UnMonitor, 2); // GPIO.h + case ESPEasy_cmd_e::unmonitorrange: COMMAND_CASE_A(Command_GPIO_UnMonitorRange, 3); // GPIO.h + case ESPEasy_cmd_e::usentp: COMMAND_CASE_R(Command_useNTP, 1); // Time.h +#ifndef LIMIT_BUILD_SIZE + 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 + + 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 + case ESPEasy_cmd_e::wifidisconnect: COMMAND_CASE_A(Command_Wifi_Disconnect, 0); // WiFi.h + 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 + 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 + + + case ESPEasy_cmd_e::NotMatched: + return false; + + // Do not add default: here + // The compiler will then warn when a command is not included + } + + #undef COMMAND_CASE_R + #undef COMMAND_CASE_A + return _data.retval; +} diff --git a/src/src/Commands/InternalCommands_decoder.cpp b/src/src/Commands/InternalCommands_decoder.cpp index daf935aaa..d78c58526 100644 --- a/src/src/Commands/InternalCommands_decoder.cpp +++ b/src/src/Commands/InternalCommands_decoder.cpp @@ -1,478 +1,486 @@ -#include "../Commands/InternalCommands_decoder.h" - -#include "../DataStructs/TimingStats.h" -#include "../Helpers/StringConverter.h" - -// Keep the order of elements in ESPEasy_cmd_e enum -// the same as in the PROGMEM strings below -// -// The first item in the PROGMEM strings below should be an enum -// which is always included in each build as it is used to set an offset -// to compute the final enum value. -// -// Keep the offset used in match_ESPEasy_internal_command in sync -// when adding new commands - - -const char Internal_commands_ab[] PROGMEM = - "accessinfo|" - "asyncevent|" - "build|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "background|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - "blynkget|" -#endif // #ifdef USES_C012 -#ifdef USES_C015 - "blynkset|" -#endif // #ifdef USES_C015 -; - -#define Int_cmd_c_offset ESPEasy_cmd_e::clearaccessblock -const char Internal_commands_c[] PROGMEM = - "clearaccessblock|" - "clearpassword|" - "clearrtcram|" -#ifdef ESP8266 - "clearsdkwifi|" - "clearwifirfcal|" -#endif // #ifdef ESP8266 - "config|" - "controllerdisable|" - "controllerenable|" -; - -#define Int_cmd_d_offset ESPEasy_cmd_e::datetime -const char Internal_commands_d[] PROGMEM = - "datetime|" - "debug|" - "dec|" - "deepsleep|" - "delay|" -#if FEATURE_PLUGIN_PRIORITY - "disableprioritytask|" -#endif // #if FEATURE_PLUGIN_PRIORITY - "dns|" - "dst|" -; - -#define Int_cmd_e_offset ESPEasy_cmd_e::erasesdkwifi -const char Internal_commands_e[] PROGMEM = - "erasesdkwifi|" - "event|" - "executerules|" -#if FEATURE_ETHERNET - "ethphyadr|" - "ethpinmdc|" - "ethpinmdio|" - "ethpinpower|" - "ethphytype|" - "ethclockmode|" - "ethip|" - "ethgateway|" - "ethsubnet|" - "ethdns|" - "ethdisconnect|" - "ethwifimode|" -#endif // FEATURE_ETHERNET -; - -#define Int_cmd_ghij_offset ESPEasy_cmd_e::gateway -const char Internal_commands_ghij[] PROGMEM = - "gateway|" - "gpio|" - "gpiotoggle|" - "hiddenssid|" - "i2cscanner|" - "inc|" - "ip|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "jsonportstatus|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_l_offset ESPEasy_cmd_e::let -const char Internal_commands_l[] PROGMEM = - "let|" - "load|" - "logentry|" - "looptimerset|" - "looptimerset_ms|" - "longpulse|" - "longpulse_ms|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "logportstatus|" - "lowmem|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_m_offset ESPEasy_cmd_e::monitor -const char Internal_commands_m[] PROGMEM = - "monitor|" - "monitorrange|" -#ifdef USES_P009 - "mcpgpio|" - "mcpgpiorange|" - "mcpgpiopattern|" - "mcpgpiotoggle|" - "mcplongpulse|" - "mcplongpulse_ms|" - "mcpmode|" - "mcpmoderange|" - "mcppulse|" -#endif // #ifdef USES_P009 -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "malloc|" - "meminfo|" - "meminfodetail|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_n_offset ESPEasy_cmd_e::name -const char Internal_commands_n[] PROGMEM = - "name|" - "nosleep|" -#if FEATURE_NOTIFIER - "notify|" -#endif // #if FEATURE_NOTIFIER - "ntphost|" -; - -#define Int_cmd_p_offset ESPEasy_cmd_e::password -const char Internal_commands_p[] PROGMEM = - "password|" -#ifdef USES_P019 - "pcfgpio|" - "pcfgpiorange|" - "pcfgpiopattern|" - "pcfgpiotoggle|" - "pcflongpulse|" - "pcflongpulse_ms|" - "pcfmode|" - "pcfmoderange|" - "pcfpulse|" -#endif // #ifdef USES_P019 -#if FEATURE_POST_TO_HTTP - "posttohttp|" -#endif // #if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - "provision|" - # ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility - "provisionconfig|" - "provisionsecurity|" - # if FEATURE_NOTIFIER - "provisionnotification|" - # endif // #if FEATURE_NOTIFIER - "provisionprovision|" - "provisionrules|" - "provisionfirmware|" - # endif // #ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // #if FEATURE_CUSTOM_PROVISIONING - "pulse|" -#if FEATURE_MQTT - "publish|" -#endif // #if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - "puttohttp|" -#endif // #if FEATURE_PUT_TO_HTTP - "pwm|" -; - -#define Int_cmd_r_offset ESPEasy_cmd_e::reboot -const char Internal_commands_r[] PROGMEM = - "reboot|" - "reset|" - "resetflashwritecounter|" - "restart|" - "rtttl|" - "rules|" -; - -#define Int_cmd_s_offset ESPEasy_cmd_e::save -const char Internal_commands_s[] PROGMEM = - "save|" - "scheduletaskrun|" -#if FEATURE_SD - "sdcard|" - "sdremove|" -#endif // #if FEATURE_SD -#if FEATURE_ESPEASY_P2P - "sendto|" -#endif // #if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - "sendtohttp|" -#endif // FEATURE_SEND_TO_HTTP - "sendtoudp|" -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "serialfloat|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "settings|" -#if FEATURE_SERVO - "servo|" -#endif // #if FEATURE_SERVO - "status|" - "subnet|" -#if FEATURE_MQTT - "subscribe|" -#endif // #if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - "sysload|" -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -; - -#define Int_cmd_t_offset ESPEasy_cmd_e::taskclear -const char Internal_commands_t[] PROGMEM = - "taskclear|" - "taskclearall|" - "taskdisable|" - "taskenable|" - "taskrun|" - "taskrunat|" - "taskvalueset|" - "taskvaluetoggle|" - "taskvaluesetandrun|" - "timerpause|" - "timerresume|" - "timerset|" - "timerset_ms|" - "timezone|" - "tone|" -; - -#define Int_cmd_u_offset ESPEasy_cmd_e::udpport -const char Internal_commands_u[] PROGMEM = - "udpport|" -#if FEATURE_ESPEASY_P2P - "udptest|" -#endif // #if FEATURE_ESPEASY_P2P - "unit|" - "unmonitor|" - "unmonitorrange|" - "usentp|" -; - -#define Int_cmd_w_offset ESPEasy_cmd_e::wifiallowap -const char Internal_commands_w[] PROGMEM = - "wifiallowap|" - "wifiapmode|" - "wificonnect|" - "wifidisconnect|" - "wifikey|" - "wifikey2|" - "wifimode|" - "wifiscan|" - "wifissid|" - "wifissid2|" - "wifistamode|" -#ifndef LIMIT_BUILD_SIZE - "wdconfig|" - "wdread|" -#endif // ifndef LIMIT_BUILD_SIZE -; - -const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) -{ - const char *haystack = nullptr; - - offset = static_cast(ESPEasy_cmd_e::NotMatched); - - // Keep the offset in sync when adding new commands - switch (firstLetter) - { - case 'a': - case 'b': - offset = 0; - haystack = Internal_commands_ab; - break; - case 'c': - offset = static_cast(Int_cmd_c_offset); - haystack = Internal_commands_c; - break; - case 'd': - offset = static_cast(Int_cmd_d_offset); - haystack = Internal_commands_d; - break; - case 'e': - offset = static_cast(Int_cmd_e_offset); - haystack = Internal_commands_e; - break; - case 'g': - case 'h': - case 'i': - case 'j': - offset = static_cast(Int_cmd_ghij_offset); - haystack = Internal_commands_ghij; - break; - case 'l': - offset = static_cast(Int_cmd_l_offset); - haystack = Internal_commands_l; - break; - case 'm': - offset = static_cast(Int_cmd_m_offset); - haystack = Internal_commands_m; - break; - case 'n': - offset = static_cast(Int_cmd_n_offset); - haystack = Internal_commands_n; - break; - case 'p': - offset = static_cast(Int_cmd_p_offset); - haystack = Internal_commands_p; - break; - case 'r': - offset = static_cast(Int_cmd_r_offset); - haystack = Internal_commands_r; - break; - case 's': - offset = static_cast(Int_cmd_s_offset); - haystack = Internal_commands_s; - break; - case 't': - offset = static_cast(Int_cmd_t_offset); - haystack = Internal_commands_t; - break; - case 'u': - offset = static_cast(Int_cmd_u_offset); - haystack = Internal_commands_u; - break; - case 'w': - offset = static_cast(Int_cmd_w_offset); - haystack = Internal_commands_w; - break; - - default: - return nullptr; - } - return haystack; -} - -ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd) -{ - START_TIMER; - ESPEasy_cmd_e res = ESPEasy_cmd_e::NotMatched; - - if (cmd.length() < 2) { - // No commands less than 2 characters - return res; - } - - int offset = 0; - const char *haystack = getInternalCommand_Haystack_Offset(cmd[0], offset); - - if (haystack == nullptr) { -/* - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: No Haystack/offset '%s', offset: %d"), - cmd.c_str(), - offset)); -*/ - return res; - } - - - if (haystack != nullptr) { - const int command_i = GetCommandCode(cmd.c_str(), haystack); - - if (command_i != -1) { - res = static_cast(command_i + offset); - } -/* - else { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: Not found '%s', haystack: %s"), - cmd.c_str(), - String(haystack).c_str())); - } -*/ - } - STOP_TIMER(COMMAND_DECODE_INTERNAL); - return res; -} - -#ifndef BUILD_NO_DEBUG -bool toString(ESPEasy_cmd_e cmd, String& str) -{ - if (cmd == ESPEasy_cmd_e::NotMatched) { - return false; - } - char c = 'z'; - bool found = false; - int offset; - const char *haystack = nullptr; - - while (!found && c >= 'a') { - haystack = getInternalCommand_Haystack_Offset(c, offset); - - if (haystack != nullptr) { - if (offset <= static_cast(cmd)) { - found = true; - } - } - --c; - } - - if (found) { - const int index = static_cast(cmd) - offset; -/* - addLog(LOG_LEVEL_INFO, strformat( - F("Internal command: cmd=%d offset=%d index=%d"), - static_cast(cmd), - offset, - index)); -*/ - - if ((index >= 0) && (haystack != nullptr)) { - // Likely long enough to parse any command - char temp[32]{}; - str = GetTextIndexed(temp, sizeof(temp), index, haystack); - return !str.isEmpty(); - } - } - return false; -} - -bool checkAll_internalCommands() -{ - constexpr int last = static_cast(ESPEasy_cmd_e::NotMatched); - bool no_error = true; - - for (int i = 0; i < last; ++i) { - const ESPEasy_cmd_e cmd = static_cast(i); - String cmd_str; - - if (!toString(cmd, cmd_str)) { - no_error = false; -// addLog(LOG_LEVEL_ERROR, concat(F("Internal command: no matching string for "), i)); - } else { - const ESPEasy_cmd_e cmd_found = match_ESPEasy_internal_command(cmd_str); - - if (cmd_found != cmd) { - if (cmd_str.isEmpty()) { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: mismatch (%d)"), i)); - } - else { - addLog(LOG_LEVEL_ERROR, strformat( - F("Internal command: mismatch '%s' (%d)"), - cmd_str.c_str(), - i)); - } - no_error = false; - } - } - } - - if (no_error) { - addLog(LOG_LEVEL_INFO, F("Internal command: All checked OK")); - } -/* - else { - const int index = static_cast(match_ESPEasy_internal_command(F("build"))); - addLog(LOG_LEVEL_ERROR, concat(F("Internal command: index 'build'="), index)); - } -*/ - return no_error; -} - -#endif // ifndef BUILD_NO_DEBUG +#include "../Commands/InternalCommands_decoder.h" + +#include "../DataStructs/TimingStats.h" +#include "../Helpers/StringConverter.h" + +// Keep the order of elements in ESPEasy_cmd_e enum +// the same as in the PROGMEM strings below +// +// The first item in the PROGMEM strings below should be an enum +// which is always included in each build as it is used to set an offset +// to compute the final enum value. +// +// Keep the offset used in match_ESPEasy_internal_command in sync +// when adding new commands + + +const char Internal_commands_ab[] PROGMEM = + "accessinfo|" + "asyncevent|" + "build|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "background|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + "blynkget|" +#endif // #ifdef USES_C012 +#ifdef USES_C015 + "blynkset|" +#endif // #ifdef USES_C015 +; + +#define Int_cmd_c_offset ESPEasy_cmd_e::clearaccessblock +const char Internal_commands_c[] PROGMEM = + "clearaccessblock|" + "clearpassword|" + "clearrtcram|" +#ifdef ESP8266 + "clearsdkwifi|" + "clearwifirfcal|" +#endif // #ifdef ESP8266 + "config|" + "controllerdisable|" + "controllerenable|" +; + +#define Int_cmd_d_offset ESPEasy_cmd_e::datetime +const char Internal_commands_d[] PROGMEM = + "datetime|" + "debug|" + "dec|" + "deepsleep|" + "delay|" +#if FEATURE_PLUGIN_PRIORITY + "disableprioritytask|" +#endif // #if FEATURE_PLUGIN_PRIORITY + "dns|" + "dst|" +; + +#define Int_cmd_e_offset ESPEasy_cmd_e::erasesdkwifi +const char Internal_commands_e[] PROGMEM = + "erasesdkwifi|" + "event|" + "executerules|" +#if FEATURE_ETHERNET + "ethphyadr|" + "ethpinmdc|" + "ethpinmdio|" + "ethpinpower|" + "ethphytype|" + "ethclockmode|" + "ethip|" + "ethgateway|" + "ethsubnet|" + "ethdns|" + "ethdisconnect|" + "ethwifimode|" +#endif // FEATURE_ETHERNET +; + +#define Int_cmd_ghij_offset ESPEasy_cmd_e::gateway +const char Internal_commands_ghij[] PROGMEM = + "gateway|" + "gpio|" + "gpiotoggle|" + "hiddenssid|" + "i2cscanner|" + "inc|" + "ip|" +#if FEATURE_USE_IPV6 + "ip6|" +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "jsonportstatus|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_l_offset ESPEasy_cmd_e::let +const char Internal_commands_l[] PROGMEM = + "let|" + "load|" + "logentry|" + "looptimerset|" + "looptimerset_ms|" + "longpulse|" + "longpulse_ms|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "logportstatus|" + "lowmem|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_m_offset ESPEasy_cmd_e::monitor +const char Internal_commands_m[] PROGMEM = + "monitor|" + "monitorrange|" +#ifdef USES_P009 + "mcpgpio|" + "mcpgpiorange|" + "mcpgpiopattern|" + "mcpgpiotoggle|" + "mcplongpulse|" + "mcplongpulse_ms|" + "mcpmode|" + "mcpmoderange|" + "mcppulse|" +#endif // #ifdef USES_P009 +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "malloc|" + "meminfo|" + "meminfodetail|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_no_offset ESPEasy_cmd_e::name +const char Internal_commands_no[] PROGMEM = + "name|" + "nosleep|" +#if FEATURE_NOTIFIER + "notify|" +#endif // #if FEATURE_NOTIFIER + "ntphost|" +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + "owscan|" +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +; + +#define Int_cmd_p_offset ESPEasy_cmd_e::password +const char Internal_commands_p[] PROGMEM = + "password|" +#ifdef USES_P019 + "pcfgpio|" + "pcfgpiorange|" + "pcfgpiopattern|" + "pcfgpiotoggle|" + "pcflongpulse|" + "pcflongpulse_ms|" + "pcfmode|" + "pcfmoderange|" + "pcfpulse|" +#endif // #ifdef USES_P019 +#if FEATURE_POST_TO_HTTP + "posttohttp|" +#endif // #if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + "provision|" + # ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility + "provisionconfig|" + "provisionsecurity|" + # if FEATURE_NOTIFIER + "provisionnotification|" + # endif // #if FEATURE_NOTIFIER + "provisionprovision|" + "provisionrules|" + "provisionfirmware|" + # endif // #ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // #if FEATURE_CUSTOM_PROVISIONING + "pulse|" +#if FEATURE_MQTT + "publish|" + "publishr|" +#endif // #if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + "puttohttp|" +#endif // #if FEATURE_PUT_TO_HTTP + "pwm|" +; + +#define Int_cmd_r_offset ESPEasy_cmd_e::reboot +const char Internal_commands_r[] PROGMEM = + "reboot|" + "reset|" + "resetflashwritecounter|" + "restart|" + "rtttl|" + "rules|" +; + +#define Int_cmd_s_offset ESPEasy_cmd_e::save +const char Internal_commands_s[] PROGMEM = + "save|" + "scheduletaskrun|" +#if FEATURE_SD + "sdcard|" + "sdremove|" +#endif // #if FEATURE_SD +#if FEATURE_ESPEASY_P2P + "sendto|" +#endif // #if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + "sendtohttp|" +#endif // FEATURE_SEND_TO_HTTP + "sendtoudp|" +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "serialfloat|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "settings|" +#if FEATURE_SERVO + "servo|" +#endif // #if FEATURE_SERVO + "status|" + "subnet|" +#if FEATURE_MQTT + "subscribe|" +#endif // #if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + "sysload|" +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +; + +#define Int_cmd_t_offset ESPEasy_cmd_e::taskclear +const char Internal_commands_t[] PROGMEM = + "taskclear|" + "taskclearall|" + "taskdisable|" + "taskenable|" + "taskrun|" + "taskrunat|" + "taskvalueset|" + "taskvaluetoggle|" + "taskvaluesetandrun|" + "timerpause|" + "timerresume|" + "timerset|" + "timerset_ms|" + "timezone|" + "tone|" +; + +#define Int_cmd_u_offset ESPEasy_cmd_e::udpport +const char Internal_commands_u[] PROGMEM = + "udpport|" +#if FEATURE_ESPEASY_P2P + "udptest|" +#endif // #if FEATURE_ESPEASY_P2P + "unit|" + "unmonitor|" + "unmonitorrange|" + "usentp|" +; + +#define Int_cmd_w_offset ESPEasy_cmd_e::wifiallowap +const char Internal_commands_w[] PROGMEM = + "wifiallowap|" + "wifiapmode|" + "wificonnect|" + "wifidisconnect|" + "wifikey|" + "wifikey2|" + "wifimode|" + "wifiscan|" + "wifissid|" + "wifissid2|" + "wifistamode|" +#ifndef LIMIT_BUILD_SIZE + "wdconfig|" + "wdread|" +#endif // ifndef LIMIT_BUILD_SIZE +; + +const char* getInternalCommand_Haystack_Offset(const char firstLetter, int& offset) +{ + const char *haystack = nullptr; + + offset = static_cast(ESPEasy_cmd_e::NotMatched); + + // Keep the offset in sync when adding new commands + switch (firstLetter) + { + case 'a': + case 'b': + offset = 0; + haystack = Internal_commands_ab; + break; + case 'c': + offset = static_cast(Int_cmd_c_offset); + haystack = Internal_commands_c; + break; + case 'd': + offset = static_cast(Int_cmd_d_offset); + haystack = Internal_commands_d; + break; + case 'e': + offset = static_cast(Int_cmd_e_offset); + haystack = Internal_commands_e; + break; + case 'g': + case 'h': + case 'i': + case 'j': + offset = static_cast(Int_cmd_ghij_offset); + haystack = Internal_commands_ghij; + break; + case 'l': + offset = static_cast(Int_cmd_l_offset); + haystack = Internal_commands_l; + break; + case 'm': + offset = static_cast(Int_cmd_m_offset); + haystack = Internal_commands_m; + break; + case 'n': + case 'o': + offset = static_cast(Int_cmd_no_offset); + haystack = Internal_commands_no; + break; + case 'p': + offset = static_cast(Int_cmd_p_offset); + haystack = Internal_commands_p; + break; + case 'r': + offset = static_cast(Int_cmd_r_offset); + haystack = Internal_commands_r; + break; + case 's': + offset = static_cast(Int_cmd_s_offset); + haystack = Internal_commands_s; + break; + case 't': + offset = static_cast(Int_cmd_t_offset); + haystack = Internal_commands_t; + break; + case 'u': + offset = static_cast(Int_cmd_u_offset); + haystack = Internal_commands_u; + break; + case 'w': + offset = static_cast(Int_cmd_w_offset); + haystack = Internal_commands_w; + break; + + default: + return nullptr; + } + return haystack; +} + +ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd) +{ + START_TIMER; + ESPEasy_cmd_e res = ESPEasy_cmd_e::NotMatched; + + if (cmd.length() < 2) { + // No commands less than 2 characters + return res; + } + + int offset = 0; + const char *haystack = getInternalCommand_Haystack_Offset(cmd[0], offset); + + if (haystack == nullptr) { +/* + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: No Haystack/offset '%s', offset: %d"), + cmd.c_str(), + offset)); +*/ + return res; + } + + + if (haystack != nullptr) { + const int command_i = GetCommandCode(cmd.c_str(), haystack); + + if (command_i != -1) { + res = static_cast(command_i + offset); + } +/* + else { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: Not found '%s', haystack: %s"), + cmd.c_str(), + String(haystack).c_str())); + } +*/ + } + STOP_TIMER(COMMAND_DECODE_INTERNAL); + return res; +} + +#ifndef BUILD_NO_DEBUG +bool toString(ESPEasy_cmd_e cmd, String& str) +{ + if (cmd == ESPEasy_cmd_e::NotMatched) { + return false; + } + char c = 'z'; + bool found = false; + int offset; + const char *haystack = nullptr; + + while (!found && c >= 'a') { + haystack = getInternalCommand_Haystack_Offset(c, offset); + + if (haystack != nullptr) { + if (offset <= static_cast(cmd)) { + found = true; + } + } + --c; + } + + if (found) { + const int index = static_cast(cmd) - offset; +/* + addLog(LOG_LEVEL_INFO, strformat( + F("Internal command: cmd=%d offset=%d index=%d"), + static_cast(cmd), + offset, + index)); +*/ + + if ((index >= 0) && (haystack != nullptr)) { + // Likely long enough to parse any command + char temp[32]{}; + str = GetTextIndexed(temp, sizeof(temp), index, haystack); + return !str.isEmpty(); + } + } + return false; +} + +bool checkAll_internalCommands() +{ + constexpr int last = static_cast(ESPEasy_cmd_e::NotMatched); + bool no_error = true; + + for (int i = 0; i < last; ++i) { + const ESPEasy_cmd_e cmd = static_cast(i); + String cmd_str; + + if (!toString(cmd, cmd_str)) { + no_error = false; +// addLog(LOG_LEVEL_ERROR, concat(F("Internal command: no matching string for "), i)); + } else { + const ESPEasy_cmd_e cmd_found = match_ESPEasy_internal_command(cmd_str); + + if (cmd_found != cmd) { + if (cmd_str.isEmpty()) { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: mismatch (%d)"), i)); + } + else { + addLog(LOG_LEVEL_ERROR, strformat( + F("Internal command: mismatch '%s' (%d)"), + cmd_str.c_str(), + i)); + } + no_error = false; + } + } + } + + if (no_error) { + addLog(LOG_LEVEL_INFO, F("Internal command: All checked OK")); + } +/* + else { + const int index = static_cast(match_ESPEasy_internal_command(F("build"))); + addLog(LOG_LEVEL_ERROR, concat(F("Internal command: index 'build'="), index)); + } +*/ + return no_error; +} + +#endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/Commands/InternalCommands_decoder.h b/src/src/Commands/InternalCommands_decoder.h index 40504720d..cebbf784a 100644 --- a/src/src/Commands/InternalCommands_decoder.h +++ b/src/src/Commands/InternalCommands_decoder.h @@ -1,240 +1,248 @@ -#ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H -#define COMMANDS_INTERNALCOMMANDS_DECODER_H - -#include "../../ESPEasy_common.h" - - -enum class ESPEasy_cmd_e : uint8_t { - accessinfo, - asyncevent, - build, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - background, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS -#ifdef USES_C012 - blynkget, -#endif // #ifdef USES_C012 -#ifdef USES_C015 - blynkset, -#endif // #ifdef USES_C015 - - - clearaccessblock, - clearpassword, - clearrtcram, -#ifdef ESP8266 - clearsdkwifi, - clearwifirfcal, -#endif // #ifdef ESP8266 - config, - controllerdisable, - controllerenable, - - datetime, - debug, - dec, - deepsleep, - delay, -#if FEATURE_PLUGIN_PRIORITY - disableprioritytask, -#endif // #if FEATURE_PLUGIN_PRIORITY - dns, - dst, - - erasesdkwifi, - event, - executerules, -#if FEATURE_ETHERNET - ethphyadr, - ethpinmdc, - ethpinmdio, - ethpinpower, - ethphytype, - ethclockmode, - ethip, - ethgateway, - ethsubnet, - ethdns, - ethdisconnect, - ethwifimode, -#endif // FEATURE_ETHERNET - - gateway, - gpio, - gpiotoggle, - hiddenssid, - - i2cscanner, - inc, - ip, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - jsonportstatus, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - let, - load, - logentry, - looptimerset, - looptimerset_ms, - longpulse, - longpulse_ms, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - logportstatus, - lowmem, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - monitor, - monitorrange, -#ifdef USES_P009 - mcpgpio, - mcpgpiorange, - mcpgpiopattern, - mcpgpiotoggle, - mcplongpulse, - mcplongpulse_ms, - mcpmode, - mcpmoderange, - mcppulse, -#endif // #ifdef USES_P009 -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - malloc, - meminfo, - meminfodetail, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - name, - nosleep, -#if FEATURE_NOTIFIER - notify, -#endif // #if FEATURE_NOTIFIER - ntphost, - - password, -#ifdef USES_P019 - pcfgpio, - pcfgpiorange, - pcfgpiopattern, - pcfgpiotoggle, - pcflongpulse, - pcflongpulse_ms, - pcfmode, - pcfmoderange, - pcfpulse, -#endif // #ifdef USES_P019 -#if FEATURE_POST_TO_HTTP - posttohttp, -#endif // #if FEATURE_POST_TO_HTTP -#if FEATURE_CUSTOM_PROVISIONING - provision, -# ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility - provisionconfig, - provisionsecurity, -# if FEATURE_NOTIFIER - provisionnotification, -# endif // #if FEATURE_NOTIFIER - provisionprovision, - provisionrules, - provisionfirmware, -# endif // #ifdef PLUGIN_BUILD_MAX_ESP32 -#endif // #if FEATURE_CUSTOM_PROVISIONING - pulse, -#if FEATURE_MQTT - publish, -#endif // #if FEATURE_MQTT -#if FEATURE_PUT_TO_HTTP - puttohttp, -#endif // #if FEATURE_PUT_TO_HTTP - pwm, - - reboot, - reset, - resetflashwritecounter, - restart, - rtttl, - rules, - - save, - scheduletaskrun, -#if FEATURE_SD - sdcard, - sdremove, -#endif // #if FEATURE_SD -#if FEATURE_ESPEASY_P2P - sendto, -#endif // #if FEATURE_ESPEASY_P2P -#if FEATURE_SEND_TO_HTTP - sendtohttp, -#endif // FEATURE_SEND_TO_HTTP - sendtoudp, -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - serialfloat, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - settings, -#if FEATURE_SERVO - servo, -#endif // #if FEATURE_SERVO - status, - subnet, -#if FEATURE_MQTT - subscribe, -#endif // #if FEATURE_MQTT -#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - sysload, -#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - - taskclear, - taskclearall, - taskdisable, - taskenable, - taskrun, - taskrunat, - taskvalueset, - taskvaluetoggle, - taskvaluesetandrun, - timerpause, - timerresume, - timerset, - timerset_ms, - timezone, - tone, - - udpport, -#if FEATURE_ESPEASY_P2P - udptest, -#endif // #if FEATURE_ESPEASY_P2P - unit, - unmonitor, - unmonitorrange, - usentp, - - wifiallowap, - wifiapmode, - wificonnect, - wifidisconnect, - wifikey, - wifikey2, - wifimode, - wifiscan, - wifissid, - wifissid2, - wifistamode, -#ifndef LIMIT_BUILD_SIZE - wdconfig, - wdread, -#endif // ifndef LIMIT_BUILD_SIZE - - - NotMatched // Keep as last one -}; - - -ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd); - -#ifndef BUILD_NO_DEBUG -bool toString(ESPEasy_cmd_e cmd, String& str); - -// Added for checking at runtime to see if all commands will be matched -bool checkAll_internalCommands(); -#endif - -#endif // ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H +#ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H +#define COMMANDS_INTERNALCOMMANDS_DECODER_H + +#include "../../ESPEasy_common.h" + + +enum class ESPEasy_cmd_e : uint8_t { + accessinfo, + asyncevent, + build, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + background, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS +#ifdef USES_C012 + blynkget, +#endif // #ifdef USES_C012 +#ifdef USES_C015 + blynkset, +#endif // #ifdef USES_C015 + + + clearaccessblock, + clearpassword, + clearrtcram, +#ifdef ESP8266 + clearsdkwifi, + clearwifirfcal, +#endif // #ifdef ESP8266 + config, + controllerdisable, + controllerenable, + + datetime, + debug, + dec, + deepsleep, + delay, +#if FEATURE_PLUGIN_PRIORITY + disableprioritytask, +#endif // #if FEATURE_PLUGIN_PRIORITY + dns, + dst, + + erasesdkwifi, + event, + executerules, +#if FEATURE_ETHERNET + ethphyadr, + ethpinmdc, + ethpinmdio, + ethpinpower, + ethphytype, + ethclockmode, + ethip, + ethgateway, + ethsubnet, + ethdns, + ethdisconnect, + ethwifimode, +#endif // FEATURE_ETHERNET + + gateway, + gpio, + gpiotoggle, + hiddenssid, + + i2cscanner, + inc, + ip, +#if FEATURE_USE_IPV6 + ip6, +#endif +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + jsonportstatus, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + let, + load, + logentry, + looptimerset, + looptimerset_ms, + longpulse, + longpulse_ms, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + logportstatus, + lowmem, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + monitor, + monitorrange, +#ifdef USES_P009 + mcpgpio, + mcpgpiorange, + mcpgpiopattern, + mcpgpiotoggle, + mcplongpulse, + mcplongpulse_ms, + mcpmode, + mcpmoderange, + mcppulse, +#endif // #ifdef USES_P009 +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + malloc, + meminfo, + meminfodetail, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + name, + nosleep, +#if FEATURE_NOTIFIER + notify, +#endif // #if FEATURE_NOTIFIER + ntphost, + +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + owscan, +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + + password, +#ifdef USES_P019 + pcfgpio, + pcfgpiorange, + pcfgpiopattern, + pcfgpiotoggle, + pcflongpulse, + pcflongpulse_ms, + pcfmode, + pcfmoderange, + pcfpulse, +#endif // #ifdef USES_P019 +#if FEATURE_POST_TO_HTTP + posttohttp, +#endif // #if FEATURE_POST_TO_HTTP +#if FEATURE_CUSTOM_PROVISIONING + provision, +# ifdef PLUGIN_BUILD_MAX_ESP32 // FIXME DEPRECATED: Fallback for temporary backward compatibility + provisionconfig, + provisionsecurity, +# if FEATURE_NOTIFIER + provisionnotification, +# endif // #if FEATURE_NOTIFIER + provisionprovision, + provisionrules, + provisionfirmware, +# endif // #ifdef PLUGIN_BUILD_MAX_ESP32 +#endif // #if FEATURE_CUSTOM_PROVISIONING + pulse, +#if FEATURE_MQTT + publish, + publishr, +#endif // #if FEATURE_MQTT +#if FEATURE_PUT_TO_HTTP + puttohttp, +#endif // #if FEATURE_PUT_TO_HTTP + pwm, + + reboot, + reset, + resetflashwritecounter, + restart, + rtttl, + rules, + + save, + scheduletaskrun, +#if FEATURE_SD + sdcard, + sdremove, +#endif // #if FEATURE_SD +#if FEATURE_ESPEASY_P2P + sendto, +#endif // #if FEATURE_ESPEASY_P2P +#if FEATURE_SEND_TO_HTTP + sendtohttp, +#endif // FEATURE_SEND_TO_HTTP + sendtoudp, +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + serialfloat, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + settings, +#if FEATURE_SERVO + servo, +#endif // #if FEATURE_SERVO + status, + subnet, +#if FEATURE_MQTT + subscribe, +#endif // #if FEATURE_MQTT +#ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + sysload, +#endif // ifndef BUILD_NO_DIAGNOSTIC_COMMANDS + + taskclear, + taskclearall, + taskdisable, + taskenable, + taskrun, + taskrunat, + taskvalueset, + taskvaluetoggle, + taskvaluesetandrun, + timerpause, + timerresume, + timerset, + timerset_ms, + timezone, + tone, + + udpport, +#if FEATURE_ESPEASY_P2P + udptest, +#endif // #if FEATURE_ESPEASY_P2P + unit, + unmonitor, + unmonitorrange, + usentp, + + wifiallowap, + wifiapmode, + wificonnect, + wifidisconnect, + wifikey, + wifikey2, + wifimode, + wifiscan, + wifissid, + wifissid2, + wifistamode, +#ifndef LIMIT_BUILD_SIZE + wdconfig, + wdread, +#endif // ifndef LIMIT_BUILD_SIZE + + + NotMatched // Keep as last one +}; + + +ESPEasy_cmd_e match_ESPEasy_internal_command(const String& cmd); + +#ifndef BUILD_NO_DEBUG +bool toString(ESPEasy_cmd_e cmd, String& str); + +// Added for checking at runtime to see if all commands will be matched +bool checkAll_internalCommands(); +#endif + +#endif // ifndef COMMANDS_INTERNALCOMMANDS_DECODER_H diff --git a/src/src/Commands/MQTT.cpp b/src/src/Commands/MQTT.cpp index b957bceac..3f4a900e5 100644 --- a/src/src/Commands/MQTT.cpp +++ b/src/src/Commands/MQTT.cpp @@ -21,7 +21,15 @@ #include "../Helpers/StringConverter.h" -const __FlashStringHelper * Command_MQTT_Publish(struct EventStruct *event, const char *Line) +const __FlashStringHelper* Command_MQTT_Publish(struct EventStruct *event, const char *Line) { + return Command_MQTT_Publish_handler(event, Line, false); +} + +const __FlashStringHelper* Command_MQTT_PublishR(struct EventStruct *event, const char *Line) { + return Command_MQTT_Publish_handler(event, Line, true); +} + +const __FlashStringHelper* Command_MQTT_Publish_handler(struct EventStruct *event, const char *Line, const bool forceRetain) { // ToDo TD-er: Not sure about this function, but at least it sends to an existing MQTTclient controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); @@ -34,13 +42,13 @@ const __FlashStringHelper * Command_MQTT_Publish(struct EventStruct *event, cons const String topic = parseStringKeepCase(Line, 2); const String value = tolerantParseStringKeepCase(Line, 3); # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, concat(F("Publish: "), topic) + value); - #endif + addLog(LOG_LEVEL_DEBUG, strformat(F("Publish%c: %s:%s"), forceRetain ? 'R' : ' ', topic.c_str(), value.c_str())); + # endif if (!topic.isEmpty()) { - bool mqtt_retainFlag; - { + bool mqtt_retainFlag = forceRetain; + if (!forceRetain) { // Place the ControllerSettings in a scope to free the memory as soon as we got all relevant information. MakeControllerSettings(ControllerSettings); //-V522 if (!AllocatedControllerSettings()) { diff --git a/src/src/Commands/MQTT.h b/src/src/Commands/MQTT.h index a1af7ecec..8fc01d6f0 100644 --- a/src/src/Commands/MQTT.h +++ b/src/src/Commands/MQTT.h @@ -5,11 +5,16 @@ #if FEATURE_MQTT -const __FlashStringHelper * Command_MQTT_Publish(struct EventStruct *event, - const char *Line); +const __FlashStringHelper* Command_MQTT_Publish(struct EventStruct *event, + const char *Line); +const __FlashStringHelper* Command_MQTT_PublishR(struct EventStruct *event, + const char *Line); +const __FlashStringHelper* Command_MQTT_Publish_handler(struct EventStruct *event, + const char *Line, + const bool forceRetain); -const __FlashStringHelper * Command_MQTT_Subscribe(struct EventStruct *event, - const char* Line); +const __FlashStringHelper* Command_MQTT_Subscribe(struct EventStruct *event, + const char *Line); #endif // if FEATURE_MQTT diff --git a/src/src/Commands/Networks.cpp b/src/src/Commands/Networks.cpp index c5aa1db21..0db3581d0 100644 --- a/src/src/Commands/Networks.cpp +++ b/src/src/Commands/Networks.cpp @@ -40,6 +40,29 @@ String Command_IP (struct EventStruct *event, const char* Line) return Command_GetORSetIP(event, F("IP:"), Line, Settings.IP, 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(); + String res; + res += '['; + bool first = true; + for (auto it = addresses.begin(); it != addresses.end(); ++it) + { + if (first) { + first = false; + } else { + res += ','; + } + res += wrap_String(it->toString(true), '"'); + } + res += ']'; + return res; +} +#endif + + String Command_Subnet (struct EventStruct *event, const char* Line) { return Command_GetORSetIP(event, F("Subnet:"), Line, Settings.Subnet, NetworkSubnetMask(), 1); @@ -53,17 +76,17 @@ String Command_ETH_Phy_Addr (struct EventStruct *event, const char* Line) String Command_ETH_Pin_mdc (struct EventStruct *event, const char* Line) { - return Command_GetORSetInt8_t(event, F("ETH_Pin_mdc:"), Line, reinterpret_cast(&Settings.ETH_Pin_mdc),1); + 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:"), Line, reinterpret_cast(&Settings.ETH_Pin_mdio),1); + 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:"), Line, reinterpret_cast(&Settings.ETH_Pin_power),1); + 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) diff --git a/src/src/Commands/Networks.h b/src/src/Commands/Networks.h index f33f04566..3ef0e8302 100644 --- a/src/src/Commands/Networks.h +++ b/src/src/Commands/Networks.h @@ -9,7 +9,11 @@ String Command_AccessInfo_Clear (struct EventStruct *event, const char* Line); String Command_DNS (struct EventStruct *event, const char* Line); String Command_Gateway (struct EventStruct *event, const char* Line); String Command_IP (struct EventStruct *event, const char* Line); +#if FEATURE_USE_IPV6 +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); @@ -22,5 +26,6 @@ String Command_ETH_Subnet (struct EventStruct *event, const char* Line); 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 #endif // COMMAND_NETWORKS_H diff --git a/src/src/Commands/Notifications.cpp b/src/src/Commands/Notifications.cpp index 2a1d8662c..56acef3f1 100644 --- a/src/src/Commands/Notifications.cpp +++ b/src/src/Commands/Notifications.cpp @@ -15,7 +15,9 @@ const __FlashStringHelper * Command_Notifications_Notify(struct EventStruct *event, const char* Line) { String message; + String subject; GetArgv(Line, message, 3); + GetArgv(Line, subject, 4); if (event->Par1 > 0) { int index = event->Par1 - 1; @@ -27,6 +29,7 @@ const __FlashStringHelper * Command_Notifications_Notify(struct EventStruct *eve // TempEvent.NotificationProtocolIndex = NotificationProtocolIndex; TempEvent.NotificationIndex = index; TempEvent.String1 = message; + TempEvent.String2 = subject; Scheduler.schedule_notification_event_timer(NotificationProtocolIndex, NPlugin::Function::NPLUGIN_NOTIFY, std::move(TempEvent)); } } diff --git a/src/src/Commands/OneWire.cpp b/src/src/Commands/OneWire.cpp new file mode 100644 index 000000000..b1b7235f4 --- /dev/null +++ b/src/src/Commands/OneWire.cpp @@ -0,0 +1,49 @@ +#include "../Commands/OneWire.h" + +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + +# include "../Commands/Common.h" + +# include "../Helpers/Dallas1WireHelper.h" +# include "../Helpers/Hardware_GPIO.h" +# include "../Helpers/StringConverter.h" + +String Command_OneWire_Owscan(struct EventStruct *event, + const char *Line) { + int pinnr; bool input; bool output1; bool output; bool warning; + + if (getGpioInfo(event->Par1, pinnr, input, output1, warning) && input) { // Input pin required + if (!parseString(Line, 3).isEmpty()) { + if (getGpioInfo(event->Par2, pinnr, input, output, warning) && !output) { // Output required if specified + return return_command_failed(); // Argument error + } + } else { + event->Par2 = event->Par1; // RX and TX on same pin + + if (!output1) { // Single pin must be input & output capable + return return_command_failed(); // Argument error + } + } + pinMode(event->Par1, INPUT); + + if (event->Par2 != event->Par1) { + pinMode(event->Par2, OUTPUT); + } + + uint8_t addr[8]{}; + + Dallas_reset(event->Par1, event->Par2); + String res; + res.reserve(80); + + while (Dallas_search(addr, event->Par1, event->Par2)) { // Scan the 1-wire + res += Dallas_format_address(addr); + res += '\n'; + } + return res; + } + + return return_command_failed(); +} + +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN diff --git a/src/src/Commands/OneWire.h b/src/src/Commands/OneWire.h new file mode 100644 index 000000000..0c5a957d7 --- /dev/null +++ b/src/src/Commands/OneWire.h @@ -0,0 +1,10 @@ +#ifndef COMMANDS_ONE_WIRE_H +#include "../Helpers/Dallas1WireHelper.h" + +#if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN +String Command_OneWire_Owscan(struct EventStruct *event, + const char *Line); + +#endif // if FEATURE_DALLAS_HELPER && FEATURE_COMMAND_OWSCAN + +#endif // ifndef COMMANDS_ONE_WIRE_H diff --git a/src/src/Commands/i2c.cpp b/src/src/Commands/i2c.cpp index 28b5a8b5a..c025a3290 100644 --- a/src/src/Commands/i2c.cpp +++ b/src/src/Commands/i2c.cpp @@ -6,25 +6,56 @@ #include "../Globals/I2Cdev.h" #include "../Globals/Settings.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/StringConverter.h" + #include "../../ESPEasy_common.h" +void i2c_scanI2Cbus(bool dbg, int8_t channel) { + uint8_t error, address; + + #if FEATURE_I2CMULTIPLEXER + + if (-1 == channel) { + serialPrintln(F("Standard I2C bus")); + } else { + serialPrintln(concat(F("Multiplexer channel "), channel)); + } + #endif // if FEATURE_I2CMULTIPLEXER + + for (address = 1; address <= 127; address++) { + Wire.beginTransmission(address); + error = Wire.endTransmission(); + + if (error == 0) { + serialPrintln(strformat(F("I2C : Found 0x%02x"), address)); + } else if ((error == 4) || dbg) { + serialPrintln(strformat(F("I2C : Error %d at 0x%02x"), error, address)); + } + } +} + const __FlashStringHelper* Command_i2c_Scanner(struct EventStruct *event, const char *Line) { - uint8_t error, address; - if (Settings.isI2CEnabled()) { - for (address = 1; address <= 127; address++) { - Wire.beginTransmission(address); - error = Wire.endTransmission(); + const bool dbg = equals(parseString(Line, 2), F("1")); + I2CSelect_Max100kHz_ClockSpeed(); // Scan bus using low speed - if (error == 0) { - serialPrint(F("I2C : Found 0x")); - serialPrintln(String(address, HEX)); - } else if (error == 4) { - serialPrint(F("I2C : Error at 0x")); - serialPrintln(String(address, HEX)); + i2c_scanI2Cbus(dbg, -1); // Base I2C bus + + #if FEATURE_I2CMULTIPLEXER + + if (isI2CMultiplexerEnabled()) { + uint8_t mux_max = I2CMultiplexerMaxChannels(); + + for (int8_t channel = 0; channel < mux_max; ++channel) { + I2CMultiplexerSelect(channel); + i2c_scanI2Cbus(dbg, channel); // Multiplexer I2C bus } + I2CMultiplexerOff(); } + #endif // if FEATURE_I2CMULTIPLEXER + I2CSelectHighClockSpeed(); // By default the bus is in standard speed } else { serialPrintln(F("I2C : Not enabled.")); } diff --git a/src/src/Commands/i2c.h b/src/src/Commands/i2c.h index 27a8c2fc3..d54a9db0d 100644 --- a/src/src/Commands/i2c.h +++ b/src/src/Commands/i2c.h @@ -3,6 +3,10 @@ #include "../../ESPEasy_common.h" -const __FlashStringHelper * Command_i2c_Scanner(struct EventStruct *event, const char* Line); +void i2c_scanI2Cbus(bool dbg, + int8_t channel); + +const __FlashStringHelper* Command_i2c_Scanner(struct EventStruct *event, + const char *Line); #endif // COMMAND_I2C_H diff --git a/src/src/ControllerQueue/C011_queue_element.h b/src/src/ControllerQueue/C011_queue_element.h index e5b7f7424..91d99167c 100644 --- a/src/src/ControllerQueue/C011_queue_element.h +++ b/src/src/ControllerQueue/C011_queue_element.h @@ -1,55 +1,55 @@ -#ifndef CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C011 - -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - - -/*********************************************************************************************\ -* C011_queue_element for queueing requests for C011: Generic HTTP Advanced. -\*********************************************************************************************/ -class C011_queue_element : public Queue_element_base { -public: - - C011_queue_element() = default; - - C011_queue_element(C011_queue_element&& other) = default; - - C011_queue_element(const C011_queue_element& other) = delete; - - C011_queue_element(const struct EventStruct *event); - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - size_t getSize() const; - - String uri; - String HttpMethod; - String header; - String postStr; - int idx = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; -}; - -#endif // USES_C011 - - -#endif // CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C011 + +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataStructs/UnitMessageCount.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + + +/*********************************************************************************************\ +* C011_queue_element for queueing requests for C011: Generic HTTP Advanced. +\*********************************************************************************************/ +class C011_queue_element : public Queue_element_base { +public: + + C011_queue_element() = default; + + C011_queue_element(C011_queue_element&& other) = default; + + C011_queue_element(const C011_queue_element& other) = delete; + + C011_queue_element(const struct EventStruct *event); + + bool isDuplicate(const Queue_element_base& other) const; + + const UnitMessageCount_t* getUnitMessageCount() const { + return nullptr; + } + + UnitMessageCount_t* getUnitMessageCount() { + return nullptr; + } + + size_t getSize() const; + + String uri; + String HttpMethod; + String header; + String postStr; + int idx = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; +}; + +#endif // USES_C011 + + +#endif // CONTROLLERQUEUE_C011_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C015_queue_element.h b/src/src/ControllerQueue/C015_queue_element.h index 7952cdc10..fec2c6658 100644 --- a/src/src/ControllerQueue/C015_queue_element.h +++ b/src/src/ControllerQueue/C015_queue_element.h @@ -1,60 +1,60 @@ -#ifndef CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C015 - -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - - -/*********************************************************************************************\ -* C015_queue_element for queueing requests for 015: Blynk -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ - -class C015_queue_element : public Queue_element_base { -public: - - C015_queue_element() = default; - - C015_queue_element(const C015_queue_element& other) = delete; - - C015_queue_element(C015_queue_element&& other); - - C015_queue_element(const struct EventStruct *event, - uint8_t value_count); - - C015_queue_element & operator=(C015_queue_element&& other); - - bool checkDone(bool succesfull) const; - - 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 txt[VARS_PER_TASK] = {}; - int vPin[VARS_PER_TASK] = { 0 }; - int idx = 0; - mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() - uint8_t valueCount = 0; -}; - -#endif // USES_C015 - - -#endif // CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C015 + +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/UnitMessageCount.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + + +/*********************************************************************************************\ +* C015_queue_element for queueing requests for 015: Blynk +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ + +class C015_queue_element : public Queue_element_base { +public: + + C015_queue_element() = default; + + C015_queue_element(const C015_queue_element& other) = delete; + + C015_queue_element(C015_queue_element&& other); + + C015_queue_element(const struct EventStruct *event, + uint8_t value_count); + + C015_queue_element & operator=(C015_queue_element&& other); + + bool checkDone(bool succesfull) const; + + 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 txt[VARS_PER_TASK] = {}; + int vPin[VARS_PER_TASK] = { 0 }; + int idx = 0; + mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() + uint8_t valueCount = 0; +}; + +#endif // USES_C015 + + +#endif // CONTROLLERQUEUE_C015_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C016_queue_element.cpp b/src/src/ControllerQueue/C016_queue_element.cpp index 7f4ffb350..d21222ee3 100644 --- a/src/src/ControllerQueue/C016_queue_element.cpp +++ b/src/src/ControllerQueue/C016_queue_element.cpp @@ -1,102 +1,102 @@ -#include "../ControllerQueue/C016_queue_element.h" - -#ifdef USES_C016 - -# include "../DataStructs/ESPEasy_EventStruct.h" -# include "../Globals/Plugins.h" -# include "../Globals/RuntimeData.h" -# include "../Helpers/_Plugin_SensorTypeHelper.h" -# include "../Helpers/ESPEasy_math.h" - -C016_queue_element::C016_queue_element() : sensorType( - Sensor_VType::SENSOR_TYPE_NONE) { - _timestamp = 0; - _controller_idx = 0; - _taskIndex = INVALID_TASK_INDEX; - values.clear(); -} - -C016_queue_element::C016_queue_element(C016_queue_element&& other) - : sensorType(other.sensorType) - , valueCount(other.valueCount) -{ - _timestamp = other._timestamp; - _controller_idx = other._controller_idx; - _taskIndex = other._taskIndex; - values = other.values; -} - -C016_queue_element::C016_queue_element(const struct EventStruct *event, uint8_t value_count) : - unixTime(event->timestamp), - sensorType(event->sensorType), - valueCount(value_count) -{ - _controller_idx = event->ControllerIndex; - _taskIndex = event->TaskIndex; - values.clear(); - const TaskValues_Data_t* data = UserVar.getRawTaskValues_Data(event->TaskIndex); - - if (data != nullptr) { - for (uint8_t i = 0; i < value_count; ++i) { - values.copyValue(*data, i, sensorType); - } - } -} - -C016_queue_element& C016_queue_element::operator=(C016_queue_element&& other) { - _timestamp = other._timestamp; - _taskIndex = other._taskIndex; - _controller_idx = other._controller_idx; - sensorType = other.sensorType; - valueCount = other.valueCount; - unixTime = other.unixTime; - values = other.values; - - return *this; -} - -size_t C016_queue_element::getSize() const { - return sizeof(*this); -} - -bool C016_queue_element::isDuplicate(const Queue_element_base& other) const { - const C016_queue_element& oth = static_cast(other); - - if ((oth._controller_idx != _controller_idx) || - (oth._taskIndex != _taskIndex) || - (oth.sensorType != sensorType) || - (oth.valueCount != valueCount)) { - return false; - } - - for (uint8_t i = 0; i < valueCount; ++i) { - if (isFloatOutputDataType(sensorType)) { - if (!essentiallyEqual(oth.values.getFloat(i), values.getFloat(i))) { - return false; - } - } else { - if (oth.values.getUint32(i) != values.getUint32(i)) { - return false; - } - } - } - return true; -} - -C016_binary_element C016_queue_element::getBinary() const { - C016_binary_element element; - - element.unixTime = unixTime; - element.TaskIndex = _taskIndex; - element.sensorType = sensorType; - element.valueCount = valueCount; - element.values = values; - - // It makes no sense to keep the controller index when storing it. - // re-purpose it to store the pluginID - element.pluginID = getPluginID_from_TaskIndex(_taskIndex); - - return element; -} - -#endif // ifdef USES_C016 +#include "../ControllerQueue/C016_queue_element.h" + +#ifdef USES_C016 + +# include "../DataStructs/ESPEasy_EventStruct.h" +# include "../Globals/Plugins.h" +# include "../Globals/RuntimeData.h" +# include "../Helpers/_Plugin_SensorTypeHelper.h" +# include "../Helpers/ESPEasy_math.h" + +C016_queue_element::C016_queue_element() : sensorType( + Sensor_VType::SENSOR_TYPE_NONE) { + _timestamp = 0; + _controller_idx = 0; + _taskIndex = INVALID_TASK_INDEX; + values.clear(); +} + +C016_queue_element::C016_queue_element(C016_queue_element&& other) + : sensorType(other.sensorType) + , valueCount(other.valueCount) +{ + _timestamp = other._timestamp; + _controller_idx = other._controller_idx; + _taskIndex = other._taskIndex; + values = other.values; +} + +C016_queue_element::C016_queue_element(const struct EventStruct *event, uint8_t value_count) : + unixTime(event->timestamp_sec), + sensorType(event->sensorType), + valueCount(value_count) +{ + _controller_idx = event->ControllerIndex; + _taskIndex = event->TaskIndex; + values.clear(); + const TaskValues_Data_t* data = UserVar.getRawTaskValues_Data(event->TaskIndex); + + if (data != nullptr) { + for (uint8_t i = 0; i < value_count; ++i) { + values.copyValue(*data, i, sensorType); + } + } +} + +C016_queue_element& C016_queue_element::operator=(C016_queue_element&& other) { + _timestamp = other._timestamp; + _taskIndex = other._taskIndex; + _controller_idx = other._controller_idx; + sensorType = other.sensorType; + valueCount = other.valueCount; + unixTime = other.unixTime; + values = other.values; + + return *this; +} + +size_t C016_queue_element::getSize() const { + return sizeof(*this); +} + +bool C016_queue_element::isDuplicate(const Queue_element_base& other) const { + const C016_queue_element& oth = static_cast(other); + + if ((oth._controller_idx != _controller_idx) || + (oth._taskIndex != _taskIndex) || + (oth.sensorType != sensorType) || + (oth.valueCount != valueCount)) { + return false; + } + + for (uint8_t i = 0; i < valueCount; ++i) { + if (isFloatOutputDataType(sensorType)) { + if (!essentiallyEqual(oth.values.getFloat(i), values.getFloat(i))) { + return false; + } + } else { + if (oth.values.getUint32(i) != values.getUint32(i)) { + return false; + } + } + } + return true; +} + +C016_binary_element C016_queue_element::getBinary() const { + C016_binary_element element; + + element.unixTime = unixTime; + element.TaskIndex = _taskIndex; + element.sensorType = sensorType; + element.valueCount = valueCount; + element.values = values; + + // It makes no sense to keep the controller index when storing it. + // re-purpose it to store the pluginID + element.pluginID = getPluginID_from_TaskIndex(_taskIndex); + + return element; +} + +#endif // ifdef USES_C016 diff --git a/src/src/ControllerQueue/C016_queue_element.h b/src/src/ControllerQueue/C016_queue_element.h index cc2ab547e..5c35d3df1 100644 --- a/src/src/ControllerQueue/C016_queue_element.h +++ b/src/src/ControllerQueue/C016_queue_element.h @@ -1,76 +1,76 @@ -#ifndef CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" -#ifdef USES_C016 - - -# include "../ControllerQueue/Queue_element_base.h" -# include "../CustomBuild/ESPEasyLimits.h" -# include "../DataTypes/ControllerIndex.h" -# include "../DataTypes/TaskValues_Data.h" -# include "../DataStructs/DeviceStruct.h" -# include "../DataStructs/UnitMessageCount.h" -# include "../Globals/Plugins.h" - -struct EventStruct; - - -// The binary format to store the samples using the Cache Controller -// Do NOT change order of members! -struct C016_binary_element { - TaskValues_Data_t values{}; - unsigned long unixTime{}; - taskIndex_t TaskIndex{ INVALID_TASK_INDEX }; - pluginID_t pluginID{ INVALID_PLUGIN_ID }; - Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; - uint8_t valueCount{}; -}; - - -/*********************************************************************************************\ -* C016_queue_element for queueing requests for C016: Cached HTTP. -\*********************************************************************************************/ - -// TD-er: This one has a fixed uint8_t order and is stored. -// This also means the order of members should not be changed! -class C016_queue_element : public Queue_element_base { -public: - - C016_queue_element(); - - C016_queue_element(const C016_queue_element& other) = delete; - - C016_queue_element(C016_queue_element&& other); - - C016_queue_element(const struct EventStruct *event, - uint8_t value_count); - - C016_queue_element & operator=(C016_queue_element&& other); - - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return nullptr; - } - - UnitMessageCount_t* getUnitMessageCount() { - return nullptr; - } - - C016_binary_element getBinary() const; - - TaskValues_Data_t values{}; - - unsigned long unixTime = 0; - Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; - uint8_t valueCount{}; -}; - -#endif // USES_C016 - - -#endif // CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" +#ifdef USES_C016 + + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataTypes/ControllerIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/UnitMessageCount.h" +# include "../Globals/Plugins.h" + +struct EventStruct; + + +// The binary format to store the samples using the Cache Controller +// Do NOT change order of members! +struct C016_binary_element { + TaskValues_Data_t values{}; + unsigned long unixTime{}; + taskIndex_t TaskIndex{ INVALID_TASK_INDEX }; + pluginID_t pluginID{ INVALID_PLUGIN_ID }; + Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; + uint8_t valueCount{}; +}; + + +/*********************************************************************************************\ +* C016_queue_element for queueing requests for C016: Cached HTTP. +\*********************************************************************************************/ + +// TD-er: This one has a fixed uint8_t order and is stored. +// This also means the order of members should not be changed! +class C016_queue_element : public Queue_element_base { +public: + + C016_queue_element(); + + C016_queue_element(const C016_queue_element& other) = delete; + + C016_queue_element(C016_queue_element&& other); + + C016_queue_element(const struct EventStruct *event, + uint8_t value_count); + + C016_queue_element & operator=(C016_queue_element&& other); + + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const UnitMessageCount_t* getUnitMessageCount() const { + return nullptr; + } + + UnitMessageCount_t* getUnitMessageCount() { + return nullptr; + } + + C016_binary_element getBinary() const; + + TaskValues_Data_t values{}; + + unsigned long unixTime = 0; + Sensor_VType sensorType{ Sensor_VType::SENSOR_TYPE_NONE }; + uint8_t valueCount{}; +}; + +#endif // USES_C016 + + +#endif // CONTROLLERQUEUE_C016_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/C018_queue_element.cpp b/src/src/ControllerQueue/C018_queue_element.cpp index c6bbd41ff..f3096731b 100644 --- a/src/src/ControllerQueue/C018_queue_element.cpp +++ b/src/src/ControllerQueue/C018_queue_element.cpp @@ -1,40 +1,41 @@ -#include "../ControllerQueue/C018_queue_element.h" - -#ifdef USES_C018 - -# include "../DataStructs/ESPEasy_EventStruct.h" - -# include "../ESPEasyCore/ESPEasy_Log.h" - -# include "../Helpers/_CPlugin_LoRa_TTN_helper.h" -# include "../Helpers/StringConverter.h" - -C018_queue_element::C018_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("C018 queue element: "), packed)); - } - # endif // if FEATURE_PACKED_RAW_DATA -} - -size_t C018_queue_element::getSize() const { - return sizeof(*this) + packed.length(); -} - -bool C018_queue_element::isDuplicate(const Queue_element_base& other) const { - const C018_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_C018 +#include "../ControllerQueue/C018_queue_element.h" + +#ifdef USES_C018 + +# 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" + +C018_queue_element::C018_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("C018 queue element: "), packed)); + } + # endif // if FEATURE_PACKED_RAW_DATA +} + +size_t C018_queue_element::getSize() const { + return sizeof(*this) + packed.length(); +} + +bool C018_queue_element::isDuplicate(const Queue_element_base& other) const { + const C018_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_C018 diff --git a/src/src/ControllerQueue/C018_queue_element.h b/src/src/ControllerQueue/C018_queue_element.h index b2fa1f2a1..b7c9fb8ae 100644 --- a/src/src/ControllerQueue/C018_queue_element.h +++ b/src/src/ControllerQueue/C018_queue_element.h @@ -1,51 +1,51 @@ -#ifndef CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C018 - -# include "../ControllerQueue/Queue_element_base.h" -# include "../CustomBuild/ESPEasyLimits.h" -# include "../DataStructs/UnitMessageCount.h" -# include "../Globals/CPlugins.h" - - -struct EventStruct; - -/*********************************************************************************************\ -* C018_queue_element for queueing requests for C018: TTN/RN2483 -\*********************************************************************************************/ - - -class C018_queue_element : public Queue_element_base { -public: - - C018_queue_element() = default; - - C018_queue_element(const C018_queue_element& other) = delete; - - C018_queue_element(C018_queue_element&& other) = default; - - C018_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_C018 - - -#endif // CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C018 + +# include "../ControllerQueue/Queue_element_base.h" +# include "../CustomBuild/ESPEasyLimits.h" +# include "../Globals/CPlugins.h" + + +struct EventStruct; +struct UnitMessageCount_t; + +/*********************************************************************************************\ +* C018_queue_element for queueing requests for C018: TTN/RN2483 +\*********************************************************************************************/ + + +class C018_queue_element : public Queue_element_base { +public: + + C018_queue_element() = default; + + C018_queue_element(const C018_queue_element& other) = delete; + + C018_queue_element(C018_queue_element&& other) = default; + + C018_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_C018 + + +#endif // CONTROLLERQUEUE_C018_QUEUE_ELEMENT_H diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp index 64b877c18..9bdb7ac0f 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.cpp @@ -1,284 +1,284 @@ -#include "../ControllerQueue/ControllerDelayHandlerStruct.h" - - -ControllerDelayHandlerStruct::ControllerDelayHandlerStruct() : - lastSend(0), - minTimeBetweenMessages(CONTROLLER_DELAY_QUEUE_DELAY_DFLT), - expire_timeout(0), - max_queue_depth(CONTROLLER_DELAY_QUEUE_DEPTH_DFLT), - attempt(0), - max_retries(CONTROLLER_DELAY_QUEUE_RETRY_DFLT), - delete_oldest(false), - must_check_reply(false), - deduplicate(false), - useLocalSystemTime(false) {} - -bool ControllerDelayHandlerStruct::cacheControllerSettings(controllerIndex_t ControllerIndex) -{ - MakeControllerSettings(ControllerSettings); - - if (!AllocatedControllerSettings()) { - return false; - } - LoadControllerSettings(ControllerIndex, *ControllerSettings); - cacheControllerSettings(*ControllerSettings); - return true; -} - -void ControllerDelayHandlerStruct::cacheControllerSettings(const ControllerSettingsStruct& settings) { - minTimeBetweenMessages = settings.MinimalTimeBetweenMessages; - max_queue_depth = settings.MaxQueueDepth; - max_retries = settings.MaxRetry; - delete_oldest = settings.DeleteOldest; - must_check_reply = settings.MustCheckReply; - deduplicate = settings.deduplicate(); - useLocalSystemTime = settings.useLocalSystemTime(); - - if (settings.allowExpire()) { - expire_timeout = max_queue_depth * max_retries * (minTimeBetweenMessages + settings.ClientTimeout); - - if (expire_timeout < CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME) { - expire_timeout = CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME; - } - } else { - expire_timeout = 0; - } - - // Set some sound limits when not configured - if (max_queue_depth == 0) { max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; } - - if (max_retries == 0) { max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; } - - if (minTimeBetweenMessages == 0) { minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; } - - // No less than 10 msec between messages. - if (minTimeBetweenMessages < 10) { minTimeBetweenMessages = 10; } -} - -bool ControllerDelayHandlerStruct::readyToProcess(const Queue_element_base& element) const { - const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(element._controller_idx); - - if (protocolIndex == INVALID_PROTOCOL_INDEX) { - return false; - } - - if (getProtocolStruct(protocolIndex).needsNetwork) { - return NetworkConnected(10); - } - return true; -} - -bool ControllerDelayHandlerStruct::queueFull(controllerIndex_t controller_idx) const { - if (sendQueue.size() >= max_queue_depth) { return true; } - - // Number of elements is not exceeding the limit, check memory - int freeHeap = FreeMem(); - { - /* - #ifdef USE_SECOND_HEAP - const int freeHeap2 = FreeMem2ndHeap(); - - if (freeHeap2 < freeHeap) { - freeHeap = freeHeap2; - } - #endif // ifdef USE_SECOND_HEAP - */ - } - -#ifdef ESP32 - if (freeHeap > 50000) -#else - if (freeHeap > 5000) -#endif - { - return false; // Memory is not an issue. - } -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("Controller-"); - log += controller_idx + 1; - log += F(" : Memory used: "); - log += getQueueMemorySize(); - log += F(" bytes "); - log += sendQueue.size(); - log += F(" items "); - log += freeHeap; - log += F(" free"); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - return true; -} - -// Return true if message is already present in the queue -bool ControllerDelayHandlerStruct::isDuplicate(const Queue_element_base& element) const { - // Some controllers may receive duplicate messages, due to lost acknowledgement - // This is actually the same message, so this should not be processed. - if (!unitLastMessageCount.isNew(element.getUnitMessageCount())) { - return true; - } - - // The unit message count is still stored to make sure a new one with the same count - // is considered a duplicate, even when the queue is empty. - unitLastMessageCount.add(element.getUnitMessageCount()); - - // the setting 'deduplicate' does look at the content of the message and only compares it to messages in the queue. - if (deduplicate && !sendQueue.empty()) { - // Use reverse iterator here, as it is more likely a duplicate is added shortly after another. - auto it = sendQueue.rbegin(); // Same as back() - - for (; it != sendQueue.rend(); ++it) { - if (element.isDuplicate(*(it->get()))) { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex(it->get()->_controller_idx); - addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : Remove duplicate"))); - } -#endif // ifndef BUILD_NO_DEBUG - return true; - } - } - } - return false; -} - -// 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) { - return false; - } - if (isDuplicate(*element)) { - return true; - } - - if (delete_oldest) { - // Force add to the queue. - // If max buffer is reached, the oldest in the queue (first to be served) will be removed. - while (queueFull(element->_controller_idx)) { - sendQueue.pop_front(); - attempt = 0; - } - } - - if (!queueFull(element->_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)); - - return true; - } -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex((*element)._controller_idx); - addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : queue full"))); - } -#endif // ifndef BUILD_NO_DEBUG - return false; -} - -// Get the next element. -// Remove front element when max_retries is reached. -Queue_element_base * ControllerDelayHandlerStruct::getNext() { - if (sendQueue.empty()) { return nullptr; } - - if (attempt > max_retries) { - sendQueue.pop_front(); - attempt = 0; - } - - if (expire_timeout != 0) { - bool done = false; - - while (!done && !sendQueue.empty()) { - if ((sendQueue.front().get() != nullptr) && (timePassedSince(sendQueue.front()->_timestamp) < static_cast(expire_timeout))) { - done = true; - } else { - sendQueue.pop_front(); - attempt = 0; - } - } - } - - if (sendQueue.empty()) { return nullptr; } - return sendQueue.front().get(); -} - -// 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) { - if (sendQueue.empty()) { return 0; } - - if (remove_from_queue) { - sendQueue.pop_front(); - attempt = 0; - lastSend = millis(); - } else { - ++attempt; - } - return getNextScheduleTime(); -} - -unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { - if (sendQueue.empty()) { return 0; } - unsigned long nextTime = lastSend + minTimeBetweenMessages; - - if (timePassedSince(nextTime) > 0) { - nextTime = millis(); - } - - if (nextTime == 0) { nextTime = 1; // Just to make sure it will be executed - } - return nextTime; -} - -// 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; -} - -size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { - size_t totalSize = 0; - - for (auto it = sendQueue.begin(); it != sendQueue.end(); ++it) { - if (it->get() != nullptr) { - totalSize += it->get()->getSize(); - } - } - return totalSize; -} - -void ControllerDelayHandlerStruct::process( - int controller_number, - do_process_function func, - TimingStatsElements timerstats_id, - SchedulerIntervalTimer_e timerID) -{ - Queue_element_base *element(static_cast(getNext())); - - if (element == nullptr) { return; } - - if (readyToProcess(*element)) { - MakeControllerSettings(ControllerSettings); - - if (AllocatedControllerSettings()) { - LoadControllerSettings(element->_controller_idx, *ControllerSettings); - cacheControllerSettings(*ControllerSettings); - START_TIMER; - markProcessed(func(controller_number, *element, *ControllerSettings)); - #if FEATURE_TIMING_STATS - STOP_TIMER_VAR(timerstats_id); - #endif - } - } - Scheduler.scheduleNextDelayQueue(timerID, getNextScheduleTime()); -} +#include "../ControllerQueue/ControllerDelayHandlerStruct.h" + + +ControllerDelayHandlerStruct::ControllerDelayHandlerStruct() : + lastSend(0), + minTimeBetweenMessages(CONTROLLER_DELAY_QUEUE_DELAY_DFLT), + expire_timeout(0), + max_queue_depth(CONTROLLER_DELAY_QUEUE_DEPTH_DFLT), + attempt(0), + max_retries(CONTROLLER_DELAY_QUEUE_RETRY_DFLT), + delete_oldest(false), + must_check_reply(false), + deduplicate(false), + useLocalSystemTime(false) {} + +bool ControllerDelayHandlerStruct::cacheControllerSettings(controllerIndex_t ControllerIndex) +{ + MakeControllerSettings(ControllerSettings); + + if (!AllocatedControllerSettings()) { + return false; + } + LoadControllerSettings(ControllerIndex, *ControllerSettings); + cacheControllerSettings(*ControllerSettings); + return true; +} + +void ControllerDelayHandlerStruct::cacheControllerSettings(const ControllerSettingsStruct& settings) { + minTimeBetweenMessages = settings.MinimalTimeBetweenMessages; + max_queue_depth = settings.MaxQueueDepth; + max_retries = settings.MaxRetry; + delete_oldest = settings.DeleteOldest; + must_check_reply = settings.MustCheckReply; + deduplicate = settings.deduplicate(); + useLocalSystemTime = settings.useLocalSystemTime(); + + if (settings.allowExpire()) { + expire_timeout = max_queue_depth * max_retries * (minTimeBetweenMessages + settings.ClientTimeout); + + if (expire_timeout < CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME) { + expire_timeout = CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME; + } + } else { + expire_timeout = 0; + } + + // Set some sound limits when not configured + if (max_queue_depth == 0) { max_queue_depth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; } + + if (max_retries == 0) { max_retries = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; } + + if (minTimeBetweenMessages == 0) { minTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; } + + // No less than 10 msec between messages. + if (minTimeBetweenMessages < 10) { minTimeBetweenMessages = 10; } +} + +bool ControllerDelayHandlerStruct::readyToProcess(const Queue_element_base& element) const { + const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(element._controller_idx); + + if (protocolIndex == INVALID_PROTOCOL_INDEX) { + return false; + } + + if (getProtocolStruct(protocolIndex).needsNetwork) { + return NetworkConnected(10); + } + return true; +} + +bool ControllerDelayHandlerStruct::queueFull(controllerIndex_t controller_idx) const { + if (sendQueue.size() >= max_queue_depth) { return true; } + + // Number of elements is not exceeding the limit, check memory + int freeHeap = FreeMem(); + { + /* + #ifdef USE_SECOND_HEAP + const int freeHeap2 = FreeMem2ndHeap(); + + if (freeHeap2 < freeHeap) { + freeHeap = freeHeap2; + } + #endif // ifdef USE_SECOND_HEAP + */ + } + +#ifdef ESP32 + if (freeHeap > 50000) +#else + if (freeHeap > 5000) +#endif + { + return false; // Memory is not an issue. + } +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("Controller-"); + log += controller_idx + 1; + log += F(" : Memory used: "); + log += getQueueMemorySize(); + log += F(" bytes "); + log += sendQueue.size(); + log += F(" items "); + log += freeHeap; + log += F(" free"); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + return true; +} + +// Return true if message is already present in the queue +bool ControllerDelayHandlerStruct::isDuplicate(const Queue_element_base& element) const { + // Some controllers may receive duplicate messages, due to lost acknowledgement + // This is actually the same message, so this should not be processed. + if (!unitLastMessageCount.isNew(element.getUnitMessageCount())) { + return true; + } + + // The unit message count is still stored to make sure a new one with the same count + // is considered a duplicate, even when the queue is empty. + unitLastMessageCount.add(element.getUnitMessageCount()); + + // the setting 'deduplicate' does look at the content of the message and only compares it to messages in the queue. + if (deduplicate && !sendQueue.empty()) { + // Use reverse iterator here, as it is more likely a duplicate is added shortly after another. + auto it = sendQueue.rbegin(); // Same as back() + + for (; it != sendQueue.rend(); ++it) { + if (element.isDuplicate(*(it->get()))) { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex(it->get()->_controller_idx); + addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : Remove duplicate"))); + } +#endif // ifndef BUILD_NO_DEBUG + return true; + } + } + } + return false; +} + +// 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) { + return false; + } + if (isDuplicate(*element)) { + return true; + } + + if (delete_oldest) { + // Force add to the queue. + // If max buffer is reached, the oldest in the queue (first to be served) will be removed. + while (queueFull(element->_controller_idx)) { + sendQueue.pop_front(); + attempt = 0; + } + } + + if (!queueFull(element->_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)); + + return true; + } +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + const cpluginID_t cpluginID = getCPluginID_from_ControllerIndex((*element)._controller_idx); + addLogMove(LOG_LEVEL_DEBUG, concat(get_formatted_Controller_number(cpluginID), F(" : queue full"))); + } +#endif // ifndef BUILD_NO_DEBUG + return false; +} + +// Get the next element. +// Remove front element when max_retries is reached. +Queue_element_base * ControllerDelayHandlerStruct::getNext() { + if (sendQueue.empty()) { return nullptr; } + + if (attempt > max_retries) { + sendQueue.pop_front(); + attempt = 0; + } + + if (expire_timeout != 0) { + bool done = false; + + while (!done && !sendQueue.empty()) { + if ((sendQueue.front().get() != nullptr) && (timePassedSince(sendQueue.front()->_timestamp) < static_cast(expire_timeout))) { + done = true; + } else { + sendQueue.pop_front(); + attempt = 0; + } + } + } + + if (sendQueue.empty()) { return nullptr; } + return sendQueue.front().get(); +} + +// 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) { + if (sendQueue.empty()) { return 0; } + + if (remove_from_queue) { + sendQueue.pop_front(); + attempt = 0; + lastSend = millis(); + } else { + ++attempt; + } + return getNextScheduleTime(); +} + +unsigned long ControllerDelayHandlerStruct::getNextScheduleTime() const { + if (sendQueue.empty()) { return 0; } + unsigned long nextTime = lastSend + minTimeBetweenMessages; + + if (timePassedSince(nextTime) > 0) { + nextTime = millis(); + } + + if (nextTime == 0) { nextTime = 1; // Just to make sure it will be executed + } + return nextTime; +} + +// 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; +} + +size_t ControllerDelayHandlerStruct::getQueueMemorySize() const { + size_t totalSize = 0; + + for (auto it = sendQueue.begin(); it != sendQueue.end(); ++it) { + if (it->get() != nullptr) { + totalSize += it->get()->getSize(); + } + } + return totalSize; +} + +void ControllerDelayHandlerStruct::process( + cpluginID_t cpluginID, + do_process_function func, + TimingStatsElements timerstats_id, + SchedulerIntervalTimer_e timerID) +{ + Queue_element_base *element(static_cast(getNext())); + + if (element == nullptr) { return; } + + if (readyToProcess(*element)) { + MakeControllerSettings(ControllerSettings); + + if (AllocatedControllerSettings()) { + LoadControllerSettings(element->_controller_idx, *ControllerSettings); + cacheControllerSettings(*ControllerSettings); + START_TIMER; + markProcessed(func(cpluginID, *element, *ControllerSettings)); + #if FEATURE_TIMING_STATS + STOP_TIMER_VAR(timerstats_id); + #endif + } + } + Scheduler.scheduleNextDelayQueue(timerID, getNextScheduleTime()); +} diff --git a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h index 611e7eb07..f5442cf50 100644 --- a/src/src/ControllerQueue/ControllerDelayHandlerStruct.h +++ b/src/src/ControllerQueue/ControllerDelayHandlerStruct.h @@ -1,94 +1,94 @@ -#ifndef CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H -#define CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../ControllerQueue/Queue_element_base.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/TimingStats.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/CPlugins.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Helpers/_CPlugin_Helper.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Networking.h" -#include "../Helpers/Scheduler.h" -#include "../Helpers/StringConverter.h" - - -#include -#include // For std::shared_ptr -#include // std::nothrow - -#ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME - # define CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME 10000 -#endif // ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME - -typedef bool (*do_process_function)(int, - const Queue_element_base&, - ControllerSettingsStruct&); - -/*********************************************************************************************\ -* ControllerDelayHandlerStruct -\*********************************************************************************************/ -struct ControllerDelayHandlerStruct { - ControllerDelayHandlerStruct(); - - bool cacheControllerSettings(controllerIndex_t ControllerIndex); - void cacheControllerSettings(const ControllerSettingsStruct& settings); - - bool readyToProcess(const Queue_element_base& element) const; - - bool queueFull(controllerIndex_t controller_idx) const; - - // Return true if message is already present in the queue - bool isDuplicate(const Queue_element_base& element) const; - - // 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); - - // Get the next element. - // Remove front element when max_retries is reached. - Queue_element_base* 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 markProcessed(bool remove_from_queue); - - unsigned long 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); - - size_t getQueueMemorySize() const; - - void process( - int controller_number, - 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; -}; - - -#endif // CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H +#ifndef CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H +#define CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../ControllerQueue/Queue_element_base.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../DataStructs/UnitMessageCount.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/CPlugins.h" +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Networking.h" +#include "../Helpers/Scheduler.h" +#include "../Helpers/StringConverter.h" + + +#include +#include // For std::shared_ptr +#include // std::nothrow + +#ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME + # define CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME 10000 +#endif // ifndef CONTROLLER_QUEUE_MINIMAL_EXPIRE_TIME + +typedef bool (*do_process_function)(cpluginID_t, + const Queue_element_base&, + ControllerSettingsStruct&); + +/*********************************************************************************************\ +* ControllerDelayHandlerStruct +\*********************************************************************************************/ +struct ControllerDelayHandlerStruct { + ControllerDelayHandlerStruct(); + + bool cacheControllerSettings(controllerIndex_t ControllerIndex); + void cacheControllerSettings(const ControllerSettingsStruct& settings); + + bool readyToProcess(const Queue_element_base& element) const; + + bool queueFull(controllerIndex_t controller_idx) const; + + // Return true if message is already present in the queue + bool isDuplicate(const Queue_element_base& element) const; + + // 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); + + // Get the next element. + // Remove front element when max_retries is reached. + Queue_element_base* 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 markProcessed(bool remove_from_queue); + + unsigned long 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); + + size_t getQueueMemorySize() const; + + void process( + 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; +}; + + +#endif // CONTROLLERQUEUE_CONTROLLER_DELAY_HANDLER_STRUCT_H diff --git a/src/src/ControllerQueue/DelayQueueElements.h b/src/src/ControllerQueue/DelayQueueElements.h index 6f17670d1..3c44e89ed 100644 --- a/src/src/ControllerQueue/DelayQueueElements.h +++ b/src/src/ControllerQueue/DelayQueueElements.h @@ -1,294 +1,294 @@ -#ifndef DELAY_QUEUE_ELEMENTS_H -#define DELAY_QUEUE_ELEMENTS_H - - -#include "../../ESPEasy_common.h" - - -#include "../ControllerQueue/ControllerDelayHandlerStruct.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../DataStructs/ControllerSettingsStruct.h" - - -// The most logical place to have these queue element handlers defined would be in their -// respective _Cxxx.ino file. -// But the PlatformIO/Arduino build process may then run into issues when compiling. -// Either some of the functions may not be (forward) declared yet when being called from the scheduler code. -// Or the forward declaration of a function may be generated by the pre-processor when expanding the macro to generate them. -// The #ifdef USES_Cxxx check is then no longer present in the generated ESPEasy.ino.cpp file which will lead to build errors -// when not all controllers are included in the build. -// -// To overcome build errors, one MUST forward declare the do_process_cXXX_delay_queue function in the .ino file of the controller itself. -// If someone finds a better way, please let me know. -// See: https://github.com/platformio/platformio-core/issues/2972 -// -// N.B. These queue element classes should be defined as class (not a struct), to be used as template. -// - - - - -// Uncrustify must not be used on macros, so turn it off. -// Also make sure to wrap the forward declaration of this function in the same wrappers -// as it may not split the forward declaration into multiple lines. -// -// *INDENT-OFF* - - - -// Define the function wrappers to handle the calling to Cxxx_DelayHandler etc. -// If someone knows how to add leading zeros in macros, please be my guest :) - - -// This macro defines the code needed to create the 'process_c##NNN####M##_delay_queue()' -// function and all needed objects and forward declarations. -// It is a macro to prevent common typo errors. -// This function will perform the (re)scheduling and mark if it is processed (and can be removed) -// The controller itself must implement the 'do_process_c004_delay_queue' function to actually -// send the data. -// Its return value must state whether it can be marked 'Processed'. -// N.B. some controllers only can send one value per iteration, so a returned "false" can mean it -// was still successful. The controller should keep track of the last value sent -// in the element stored in the queue. -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \ - extern struct ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler; \ - bool do_process_c##NNN####M##_delay_queue(int controller_number, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ - void process_c##NNN####M##_delay_queue(); \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ - void exit_c##NNN####M##_delay_queue(); \ - - -# ifdef USE_SECOND_HEAP - -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ - ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ - void process_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler == nullptr) return; \ - C##NNN####M##_DelayHandler->process( \ - M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ - SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ - } \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ - if (C##NNN####M##_DelayHandler == nullptr) { \ - HeapSelectDram ephemeral; \ - C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ - } \ - if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ - return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ - } \ - void exit_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler != nullptr) { \ - delete C##NNN####M##_DelayHandler; \ - C##NNN####M##_DelayHandler = nullptr; \ - } \ - } \ - -#else - -#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ - ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ - void process_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler == nullptr) return; \ - C##NNN####M##_DelayHandler->process( \ - M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ - SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ - } \ - bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ - if (C##NNN####M##_DelayHandler == nullptr) { \ - C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ - } \ - if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ - return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ - } \ - void exit_c##NNN####M##_delay_queue() { \ - if (C##NNN####M##_DelayHandler != nullptr) { \ - delete C##NNN####M##_DelayHandler; \ - C##NNN####M##_DelayHandler = nullptr; \ - } \ - } \ - - -#endif - - - -// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. -// *INDENT-ON* - - - - -#if FEATURE_MQTT -# include "../ControllerQueue/MQTT_queue_element.h" -extern struct ControllerDelayHandlerStruct *MQTTDelayHandler; - -bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, - String & pubname, - bool & retainFlag); -void exit_mqtt_delay_queue(); -#endif // if FEATURE_MQTT - - -/*********************************************************************************************\ -* C001_queue_element for queueing requests for C001. -\*********************************************************************************************/ -#ifdef USES_C001 -# include "../ControllerQueue/SimpleQueueElement_string_only.h" -typedef simple_queue_element_string_only C001_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 1) -#endif // ifdef USES_C001 - -/*********************************************************************************************\ -* C003_queue_element for queueing requests for C003 Nodo Telnet. -\*********************************************************************************************/ -#ifdef USES_C003 -# include "../ControllerQueue/SimpleQueueElement_string_only.h" -typedef simple_queue_element_string_only C003_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 3) -#endif // ifdef USES_C003 - -#ifdef USES_C004 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C004_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 4) -#endif // ifdef USES_C004 - -#ifdef USES_C007 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C007_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 7) -#endif // ifdef USES_C007 - - -/*********************************************************************************************\ -* C008_queue_element for queueing requests for 008: Generic HTTP -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C008 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C008_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 8) -#endif // ifdef USES_C008 - -#ifdef USES_C009 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C009_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 9) -#endif // ifdef USES_C009 - - -/*********************************************************************************************\ -* C010_queue_element for queueing requests for 010: Generic UDP -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C010 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C010_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 10) -#endif // ifdef USES_C010 - - -/*********************************************************************************************\ -* C011_queue_element for queueing requests for 011: Generic HTTP Advanced -\*********************************************************************************************/ -#ifdef USES_C011 -# include "../ControllerQueue/C011_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 11) -#endif // ifdef USES_C011 - - -/*********************************************************************************************\ -* C012_queue_element for queueing requests for 012: Blynk -* Using SimpleQueueElement_formatted_Strings -\*********************************************************************************************/ -#ifdef USES_C012 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C012_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 12) -#endif // ifdef USES_C012 - -/* - #ifdef USES_C013 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 13) - #endif - */ - -/* - #ifdef USES_C014 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 14) - #endif - */ - - -#ifdef USES_C015 -# include "../ControllerQueue/C015_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 15) -#endif // ifdef USES_C015 - - -#ifdef USES_C016 -# include "../ControllerQueue/C016_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 16) -#endif // ifdef USES_C016 - - -#ifdef USES_C017 -# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" -typedef SimpleQueueElement_formatted_Strings C017_queue_element; -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 17) -#endif // ifdef USES_C017 - -#ifdef USES_C018 -# include "../ControllerQueue/C018_queue_element.h" -DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 18) -#endif // ifdef USES_C018 - - -/* - #ifdef USES_C019 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 19) - #endif - */ - -/* - #ifdef USES_C020 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 20) - #endif - */ - -/* - #ifdef USES_C021 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 21) - #endif - */ - -/* - #ifdef USES_C022 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 22) - #endif - */ - -/* - #ifdef USES_C023 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 23) - #endif - */ - -/* - #ifdef USES_C024 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 24) - #endif - */ - -/* - #ifdef USES_C025 - DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 25) - #endif - */ - - -// When extending this, search for EXTEND_CONTROLLER_IDS -// in the code to find all places that need to be updated too. - - -#endif // ifndef DELAY_QUEUE_ELEMENTS_H +#ifndef DELAY_QUEUE_ELEMENTS_H +#define DELAY_QUEUE_ELEMENTS_H + + +#include "../../ESPEasy_common.h" + + +#include "../ControllerQueue/ControllerDelayHandlerStruct.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../DataStructs/ControllerSettingsStruct.h" + + +// The most logical place to have these queue element handlers defined would be in their +// respective _Cxxx.ino file. +// But the PlatformIO/Arduino build process may then run into issues when compiling. +// Either some of the functions may not be (forward) declared yet when being called from the scheduler code. +// Or the forward declaration of a function may be generated by the pre-processor when expanding the macro to generate them. +// The #ifdef USES_Cxxx check is then no longer present in the generated ESPEasy.ino.cpp file which will lead to build errors +// when not all controllers are included in the build. +// +// To overcome build errors, one MUST forward declare the do_process_cXXX_delay_queue function in the .ino file of the controller itself. +// If someone finds a better way, please let me know. +// See: https://github.com/platformio/platformio-core/issues/2972 +// +// N.B. These queue element classes should be defined as class (not a struct), to be used as template. +// + + + + +// Uncrustify must not be used on macros, so turn it off. +// Also make sure to wrap the forward declaration of this function in the same wrappers +// as it may not split the forward declaration into multiple lines. +// +// *INDENT-OFF* + + + +// Define the function wrappers to handle the calling to Cxxx_DelayHandler etc. +// If someone knows how to add leading zeros in macros, please be my guest :) + + +// This macro defines the code needed to create the 'process_c##NNN####M##_delay_queue()' +// function and all needed objects and forward declarations. +// It is a macro to prevent common typo errors. +// This function will perform the (re)scheduling and mark if it is processed (and can be removed) +// The controller itself must implement the 'do_process_c004_delay_queue' function to actually +// send the data. +// Its return value must state whether it can be marked 'Processed'. +// N.B. some controllers only can send one value per iteration, so a returned "false" can mean it +// was still successful. The controller should keep track of the last value sent +// in the element stored in the queue. +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO(NNN, M) \ + extern struct ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler; \ + bool do_process_c##NNN####M##_delay_queue(cpluginID_t cpluginID, const Queue_element_base & element, ControllerSettingsStruct & ControllerSettings); \ + void process_c##NNN####M##_delay_queue(); \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex); \ + void exit_c##NNN####M##_delay_queue(); \ + + +# ifdef USE_SECOND_HEAP + +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ + ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ + void process_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler == nullptr) return; \ + C##NNN####M##_DelayHandler->process( \ + M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ + SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ + } \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ + if (C##NNN####M##_DelayHandler == nullptr) { \ + HeapSelectDram ephemeral; \ + C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ + } \ + if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ + return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ + } \ + void exit_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler != nullptr) { \ + delete C##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler = nullptr; \ + } \ + } \ + +#else + +#define DEFINE_Cxxx_DELAY_QUEUE_MACRO_CPP(NNN, M) \ + ControllerDelayHandlerStruct *C##NNN####M##_DelayHandler = nullptr; \ + void process_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler == nullptr) return; \ + C##NNN####M##_DelayHandler->process( \ + M, do_process_c##NNN####M##_delay_queue, TimingStatsElements::C##NNN####M##_DELAY_QUEUE, \ + SchedulerIntervalTimer_e::TIMER_C##NNN####M##_DELAY_QUEUE); \ + } \ + bool init_c##NNN####M##_delay_queue(controllerIndex_t ControllerIndex) { \ + if (C##NNN####M##_DelayHandler == nullptr) { \ + C##NNN####M##_DelayHandler = new (std::nothrow) (ControllerDelayHandlerStruct); \ + } \ + if (C##NNN####M##_DelayHandler == nullptr) { return false; } \ + return C##NNN####M##_DelayHandler->cacheControllerSettings(ControllerIndex); \ + } \ + void exit_c##NNN####M##_delay_queue() { \ + if (C##NNN####M##_DelayHandler != nullptr) { \ + delete C##NNN####M##_DelayHandler; \ + C##NNN####M##_DelayHandler = nullptr; \ + } \ + } \ + + +#endif + + + +// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. +// *INDENT-ON* + + + + +#if FEATURE_MQTT +# include "../ControllerQueue/MQTT_queue_element.h" +extern struct ControllerDelayHandlerStruct *MQTTDelayHandler; + +bool init_mqtt_delay_queue(controllerIndex_t ControllerIndex, + String & pubname, + bool & retainFlag); +void exit_mqtt_delay_queue(); +#endif // if FEATURE_MQTT + + +/*********************************************************************************************\ +* C001_queue_element for queueing requests for C001. +\*********************************************************************************************/ +#ifdef USES_C001 +# include "../ControllerQueue/SimpleQueueElement_string_only.h" +typedef simple_queue_element_string_only C001_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 1) +#endif // ifdef USES_C001 + +/*********************************************************************************************\ +* C003_queue_element for queueing requests for C003 Nodo Telnet. +\*********************************************************************************************/ +#ifdef USES_C003 +# include "../ControllerQueue/SimpleQueueElement_string_only.h" +typedef simple_queue_element_string_only C003_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 3) +#endif // ifdef USES_C003 + +#ifdef USES_C004 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C004_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 4) +#endif // ifdef USES_C004 + +#ifdef USES_C007 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C007_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 7) +#endif // ifdef USES_C007 + + +/*********************************************************************************************\ +* C008_queue_element for queueing requests for 008: Generic HTTP +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C008 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C008_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 8) +#endif // ifdef USES_C008 + +#ifdef USES_C009 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C009_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(00, 9) +#endif // ifdef USES_C009 + + +/*********************************************************************************************\ +* C010_queue_element for queueing requests for 010: Generic UDP +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C010 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C010_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 10) +#endif // ifdef USES_C010 + + +/*********************************************************************************************\ +* C011_queue_element for queueing requests for 011: Generic HTTP Advanced +\*********************************************************************************************/ +#ifdef USES_C011 +# include "../ControllerQueue/C011_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 11) +#endif // ifdef USES_C011 + + +/*********************************************************************************************\ +* C012_queue_element for queueing requests for 012: Blynk +* Using SimpleQueueElement_formatted_Strings +\*********************************************************************************************/ +#ifdef USES_C012 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C012_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 12) +#endif // ifdef USES_C012 + +/* + #ifdef USES_C013 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 13) + #endif + */ + +/* + #ifdef USES_C014 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 14) + #endif + */ + + +#ifdef USES_C015 +# include "../ControllerQueue/C015_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 15) +#endif // ifdef USES_C015 + + +#ifdef USES_C016 +# include "../ControllerQueue/C016_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 16) +#endif // ifdef USES_C016 + + +#ifdef USES_C017 +# include "../ControllerQueue/SimpleQueueElement_formatted_Strings.h" +typedef SimpleQueueElement_formatted_Strings C017_queue_element; +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 17) +#endif // ifdef USES_C017 + +#ifdef USES_C018 +# include "../ControllerQueue/C018_queue_element.h" +DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 18) +#endif // ifdef USES_C018 + + +/* + #ifdef USES_C019 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 19) + #endif + */ + +/* + #ifdef USES_C020 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 20) + #endif + */ + +/* + #ifdef USES_C021 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 21) + #endif + */ + +/* + #ifdef USES_C022 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 22) + #endif + */ + +/* + #ifdef USES_C023 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 23) + #endif + */ + +/* + #ifdef USES_C024 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 24) + #endif + */ + +/* + #ifdef USES_C025 + DEFINE_Cxxx_DELAY_QUEUE_MACRO(0, 25) + #endif + */ + + +// When extending this, search for EXTEND_CONTROLLER_IDS +// in the code to find all places that need to be updated too. + + +#endif // ifndef DELAY_QUEUE_ELEMENTS_H diff --git a/src/src/ControllerQueue/MQTT_queue_element.h b/src/src/ControllerQueue/MQTT_queue_element.h index 55ab0545f..4e0436cb5 100644 --- a/src/src/ControllerQueue/MQTT_queue_element.h +++ b/src/src/ControllerQueue/MQTT_queue_element.h @@ -1,60 +1,60 @@ -#ifndef CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H -#define CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_MQTT - -# include "../ControllerQueue/Queue_element_base.h" -# include "../DataStructs/UnitMessageCount.h" -# include "../Globals/CPlugins.h" - -/*********************************************************************************************\ -* MQTT_queue_element for all MQTT base controllers -\*********************************************************************************************/ -class MQTT_queue_element : public Queue_element_base { -public: - - MQTT_queue_element() = default; - - MQTT_queue_element(const MQTT_queue_element& other) = delete; - - MQTT_queue_element(MQTT_queue_element&& other) = default; - - explicit MQTT_queue_element(int ctrl_idx, - taskIndex_t TaskIndex, - const String& topic, - const String& payload, - bool retained, - bool callbackTask); - - explicit MQTT_queue_element(int ctrl_idx, - taskIndex_t TaskIndex, - String && topic, - String && payload, - bool retained, - bool callbackTask); - - size_t getSize() const; - - bool isDuplicate(const Queue_element_base& other) const; - - const UnitMessageCount_t* getUnitMessageCount() const { - return &UnitMessageCount; - } - - UnitMessageCount_t* getUnitMessageCount() { - return &UnitMessageCount; - } - - void removeEmptyTopics(); - - String _topic{}; - String _payload{}; - UnitMessageCount_t UnitMessageCount{}; - bool _retained = false; -}; - -#endif // if FEATURE_MQTT - -#endif // CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H +#ifndef CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H +#define CONTROLLERQUEUE_MQTT_QUEUE_ELEMENT_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_MQTT + +# include "../ControllerQueue/Queue_element_base.h" +# include "../DataStructs/UnitMessageCount.h" +# include "../Globals/CPlugins.h" + +/*********************************************************************************************\ +* MQTT_queue_element for all MQTT base controllers +\*********************************************************************************************/ +class MQTT_queue_element : public Queue_element_base { +public: + + MQTT_queue_element() = default; + + MQTT_queue_element(const MQTT_queue_element& other) = delete; + + MQTT_queue_element(MQTT_queue_element&& other) = default; + + explicit MQTT_queue_element(int ctrl_idx, + taskIndex_t TaskIndex, + const String& topic, + const String& payload, + bool retained, + bool callbackTask); + + explicit MQTT_queue_element(int ctrl_idx, + taskIndex_t TaskIndex, + String && topic, + String && payload, + bool retained, + bool callbackTask); + + size_t getSize() const; + + bool isDuplicate(const Queue_element_base& other) const; + + const UnitMessageCount_t* getUnitMessageCount() const { + return &UnitMessageCount; + } + + UnitMessageCount_t* getUnitMessageCount() { + return &UnitMessageCount; + } + + void removeEmptyTopics(); + + String _topic{}; + String _payload{}; + UnitMessageCount_t UnitMessageCount{}; + bool _retained = false; +}; + +#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 aa05a57b3..68e5f6a43 100644 --- a/src/src/ControllerQueue/Queue_element_base.h +++ b/src/src/ControllerQueue/Queue_element_base.h @@ -1,39 +1,39 @@ -#ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H -#define CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" - -/*********************************************************************************************\ -* Base class for all controller queue elements -\*********************************************************************************************/ -class Queue_element_base { -public: - Queue_element_base(); - - virtual ~Queue_element_base(); - - virtual size_t getSize() const = 0; - - virtual bool isDuplicate(const Queue_element_base& other) const = 0; - - virtual const UnitMessageCount_t* getUnitMessageCount() const = 0; - virtual UnitMessageCount_t * getUnitMessageCount() = 0; - - unsigned long _timestamp; - controllerIndex_t _controller_idx; - taskIndex_t _taskIndex; - - // Call PLUGIN_PROCESS_CONTROLLER_DATA which may process the data. - // Typical use case is dumping large data which would otherwise take up lot of RAM. - bool _call_PLUGIN_PROCESS_CONTROLLER_DATA; - - // Some formatting of values can be done when actually sending it. - // This may require less RAM than keeping formatted strings in memory - bool _processByController; -}; - -#endif // ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H +#ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H +#define CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H + + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/UnitMessageCount.h" +#include "../Globals/CPlugins.h" + +/*********************************************************************************************\ +* Base class for all controller queue elements +\*********************************************************************************************/ +class Queue_element_base { +public: + Queue_element_base(); + + virtual ~Queue_element_base(); + + virtual size_t getSize() const = 0; + + virtual bool isDuplicate(const Queue_element_base& other) const = 0; + + virtual const UnitMessageCount_t* getUnitMessageCount() const = 0; + virtual UnitMessageCount_t * getUnitMessageCount() = 0; + + unsigned long _timestamp; + controllerIndex_t _controller_idx; + taskIndex_t _taskIndex; + + // Call PLUGIN_PROCESS_CONTROLLER_DATA which may process the data. + // Typical use case is dumping large data which would otherwise take up lot of RAM. + bool _call_PLUGIN_PROCESS_CONTROLLER_DATA; + + // Some formatting of values can be done when actually sending it. + // This may require less RAM than keeping formatted strings in memory + bool _processByController; +}; + +#endif // ifndef CONTROLLERQUEUE_QUEUE_ELEMENT_BASE_H diff --git a/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h b/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h index 6c5a7d797..f812063e5 100644 --- a/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h +++ b/src/src/ControllerQueue/SimpleQueueElement_formatted_Strings.h @@ -1,62 +1,62 @@ -#ifndef CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H -#define CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H - - -#include "../../ESPEasy_common.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" -#include "../Globals/Plugins.h" - -struct EventStruct; - -/*********************************************************************************************\ -* Base element class for keeping task value strings in a controller queue -* Can also be used for controllers only sending a single value at a time. -\*********************************************************************************************/ -class SimpleQueueElement_formatted_Strings : public Queue_element_base { -public: - - SimpleQueueElement_formatted_Strings() = default; - - // Constructor formatting the task values using the default formatter - SimpleQueueElement_formatted_Strings(struct EventStruct *event); - - // Constructor not formatting the values - SimpleQueueElement_formatted_Strings(const struct EventStruct *event, - uint8_t value_count); - - - SimpleQueueElement_formatted_Strings(const SimpleQueueElement_formatted_Strings& rval) = delete; - - SimpleQueueElement_formatted_Strings(SimpleQueueElement_formatted_Strings&& rval); - - SimpleQueueElement_formatted_Strings& operator=(SimpleQueueElement_formatted_Strings&& other); - - - // For controllers that only send a single value per request and thus need to keep track of the number of values already sent. - bool checkDone(bool succesfull) const; - - 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 txt[VARS_PER_TASK] = {}; - int idx = 0; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; - mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() - uint8_t valueCount = 0; -}; - - -#endif // CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H +#ifndef CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H +#define CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H + + +#include "../../ESPEasy_common.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataStructs/UnitMessageCount.h" +#include "../Globals/CPlugins.h" +#include "../Globals/Plugins.h" + +struct EventStruct; + +/*********************************************************************************************\ +* Base element class for keeping task value strings in a controller queue +* Can also be used for controllers only sending a single value at a time. +\*********************************************************************************************/ +class SimpleQueueElement_formatted_Strings : public Queue_element_base { +public: + + SimpleQueueElement_formatted_Strings() = default; + + // Constructor formatting the task values using the default formatter + SimpleQueueElement_formatted_Strings(struct EventStruct *event); + + // Constructor not formatting the values + SimpleQueueElement_formatted_Strings(const struct EventStruct *event, + uint8_t value_count); + + + SimpleQueueElement_formatted_Strings(const SimpleQueueElement_formatted_Strings& rval) = delete; + + SimpleQueueElement_formatted_Strings(SimpleQueueElement_formatted_Strings&& rval); + + SimpleQueueElement_formatted_Strings& operator=(SimpleQueueElement_formatted_Strings&& other); + + + // For controllers that only send a single value per request and thus need to keep track of the number of values already sent. + bool checkDone(bool succesfull) const; + + 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 txt[VARS_PER_TASK] = {}; + int idx = 0; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + mutable uint8_t valuesSent = 0; // Value must be set by const function checkDone() + uint8_t valueCount = 0; +}; + + +#endif // CONTROLQUEUE_QUEUE_ELEMENT_SINGLE_VALUE_BASE_H diff --git a/src/src/ControllerQueue/SimpleQueueElement_string_only.h b/src/src/ControllerQueue/SimpleQueueElement_string_only.h index ea1f2f0c7..488eeabe3 100644 --- a/src/src/ControllerQueue/SimpleQueueElement_string_only.h +++ b/src/src/ControllerQueue/SimpleQueueElement_string_only.h @@ -1,42 +1,42 @@ -#ifndef CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H -#define CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H - -#include "../../ESPEasy_common.h" -#include "../ControllerQueue/Queue_element_base.h" -#include "../DataStructs/UnitMessageCount.h" -#include "../Globals/CPlugins.h" - - -/*********************************************************************************************\ -* Simple queue element, only storing controller index and some String -\*********************************************************************************************/ -class simple_queue_element_string_only : public Queue_element_base { -public: - - simple_queue_element_string_only() = default; - - simple_queue_element_string_only(const simple_queue_element_string_only& other) = delete; - - simple_queue_element_string_only(simple_queue_element_string_only&& other) = default; - - explicit simple_queue_element_string_only(int ctrl_idx, - taskIndex_t TaskIndex, - String && req); - - 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 txt; -}; - - -#endif // CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H +#ifndef CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H +#define CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H + +#include "../../ESPEasy_common.h" +#include "../ControllerQueue/Queue_element_base.h" +#include "../DataStructs/UnitMessageCount.h" +#include "../Globals/CPlugins.h" + + +/*********************************************************************************************\ +* Simple queue element, only storing controller index and some String +\*********************************************************************************************/ +class simple_queue_element_string_only : public Queue_element_base { +public: + + simple_queue_element_string_only() = default; + + simple_queue_element_string_only(const simple_queue_element_string_only& other) = delete; + + simple_queue_element_string_only(simple_queue_element_string_only&& other) = default; + + explicit simple_queue_element_string_only(int ctrl_idx, + taskIndex_t TaskIndex, + String && req); + + 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 txt; +}; + + +#endif // CONTROLLERQUEUE_SIMPLE_QUEUE_ELEMENT_STRING_ONLY_H diff --git a/src/src/Controller_config/C018_config.cpp b/src/src/Controller_config/C018_config.cpp index a3073bcae..c4fac1b5d 100644 --- a/src/src/Controller_config/C018_config.cpp +++ b/src/src/Controller_config/C018_config.cpp @@ -1,204 +1,204 @@ -#include "../Controller_config/C018_config.h" - -#ifdef USES_C018 - -# include "../Controller_struct/C018_data_struct.h" - -# define C018_BAUDRATE_LABEL "baudrate" - -void C018_ConfigStruct::validate() { - ZERO_TERMINATE(DeviceEUI); - ZERO_TERMINATE(DeviceAddr); - ZERO_TERMINATE(NetworkSessionKey); - ZERO_TERMINATE(AppSessionKey); - - if ((baudrate < 2400) || (baudrate > 115200)) { - reset(); - } - - if (stackVersion >= RN2xx3_datatypes::TTN_stack_version::TTN_NOT_SET) { - stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; - } - - 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 C018_ConfigStruct::reset() { - ZERO_FILL(DeviceEUI); - ZERO_FILL(DeviceAddr); - ZERO_FILL(NetworkSessionKey); - ZERO_FILL(AppSessionKey); - baudrate = 57600; - rxpin = -1; - txpin = -1; - resetpin = -1; - sf = 7; - frequencyplan = RN2xx3_datatypes::Freq_plan::TTN_EU; - rx2_freq = 0; - stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; - joinmethod = C018_USE_OTAA; -} - -void C018_ConfigStruct::webform_load(C018_data_struct *C018_data) { - validate(); - ESPEasySerialPort port = static_cast(serialPort); - - { - addFormTextBox(F("Device EUI"), F("deveui"), DeviceEUI, C018_DEVICE_EUI_LEN - 1); - String deveui_note = F("Leave empty to use HW DevEUI: "); - - if (C018_data != nullptr) { - deveui_note += C018_data->hweui(); - } - addFormNote(deveui_note, F("deveui_note")); - } - - addFormTextBox(F("Device Addr"), F("devaddr"), DeviceAddr, C018_DEVICE_ADDR_LEN - 1); - addFormTextBox(F("Network Session Key"), F("nskey"), NetworkSessionKey, C018_NETWORK_SESSION_KEY_LEN - 1); - addFormTextBox(F("App Session Key"), F("appskey"), AppSessionKey, C018_APP_SESSION_KEY_LEN - 1); - - { - const __FlashStringHelper *options[2] = { F("OTAA"), F("ABP") }; - const int values[2] = { C018_USE_OTAA, C018_USE_ABP }; - addFormSelector_script(F("Activation Method"), F("joinmethod"), 2, - options, values, nullptr, joinmethod, - F("joinChanged(this)")); // Script to toggle OTAA/ABP fields visibility when changing selection. - } - html_add_script(F("document.getElementById('joinmethod').onchange();"), false); - - addTableSeparator(F("Connection Configuration"), 2, 3); - { - const __FlashStringHelper *options[4] = { F("SINGLE_CHANNEL_EU"), F("TTN_EU"), F("TTN_US"), F("DEFAULT_EU") }; - int values[4] = - { - 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 - }; - - addFormSelector(F("Frequency Plan"), F("frequencyplan"), 4, options, values, nullptr, frequencyplan, false); - addFormNumericBox(F("RX2 Frequency"), F("rx2freq"), rx2_freq, 0); - addUnit(F("Hz")); - addFormNote(F("0 = default, or else override default")); - } - { - const __FlashStringHelper *options[2] = { F("TTN v2"), F("TTN v3") }; - int values[2] = { - RN2xx3_datatypes::TTN_stack_version::TTN_v2, - RN2xx3_datatypes::TTN_stack_version::TTN_v3 - }; - - addFormSelector(F("TTN Stack"), F("ttnstack"), 2, options, values, nullptr, stackVersion, false); - } - - addFormNumericBox(F("Spread Factor"), F("sf"), sf, 7, 12); - addFormCheckBox(F("Adaptive Data Rate (ADR)"), F("adr"), adr); - - - 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(C018_BAUDRATE_LABEL), baudrate, 2400, 115200); - addUnit(F("baud")); - addFormNote(F("Module default baudrate: 57600 bps")); - - // Optional reset pin RN2xx3 - addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Reset")), F("taskdevicepin3"), resetpin); - - addTableSeparator(F("Device Status"), 2, 3); - - if (C018_data != nullptr) { - // Some information on detected device - addRowLabel(F("Hardware DevEUI")); - addHtml(C018_data->hweui()); - addRowLabel(F("Version Number")); - addHtml(C018_data->sysver()); - - addRowLabel(F("Voltage")); - addHtmlFloat(static_cast(C018_data->getVbat()) / 1000.0f, 3); - - addRowLabel(F("Device Addr")); - addHtml(C018_data->getDevaddr()); - - uint32_t dnctr, upctr; - - if (C018_data->getFrameCounters(dnctr, upctr)) { - addRowLabel(F("Frame Counters (down/up)")); - String values = String(dnctr); - values += '/'; - values += upctr; - addHtml(values); - } - - addRowLabel(F("Last Command Error")); - addHtml(C018_data->getLastError()); - - addRowLabel(F("Sample Set Counter")); - addHtmlInt(static_cast(C018_data->getSampleSetCount())); - - addRowLabel(F("Data Rate")); - addHtml(C018_data->getDataRate()); - - { - RN2xx3_status status = C018_data->getStatus(); - - addRowLabel(F("Status RAW value")); - addHtmlInt(status.getRawStatus()); - - addRowLabel(F("Activation Status")); - addEnabled(status.Joined); - - addRowLabel(F("Silent Immediately")); - addHtmlInt(static_cast(status.SilentImmediately ? 1 : 0)); - } - } -} - -void C018_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(C018_BAUDRATE_LABEL), baudrate); - rxpin = getFormItemInt(F("taskdevicepin1"), rxpin); - txpin = getFormItemInt(F("taskdevicepin2"), txpin); - resetpin = getFormItemInt(F("taskdevicepin3"), resetpin); - sf = getFormItemInt(F("sf"), sf); - frequencyplan = getFormItemInt(F("frequencyplan"), frequencyplan); - rx2_freq = getFormItemInt(F("rx2freq"), rx2_freq); - joinmethod = getFormItemInt(F("joinmethod"), joinmethod); - stackVersion = getFormItemInt(F("ttnstack"), stackVersion); - adr = isFormItemChecked(F("adr")); - serialHelper_webformSave(serialPort, rxpin, txpin); -} - -#endif // ifdef USES_C018 +#include "../Controller_config/C018_config.h" + +#ifdef USES_C018 + +# include "../Controller_struct/C018_data_struct.h" + +# define C018_BAUDRATE_LABEL "baudrate" + +void C018_ConfigStruct::validate() { + ZERO_TERMINATE(DeviceEUI); + ZERO_TERMINATE(DeviceAddr); + ZERO_TERMINATE(NetworkSessionKey); + ZERO_TERMINATE(AppSessionKey); + + if ((baudrate < 2400) || (baudrate > 115200)) { + reset(); + } + + if (stackVersion >= RN2xx3_datatypes::TTN_stack_version::TTN_NOT_SET) { + stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; + } + + 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 C018_ConfigStruct::reset() { + ZERO_FILL(DeviceEUI); + ZERO_FILL(DeviceAddr); + ZERO_FILL(NetworkSessionKey); + ZERO_FILL(AppSessionKey); + baudrate = 57600; + rxpin = -1; + txpin = -1; + resetpin = -1; + sf = 7; + frequencyplan = RN2xx3_datatypes::Freq_plan::TTN_EU; + rx2_freq = 0; + stackVersion = RN2xx3_datatypes::TTN_stack_version::TTN_v3; + joinmethod = C018_USE_OTAA; +} + +void C018_ConfigStruct::webform_load(C018_data_struct *C018_data) { + validate(); + ESPEasySerialPort port = static_cast(serialPort); + + { + addFormTextBox(F("Device EUI"), F("deveui"), DeviceEUI, C018_DEVICE_EUI_LEN - 1); + String deveui_note = F("Leave empty to use HW DevEUI: "); + + if (C018_data != nullptr) { + deveui_note += C018_data->hweui(); + } + addFormNote(deveui_note, F("deveui_note")); + } + + addFormTextBox(F("Device Addr"), F("devaddr"), DeviceAddr, C018_DEVICE_ADDR_LEN - 1); + addFormTextBox(F("Network Session Key"), F("nskey"), NetworkSessionKey, C018_NETWORK_SESSION_KEY_LEN - 1); + addFormTextBox(F("App Session Key"), F("appskey"), AppSessionKey, C018_APP_SESSION_KEY_LEN - 1); + + { + const __FlashStringHelper *options[2] = { F("OTAA"), F("ABP") }; + const int values[2] = { C018_USE_OTAA, C018_USE_ABP }; + addFormSelector_script(F("Activation Method"), F("joinmethod"), 2, + options, values, nullptr, joinmethod, + F("joinChanged(this)")); // Script to toggle OTAA/ABP fields visibility when changing selection. + } + html_add_script(F("document.getElementById('joinmethod').onchange();"), false); + + addTableSeparator(F("Connection Configuration"), 2, 3); + { + const __FlashStringHelper *options[4] = { F("SINGLE_CHANNEL_EU"), F("TTN_EU"), F("TTN_US"), F("DEFAULT_EU") }; + int values[4] = + { + 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 + }; + + addFormSelector(F("Frequency Plan"), F("frequencyplan"), 4, options, values, nullptr, frequencyplan, false); + addFormNumericBox(F("RX2 Frequency"), F("rx2freq"), rx2_freq, 0); + addUnit(F("Hz")); + addFormNote(F("0 = default, or else override default")); + } + { + const __FlashStringHelper *options[2] = { F("TTN v2"), F("TTN v3") }; + int values[2] = { + RN2xx3_datatypes::TTN_stack_version::TTN_v2, + RN2xx3_datatypes::TTN_stack_version::TTN_v3 + }; + + addFormSelector(F("TTN Stack"), F("ttnstack"), 2, options, values, nullptr, stackVersion, false); + } + + addFormNumericBox(F("Spread Factor"), F("sf"), sf, 7, 12); + addFormCheckBox(F("Adaptive Data Rate (ADR)"), F("adr"), adr); + + + 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(C018_BAUDRATE_LABEL), baudrate, 2400, 115200); + addUnit(F("baud")); + addFormNote(F("Module default baudrate: 57600 bps")); + + // Optional reset pin RN2xx3 + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Reset")), F("taskdevicepin3"), resetpin); + + addTableSeparator(F("Device Status"), 2, 3); + + if (C018_data != nullptr) { + // Some information on detected device + addRowLabel(F("Hardware DevEUI")); + addHtml(C018_data->hweui()); + addRowLabel(F("Version Number")); + addHtml(C018_data->sysver()); + + addRowLabel(F("Voltage")); + addHtmlFloat(static_cast(C018_data->getVbat()) / 1000.0f, 3); + + addRowLabel(F("Device Addr")); + addHtml(C018_data->getDevaddr()); + + uint32_t dnctr, upctr; + + if (C018_data->getFrameCounters(dnctr, upctr)) { + addRowLabel(F("Frame Counters (down/up)")); + String values = String(dnctr); + values += '/'; + values += upctr; + addHtml(values); + } + + addRowLabel(F("Last Command Error")); + addHtml(C018_data->getLastError()); + + addRowLabel(F("Sample Set Counter")); + addHtmlInt(static_cast(C018_data->getSampleSetCount())); + + addRowLabel(F("Data Rate")); + addHtml(C018_data->getDataRate()); + + { + RN2xx3_status status = C018_data->getStatus(); + + addRowLabel(F("Status RAW value")); + addHtmlInt(status.getRawStatus()); + + addRowLabel(F("Activation Status")); + addEnabled(status.Joined); + + addRowLabel(F("Silent Immediately")); + addHtmlInt(static_cast(status.SilentImmediately ? 1 : 0)); + } + } +} + +void C018_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(C018_BAUDRATE_LABEL), baudrate); + rxpin = getFormItemInt(F("taskdevicepin1"), rxpin); + txpin = getFormItemInt(F("taskdevicepin2"), txpin); + resetpin = getFormItemInt(F("taskdevicepin3"), resetpin); + sf = getFormItemInt(F("sf"), sf); + frequencyplan = getFormItemInt(F("frequencyplan"), frequencyplan); + rx2_freq = getFormItemInt(F("rx2freq"), rx2_freq); + joinmethod = getFormItemInt(F("joinmethod"), joinmethod); + stackVersion = getFormItemInt(F("ttnstack"), stackVersion); + adr = isFormItemChecked(F("adr")); + serialHelper_webformSave(serialPort, rxpin, txpin); +} + +#endif // ifdef USES_C018 diff --git a/src/src/Controller_config/C018_config.h b/src/src/Controller_config/C018_config.h index 8a142b352..0bc9f4fde 100644 --- a/src/src/Controller_config/C018_config.h +++ b/src/src/Controller_config/C018_config.h @@ -8,8 +8,7 @@ // Forward declaration struct C018_data_struct; -# include - +#include # define C018_DEVICE_EUI_LEN 17 # define C018_DEVICE_ADDR_LEN 33 diff --git a/src/src/Controller_struct/C018_data_struct.cpp b/src/src/Controller_struct/C018_data_struct.cpp index 782c7e473..16e1ce911 100644 --- a/src/src/Controller_struct/C018_data_struct.cpp +++ b/src/src/Controller_struct/C018_data_struct.cpp @@ -2,6 +2,11 @@ #ifdef USES_C018 + +# include +# include + + C018_data_struct::C018_data_struct() : C018_easySerial(nullptr), myLora(nullptr) {} diff --git a/src/src/Controller_struct/C018_data_struct.h b/src/src/Controller_struct/C018_data_struct.h index 97d266a97..2d8961913 100644 --- a/src/src/Controller_struct/C018_data_struct.h +++ b/src/src/Controller_struct/C018_data_struct.h @@ -5,7 +5,10 @@ #ifdef USES_C018 -# include +#include + +class rn2xx3; +class ESPeasySerial; struct C018_data_struct { diff --git a/src/src/CustomBuild/ESPEasyDefaults.h b/src/src/CustomBuild/ESPEasyDefaults.h index d4eba68df..389011813 100644 --- a/src/src/CustomBuild/ESPEasyDefaults.h +++ b/src/src/CustomBuild/ESPEasyDefaults.h @@ -1,461 +1,477 @@ -#ifndef CUSTOMBUILD_ESPEASY_DEFAULTS_H_ -#define CUSTOMBUILD_ESPEASY_DEFAULTS_H_ - -// Needed to make sure Custom.h is used. -#include "../../ESPEasy_common.h" - -#include "../DataTypes/NetworkMedium.h" - -#include "../Helpers/Hardware_defines.h" - -// ******************************************************************************** -// User specific configuration -// ******************************************************************************** - -// Set default configuration settings if you want (not mandatory) -// You can always change these during runtime and save to eeprom -// After loading firmware, issue a 'reset' command to load the defaults. -// --- Basic Config Settings ------------------------------------------------------------------------ -#ifndef DEFAULT_NAME -#define DEFAULT_NAME "ESP_Easy" // Enter your device friendly name -#endif -#ifndef UNIT -#define UNIT 0 // Unit Number -#endif -#ifndef DEFAULT_DELAY -#define DEFAULT_DELAY 60 // Sleep Delay in seconds -#endif - -// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- -#ifndef DEFAULT_AP_IP -#define DEFAULT_AP_IP 192,168,4,1 // Enter IP address (comma separated) for AP (config) mode -#endif -#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_KEY -#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode -#endif - -// --- Wifi Client Mode ----------------------------------------------------------------------------- -#ifndef DEFAULT_SSID -#define DEFAULT_SSID "ssid" // Enter your Wifi network SSID -#endif -#ifndef DEFAULT_KEY -#define DEFAULT_KEY "wpakey" // Enter your Wifi network WPA key -#endif -#ifndef DEFAULT_SSID2 -#define DEFAULT_SSID2 "" // Enter your fallback Wifi network SSID -#endif -#ifndef DEFAULT_KEY2 -#define DEFAULT_KEY2 "" // Enter your fallback Wifi network WPA key -#endif -#ifndef DEFAULT_WIFI_INCLUDE_HIDDEN_SSID -#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs -#endif -#ifndef DEFAULT_USE_STATIC_IP -#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP -#endif -#ifndef DEFAULT_IP -#define DEFAULT_IP "192.168.0.50" // Enter your IP address -#endif -#ifndef DEFAULT_DNS -#define DEFAULT_DNS "192.168.0.1" // Enter your DNS -#endif -#ifndef DEFAULT_GW -#define DEFAULT_GW "192.168.0.1" // Enter your Gateway -#endif -#ifndef DEFAULT_SUBNET -#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet -#endif -#ifndef DEFAULT_IPRANGE_LOW -#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver -#endif -#ifndef DEFAULT_IPRANGE_HIGH -#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver -#endif -#ifndef DEFAULT_IP_BLOCK_LEVEL -#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: ONLY_IP_RANGE_ALLOWED -#endif -#ifndef DEFAULT_ADMIN_USERNAME -#define DEFAULT_ADMIN_USERNAME "admin" -#endif -#ifndef DEFAULT_ADMIN_PASS -#define DEFAULT_ADMIN_PASS "" -#endif - -#ifndef DEFAULT_WIFI_CONNECTION_TIMEOUT -#define DEFAULT_WIFI_CONNECTION_TIMEOUT 20000 // minimum timeout in ms for WiFi to be connected. -#endif -#ifndef DEFAULT_WIFI_FORCE_BG_MODE -#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) -#endif -#ifndef DEFAULT_WIFI_RESTART_WIFI_CONN_LOST -#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. -#endif -#ifndef DEFAULT_ECO_MODE -#ifdef CORE32SOLO1 -// ESP32-solo1 will be the "go to build" for unknown devices. -// So best to use the CPU frequency reported by the ESP's e-fuses. -// When enabling eco power mode, the max. CPU frequency is set to the frequency read from these efuses. -// Also, if a vendor really needs to cut the last cent from the BOM by picking the solo1, what else might be done to cut costs? -// Wouldn't be surprised if the power supply of those units isn't that good. -#define DEFAULT_ECO_MODE true // When set, make idle calls between executing tasks. -#else -#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. -#endif -#endif -#ifndef DEFAULT_WIFI_NONE_SLEEP -#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power used and need reboot to reset mode) -#endif -#ifndef DEFAULT_GRATUITOUS_ARP -#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP packets to announce itself. -#endif -#ifndef DEFAULT_TOLERANT_LAST_ARG_PARSE -#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line - // See: https://github.com/letscontrolit/ESPEasy/issues/2724 -#endif -#ifndef DEFAULT_SEND_TO_HTTP_ACK -#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 -#endif - -#ifndef DEFAULT_DONT_ALLOW_START_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. -#endif - -// --- Default Controller ------------------------------------------------------------------------------ -#ifndef DEFAULT_CONTROLLER -#define DEFAULT_CONTROLLER true // true or false enabled or disabled, set 1st controller defaults -#endif - -#ifndef DEFAULT_CONTROLLER_ENABLED -#define DEFAULT_CONTROLLER_ENABLED false // Enable default controller by default -#endif - -#ifndef DEFAULT_CONTROLLER_USER -#define DEFAULT_CONTROLLER_USER "" // Default controller user -#endif -#ifndef DEFAULT_CONTROLLER_PASS -#define DEFAULT_CONTROLLER_PASS "" // Default controller Password -#endif -#ifndef DEFAULT_CONTROLLER_TIMEOUT -#define DEFAULT_CONTROLLER_TIMEOUT 100 -#endif - -// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! -#ifndef DEFAULT_PUB -#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub -#endif -#ifndef DEFAULT_SUB -#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub -#endif -#ifndef DEFAULT_SERVER -#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address -#endif -#ifndef DEFAULT_SERVER_HOST -#define DEFAULT_SERVER_HOST "" // Server hostname -#endif -#ifndef DEFAULT_SERVER_USEDNS -#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP -#endif -#ifndef DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS -#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers -#endif - -#ifndef DEFAULT_PORT -#define DEFAULT_PORT 8080 // Enter your Server port value -#endif - - - -#ifndef DEFAULT_PROTOCOL -#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications - // 0 = Stand-alone (no controller set) - // 1 = Domoticz HTTP - // 2 = Domoticz MQTT - // 3 = Nodo Telnet - // 4 = ThingSpeak - // 5 = Home Assistant (openHAB) MQTT - // 6 = PiDome MQTT - // 7 = EmonCMS - // 8 = Generic HTTP - // 9 = FHEM HTTP -#endif - -#ifndef DEFAULT_CONSOLE_PORT -#if USES_HWCDC -#define DEFAULT_CONSOLE_PORT 7 // 7 = ESPEasySerialPort::usb_hw_cdc -#elif USES_USBCDC -#define DEFAULT_CONSOLE_PORT 8 // 8 = ESPEasySerialPort::usb_cdc_0 -#else -#define DEFAULT_CONSOLE_PORT 2 // 2 = ESPEasySerialPort::serial0 -#endif -#endif -#ifndef DEFAULT_CONSOLE_PORT_RXPIN -#define DEFAULT_CONSOLE_PORT_RXPIN SOC_RX0 -#endif -#ifndef DEFAULT_CONSOLE_PORT_TXPIN -#define DEFAULT_CONSOLE_PORT_TXPIN SOC_TX0 -#endif -#ifndef DEFAULT_CONSOLE_SER0_FALLBACK -#if USES_HWCDC -#define DEFAULT_CONSOLE_SER0_FALLBACK 1 -#elif USES_USBCDC -#define DEFAULT_CONSOLE_SER0_FALLBACK 1 -#else -#define DEFAULT_CONSOLE_SER0_FALLBACK 0 -#endif -#endif - - -#ifndef DEFAULT_PIN_I2C_SDA -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SDA 4 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SDA -1 // Undefined -#endif -#endif -#ifndef DEFAULT_PIN_I2C_SCL -#ifdef ESP8266 -#define DEFAULT_PIN_I2C_SCL 5 -#endif -#ifdef ESP32 -#define DEFAULT_PIN_I2C_SCL -1 // Undefined -#endif -#endif -#ifndef DEFAULT_I2C_CLOCK_SPEED -#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips -#endif -#ifndef DEFAULT_I2C_CLOCK_SPEED_SLOW -#define DEFAULT_I2C_CLOCK_SPEED_SLOW 100000 // Use 100 kHz for old/slow I2C chips -#endif -#ifndef FEATURE_I2C_DEVICE_SCAN -#define FEATURE_I2C_DEVICE_SCAN 1 // Show device name in I2C scan -#endif - -#ifndef DEFAULT_PIN_STATUS_LED -#define DEFAULT_PIN_STATUS_LED (-1) -#endif -#ifndef DEFAULT_PIN_STATUS_LED_INVERSED -#define DEFAULT_PIN_STATUS_LED_INVERSED true -#endif - -#ifndef DEFAULT_PIN_RESET_BUTTON -#define DEFAULT_PIN_RESET_BUTTON (-1) -#endif -#ifndef DEFAULT_ETH_PHY_ADDR -#define DEFAULT_ETH_PHY_ADDR 0 -#endif -#ifndef DEFAULT_ETH_PHY_TYPE -#define DEFAULT_ETH_PHY_TYPE EthPhyType_t::LAN8710 -#endif -#ifndef DEFAULT_ETH_PIN_MDC -#define DEFAULT_ETH_PIN_MDC 23 -#endif -#ifndef DEFAULT_ETH_PIN_MDIO -#define DEFAULT_ETH_PIN_MDIO 18 -#endif -#ifndef DEFAULT_ETH_PIN_POWER -#define DEFAULT_ETH_PIN_POWER -1 -#endif -#ifndef DEFAULT_ETH_CLOCK_MODE -#define DEFAULT_ETH_CLOCK_MODE EthClockMode_t::Ext_crystal_osc -#endif -#ifndef DEFAULT_NETWORK_MEDIUM - #if FEATURE_ETHERNET - #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::Ethernet - #else - #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::WIFI - #endif -#endif -#ifndef DEFAULT_JSON_BOOL_WITHOUT_QUOTES -#define DEFAULT_JSON_BOOL_WITHOUT_QUOTES false -#endif -#ifndef DEFAULT_ENABLE_TIMING_STATS -#define DEFAULT_ENABLE_TIMING_STATS false -#endif - - - -// --- Advanced Settings --------------------------------------------------------------------------------- -#if defined(ESP32) - #define USE_RTOS_MULTITASKING -#endif -#ifdef M5STACK_ESP -// #include -#endif - -#ifndef DEFAULT_USE_RULES -#define DEFAULT_USE_RULES false // (true|false) Enable Rules? -#endif -#ifndef DEFAULT_RULES_OLDENGINE -#define DEFAULT_RULES_OLDENGINE true -#endif - -#ifndef DEFAULT_MQTT_RETAIN -#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? -#endif - -#ifndef DEFAULT_CONTROLLER_DELETE_OLDEST -#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full -#endif - -#ifndef DEFAULT_CONTROLLER_MUST_CHECK_REPLY -#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment -#endif - -#ifndef DEFAULT_MQTT_DELAY -#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages -#endif -#ifndef DEFAULT_MQTT_LWT_TOPIC -#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic -#endif -#ifndef DEFAULT_MQTT_LWT_CONNECT_MESSAGE -#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message -#endif -#ifndef DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE -#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message -#endif -#ifndef DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID -#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 -#endif - -#ifndef DEFAULT_USE_NTP -#define DEFAULT_USE_NTP false // (true|false) Use NTP Server -#endif -#ifndef DEFAULT_NTP_HOST -#define DEFAULT_NTP_HOST "" // NTP Server Hostname -#endif -#ifndef DEFAULT_TIME_ZONE -#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) -#endif -#ifndef DEFAULT_USE_DST -#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving -#endif - -#ifndef DEFAULT_SYSLOG_IP -#define DEFAULT_SYSLOG_IP "" // Syslog IP Address -#endif -#ifndef DEFAULT_SYSLOG_LEVEL -#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level -#endif -#ifndef DEFAULT_SERIAL_LOG_LEVEL -#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level -#endif -#ifndef DEFAULT_WEB_LOG_LEVEL -#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level -#endif -#ifndef DEFAULT_SD_LOG_LEVEL -#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level -#endif -#ifndef DEFAULT_USE_SD_LOG -#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card -#endif - -#ifndef DEFAULT_USE_SERIAL -#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port -#endif -#ifndef DEFAULT_SERIAL_BAUD -#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate -#endif -#ifndef DEFAULT_SYSLOG_FACILITY -#define DEFAULT_SYSLOG_FACILITY 0 // kern -#endif -#ifndef DEFAULT_SYSLOG_PORT -#define DEFAULT_SYSLOG_PORT 0 -#endif - -#ifndef DEFAULT_SYNC_UDP_PORT -#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) -#endif - -// --- Defaults to be used for custom automatic provisioning builds ------------------------------------ -#if FEATURE_CUSTOM_PROVISIONING - #ifndef DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL - #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES1 - #define DEFAULT_PROVISIONING_FETCH_RULES1 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES2 - #define DEFAULT_PROVISIONING_FETCH_RULES2 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES3 - #define DEFAULT_PROVISIONING_FETCH_RULES3 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_RULES4 - #define DEFAULT_PROVISIONING_FETCH_RULES4 false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS - #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_SECURITY - #define DEFAULT_PROVISIONING_FETCH_SECURITY false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_CONFIG - #define DEFAULT_PROVISIONING_FETCH_CONFIG false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_PROVISIONING - #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false - #endif - #ifndef DEFAULT_PROVISIONING_FETCH_FIRMWARE - #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false - #endif - #ifndef DEFAULT_PROVISIONING_SAVE_URL - #define DEFAULT_PROVISIONING_SAVE_URL false - #endif - #ifndef DEFAULT_PROVISIONING_SAVE_CREDENTIALS - #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false - #endif - #ifndef DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND - #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false - #endif - #ifndef DEFAULT_PROVISIONING_URL - #define DEFAULT_PROVISIONING_URL "" - #endif - #ifndef DEFAULT_PROVISIONING_USER - #define DEFAULT_PROVISIONING_USER "" - #endif - #ifndef DEFAULT_PROVISIONING_PASS - #define DEFAULT_PROVISIONING_PASS "" - #endif -#endif // if FEATURE_CUSTOM_PROVISIONING - -#ifndef BUILD_IN_WEBHEADER -#define BUILD_IN_WEBHEADER false -#endif -#ifndef BUILD_IN_WEBFOOTER -#define BUILD_IN_WEBFOOTER true // If not defined show build in footer of webpage -#endif - -#ifndef GITHUB_RELEASES_LINK_PREFIX -# define GITHUB_RELEASES_LINK_PREFIX "" -#endif -#ifndef GITHUB_RELEASES_LINK_SUFFIX -# define GITHUB_RELEASES_LINK_SUFFIX "" -#endif - - -// --- We define the default features to be enabled here -#ifndef FEATURE_ESPEASY_P2P - #define FEATURE_ESPEASY_P2P 1 -#endif - -/* -// --- Experimental Advanced Settings (NOT ACTIVES at this time) ------------------------------------ - -#define DEFAULT_USE_GLOBAL_SYNC false // (true|false) - -#define DEFAULT_IP_OCTET 0 // -#define DEFAULT_WD_IC2_ADDRESS 0 // -#define DEFAULT_USE_SSDP false // (true|false) -#define DEFAULT_CON_FAIL_THRES 0 // -#define DEFAULT_I2C_CLOCK_LIMIT 0 // -*/ - -#endif // CUSTOMBUILD_ESPEASY_DEFAULTS_H_ +#ifndef CUSTOMBUILD_ESPEASY_DEFAULTS_H_ +#define CUSTOMBUILD_ESPEASY_DEFAULTS_H_ + +// Needed to make sure Custom.h is used. +#include "../../ESPEasy_common.h" + +#include "../DataTypes/NetworkMedium.h" + +#include "../Helpers/Hardware_defines.h" + +// ******************************************************************************** +// User specific configuration +// ******************************************************************************** + +// Set default configuration settings if you want (not mandatory) +// You can always change these during runtime and save to eeprom +// After loading firmware, issue a 'reset' command to load the defaults. +// --- Basic Config Settings ------------------------------------------------------------------------ +#ifndef DEFAULT_NAME +#define DEFAULT_NAME "ESP_Easy" // Enter your device friendly name +#endif +#ifndef UNIT +#define UNIT 0 // Unit Number +#endif +#ifndef DEFAULT_DELAY +#define DEFAULT_DELAY 60 // Sleep Delay in seconds +#endif + +// --- Wifi AP Mode (when your Wifi Network is not reachable) ---------------------------------------- +#ifndef DEFAULT_AP_IP +#define DEFAULT_AP_IP 192,168,4,1 // Enter IP address (comma separated) for AP (config) mode +#endif +#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_KEY +#define DEFAULT_AP_KEY "configesp" // Enter network WPA key for AP (config) mode +#endif + +// --- Wifi Client Mode ----------------------------------------------------------------------------- +#ifndef DEFAULT_SSID +#define DEFAULT_SSID "ssid" // Enter your Wifi network SSID +#endif +#ifndef DEFAULT_KEY +#define DEFAULT_KEY "wpakey" // Enter your Wifi network WPA key +#endif +#ifndef DEFAULT_SSID2 +#define DEFAULT_SSID2 "" // Enter your fallback Wifi network SSID +#endif +#ifndef DEFAULT_KEY2 +#define DEFAULT_KEY2 "" // Enter your fallback Wifi network WPA key +#endif +#ifndef DEFAULT_WIFI_INCLUDE_HIDDEN_SSID +#define DEFAULT_WIFI_INCLUDE_HIDDEN_SSID false // Allow to connect to hidden SSID APs +#endif +#ifndef DEFAULT_USE_STATIC_IP +#define DEFAULT_USE_STATIC_IP false // (true|false) enabled or disabled static IP +#endif +#ifndef DEFAULT_IP +#define DEFAULT_IP "192.168.0.50" // Enter your IP address +#endif +#ifndef DEFAULT_DNS +#define DEFAULT_DNS "192.168.0.1" // Enter your DNS +#endif +#ifndef DEFAULT_GW +#define DEFAULT_GW "192.168.0.1" // Enter your Gateway +#endif +#ifndef DEFAULT_SUBNET +#define DEFAULT_SUBNET "255.255.255.0" // Enter your Subnet +#endif +#ifndef DEFAULT_IPRANGE_LOW +#define DEFAULT_IPRANGE_LOW "0.0.0.0" // Allowed IP range to access webserver +#endif +#ifndef DEFAULT_IPRANGE_HIGH +#define DEFAULT_IPRANGE_HIGH "255.255.255.255" // Allowed IP range to access webserver +#endif +#ifndef DEFAULT_IP_BLOCK_LEVEL +#define DEFAULT_IP_BLOCK_LEVEL 1 // 0: ALL_ALLOWED 1: LOCAL_SUBNET_ALLOWED 2: ONLY_IP_RANGE_ALLOWED +#endif +#ifndef DEFAULT_ADMIN_USERNAME +#define DEFAULT_ADMIN_USERNAME "admin" +#endif +#ifndef DEFAULT_ADMIN_PASS +#define DEFAULT_ADMIN_PASS "" +#endif + +#ifndef DEFAULT_WIFI_CONNECTION_TIMEOUT +#define DEFAULT_WIFI_CONNECTION_TIMEOUT 20000 // minimum timeout in ms for WiFi to be connected. +#endif +#ifndef DEFAULT_WIFI_FORCE_BG_MODE +#define DEFAULT_WIFI_FORCE_BG_MODE false // when set, only allow to connect in 802.11B or G mode (not N) +#endif +#ifndef DEFAULT_WIFI_RESTART_WIFI_CONN_LOST +#define DEFAULT_WIFI_RESTART_WIFI_CONN_LOST false // Perform wifi off and on when connection was lost. +#endif +#ifndef DEFAULT_ECO_MODE +#ifdef CORE32SOLO1 +// ESP32-solo1 will be the "go to build" for unknown devices. +// So best to use the CPU frequency reported by the ESP's e-fuses. +// When enabling eco power mode, the max. CPU frequency is set to the frequency read from these efuses. +// Also, if a vendor really needs to cut the last cent from the BOM by picking the solo1, what else might be done to cut costs? +// Wouldn't be surprised if the power supply of those units isn't that good. +#define DEFAULT_ECO_MODE true // When set, make idle calls between executing tasks. +#else +#define DEFAULT_ECO_MODE false // When set, make idle calls between executing tasks. +#endif +#endif +#ifndef DEFAULT_WIFI_NONE_SLEEP +#define DEFAULT_WIFI_NONE_SLEEP false // When set, the wifi will be set to no longer sleep (more power used and need reboot to reset mode) +#endif +#ifndef DEFAULT_GRATUITOUS_ARP +#define DEFAULT_GRATUITOUS_ARP false // When set, the node will send periodical gratuitous ARP packets to announce itself. +#endif +#ifndef DEFAULT_TOLERANT_LAST_ARG_PARSE +#define DEFAULT_TOLERANT_LAST_ARG_PARSE false // When set, the last argument of some commands will be parsed to the end of the line + // See: https://github.com/letscontrolit/ESPEasy/issues/2724 +#endif +#ifndef DEFAULT_SEND_TO_HTTP_ACK +#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 +#endif + +#ifndef DEFAULT_DONT_ALLOW_START_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. +#endif + +// --- Default Controller ------------------------------------------------------------------------------ +#ifndef DEFAULT_CONTROLLER +#define DEFAULT_CONTROLLER true // true or false enabled or disabled, set 1st controller defaults +#endif + +#ifndef DEFAULT_CONTROLLER_ENABLED +#define DEFAULT_CONTROLLER_ENABLED false // Enable default controller by default +#endif + +#ifndef DEFAULT_CONTROLLER_USER +#define DEFAULT_CONTROLLER_USER "" // Default controller user +#endif +#ifndef DEFAULT_CONTROLLER_PASS +#define DEFAULT_CONTROLLER_PASS "" // Default controller Password +#endif +#ifndef DEFAULT_CONTROLLER_TIMEOUT +#define DEFAULT_CONTROLLER_TIMEOUT 100 +#endif + +// using a default template, you also need to set a DEFAULT PROTOCOL to a suitable MQTT protocol ! +#ifndef DEFAULT_PUB +#define DEFAULT_PUB "sensors/espeasy/%sysname%/%tskname%/%valname%" // Enter your pub +#endif +#ifndef DEFAULT_SUB +#define DEFAULT_SUB "sensors/espeasy/%sysname%/#" // Enter your sub +#endif +#ifndef DEFAULT_SERVER +#define DEFAULT_SERVER "192.168.0.8" // Enter your Server IP address +#endif +#ifndef DEFAULT_SERVER_HOST +#define DEFAULT_SERVER_HOST "" // Server hostname +#endif +#ifndef DEFAULT_SERVER_USEDNS +#define DEFAULT_SERVER_USEDNS false // true: Use hostname. false: use IP +#endif +#ifndef DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS +#define DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS false // true: Allow longer user credentials for controllers +#endif + +#ifndef DEFAULT_PORT +#define DEFAULT_PORT 8080 // Enter your Server port value +#endif + + + +#ifndef DEFAULT_PROTOCOL +#define DEFAULT_PROTOCOL 0 // Protocol used for controller communications + // 0 = Stand-alone (no controller set) + // 1 = Domoticz HTTP + // 2 = Domoticz MQTT + // 3 = Nodo Telnet + // 4 = ThingSpeak + // 5 = Home Assistant (openHAB) MQTT + // 6 = PiDome MQTT + // 7 = EmonCMS + // 8 = Generic HTTP + // 9 = FHEM HTTP +#endif + +#ifndef DEFAULT_CONSOLE_PORT +#if USES_HWCDC +#define DEFAULT_CONSOLE_PORT 7 // 7 = ESPEasySerialPort::usb_hw_cdc +#elif USES_USBCDC +#define DEFAULT_CONSOLE_PORT 8 // 8 = ESPEasySerialPort::usb_cdc_0 +#else +#define DEFAULT_CONSOLE_PORT 2 // 2 = ESPEasySerialPort::serial0 +#endif +#endif +#ifndef DEFAULT_CONSOLE_PORT_RXPIN +#define DEFAULT_CONSOLE_PORT_RXPIN SOC_RX0 +#endif +#ifndef DEFAULT_CONSOLE_PORT_TXPIN +#define DEFAULT_CONSOLE_PORT_TXPIN SOC_TX0 +#endif +#ifndef DEFAULT_CONSOLE_SER0_FALLBACK +#if USES_HWCDC +#define DEFAULT_CONSOLE_SER0_FALLBACK 1 +#elif USES_USBCDC +#define DEFAULT_CONSOLE_SER0_FALLBACK 1 +#else +#define DEFAULT_CONSOLE_SER0_FALLBACK 0 +#endif +#endif + + +#ifndef DEFAULT_PIN_I2C_SDA +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SDA 4 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SDA -1 // Undefined +#endif +#endif +#ifndef DEFAULT_PIN_I2C_SCL +#ifdef ESP8266 +#define DEFAULT_PIN_I2C_SCL 5 +#endif +#ifdef ESP32 +#define DEFAULT_PIN_I2C_SCL -1 // Undefined +#endif +#endif +#ifndef DEFAULT_I2C_CLOCK_SPEED +#define DEFAULT_I2C_CLOCK_SPEED 400000 // Use 100 kHz if working with old I2C chips +#endif +#ifndef DEFAULT_I2C_CLOCK_SPEED_SLOW +#define DEFAULT_I2C_CLOCK_SPEED_SLOW 100000 // Use 100 kHz for old/slow I2C chips +#endif +#ifndef FEATURE_I2C_DEVICE_SCAN +#define FEATURE_I2C_DEVICE_SCAN 1 // Show device name in I2C scan +#endif + +#ifndef DEFAULT_PIN_STATUS_LED +#define DEFAULT_PIN_STATUS_LED (-1) +#endif +#ifndef DEFAULT_PIN_STATUS_LED_INVERSED +#define DEFAULT_PIN_STATUS_LED_INVERSED true +#endif + +#ifndef DEFAULT_PIN_RESET_BUTTON +#define DEFAULT_PIN_RESET_BUTTON (-1) +#endif +#ifndef DEFAULT_ETH_PHY_ADDR +#define DEFAULT_ETH_PHY_ADDR 0 +#endif +#ifndef DEFAULT_ETH_PHY_TYPE +#define DEFAULT_ETH_PHY_TYPE EthPhyType_t::notSet +#endif +#ifndef DEFAULT_ETH_PIN_MDC +#define DEFAULT_ETH_PIN_MDC -1 +#endif +#ifndef DEFAULT_ETH_PIN_MDIO +#define DEFAULT_ETH_PIN_MDIO -1 +#endif +#ifndef DEFAULT_ETH_PIN_POWER +#define DEFAULT_ETH_PIN_POWER -1 +#endif +#ifndef DEFAULT_ETH_CLOCK_MODE +#define DEFAULT_ETH_CLOCK_MODE EthClockMode_t::Ext_crystal_osc +#endif +#ifndef DEFAULT_NETWORK_MEDIUM + #define DEFAULT_NETWORK_MEDIUM NetworkMedium_t::WIFI +#endif +#ifndef DEFAULT_JSON_BOOL_WITHOUT_QUOTES +#define DEFAULT_JSON_BOOL_WITHOUT_QUOTES false +#endif +#ifndef DEFAULT_ENABLE_TIMING_STATS +#define DEFAULT_ENABLE_TIMING_STATS false +#endif + + + +// --- Advanced Settings --------------------------------------------------------------------------------- +#if defined(ESP32) + #define USE_RTOS_MULTITASKING +#endif +#ifdef M5STACK_ESP +// #include +#endif + +#ifndef DEFAULT_USE_RULES +#define DEFAULT_USE_RULES false // (true|false) Enable Rules? +#endif +#ifndef DEFAULT_RULES_OLDENGINE +#define DEFAULT_RULES_OLDENGINE true +#endif + +#ifndef DEFAULT_MQTT_RETAIN +#define DEFAULT_MQTT_RETAIN false // (true|false) Retain MQTT messages? +#endif + +#ifndef DEFAULT_CONTROLLER_DELETE_OLDEST +#define DEFAULT_CONTROLLER_DELETE_OLDEST false // (true|false) to delete oldest message when queue is full +#endif + +#ifndef DEFAULT_CONTROLLER_MUST_CHECK_REPLY +#define DEFAULT_CONTROLLER_MUST_CHECK_REPLY false // (true|false) Check Acknowledgment +#endif + +#ifndef DEFAULT_MQTT_DELAY +#define DEFAULT_MQTT_DELAY 100 // Time in milliseconds to retain MQTT messages +#endif +#ifndef DEFAULT_MQTT_LWT_TOPIC +#define DEFAULT_MQTT_LWT_TOPIC "" // Default lwt topic +#endif +#ifndef DEFAULT_MQTT_LWT_CONNECT_MESSAGE +#define DEFAULT_MQTT_LWT_CONNECT_MESSAGE "Connected" // Default lwt message +#endif +#ifndef DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE +#define DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE "Connection Lost" // Default lwt message +#endif +#ifndef DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID +#define DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID 0 +#endif + +#ifndef DEFAULT_USE_NTP +#define DEFAULT_USE_NTP false // (true|false) Use NTP Server +#endif +#ifndef DEFAULT_NTP_HOST +#define DEFAULT_NTP_HOST "" // NTP Server Hostname +#endif +#ifndef DEFAULT_TIME_ZONE +#define DEFAULT_TIME_ZONE 0 // Time Offset (in minutes) +#endif +#ifndef DEFAULT_USE_DST +#define DEFAULT_USE_DST false // (true|false) Use Daily Time Saving +#endif + +#ifndef DEFAULT_SYSLOG_IP +#define DEFAULT_SYSLOG_IP "" // Syslog IP Address +#endif +#ifndef DEFAULT_SYSLOG_LEVEL +#define DEFAULT_SYSLOG_LEVEL 0 // Syslog Log Level +#endif +#ifndef DEFAULT_SERIAL_LOG_LEVEL +#define DEFAULT_SERIAL_LOG_LEVEL LOG_LEVEL_INFO // Serial Log Level +#endif +#ifndef DEFAULT_WEB_LOG_LEVEL +#define DEFAULT_WEB_LOG_LEVEL LOG_LEVEL_INFO // Web Log Level +#endif +#ifndef DEFAULT_SD_LOG_LEVEL +#define DEFAULT_SD_LOG_LEVEL 0 // SD Card Log Level +#endif +#ifndef DEFAULT_USE_SD_LOG +#define DEFAULT_USE_SD_LOG false // (true|false) Enable Logging to the SD card +#endif + +#ifndef DEFAULT_USE_SERIAL +#define DEFAULT_USE_SERIAL true // (true|false) Enable Logging to the Serial Port +#endif +#ifndef DEFAULT_SERIAL_BAUD +#define DEFAULT_SERIAL_BAUD 115200 // Serial Port Baud Rate +#endif +#ifndef DEFAULT_SYSLOG_FACILITY +#define DEFAULT_SYSLOG_FACILITY 0 // kern +#endif +#ifndef DEFAULT_SYSLOG_PORT +#define DEFAULT_SYSLOG_PORT 0 +#endif + +#ifndef DEFAULT_SYNC_UDP_PORT +#define DEFAULT_SYNC_UDP_PORT 8266 // Used for ESPEasy p2p. (IANA registered port: 8266) +#endif + + +// Factory Reset defaults +#ifndef DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME +#define DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_WIFI +#define DEFAULT_FACTORY_RESET_KEEP_WIFI true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_NETWORK +#define DEFAULT_FACTORY_RESET_KEEP_NETWORK true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_NTP_DST +#define DEFAULT_FACTORY_RESET_KEEP_NTP_DST true +#endif +#ifndef DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG +#define DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG true +#endif + + + +// --- Defaults to be used for custom automatic provisioning builds ------------------------------------ +#if FEATURE_CUSTOM_PROVISIONING + #ifndef DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL + #define DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL 0 // DeviceModel_default + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES1 + #define DEFAULT_PROVISIONING_FETCH_RULES1 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES2 + #define DEFAULT_PROVISIONING_FETCH_RULES2 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES3 + #define DEFAULT_PROVISIONING_FETCH_RULES3 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_RULES4 + #define DEFAULT_PROVISIONING_FETCH_RULES4 false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS + #define DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_SECURITY + #define DEFAULT_PROVISIONING_FETCH_SECURITY false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_CONFIG + #define DEFAULT_PROVISIONING_FETCH_CONFIG false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_PROVISIONING + #define DEFAULT_PROVISIONING_FETCH_PROVISIONING false + #endif + #ifndef DEFAULT_PROVISIONING_FETCH_FIRMWARE + #define DEFAULT_PROVISIONING_FETCH_FIRMWARE false + #endif + #ifndef DEFAULT_PROVISIONING_SAVE_URL + #define DEFAULT_PROVISIONING_SAVE_URL false + #endif + #ifndef DEFAULT_PROVISIONING_SAVE_CREDENTIALS + #define DEFAULT_PROVISIONING_SAVE_CREDENTIALS false + #endif + #ifndef DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND + #define DEFAULT_PROVISIONING_ALLOW_FETCH_COMMAND false + #endif + #ifndef DEFAULT_PROVISIONING_URL + #define DEFAULT_PROVISIONING_URL "" + #endif + #ifndef DEFAULT_PROVISIONING_USER + #define DEFAULT_PROVISIONING_USER "" + #endif + #ifndef DEFAULT_PROVISIONING_PASS + #define DEFAULT_PROVISIONING_PASS "" + #endif +#endif // if FEATURE_CUSTOM_PROVISIONING + +#ifndef BUILD_IN_WEBHEADER +#define BUILD_IN_WEBHEADER false +#endif +#ifndef BUILD_IN_WEBFOOTER +#define BUILD_IN_WEBFOOTER true // If not defined show build in footer of webpage +#endif + +#ifndef GITHUB_RELEASES_LINK_PREFIX +# define GITHUB_RELEASES_LINK_PREFIX "" +#endif +#ifndef GITHUB_RELEASES_LINK_SUFFIX +# define GITHUB_RELEASES_LINK_SUFFIX "" +#endif + + +// --- We define the default features to be enabled here +#ifndef FEATURE_ESPEASY_P2P + #define FEATURE_ESPEASY_P2P 1 +#endif + +/* +// --- Experimental Advanced Settings (NOT ACTIVES at this time) ------------------------------------ + +#define DEFAULT_USE_GLOBAL_SYNC false // (true|false) + +#define DEFAULT_IP_OCTET 0 // +#define DEFAULT_WD_IC2_ADDRESS 0 // +#define DEFAULT_USE_SSDP false // (true|false) +#define DEFAULT_CON_FAIL_THRES 0 // +#define DEFAULT_I2C_CLOCK_LIMIT 0 // +*/ + +#endif // CUSTOMBUILD_ESPEASY_DEFAULTS_H_ diff --git a/src/src/CustomBuild/ESPEasyLimits.h b/src/src/CustomBuild/ESPEasyLimits.h index 30e9b495b..73b5ecdeb 100644 --- a/src/src/CustomBuild/ESPEasyLimits.h +++ b/src/src/CustomBuild/ESPEasyLimits.h @@ -1,163 +1,163 @@ -#ifndef CUSTOMBUILD_ESPEASY_LIMITS_H -#define CUSTOMBUILD_ESPEASY_LIMITS_H - -#include "../../include/ESPEasy_config.h" - -// *********************************************************************** -// * These limits have direct impact on the settings files -// * Do not change them! -// * Else settings files will no longer be compatible with official builds -// * Some of these are related to the values defined in StorageLayout.h -// *********************************************************************** - - -// Performing a 2-stage define assignment using the _TMP defines -// See: https://github.com/letscontrolit/ESPEasy/issues/2621 -#if FEATURE_NON_STANDARD_24_TASKS - #define TASKS_MAX_TMP 24 -#else - #define TASKS_MAX_TMP 12 -#endif - - -#if defined(ESP8266) - #ifndef TASKS_MAX - #define TASKS_MAX TASKS_MAX_TMP - #endif - #ifndef MAX_GPIO - #define MAX_GPIO 16 - #endif -#endif -#if defined(ESP32) - #ifndef TASKS_MAX - #define TASKS_MAX 32 - #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 CONTROLLER_MAX - #define CONTROLLER_MAX 3 // max 4! -#endif -#ifndef NOTIFICATION_MAX - #define NOTIFICATION_MAX 3 // max 4! -#endif -#ifndef VARS_PER_TASK - #define VARS_PER_TASK 4 -#endif -#ifndef PLUGIN_CONFIGVAR_MAX - #define PLUGIN_CONFIGVAR_MAX 8 -#endif -#ifndef PLUGIN_CONFIGFLOATVAR_MAX - #define PLUGIN_CONFIGFLOATVAR_MAX 4 -#endif -#ifndef PLUGIN_CONFIGLONGVAR_MAX - #define PLUGIN_CONFIGLONGVAR_MAX 4 -#endif -#ifndef PLUGIN_EXTRACONFIGVAR_MAX - #define PLUGIN_EXTRACONFIGVAR_MAX 16 -#endif -#ifndef NAME_FORMULA_LENGTH_MAX - #define NAME_FORMULA_LENGTH_MAX 40 -#endif - -#define USERVAR_MAX_INDEX (VARS_PER_TASK * TASKS_MAX) - -// *********************************************************************** -// * The next limits affect memory usage -// *********************************************************************** -#ifndef DEVICES_MAX - // TODO TD-er: This should be set automatically by counting the number of included plugins. - # ifdef ESP32 - # define DEVICES_MAX 175 - #else - #if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) - # define DEVICES_MAX 95 - # else - # define DEVICES_MAX 60 - # endif - #endif -#endif - -#ifndef DEVICE_INDEX_MAX - #define DEVICE_INDEX_MAX 255 -#endif -#ifndef PLUGIN_MAX - #define PLUGIN_MAX 255 -#endif -#ifndef CPLUGIN_MAX - #define CPLUGIN_MAX 255 -#endif -#ifndef NPLUGIN_MAX - #define NPLUGIN_MAX 4 -#endif - -#ifndef UNIT_NUMBER_MAX - #define UNIT_NUMBER_MAX 254 // Stored in Settings.Unit unit 255 = broadcast -#endif - - -// *********************************************************************** -// * Limits regarding Rules -// *********************************************************************** - -#ifndef RULES_TIMER_MAX - #define RULES_TIMER_MAX 256 -#endif -//#ifndef PINSTATE_TABLE_MAX -//#define PINSTATE_TABLE_MAX 32 -//#endif -#ifndef RULES_MAX_SIZE - #define RULES_MAX_SIZE 2048 -#endif -#ifndef RULES_MAX_NESTING_LEVEL - #define RULES_MAX_NESTING_LEVEL 3 -#endif -#ifndef RULESETS_MAX - #define RULESETS_MAX 4 -#endif -#ifndef RULES_BUFFER_SIZE - #define RULES_BUFFER_SIZE 64 -#endif - -#ifndef RULES_IF_MAX_NESTING_LEVEL - #define RULES_IF_MAX_NESTING_LEVEL 4 -#endif - - -// *********************************************************************** -// * Extended SecuritySettings -// *********************************************************************** -#ifndef EXT_SECURITY_MAX_USER_LENGTH - #define EXT_SECURITY_MAX_USER_LENGTH 128 -#endif -#ifndef EXT_SECURITY_MAX_PASS_LENGTH - #define EXT_SECURITY_MAX_PASS_LENGTH 128 -#endif - -// *********************************************************************** -// * Other operational limits -// *********************************************************************** - -#ifndef MAX_FLASHWRITES_PER_DAY - #define MAX_FLASHWRITES_PER_DAY 100 // per 24 hour window -#endif -#ifndef UDP_PACKETSIZE_MAX - #define UDP_PACKETSIZE_MAX 256 // Currently only needed for C013_Receive -#endif -#ifndef TIMER_GRATUITOUS_ARP_MAX - #define TIMER_GRATUITOUS_ARP_MAX 5000 -#endif - -#define DOMOTICZ_MAX_IDX 999999999 // Looks like it is an unsigned int, so could be up to 4 bln. - - -#endif // CUSTOMBUILD_ESPEASY_LIMITS_H +#ifndef CUSTOMBUILD_ESPEASY_LIMITS_H +#define CUSTOMBUILD_ESPEASY_LIMITS_H + +#include "../../include/ESPEasy_config.h" + +// *********************************************************************** +// * These limits have direct impact on the settings files +// * Do not change them! +// * Else settings files will no longer be compatible with official builds +// * Some of these are related to the values defined in StorageLayout.h +// *********************************************************************** + + +// Performing a 2-stage define assignment using the _TMP defines +// See: https://github.com/letscontrolit/ESPEasy/issues/2621 +#if FEATURE_NON_STANDARD_24_TASKS + #define TASKS_MAX_TMP 24 +#else + #define TASKS_MAX_TMP 12 +#endif + + +#if defined(ESP8266) + #ifndef TASKS_MAX + #define TASKS_MAX TASKS_MAX_TMP + #endif + #ifndef MAX_GPIO + #define MAX_GPIO 16 + #endif +#endif +#if defined(ESP32) + #ifndef TASKS_MAX + #define TASKS_MAX 32 + #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 CONTROLLER_MAX + #define CONTROLLER_MAX 3 // max 4! +#endif +#ifndef NOTIFICATION_MAX + #define NOTIFICATION_MAX 3 // max 4! +#endif +#ifndef VARS_PER_TASK + #define VARS_PER_TASK 4 +#endif +#ifndef PLUGIN_CONFIGVAR_MAX + #define PLUGIN_CONFIGVAR_MAX 8 +#endif +#ifndef PLUGIN_CONFIGFLOATVAR_MAX + #define PLUGIN_CONFIGFLOATVAR_MAX 4 +#endif +#ifndef PLUGIN_CONFIGLONGVAR_MAX + #define PLUGIN_CONFIGLONGVAR_MAX 4 +#endif +#ifndef PLUGIN_EXTRACONFIGVAR_MAX + #define PLUGIN_EXTRACONFIGVAR_MAX 16 +#endif +#ifndef NAME_FORMULA_LENGTH_MAX + #define NAME_FORMULA_LENGTH_MAX 40 +#endif + +#define USERVAR_MAX_INDEX (VARS_PER_TASK * TASKS_MAX) + +// *********************************************************************** +// * The next limits affect memory usage +// *********************************************************************** +#ifndef DEVICES_MAX + // TODO TD-er: This should be set automatically by counting the number of included plugins. + # ifdef ESP32 + # define DEVICES_MAX 175 + #else + #if defined(PLUGIN_BUILD_COLLECTION) || defined(PLUGIN_BUILD_DEV) + # define DEVICES_MAX 95 + # else + # define DEVICES_MAX 60 + # endif + #endif +#endif + +#ifndef DEVICE_INDEX_MAX + #define DEVICE_INDEX_MAX 255 +#endif +#ifndef PLUGIN_MAX + #define PLUGIN_MAX 255 +#endif +#ifndef CPLUGIN_MAX + #define CPLUGIN_MAX 255 +#endif +#ifndef NPLUGIN_MAX + #define NPLUGIN_MAX 4 +#endif + +#ifndef UNIT_NUMBER_MAX + #define UNIT_NUMBER_MAX 254 // Stored in Settings.Unit unit 255 = broadcast +#endif + + +// *********************************************************************** +// * Limits regarding Rules +// *********************************************************************** + +#ifndef RULES_TIMER_MAX + #define RULES_TIMER_MAX 256 +#endif +//#ifndef PINSTATE_TABLE_MAX +//#define PINSTATE_TABLE_MAX 32 +//#endif +#ifndef RULES_MAX_SIZE + #define RULES_MAX_SIZE 2048 +#endif +#ifndef RULES_MAX_NESTING_LEVEL + #define RULES_MAX_NESTING_LEVEL 3 +#endif +#ifndef RULESETS_MAX + #define RULESETS_MAX 4 +#endif +#ifndef RULES_BUFFER_SIZE + #define RULES_BUFFER_SIZE 64 +#endif + +#ifndef RULES_IF_MAX_NESTING_LEVEL + #define RULES_IF_MAX_NESTING_LEVEL 4 +#endif + + +// *********************************************************************** +// * Extended SecuritySettings +// *********************************************************************** +#ifndef EXT_SECURITY_MAX_USER_LENGTH + #define EXT_SECURITY_MAX_USER_LENGTH 128 +#endif +#ifndef EXT_SECURITY_MAX_PASS_LENGTH + #define EXT_SECURITY_MAX_PASS_LENGTH 128 +#endif + +// *********************************************************************** +// * Other operational limits +// *********************************************************************** + +#ifndef MAX_FLASHWRITES_PER_DAY + #define MAX_FLASHWRITES_PER_DAY 100 // per 24 hour window +#endif +#ifndef UDP_PACKETSIZE_MAX + #define UDP_PACKETSIZE_MAX 512 // Currently only needed for C013_Receive +#endif +#ifndef TIMER_GRATUITOUS_ARP_MAX + #define TIMER_GRATUITOUS_ARP_MAX 5000 +#endif + +#define DOMOTICZ_MAX_IDX 999999999 // Looks like it is an unsigned int, so could be up to 4 bln. + + +#endif // CUSTOMBUILD_ESPEASY_LIMITS_H diff --git a/src/src/CustomBuild/StorageLayout.h b/src/src/CustomBuild/StorageLayout.h index c9903597b..3afa28391 100644 --- a/src/src/CustomBuild/StorageLayout.h +++ b/src/src/CustomBuild/StorageLayout.h @@ -17,15 +17,15 @@ /* - The settings files have some reserved space for each struct stored in there. + The settings files have some reserved space for each struct stored in there. CONFIG_FILE_SIZE File size of config.dat - Settings struct is positioned at the start of the settings file Config.dat - + Settings struct is positioned at the start of the settings file Config.dat + DAT_BASIC_SETTINGS_SIZE Reserved size for Settings struct - Parameters used for locating the task data: + Parameters used for locating the task data: DAT_OFFSET_TASKS Position of first TaskSettings in the Settings file DAT_TASKS_SIZE Reserved size for TaskSettings @@ -36,8 +36,8 @@ DAT_TASKS_DISTANCE = DAT_TASKS_SIZE + DAT_TASKS_CUSTOM_SIZE - Parameters used for locating the controller data: - + Parameters used for locating the controller data: + DAT_OFFSET_CONTROLLER Position of first ControllerSettings in the Settings file DAT_CONTROLLER_SIZE Reserved size for ControllerSettings @@ -47,12 +47,12 @@ ControllerSettings and CustomControllerSettings are not located interleaved in the settings file. So there is no distance value for controller settings. (equal to the controller size) - Notification settings are located in a different file. + Notification settings are located in a different file. - DAT_NOTIFICATION_SIZE Reserved size for NotificationSettings + DAT_NOTIFICATION_SIZE Reserved size for NotificationSettings -*/ + */ #ifndef DAT_BASIC_SETTINGS_SIZE // For size of SettingsStruct stored at this area in config.dat @@ -79,6 +79,25 @@ #ifndef DAT_TASKS_CUSTOM_SIZE # define DAT_TASKS_CUSTOM_SIZE 1024 #endif // ifndef DAT_TASKS_CUSTOM_SIZE +// FEATURE_EXTENDED_CUSTOM_SETTINGS: Default will be determined in define_plugin_sets.h, based on used plugins +#ifndef FEATURE_EXTENDED_CUSTOM_SETTINGS +# ifdef BUILD_MINIMAL_OTA +# define FEATURE_EXTENDED_CUSTOM_SETTINGS 0 // Never on minimal builds? +# else // ifdef BUILD_MINIMAL_OTA +# define FEATURE_EXTENDED_CUSTOM_SETTINGS 1 +# endif // ifdef BUILD_MINIMAL_OTA +#endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS +#ifndef DAT_TASKS_CUSTOM_EXTENSION_SIZE +# if FEATURE_EXTENDED_CUSTOM_SETTINGS +# define DAT_TASKS_CUSTOM_EXTENSION_SIZE 4096 // 4kB extension with external file extcfg.dat +# else // if FEATURE_EXTENDED_CUSTOM_SETTINGS +# define DAT_TASKS_CUSTOM_EXTENSION_SIZE 0 // No extension, but defined to avoid #if checks all over the code +# endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS +#endif // ifndef DAT_TASKS_CUSTOM_EXTENSION_SIZE +// Filename _must_ include the task number (1-based, as shown in the UI) and the % is also used elsewhere, so keep the %02d ! +#ifndef DAT_TASKS_CUSTOM_EXTENSION_FILEMASK +# define DAT_TASKS_CUSTOM_EXTENSION_FILEMASK "extcfg%02d.dat" +#endif // ifndef DAT_TASKS_CUSTOM_EXTENSION_FILEMASK #ifndef DAT_TASKS_DISTANCE # define DAT_TASKS_DISTANCE 2048 // DAT_TASKS_SIZE + DAT_TASKS_CUSTOM_SIZE #endif // ifndef DAT_TASKS_DISTANCE @@ -119,7 +138,8 @@ #define TASKS_MAX 24 #define DAT_OFFSET_CONTROLLER (DAT_OFFSET_TASKS + (DAT_TASKS_DISTANCE * TASKS_MAX)) // each controller = 1k, 4 max - #define DAT_OFFSET_CUSTOM_CONTROLLER (DAT_OFFSET_CONTROLLER + (DAT_CUSTOM_CONTROLLER_SIZE * CONTROLLER_MAX)) // each custom controller config = + #define DAT_OFFSET_CUSTOM_CONTROLLER (DAT_OFFSET_CONTROLLER + (DAT_CUSTOM_CONTROLLER_SIZE * CONTROLLER_MAX)) // each custom controller + config = 1k, 4 max @@ -136,7 +156,9 @@ #if defined(ESP8266) # if FEATURE_NON_STANDARD_24_TASKS # ifndef DAT_OFFSET_TASKS - # define DAT_OFFSET_TASKS 4096 // 0x1000 each task = 2k, (1024 basic + 1024 bytes custom) + # define DAT_OFFSET_TASKS 4096 // 0x1000 each task = 2k, + // (1024 basic + 1024 bytes + // custom) # endif // ifndef DAT_OFFSET_TASKS # ifndef DAT_OFFSET_CONTROLLER # define DAT_OFFSET_CONTROLLER (DAT_OFFSET_TASKS + (DAT_TASKS_DISTANCE * TASKS_MAX)) // each controller = 1k, 3 max, DAT_OFFSET_CDN is at position of any 4th controller. @@ -167,24 +189,25 @@ # define DAT_OFFSET_CDN (DAT_OFFSET_TASKS - DAT_CDN_SIZE) // single CDN settings block of 1k # endif # ifdef LIMIT_BUILD_SIZE - // Limit the config size for 1M builds, since their file system is also quite small + +// Limit the config size for 1M builds, since their file system is also quite small # ifndef CONFIG_FILE_SIZE # define CONFIG_FILE_SIZE 36864 // DAT_OFFSET_CUSTOM_CONTROLLER + 4x DAT_CUSTOM_CONTROLLER_SIZE # endif // ifndef CONFIG_FILE_SIZE - # else + # else // ifdef LIMIT_BUILD_SIZE # ifndef CONFIG_FILE_SIZE # define CONFIG_FILE_SIZE 65536 # endif // ifndef CONFIG_FILE_SIZE - # endif + # endif // ifdef LIMIT_BUILD_SIZE # endif // if FEATURE_NON_STANDARD_24_TASKS #endif // if defined(ESP8266) #if defined(ESP32) # ifndef DAT_OFFSET_TASKS - # define DAT_OFFSET_TASKS 32768 // each task = 2k, (1024 basic + 1024 bytes custom), 32 max + # define DAT_OFFSET_TASKS 32768 // each task = 2k, (1024 basic + 1024 bytes custom), 32 max # endif // ifndef DAT_OFFSET_TASKS # ifndef DAT_OFFSET_CONTROLLER - # define DAT_OFFSET_CONTROLLER 8192 // each controller = 1k, 4 max + # define DAT_OFFSET_CONTROLLER 8192 // each controller = 1k, 4 max # endif // ifndef DAT_OFFSET_CONTROLLER # ifndef DAT_OFFSET_CUSTOM_CONTROLLER # define DAT_OFFSET_CUSTOM_CONTROLLER 12288 // each custom controller config = 1k, 4 max. diff --git a/src/src/CustomBuild/define_plugin_sets.h b/src/src/CustomBuild/define_plugin_sets.h index f8c7ee258..826a9cfec 100644 --- a/src/src/CustomBuild/define_plugin_sets.h +++ b/src/src/CustomBuild/define_plugin_sets.h @@ -500,6 +500,11 @@ To create/register a plugin, you have to : #endif #define FEATURE_I2C_GET_ADDRESS 0 // Disable fetching I2C device address + #ifdef FEATURE_TARSTREAM_SUPPORT + #undef FEATURE_TARSTREAM_SUPPORT + #endif + #define FEATURE_TARSTREAM_SUPPORT 0 // Disable TarFile support for size + #ifndef USES_P001 #define USES_P001 // switch #endif @@ -631,11 +636,11 @@ To create/register a plugin, you have to : #if !defined(PLUGIN_DESCR) && !defined(PLUGIN_BUILD_MAX_ESP32) #define PLUGIN_DESCR "IR" #endif - #ifndef USES_P016 + #ifndef USES_P016 #define USES_P016 // IR #endif #define P016_SEND_IR_TO_CONTROLLER false //IF true then the JSON replay solution is transmited back to the condroller. - #ifndef USES_P035 + #ifndef USES_P035 #define USES_P035 // IRTX #endif #define P016_P035_USE_RAW_RAW2 //Use the RAW and RAW2 encodings, disabling it saves 3.7Kb @@ -645,11 +650,11 @@ To create/register a plugin, you have to : #if !defined(PLUGIN_DESCR) && !defined(PLUGIN_BUILD_MAX_ESP32) #define PLUGIN_DESCR "IR Extended" #endif // PLUGIN_DESCR - #ifndef USES_P016 + #ifndef USES_P016 #define USES_P016 // IR #endif #define P016_SEND_IR_TO_CONTROLLER false //IF true then the JSON replay solution is transmited back to the condroller. - #ifndef USES_P035 + #ifndef USES_P035 #define USES_P035 // IRTX #endif // The following define is needed for extended decoding of A/C Messages and or using standardised common arguments for controlling all deeply supported A/C units @@ -668,7 +673,7 @@ To create/register a plugin, you have to : #if !defined(PLUGIN_DESCR) && !defined(PLUGIN_BUILD_MAX_ESP32) #define PLUGIN_DESCR "IR Extended, no IR RX" #endif // PLUGIN_DESCR - #ifndef USES_P035 + #ifndef USES_P035 #define USES_P035 // IRTX #endif // The following define is needed for extended decoding of A/C Messages and or using standardised common arguments for controlling all deeply supported A/C units @@ -705,7 +710,7 @@ To create/register a plugin, you have to : #define CONTROLLER_SET_STABLE #define PLUGIN_SET_ONLY_SWITCH #define NOTIFIER_SET_STABLE - #define USES_P076 // HWL8012 in POW r1 + #define USES_P076 // HLW8012 in POW r1 // Needs CSE7766 Energy sensor, via Serial RXD 4800 baud 8E1 (GPIO1), TXD (GPIO3) #define USES_P077 // CSE7766 in POW R2 #define USES_P081 // Cron @@ -779,7 +784,7 @@ To create/register a plugin, you have to : #define PLUGIN_SET_ONLY_SWITCH #define CONTROLLER_SET_STABLE #define NOTIFIER_SET_STABLE - #define USES_P076 // HWL8012 in POW r1 + #define USES_P076 // HLW8012 in POW r1 #define USES_P077 // CSE7766 in POW R2 #define USES_P081 // Cron #endif @@ -831,7 +836,7 @@ To create/register a plugin, you have to : #ifdef PLUGIN_SET_MAGICHOME_IR #define PLUGIN_SET_ONLY_LEDSTRIP - #ifndef USES_P016 + #ifndef USES_P016 #define USES_P016 // IR #endif @@ -1088,6 +1093,9 @@ 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 @@ -1119,6 +1127,7 @@ To create/register a plugin, you have to : #ifdef FEATURE_CUSTOM_PROVISIONING #undef FEATURE_CUSTOM_PROVISIONING #endif + // FIXME TD-er: Should this be enabled on non-Custom builds??? #define FEATURE_CUSTOM_PROVISIONING 1 @@ -1465,6 +1474,12 @@ To create/register a plugin, you have to : #ifndef NOTIFIER_SET_NONE #define NOTIFIER_SET_NONE #endif + #ifdef USES_N001 + #undef USES_N001 // Email + #endif + #ifdef USES_N002 + #undef USES_N002 // Buzzer + #endif // Do not include large blobs but fetch them from CDN #ifndef WEBSERVER_USE_CDN_JS_CSS @@ -1496,13 +1511,16 @@ To create/register a plugin, you have to : #define USES_P066 // VEML6040 #define USES_P075 // Nextion - //#define USES_P076 // HWL8012 in POW r1 + //#define USES_P076 // HLW8012 in POW r1 // Needs CSE7766 Energy sensor, via Serial RXD 4800 baud 8E1 (GPIO1), TXD (GPIO3) //#define USES_P077 // CSE7766 in POW R2 //#define USES_P078 // Eastron Modbus Energy meters #define USES_P081 // Cron #define USES_P082 // GPS #define USES_P089 // Ping + #if !defined(USES_P095) && defined(ESP32) && !defined(PLUGIN_BUILD_IR_EXTENDED) + #define USES_P095 // TFT ILI9xxx + #endif #if !defined(USES_P137) && defined(ESP32) #define USES_P137 // AXP192 #endif @@ -1579,7 +1597,7 @@ To create/register a plugin, you have to : #ifdef PLUGIN_SET_COLLECTION_E #define USES_P119 // ITG3205 Gyro #define USES_P120 // ADXL345 I2C - #define USES_P121 // HMC5883L + #define USES_P121 // HMC5883L #define USES_P125 // ADXL345 SPI #define USES_P126 // 74HC595 Shift register #define USES_P129 // 74HC165 Input shiftregisters @@ -1619,11 +1637,29 @@ To create/register a plugin, you have to : #ifdef PLUGIN_SET_COLLECTION_G #ifndef USES_P154 - #define USES_P154 // Environment - BMP3xx + #define USES_P154 // Environment - BMP3xx I2C + #endif + #ifndef USES_P172 + #define USES_P172 // Environment - BMP3xx SPI #endif #ifndef USES_P159 #define USES_P159 // Presence - LD2410 Radar detection #endif + #ifndef USES_P162 + #define USES_P162 // Output - MCP42xxx Digipot + #endif + #ifndef USES_P164 + #define USES_P164 // Gases - ENS16x TVOC\eCO2 + #endif + #ifndef USES_P166 + #define USES_P166 // Output - GP8403 DAC 0-10V + #endif + #ifndef USES_P168 + #define USES_P168 // Light - VEML6030/VEML7700 + #endif + #ifndef USES_P170 + #define USES_P170 // Input - I2C Liquid level sensor + #endif #endif @@ -1649,7 +1685,7 @@ To create/register a plugin, you have to : #define USES_P027 // INA219 #endif #ifndef USES_P076 - #define USES_P076 // HWL8012 in POW r1 + #define USES_P076 // HLW8012 in POW r1 #endif #ifndef USES_P077 // Needs CSE7766 Energy sensor, via Serial RXD 4800 baud 8E1 (GPIO1), TXD (GPIO3) @@ -1682,7 +1718,7 @@ To create/register a plugin, you have to : #if !defined(USES_P138) && defined(ESP32) #define USES_P138 // IP5306 #endif - #ifndef USES_P148 + #if !defined(USES_P148) && defined(ESP32) #define USES_P148 // Sonoff POWR3xxD and THR3xxD display #endif @@ -1697,6 +1733,19 @@ To create/register a plugin, you have to : #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) @@ -1704,6 +1753,9 @@ To create/register a plugin, you have to : #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 @@ -1754,6 +1806,9 @@ To create/register a plugin, you have to : #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 @@ -1904,11 +1959,28 @@ To create/register a plugin, you have to : #define USES_P153 // Environment - SHT4x #endif #ifndef USES_P154 - #define USES_P154 // Environment - BMP3xx + #define USES_P154 // Environment - BMP3xx I2C + #endif + #ifndef USES_P172 + #define USES_P172 // Environment - BMP3xx SPI + #endif + #ifndef USES_P164 + #define USES_P164 // Gases - ENS16x TVOC/eCO2 + #endif + #ifndef USES_P166 + #define USES_P166 // Output - GP8403 DAC 0-10V + #endif + #ifndef USES_P167 + #define USES_P167 // Environment - Sensirion SEN5x / Ikea Vindstyrka + #endif + #ifndef USES_P168 + #define USES_P168 // Light - VEML6030/VEML7700 #endif - - + #ifndef USES_P169 + #define USES_P169 // Environment - AS3935 Lightning Detector + #endif + // Controllers #ifndef USES_C011 #define USES_C011 // HTTP Advanced @@ -1940,6 +2012,12 @@ To create/register a plugin, you have to : #endif #ifndef USES_P131 #define USES_P131 // NeoMatrix + #ifdef ESP32 + #define TOMTHUMB_USE_EXTENDED 1 + #endif + #endif + #if !defined(USES_P105) && defined(ESP32) + #define USES_P105 // AHT10/20/21 (used in TinyTronics Smart Home RGB LED Matrix) #endif #if !defined(USES_P137) && defined(ESP32) #define USES_P137 // AXP192 @@ -1964,13 +2042,29 @@ To create/register a plugin, you have to : #endif #ifdef CONTROLLER_SET_COLLECTION + #ifndef USES_C011 #define USES_C011 // Generic HTTP Advanced + #endif + #ifndef USES_C012 #define USES_C012 // Blynk HTTP + #endif + #ifndef USES_C014 #define USES_C014 // homie 3 & 4dev MQTT + #endif + #ifndef USES_C015 //#define USES_C015 // Blynk + #endif + #ifndef USES_C017 #define USES_C017 // Zabbix - // #define USES_C018 // TTN RN2483 + #endif + #ifdef ESP32 + #ifndef USES_C018 + #define USES_C018 // TTN RN2483 + #endif + #endif + #ifndef USES_C019 // #define USES_C019 // ESPEasy-NOW + #endif #endif @@ -1978,6 +2072,12 @@ To create/register a plugin, you have to : // To be defined #endif +// Disable few plugin(s) to make the build fit :/ +#ifdef PLUGIN_BUILD_IR_EXTENDED_NO_RX + #ifdef USES_P039 + #undef USES_P039 // Environment - Thermocouple + #endif +#endif // ifdef PLUGIN_BUILD_IR_EXTENDED_NO_RX // EXPERIMENTAL (playground) ####################### #ifdef PLUGIN_SET_EXPERIMENTAL @@ -2197,13 +2297,13 @@ To create/register a plugin, you have to : #define USES_P120 // ADXL345 I2C Acceleration / Gravity #endif #ifndef USES_P121 - #define USES_P121 // HMC5883L + #define USES_P121 // HMC5883L #endif #ifndef USES_P122 #define USES_P122 // SHT2x #endif #ifndef USES_P123 -// #define USES_P123 // FT62x6 + #define USES_P123 // I2C Touchscreens #endif #ifndef USES_P124 #define USES_P124 // I2C Multi relay @@ -2296,11 +2396,33 @@ To create/register a plugin, you have to : #define USES_P153 // Environment - SHT4x #endif #ifndef USES_P154 - #define USES_P154 // Environment - BMP3xx + #define USES_P154 // Environment - BMP3xx I2C + #endif + #ifndef USES_P172 + #define USES_P172 // Environment - BMP3xx SPI #endif #ifndef USES_P159 #define USES_P159 // Presence - LD2410 Radar detection #endif + #ifndef USES_P162 + #define USES_P162 // Output - MCP42xxx Digipot + #endif + #ifndef USES_P166 + #define USES_P166 // Output - GP8403 DAC 0-10V + #endif + #ifndef USES_P167 + #define USES_P167 // Environment - IKEA Vindstyrka SEN54 temperature , humidity and air quality + #endif + #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 // Controllers #ifndef USES_C015 @@ -2349,6 +2471,9 @@ To create/register a plugin, you have to : /******************************************************************************\ * Libraries dependencies ***************************************************** \******************************************************************************/ +#if defined(USES_P044) && !defined(USES_P020) // P020 is used to replace/emulate P044 + #define USES_P020 +#endif #if defined(USES_P020) || defined(USES_P049) || defined(USES_P052) || defined(USES_P053) || defined(USES_P056) || defined(USES_P065) || defined(USES_P071) || defined(USES_P075) || defined(USES_P077) || defined(USES_P078) || defined(USES_P082) || defined(USES_P085) || defined(USES_P087) || defined(USES_P093)|| defined(USES_P094) || defined(USES_P102) || defined(USES_P105) || defined(USES_P108) || defined(USES_P144) || defined(USES_C018) // At least one plugin uses serial. #ifndef PLUGIN_USES_SERIAL @@ -2359,12 +2484,18 @@ To create/register a plugin, you have to : #define DISABLE_SOFTWARE_SERIAL #endif -#if defined(USES_P095) || defined(USES_P096) || defined(USES_P116) || defined(USES_P131) || defined(USES_P141) // Add any plugin that uses AdafruitGFX_Helper +#if defined(USES_P095) || defined(USES_P096) || defined(USES_P116) || defined(USES_P131) || defined(USES_P141) || defined(USES_P123) // Add any plugin that uses AdafruitGFX_Helper #ifndef PLUGIN_USES_ADAFRUITGFX #define PLUGIN_USES_ADAFRUITGFX // Ensure AdafruitGFX_helper is available for graphics displays (only) #endif #endif +#if defined(USES_P099) || defined(USES_P123) + #ifndef PLUGIN_USES_TOUCHHANDLER + #define PLUGIN_USES_TOUCHHANDLER + #endif +#endif + /* #if defined(USES_P00x) || defined(USES_P00y) #include @@ -2557,10 +2688,12 @@ To create/register a plugin, you have to : #endif #define FEATURE_SETTINGS_ARCHIVE 0 + #ifndef PLUGIN_BUILD_CUSTOM #ifdef FEATURE_SERVO #undef FEATURE_SERVO #endif #define FEATURE_SERVO 0 + #endif #ifdef FEATURE_RTTTL #undef FEATURE_RTTTL #endif @@ -2575,7 +2708,7 @@ To create/register a plugin, you have to : #define FEATURE_BLYNK 0 #if !defined(PLUGIN_SET_COLLECTION) && !defined(PLUGIN_SET_SONOFF_POW) #ifdef USES_P076 - #undef USES_P076 // HWL8012 in POW r1 + #undef USES_P076 // HLW8012 in POW r1 #endif #ifdef USES_P093 #undef USES_P093 // Mitsubishi Heat Pump @@ -2747,7 +2880,13 @@ To create/register a plugin, you have to : #ifndef LIMIT_BUILD_SIZE #ifndef FEATURE_MDNS #ifdef ESP32 - #define FEATURE_MDNS 1 + #if ESP_IDF_VERSION_MAJOR >= 5 + // See if it is now more usable... + // See: https://github.com/letscontrolit/ESPEasy/issues/5061 + #define FEATURE_MDNS 0 + #else + #define FEATURE_MDNS 0 + #endif #else // Do not use MDNS on ESP8266 due to memory leak #define FEATURE_MDNS 0 @@ -2845,11 +2984,11 @@ To create/register a plugin, you have to : // This should be done at the end of this file. // Keep them alfabetically sorted so it is easier to add new ones -#ifndef FEATURE_BLYNK +#ifndef FEATURE_BLYNK #define FEATURE_BLYNK 0 #endif -#ifndef FEATURE_CHART_JS +#ifndef FEATURE_CHART_JS #define FEATURE_CHART_JS 0 #endif @@ -2858,7 +2997,7 @@ To create/register a plugin, you have to : #endif -#ifndef FEATURE_CUSTOM_PROVISIONING +#ifndef FEATURE_CUSTOM_PROVISIONING #define FEATURE_CUSTOM_PROVISIONING 0 #endif @@ -2870,27 +3009,27 @@ To create/register a plugin, you have to : #define FEATURE_DNS_SERVER 0 #endif -#ifndef FEATURE_DOMOTICZ +#ifndef FEATURE_DOMOTICZ #define FEATURE_DOMOTICZ 0 #endif -#ifndef FEATURE_DOWNLOAD +#ifndef FEATURE_DOWNLOAD #define FEATURE_DOWNLOAD 0 #endif -#ifndef FEATURE_ESPEASY_P2P +#ifndef FEATURE_ESPEASY_P2P #define FEATURE_ESPEASY_P2P 0 #endif -#ifndef FEATURE_ETHERNET +#ifndef FEATURE_ETHERNET #define FEATURE_ETHERNET 0 #endif -#ifndef FEATURE_EXT_RTC +#ifndef FEATURE_EXT_RTC #define FEATURE_EXT_RTC 0 #endif -#ifndef FEATURE_FHEM +#ifndef FEATURE_FHEM #define FEATURE_FHEM 0 #endif @@ -2906,11 +3045,11 @@ To create/register a plugin, you have to : #define FEATURE_HOMEASSISTANT_OPENHAB 0 #endif -#ifndef FEATURE_I2CMULTIPLEXER +#ifndef FEATURE_I2CMULTIPLEXER #define FEATURE_I2CMULTIPLEXER 0 #endif -#ifndef FEATURE_I2C_DEVICE_SCAN +#ifndef FEATURE_I2C_DEVICE_SCAN #ifdef ESP32 #define FEATURE_I2C_DEVICE_SCAN 1 #else @@ -2918,39 +3057,39 @@ To create/register a plugin, you have to : #endif #endif -#ifndef FEATURE_MDNS +#ifndef FEATURE_MDNS #define FEATURE_MDNS 0 #endif -#ifndef FEATURE_MODBUS +#ifndef FEATURE_MODBUS #define FEATURE_MODBUS 0 #endif -#ifndef FEATURE_MQTT +#ifndef FEATURE_MQTT #define FEATURE_MQTT 0 #endif -#ifndef FEATURE_NON_STANDARD_24_TASKS +#ifndef FEATURE_NON_STANDARD_24_TASKS #define FEATURE_NON_STANDARD_24_TASKS 0 #endif -#ifndef FEATURE_NOTIFIER +#ifndef FEATURE_NOTIFIER #define FEATURE_NOTIFIER 0 #endif -#ifndef FEATURE_PACKED_RAW_DATA +#ifndef FEATURE_PACKED_RAW_DATA #define FEATURE_PACKED_RAW_DATA 0 #endif -#ifndef FEATURE_PLUGIN_STATS +#ifndef FEATURE_PLUGIN_STATS #define FEATURE_PLUGIN_STATS 0 #endif -#ifndef FEATURE_REPORTING +#ifndef FEATURE_REPORTING #define FEATURE_REPORTING 0 #endif -#ifndef FEATURE_RTTTL +#ifndef FEATURE_RTTTL #define FEATURE_RTTTL 0 #endif #if defined(FEATURE_RTTTL) && !FEATURE_RTTTL && defined(KEEP_RTTTL) @@ -2970,11 +3109,11 @@ To create/register a plugin, you have to : #define FEATURE_RTTTL_EVENTS 1 // Enable RTTTL events for Async use, for blocking it doesn't make sense #endif -#ifndef FEATURE_SD +#ifndef FEATURE_SD #define FEATURE_SD 0 #endif -#ifndef FEATURE_SERVO +#ifndef FEATURE_SERVO #define FEATURE_SERVO 0 #endif @@ -2996,19 +3135,19 @@ To create/register a plugin, you have to : #endif #endif -#ifndef FEATURE_SSDP +#ifndef FEATURE_SSDP #define FEATURE_SSDP 0 #endif -#ifndef FEATURE_TIMING_STATS +#ifndef FEATURE_TIMING_STATS #define FEATURE_TIMING_STATS 0 #endif -#ifndef FEATURE_TOOLTIPS +#ifndef FEATURE_TOOLTIPS #define FEATURE_TOOLTIPS 0 #endif -#ifndef FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES +#ifndef FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES #define FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES 0 #endif @@ -3225,6 +3364,20 @@ To create/register a plugin, you have to : #endif #endif +#ifndef FEATURE_TARSTREAM_SUPPORT + #define FEATURE_TARSTREAM_SUPPORT 1 +#endif // FEATURE_TARSTREAM_SUPPORT + +// Check for plugins that will use Extended Custom Settings storage when available +#ifndef FEATURE_EXTENDED_CUSTOM_SETTINGS + #if defined(USES_P094) || defined(USES_P095) || defined(USES_P096) || defined(USES_P099) || defined(USES_P104) || defined(USES_P116) || defined(USES_P123) || defined(USES_P131) + #define FEATURE_EXTENDED_CUSTOM_SETTINGS 1 + #else + #define FEATURE_EXTENDED_CUSTOM_SETTINGS 0 + #endif +#endif // ifndef FEATURE_EXTENDED_CUSTOM_SETTINGS + + #ifndef FEATURE_CLEAR_I2C_STUCK #ifdef ESP8266 @@ -3250,8 +3403,8 @@ To create/register a plugin, you have to : # endif #endif -// Incompatible plugins with ESP32-C2/C6 -#if defined(ESP32C2) || defined(ESP32C6) +// Incompatible plugins with ESP32-C2 // (C6 seems to work as intended) +#if defined(ESP32C2) // || defined(ESP32C6) #define DISABLE_NEOPIXEL_PLUGINS 1 #endif @@ -3267,6 +3420,14 @@ To create/register a plugin, you have to : # endif #endif +// Make sure CONFIG_ETH_USE_ESP32_EMAC is defined on older SDK versions. +#if FEATURE_ETHERNET && ESP_IDF_VERSION_MAJOR<5 +#ifndef CONFIG_ETH_USE_ESP32_EMAC +#ifdef ESP32_CLASSIC +#define CONFIG_ETH_USE_ESP32_EMAC 1 +#endif +#endif +#endif #if defined(DISABLE_NEOPIXEL_PLUGINS) && DISABLE_NEOPIXEL_PLUGINS // Disable NeoPixel plugins @@ -3306,6 +3467,36 @@ To create/register a plugin, you have to : #endif +// Enable dependencies for custom provisioning +// FIXME TD-er: What about using this feature on non-Custom builds???? +#if FEATURE_CUSTOM_PROVISIONING + #ifdef FEATURE_DOWNLOAD + #undef FEATURE_DOWNLOAD + #endif + #define FEATURE_DOWNLOAD 1 + #ifdef FEATURE_SETTINGS_ARCHIVE + #undef FEATURE_SETTINGS_ARCHIVE + #endif + #define FEATURE_SETTINGS_ARCHIVE 1 +#endif + + +#if defined(USES_P004) || defined(USES_P080) || defined(USES_P100) + #define FEATURE_DALLAS_HELPER 1 +#endif +#ifndef FEATURE_DALLAS_HELPER + #define FEATURE_DALLAS_HELPER 0 // Only when Dallas/Maxim 1-wire plugins are included +#endif +#if FEATURE_DALLAS_HELPER && !defined(FEATURE_COMMAND_OWSCAN) + #ifdef MINIMAL_OTA + #define FEATURE_COMMAND_OWSCAN 0 // Exclude owscan command for minimal-OTA builds + #else // ifdef MINIMAL_OTA + #define FEATURE_COMMAND_OWSCAN 1 + #endif // ifdef MINIMAL_OTA +#endif +#ifndef FEATURE_COMMAND_OWSCAN + #define FEATURE_COMMAND_OWSCAN 0 // Remaining cases: disable command +#endif // ifndef FEATURE_COMMAND_OWSCAN // TODO TD-er: Test feature, must remove /* @@ -3316,5 +3507,12 @@ To create/register a plugin, you have to : */ + #ifndef FEATURE_THINGSPEAK_EVENT + #ifdef LIMIT_BUILD_SIZE + #define FEATURE_THINGSPEAK_EVENT 0 + #else + #define FEATURE_THINGSPEAK_EVENT 1 + #endif + #endif -#endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H +#endif // CUSTOMBUILD_DEFINE_PLUGIN_SETS_H \ No newline at end of file diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp new file mode 100644 index 000000000..881a37823 --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.cpp @@ -0,0 +1,116 @@ +#include "../DataStructs/C013_p2p_SensorDataStruct.h" + +#ifdef USES_C013 + +# include "../DataStructs/NodeStruct.h" +# include "../Globals/Nodes.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/Plugins.h" + +# include "../CustomBuild/CompiletimeDefines.h" + +bool C013_SensorDataStruct::prepareForSend() +{ + sourceNodeBuild = get_build_nr(); + checksum.clear(); + + if (sourceNodeBuild >= 20871) { + if (node_time.systemTimePresent()) { + uint32_t unix_time_frac{}; + timestamp_sec = node_time.getUnixTime(unix_time_frac); + timestamp_frac = unix_time_frac >> 16; + } + + + // Make sure to add checksum as last step + constexpr unsigned len_upto_checksum = offsetof(C013_SensorDataStruct, checksum); + + const ShortChecksumType tmpChecksum( + reinterpret_cast(this), + sizeof(C013_SensorDataStruct), + len_upto_checksum); + + checksum = tmpChecksum; + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex); +} + +bool C013_SensorDataStruct::setData(const uint8_t *data, size_t size) +{ + // First clear entire struct + memset(this, 0, sizeof(C013_SensorDataStruct)); + + if (size < 6) { + return false; + } + + 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. + if (size > sizeof(C013_SensorDataStruct)) { + size = sizeof(C013_SensorDataStruct); + } + NodeStruct *sourceNode = Nodes.getNode(data[2]); // sourceUnit + + if (sourceNode != nullptr) { + if (sourceNode->build < 20871) { + if (size > 24) { + size = 24; + } + } + } + + if (size <= 24) { + deviceNumber = INVALID_PLUGIN_ID; + sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + if (sourceNode != nullptr) { + sourceNodeBuild = sourceNode->build; + } + } + + memcpy(this, data, size); + + if (checksum.isSet()) { + if (!(tmpChecksum == checksum)) { + return false; + } + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex); +} + +bool C013_SensorDataStruct::matchesPluginID(pluginID_t pluginID) const +{ + if ((deviceNumber.value == 255) || !validPluginID(deviceNumber) || !validPluginID(pluginID)) { + // Was never set, so probably received data from older node. + return true; + } + return pluginID == deviceNumber; +} + +bool C013_SensorDataStruct::matchesSensorType(Sensor_VType sensor_type) const +{ + if ((deviceNumber.value == 255) || (sensorType == Sensor_VType::SENSOR_TYPE_NONE)) { + // Was never set, so probably received data from older node. + return true; + } + return sensorType == sensor_type; +} + +#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_SensorDataStruct.h b/src/src/DataStructs/C013_p2p_SensorDataStruct.h new file mode 100644 index 000000000..8306d2d0b --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorDataStruct.h @@ -0,0 +1,59 @@ +#ifndef DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H +#define DATASTRUCTS_C013_P2P_SENSORDATASTRUCTS_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C013 + + +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/ShortChecksumType.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataTypes/PluginID.h" + + +// 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); + + bool prepareForSend(); + + bool matchesPluginID(pluginID_t pluginID) const; + + bool matchesSensorType(Sensor_VType sensor_type) const; + + uint8_t header = 255; + uint8_t ID = 5; + uint8_t sourceUnit = 0; + uint8_t destUnit = 0; + taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; + taskIndex_t destTaskIndex = INVALID_TASK_INDEX; + + // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) + // See: + // https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 + // Thus should not be checked + pluginID_t deviceNumber = INVALID_PLUGIN_ID; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + TaskValues_Data_t values{}; + + // Extra info added on 20240619 (build ID 20871) + ShortChecksumType checksum; + uint16_t sourceNodeBuild = 0; + uint16_t timestamp_frac = 0; + uint32_t timestamp_sec = 0; + + // Optional IDX value to allow receiving remote + // feed data on a different task index as is used on the sender node. + uint32_t IDX = 0; +}; + +#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 new file mode 100644 index 000000000..d215bd07d --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.cpp @@ -0,0 +1,147 @@ +#include "../DataStructs/C013_p2p_SensorInfoStruct.h" + +#ifdef USES_C013 + +# include "../DataStructs/NodeStruct.h" +# include "../Globals/ExtraTaskSettings.h" +# include "../Globals/Nodes.h" +# include "../Globals/Plugins.h" +# include "../Globals/Settings.h" + +# include "../CustomBuild/CompiletimeDefines.h" + +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + +bool C013_SensorInfoStruct::prepareForSend(size_t& sizeToSend) +{ + if (!(validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex) && + validPluginID(deviceNumber))) { + return false; + } + + sizeToSend = sizeof(C013_SensorInfoStruct); + + sourceNodeBuild = get_build_nr(); + checksum.clear(); + + ZERO_FILL(taskName); + safe_strncpy(taskName, getTaskDeviceName(sourceTaskIndex), sizeof(taskName)); + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { + ZERO_FILL(ValueNames[x]); + safe_strncpy(ValueNames[x], getTaskValueName(sourceTaskIndex, x), sizeof(ValueNames[x])); + } + + + if (sourceNodeBuild >= 20871) { + LoadTaskSettings(sourceTaskIndex); + + ExtraTaskSettings_version = ExtraTaskSettings.version; + + for (uint8_t x = 0; x < VARS_PER_TASK; x++) { + TaskDeviceValueDecimals[x] = ExtraTaskSettings.TaskDeviceValueDecimals[x]; + TaskDeviceMinValue[x] = ExtraTaskSettings.TaskDeviceMinValue[x]; + TaskDeviceMaxValue[x] = ExtraTaskSettings.TaskDeviceMaxValue[x]; + TaskDeviceErrorValue[x] = ExtraTaskSettings.TaskDeviceErrorValue[x]; + VariousBits[x] = ExtraTaskSettings.VariousBits[x]; + +/* + ZERO_FILL(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) { + TaskDevicePluginConfig[x] = Settings.TaskDevicePluginConfig[sourceTaskIndex][x]; + } + } + + // Check to see if last bytes are all zero, so we can simply not send them + bool doneShrinking = false; + constexpr unsigned len_upto_sourceNodeBuild = offsetof(C013_SensorInfoStruct, sourceNodeBuild); + + const uint8_t *data = reinterpret_cast(this); + + while (!doneShrinking) { + if (sizeToSend < len_upto_sourceNodeBuild) { + doneShrinking = true; + } + else { + if (data[sizeToSend - 1] == 0) { + --sizeToSend; + } else { + doneShrinking = true; + } + } + } + + if (sourceNodeBuild >= 20871) { + // Make sure to add checksum as last step + constexpr unsigned len_upto_checksum = offsetof(C013_SensorInfoStruct, checksum); + const ShortChecksumType tmpChecksum( + reinterpret_cast(this), + sizeToSend, + len_upto_checksum); + + checksum = tmpChecksum; + } + + return true; +} + +bool C013_SensorInfoStruct::setData(const uint8_t *data, size_t size) +{ + // First clear entire struct + memset(this, 0, sizeof(C013_SensorInfoStruct)); + + if (size < 6) { + return false; + } + + 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); + } + + if (size <= 138) { + deviceNumber = INVALID_PLUGIN_ID; + sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + NodeStruct *sourceNode = Nodes.getNode(data[2]); // sourceUnit + + if (sourceNode != nullptr) { + sourceNodeBuild = sourceNode->build; + } + } + + memcpy(this, data, size); + + if (checksum.isSet()) { + if (!(tmpChecksum == checksum)) { + return false; + } + } + + return validTaskIndex(sourceTaskIndex) && + validTaskIndex(destTaskIndex) && + validPluginID(deviceNumber); +} + +#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_SensorInfoStruct.h b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h new file mode 100644 index 000000000..7f4650698 --- /dev/null +++ b/src/src/DataStructs/C013_p2p_SensorInfoStruct.h @@ -0,0 +1,65 @@ +#ifndef DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H +#define DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H + +#include "../../ESPEasy_common.h" + +#ifdef USES_C013 + + +# include "../CustomBuild/ESPEasyLimits.h" +# include "../DataStructs/DeviceStruct.h" +# include "../DataStructs/ShortChecksumType.h" +# include "../DataTypes/TaskIndex.h" +# include "../DataTypes/TaskValues_Data.h" +# include "../DataTypes/PluginID.h" + + +// 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); + + bool prepareForSend(size_t& sizeToSend); + + uint8_t header = 255; + uint8_t ID = 3; + uint8_t sourceUnit = 0; + uint8_t destUnit = 0; + taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; + taskIndex_t destTaskIndex = INVALID_TASK_INDEX; + pluginID_t deviceNumber = INVALID_PLUGIN_ID; + char taskName[26]{}; + char ValueNames[VARS_PER_TASK][26]{}; + Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; + + // Extra info added on 20240619 (build ID 20871) + ShortChecksumType checksum; + uint16_t sourceNodeBuild = 0; + + // Optional IDX value to allow receiving remote + // feed data on a different task index as is used on the sender node. + uint32_t IDX = 0; + + // Settings PCONFIG values + int16_t TaskDevicePluginConfig[PLUGIN_CONFIGVAR_MAX]{}; + + // Some info from ExtraTaskSettings Sorted so the most likely member to be 0 is at the end. + uint8_t ExtraTaskSettings_version = 0; + uint8_t TaskDeviceValueDecimals[VARS_PER_TASK]{}; + uint32_t VariousBits[VARS_PER_TASK]{}; + float TaskDeviceErrorValue[VARS_PER_TASK]{}; + float TaskDeviceMinValue[VARS_PER_TASK]{}; + float TaskDeviceMaxValue[VARS_PER_TASK]{}; + + // Put these as last as they are most likely to be empty + // FIXME TD-er: Sending formula over is not working well on the receiving end. +// char TaskDeviceFormula[VARS_PER_TASK][NAME_FORMULA_LENGTH_MAX + 1]{}; +}; + + +#endif // ifdef USES_C013 + +#endif // ifndef DATASTRUCTS_C013_P2P_SENSORINFOSTRUCTS_H diff --git a/src/src/DataStructs/C013_p2p_dataStructs.cpp b/src/src/DataStructs/C013_p2p_dataStructs.cpp deleted file mode 100644 index 6587ff001..000000000 --- a/src/src/DataStructs/C013_p2p_dataStructs.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "../DataStructs/C013_p2p_dataStructs.h" - -#ifdef USES_C013 - -# include "../Globals/Plugins.h" - - - -bool C013_SensorInfoStruct::isValid() const -{ - if ((header != 255) || (ID != 3)) { return false; } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex) && - validPluginID(deviceNumber); -} - -bool C013_SensorDataStruct::isValid() const -{ - if ((header != 255) || (ID != 5)) { return false; } - - return validTaskIndex(sourceTaskIndex) && - validTaskIndex(destTaskIndex); -} - -bool C013_SensorDataStruct::matchesPluginID(pluginID_t pluginID) const -{ - if (deviceNumber.value == 255 || !validPluginID(deviceNumber) || !validPluginID(pluginID)) { - // Was never set, so probably received data from older node. - return true; - } - return pluginID == deviceNumber; -} - -bool C013_SensorDataStruct::matchesSensorType(Sensor_VType sensor_type) const -{ - if (deviceNumber.value == 255 || sensorType == Sensor_VType::SENSOR_TYPE_NONE) { - // Was never set, so probably received data from older node. - return true; - } - return sensorType == sensor_type; -} - -#endif // ifdef USES_C013 diff --git a/src/src/DataStructs/C013_p2p_dataStructs.h b/src/src/DataStructs/C013_p2p_dataStructs.h deleted file mode 100644 index fc316f93b..000000000 --- a/src/src/DataStructs/C013_p2p_dataStructs.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef DATASTRUCTS_C013_P2P_DATASTRUCTS_H -#define DATASTRUCTS_C013_P2P_DATASTRUCTS_H - -#include "../../ESPEasy_common.h" - -#ifdef USES_C013 - - -# include "../CustomBuild/ESPEasyLimits.h" -# include "../DataStructs/DeviceStruct.h" -# include "../DataTypes/TaskIndex.h" -# include "../DataTypes/TaskValues_Data.h" -# include "../DataTypes/PluginID.h" - -// 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 isValid() const; - - uint8_t header = 255; - uint8_t ID = 3; - uint8_t sourceUnit = 0; - uint8_t destUnit = 0; - taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; - taskIndex_t destTaskIndex = INVALID_TASK_INDEX; - pluginID_t deviceNumber = INVALID_PLUGIN_ID; - char taskName[26]{}; - char ValueNames[VARS_PER_TASK][26]{}; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; -}; - -struct C013_SensorDataStruct -{ - C013_SensorDataStruct() = default; - - bool isValid() const; - - bool matchesPluginID(pluginID_t pluginID) const; - - bool matchesSensorType(Sensor_VType sensor_type) const; - - uint8_t header = 255; - uint8_t ID = 5; - uint8_t sourceUnit = 0; - uint8_t destUnit = 0; - taskIndex_t sourceTaskIndex = INVALID_TASK_INDEX; - taskIndex_t destTaskIndex = INVALID_TASK_INDEX; - - // deviceNumber and sensorType were not present before build 2023-05-05. (build NR 20460) - // See: https://github.com/letscontrolit/ESPEasy/commit/cf791527eeaf31ca98b07c45c1b64e2561a7b041#diff-86b42dd78398b103e272503f05f55ee0870ae5fb907d713c2505d63279bb0321 - // Thus should not be checked - pluginID_t deviceNumber = INVALID_PLUGIN_ID; - Sensor_VType sensorType = Sensor_VType::SENSOR_TYPE_NONE; - TaskValues_Data_t values{}; -}; - -constexpr unsigned int size = sizeof(C013_SensorDataStruct); - -#endif // ifdef USES_C013 - -#endif // DATASTRUCTS_C013_P2P_DATASTRUCTS_H diff --git a/src/src/DataStructs/Caches.cpp b/src/src/DataStructs/Caches.cpp index 017cee9ae..d8e81269d 100644 --- a/src/src/DataStructs/Caches.cpp +++ b/src/src/DataStructs/Caches.cpp @@ -357,7 +357,7 @@ void Caches::updateExtraTaskSettingsCache() } #endif // ifdef ESP32 - extraTaskSettings_cache[TaskIndex] = tmp; + extraTaskSettings_cache.emplace(std::make_pair(TaskIndex, std::move(tmp))); } } diff --git a/src/src/DataStructs/ChecksumType.cpp b/src/src/DataStructs/ChecksumType.cpp index 7c998362f..9c894fb9f 100644 --- a/src/src/DataStructs/ChecksumType.cpp +++ b/src/src/DataStructs/ChecksumType.cpp @@ -1,107 +1,107 @@ -#include "../DataStructs/ChecksumType.h" - -#include "../Helpers/StringConverter.h" - -#include - -ChecksumType::ChecksumType(const ChecksumType& rhs) -{ - memcpy(_checksum, rhs._checksum, 16); -} - -ChecksumType::ChecksumType(uint8_t checksum[16]) -{ - memcpy(_checksum, checksum, 16); -} - -ChecksumType::ChecksumType(const uint8_t *data, - size_t data_length) -{ - computeChecksum(_checksum, data, data_length, data_length, true); -} - -ChecksumType::ChecksumType(const uint8_t *data, - size_t data_length, - size_t len_upto_md5) -{ - computeChecksum(_checksum, data, data_length, len_upto_md5, true); -} - -ChecksumType::ChecksumType(const String strings[], size_t nrStrings) -{ - MD5Builder md5; - - md5.begin(); - - for (size_t i = 0; i < nrStrings; ++i) { - md5.add(strings[i].c_str()); - } - md5.calculate(); - md5.getBytes(_checksum); -} - -bool ChecksumType::computeChecksum( - uint8_t checksum[16], - const uint8_t *data, - size_t data_length, - size_t len_upto_md5, - bool updateChecksum) -{ - if (len_upto_md5 > data_length) { len_upto_md5 = data_length; } - MD5Builder md5; - - md5.begin(); - - if (len_upto_md5 > 0) { - // MD5Builder::add has non-const argument - md5.add(const_cast(data), len_upto_md5); - } - - if ((len_upto_md5 + 16) < data_length) { - data += len_upto_md5 + 16; - const int len_after_md5 = data_length - 16 - len_upto_md5; - - if (len_after_md5 > 0) { - // MD5Builder::add has non-const argument - md5.add(const_cast(data), len_after_md5); - } - } - md5.calculate(); - uint8_t tmp_md5[16] = { 0 }; - - md5.getBytes(tmp_md5); - - if (memcmp(tmp_md5, checksum, 16) != 0) { - // Data has changed, copy computed checksum - if (updateChecksum) { - memcpy(checksum, tmp_md5, 16); - } - return false; - } - return true; -} - -void ChecksumType::getChecksum(uint8_t checksum[16]) const { - memcpy(checksum, _checksum, 16); -} - -void ChecksumType::setChecksum(const uint8_t checksum[16]) { - memcpy(_checksum, checksum, 16); -} - -bool ChecksumType::matchChecksum(const uint8_t checksum[16]) const { - return memcmp(_checksum, checksum, 16) == 0; -} - -bool ChecksumType::operator==(const ChecksumType& rhs) const { - return memcmp(_checksum, rhs._checksum, 16) == 0; -} - -ChecksumType& ChecksumType::operator=(const ChecksumType& rhs) { - memcpy(_checksum, rhs._checksum, 16); - return *this; -} - -String ChecksumType::toString() const { - return formatToHex_array(_checksum, 16); +#include "../DataStructs/ChecksumType.h" + +#include "../Helpers/StringConverter.h" + +#include + +ChecksumType::ChecksumType(const ChecksumType& rhs) +{ + memcpy(_checksum, rhs._checksum, 16); +} + +ChecksumType::ChecksumType(uint8_t checksum[16]) +{ + memcpy(_checksum, checksum, 16); +} + +ChecksumType::ChecksumType(const uint8_t *data, + size_t data_length) +{ + computeChecksum(_checksum, data, data_length, data_length, true); +} + +ChecksumType::ChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_md5) +{ + computeChecksum(_checksum, data, data_length, len_upto_md5, true); +} + +ChecksumType::ChecksumType(const String strings[], size_t nrStrings) +{ + MD5Builder md5; + + md5.begin(); + + for (size_t i = 0; i < nrStrings; ++i) { + md5.add(strings[i].c_str()); + } + md5.calculate(); + md5.getBytes(_checksum); +} + +bool ChecksumType::computeChecksum( + uint8_t checksum[16], + const uint8_t *data, + size_t data_length, + size_t len_upto_md5, + bool updateChecksum) +{ + if (len_upto_md5 > data_length) { len_upto_md5 = data_length; } + MD5Builder md5; + + md5.begin(); + + if (len_upto_md5 > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_upto_md5); + } + + if ((len_upto_md5 + 16) < data_length) { + data += len_upto_md5 + 16; + const int len_after_md5 = data_length - 16 - len_upto_md5; + + if (len_after_md5 > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_after_md5); + } + } + md5.calculate(); + uint8_t tmp_md5[16] = { 0 }; + + md5.getBytes(tmp_md5); + + if (memcmp(tmp_md5, checksum, 16) != 0) { + // Data has changed, copy computed checksum + if (updateChecksum) { + memcpy(checksum, tmp_md5, 16); + } + return false; + } + return true; +} + +void ChecksumType::getChecksum(uint8_t checksum[16]) const { + memcpy(checksum, _checksum, 16); +} + +void ChecksumType::setChecksum(const uint8_t checksum[16]) { + memcpy(_checksum, checksum, 16); +} + +bool ChecksumType::matchChecksum(const uint8_t checksum[16]) const { + return memcmp(_checksum, checksum, 16) == 0; +} + +bool ChecksumType::operator==(const ChecksumType& rhs) const { + return memcmp(_checksum, rhs._checksum, 16) == 0; +} + +ChecksumType& ChecksumType::operator=(const ChecksumType& rhs) { + memcpy(_checksum, rhs._checksum, 16); + return *this; +} + +String ChecksumType::toString() const { + return formatToHex_array(_checksum, 16); } \ No newline at end of file diff --git a/src/src/DataStructs/ControllerSettingsStruct.cpp b/src/src/DataStructs/ControllerSettingsStruct.cpp index 9ffaa6753..ea2b32ea2 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.cpp +++ b/src/src/DataStructs/ControllerSettingsStruct.cpp @@ -27,16 +27,25 @@ void ControllerSettingsStruct::reset() { // Otherwise the checksum will fail and settings will be saved too often. memset(this, 0, sizeof(ControllerSettingsStruct)); - UseDNS = DEFAULT_SERVER_USEDNS; - Port = DEFAULT_PORT; - MinimalTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; - MaxQueueDepth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; - MaxRetry = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; - DeleteOldest = DEFAULT_CONTROLLER_DELETE_OLDEST; - ClientTimeout = CONTROLLER_CLIENTTIMEOUT_DFLT; - MustCheckReply = DEFAULT_CONTROLLER_MUST_CHECK_REPLY ; - SampleSetInitiator = INVALID_TASK_INDEX; - VariousFlags = 0; + UseDNS = DEFAULT_SERVER_USEDNS; + Port = DEFAULT_PORT; + MinimalTimeBetweenMessages = CONTROLLER_DELAY_QUEUE_DELAY_DFLT; + MaxQueueDepth = CONTROLLER_DELAY_QUEUE_DEPTH_DFLT; + MaxRetry = CONTROLLER_DELAY_QUEUE_RETRY_DFLT; + DeleteOldest = DEFAULT_CONTROLLER_DELETE_OLDEST; + ClientTimeout = CONTROLLER_CLIENTTIMEOUT_DFLT; + MustCheckReply = DEFAULT_CONTROLLER_MUST_CHECK_REPLY; + SampleSetInitiator = INVALID_TASK_INDEX; + VariousBits1.mqtt_cleanSession = 0; + VariousBits1.mqtt_not_sendLWT = 0; + VariousBits1.mqtt_not_willRetain = 0; + VariousBits1.mqtt_uniqueMQTTclientIdReconnect = 0; + VariousBits1.mqtt_retainFlag = 0; + VariousBits1.useExtendedCredentials = 0; + VariousBits1.sendBinary = 0; + VariousBits1.allowExpire = 0; + VariousBits1.deduplicate = 0; + VariousBits1.useLocalSystemTime = 0; safe_strncpy(ClientID, F(CONTROLLER_DEFAULT_CLIENTID), sizeof(ClientID)); } @@ -97,7 +106,6 @@ void ControllerSettingsStruct::validate() { } - String ControllerSettingsStruct::getHost() const { if (UseDNS) { return HostName; @@ -115,6 +123,7 @@ bool ControllerSettingsStruct::checkHostReachable(bool quick) { // No IP/hostname set return false; } + if (!NetworkConnected(10)) { return false; // Not connected, so no use in wasting time to connect to a host. } @@ -136,7 +145,7 @@ bool ControllerSettingsStruct::connectToHost(WiFiClient& client) { return false; // Host not reachable } uint8_t retry = 2; - bool connected = false; + bool connected = false; while (retry > 0 && !connected) { --retry; @@ -150,16 +159,19 @@ bool ControllerSettingsStruct::connectToHost(WiFiClient& client) { } return false; } + #endif // FEATURE_HTTP_CLIENT bool ControllerSettingsStruct::beginPacket(WiFiUDP& client) { if (!checkHostReachable(true)) { return false; // Host not reachable } - uint8_t retry = 2; + uint8_t retry = 2; + while (retry > 0) { --retry; FeedSW_watchdog(); + if (client.beginPacket(getIP(), Port) == 1) { return true; } @@ -207,102 +219,101 @@ bool ControllerSettingsStruct::updateIPcache() { /* bool ControllerSettingsStruct::mqtt_cleanSession() const { - return bitRead(VariousFlags, 1); -} + return bitRead(VariousFlags, 1); + } -void ControllerSettingsStruct::mqtt_cleanSession(bool value) -{ - bitWrite(VariousFlags, 1, value); -} + void ControllerSettingsStruct::mqtt_cleanSession(bool value) + { + bitWrite(VariousFlags, 1, value); + } -bool ControllerSettingsStruct::mqtt_sendLWT() const -{ - return !bitRead(VariousFlags, 2); -} + bool ControllerSettingsStruct::mqtt_sendLWT() const + { + return !bitRead(VariousFlags, 2); + } -void ControllerSettingsStruct::mqtt_sendLWT(bool value) -{ - bitWrite(VariousFlags, 2, !value); -} + void ControllerSettingsStruct::mqtt_sendLWT(bool value) + { + bitWrite(VariousFlags, 2, !value); + } -bool ControllerSettingsStruct::mqtt_willRetain() const -{ - return !bitRead(VariousFlags, 3); -} + bool ControllerSettingsStruct::mqtt_willRetain() const + { + return !bitRead(VariousFlags, 3); + } -void ControllerSettingsStruct::mqtt_willRetain(bool value) -{ - bitWrite(VariousFlags, 3, !value); -} + void ControllerSettingsStruct::mqtt_willRetain(bool value) + { + bitWrite(VariousFlags, 3, !value); + } -bool ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect() const -{ - return bitRead(VariousFlags, 4); -} + bool ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect() const + { + return bitRead(VariousFlags, 4); + } -void ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect(bool value) -{ - bitWrite(VariousFlags, 4, value); -} + void ControllerSettingsStruct::mqtt_uniqueMQTTclientIdReconnect(bool value) + { + bitWrite(VariousFlags, 4, value); + } -bool ControllerSettingsStruct::mqtt_retainFlag() const -{ - return bitRead(VariousFlags, 5); -} + bool ControllerSettingsStruct::mqtt_retainFlag() const + { + return bitRead(VariousFlags, 5); + } -void ControllerSettingsStruct::mqtt_retainFlag(bool value) -{ - bitWrite(VariousFlags, 5, value); -} -#endif + void ControllerSettingsStruct::mqtt_retainFlag(bool value) + { + bitWrite(VariousFlags, 5, value); + } -bool ControllerSettingsStruct::useExtendedCredentials() const -{ - return bitRead(VariousFlags, 6); -} + bool ControllerSettingsStruct::useExtendedCredentials() const + { + return bitRead(VariousFlags, 6); + } -void ControllerSettingsStruct::useExtendedCredentials(bool value) -{ - bitWrite(VariousFlags, 6, value); -} + void ControllerSettingsStruct::useExtendedCredentials(bool value) + { + bitWrite(VariousFlags, 6, value); + } -bool ControllerSettingsStruct::sendBinary() const -{ - return bitRead(VariousFlags, 7); -} + bool ControllerSettingsStruct::sendBinary() const + { + return bitRead(VariousFlags, 7); + } -void ControllerSettingsStruct::sendBinary(bool value) -{ - bitWrite(VariousFlags, 7, value); -} + void ControllerSettingsStruct::sendBinary(bool value) + { + bitWrite(VariousFlags, 7, value); + } -bool ControllerSettingsStruct::allowExpire() const -{ - return bitRead(VariousFlags, 9); -} + bool ControllerSettingsStruct::allowExpire() const + { + return bitRead(VariousFlags, 9); + } -void ControllerSettingsStruct::allowExpire(bool value) -{ - bitWrite(VariousFlags, 9, value); -} + void ControllerSettingsStruct::allowExpire(bool value) + { + bitWrite(VariousFlags, 9, value); + } -bool ControllerSettingsStruct::deduplicate() const -{ - return bitRead(VariousFlags, 10); -} + bool ControllerSettingsStruct::deduplicate() const + { + return bitRead(VariousFlags, 10); + } -void ControllerSettingsStruct::deduplicate(bool value) -{ - bitWrite(VariousFlags, 10, value); -} + void ControllerSettingsStruct::deduplicate(bool value) + { + bitWrite(VariousFlags, 10, value); + } -bool ControllerSettingsStruct::useLocalSystemTime() const -{ - return bitRead(VariousFlags, 11); -} + bool ControllerSettingsStruct::useLocalSystemTime() const + { + return bitRead(VariousFlags, 11); + } -void ControllerSettingsStruct::useLocalSystemTime(bool value) -{ + void ControllerSettingsStruct::useLocalSystemTime(bool value) + { bitWrite(VariousFlags, 11, value); } */ diff --git a/src/src/DataStructs/ControllerSettingsStruct.h b/src/src/DataStructs/ControllerSettingsStruct.h index cf5cefe3a..f7b9950c7 100644 --- a/src/src/DataStructs/ControllerSettingsStruct.h +++ b/src/src/DataStructs/ControllerSettingsStruct.h @@ -139,8 +139,7 @@ struct ControllerSettingsStruct String getHostPortString() const; -#if FEATURE_MQTT - // VariousFlags defaults to 0, keep in mind when adding bit lookups. + // VariousBits1 defaults to 0, keep in mind when adding bit lookups. bool mqtt_cleanSession() const { return VariousBits1.mqtt_cleanSession; } void mqtt_cleanSession(bool value) { VariousBits1.mqtt_cleanSession = value; } @@ -183,6 +182,7 @@ struct ControllerSettingsStruct bool UseDNS; uint8_t IP[4]; + uint8_t UNUSED_1[3]; unsigned int Port; char HostName[65]; char Publish[129]; @@ -190,52 +190,51 @@ struct ControllerSettingsStruct char MQTTLwtTopic[129]; char LWTMessageConnect[129]; char LWTMessageDisconnect[129]; + uint8_t UNUSED_2[2]; unsigned int MinimalTimeBetweenMessages; unsigned int MaxQueueDepth; unsigned int MaxRetry; bool DeleteOldest; // Action to perform when buffer full, delete oldest, or ignore newest. + uint8_t UNUSED_3[3]; unsigned int ClientTimeout; bool MustCheckReply; // When set to false, a sent message is considered always successful. taskIndex_t SampleSetInitiator; // The first task to start a sample set. + uint8_t UNUSED_4[2]; - union { - struct { - uint32_t unused_00 : 1; // Bit 00 - uint32_t mqtt_cleanSession : 1; // Bit 01 - uint32_t mqtt_not_sendLWT : 1; // Bit 02, !value, default enabled - uint32_t mqtt_not_willRetain : 1; // Bit 03, !value, default enabled - uint32_t mqtt_uniqueMQTTclientIdReconnect : 1; // Bit 04 - uint32_t mqtt_retainFlag : 1; // Bit 05 - uint32_t useExtendedCredentials : 1; // Bit 06 - uint32_t sendBinary : 1; // Bit 07 - uint32_t unused_08 : 1; // Bit 08 - uint32_t allowExpire : 1; // Bit 09 - uint32_t deduplicate : 1; // Bit 10 - uint32_t useLocalSystemTime : 1; // Bit 11 - // FIXME TD-er: Store TLS bits - 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 - } VariousBits1; - uint32_t VariousFlags; // Various flags - }; + struct { + uint32_t unused_00 : 1; // Bit 00 + uint32_t mqtt_cleanSession : 1; // Bit 01 + uint32_t mqtt_not_sendLWT : 1; // Bit 02, !value, default enabled + uint32_t mqtt_not_willRetain : 1; // Bit 03, !value, default enabled + uint32_t mqtt_uniqueMQTTclientIdReconnect : 1; // Bit 04 + uint32_t mqtt_retainFlag : 1; // Bit 05 + uint32_t useExtendedCredentials : 1; // Bit 06 + uint32_t sendBinary : 1; // Bit 07 + uint32_t unused_08 : 1; // Bit 08 + uint32_t allowExpire : 1; // Bit 09 + uint32_t deduplicate : 1; // Bit 10 + uint32_t useLocalSystemTime : 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 + } VariousBits1; char ClientID[65]; // Used to define the Client ID used by the controller private: @@ -245,13 +244,15 @@ private: bool updateIPcache(); }; +#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) ControllerSettingsStruct_ptr_type T(new (std::nothrow) ControllerSettingsStruct()); +#define MakeControllerSettings(T) void * calloc_ptr = special_calloc(1,sizeof(ControllerSettingsStruct)); ControllerSettingsStruct_ptr_type T(new (calloc_ptr) ControllerSettingsStruct()); //#endif // Check to see if MakeControllerSettings was successful diff --git a/src/src/DataStructs/DeviceStruct.cpp b/src/src/DataStructs/DeviceStruct.cpp index f23b9a3fd..d58f798fe 100644 --- a/src/src/DataStructs/DeviceStruct.cpp +++ b/src/src/DataStructs/DeviceStruct.cpp @@ -1,69 +1,70 @@ -#include "../DataStructs/DeviceStruct.h" - - - -DeviceStruct::DeviceStruct() : - Number(0), Type(0), VType(Sensor_VType::SENSOR_TYPE_NONE), Ports(0), ValueCount(0), - OutputDataType(Output_Data_type_t::Default), - PullUpOption(false), InverseLogicOption(false), FormulaOption(false), - Custom(false), SendDataOption(false), GlobalSyncOption(false), - TimerOption(false), TimerOptional(false), DecimalsOnly(false), - DuplicateDetection(false), ExitTaskBeforeSave(true), ErrorStateValues(false), - PluginStats(false), PluginLogsPeaks(false), PowerManager(false), - TaskLogsOwnPeaks(false), I2CNoDeviceCheck(false) {} - -bool DeviceStruct::connectedToGPIOpins() const { - switch(Type) { - case DEVICE_TYPE_SINGLE: // Single GPIO - case DEVICE_TYPE_SPI: - case DEVICE_TYPE_CUSTOM1: - - case DEVICE_TYPE_DUAL: // Dual GPIOs - case DEVICE_TYPE_SERIAL: - case DEVICE_TYPE_SPI2: - case DEVICE_TYPE_CUSTOM2: - - case DEVICE_TYPE_TRIPLE: // Triple GPIOs - case DEVICE_TYPE_SERIAL_PLUS1: - case DEVICE_TYPE_SPI3: - case DEVICE_TYPE_CUSTOM3: - return true; - default: - return false; - } -} - -bool DeviceStruct::usesTaskDevicePin(int pin) const { - if (pin == 1) - return connectedToGPIOpins(); - if (pin == 2) - return connectedToGPIOpins() && - !(Type == DEVICE_TYPE_SINGLE || - Type == DEVICE_TYPE_SPI || - Type == DEVICE_TYPE_CUSTOM1); - if (pin == 3) - return Type == DEVICE_TYPE_TRIPLE || - Type == DEVICE_TYPE_SERIAL_PLUS1 || - Type == DEVICE_TYPE_SPI3 || - Type == DEVICE_TYPE_CUSTOM3; - return false; -} - -bool DeviceStruct::isSerial() const { - return (Type == DEVICE_TYPE_SERIAL) || - (Type == DEVICE_TYPE_SERIAL_PLUS1); -} - -bool DeviceStruct::isSPI() const { - return (Type == DEVICE_TYPE_SPI) || - (Type == DEVICE_TYPE_SPI2) || - (Type == DEVICE_TYPE_SPI3); -} - -bool DeviceStruct::isCustom() const { - return (Type == DEVICE_TYPE_CUSTOM0) || - (Type == DEVICE_TYPE_CUSTOM1) || - (Type == DEVICE_TYPE_CUSTOM2) || - (Type == DEVICE_TYPE_CUSTOM3); -} - +#include "../DataStructs/DeviceStruct.h" + + + +DeviceStruct::DeviceStruct() : + Number(0), Type(0), VType(Sensor_VType::SENSOR_TYPE_NONE), Ports(0), ValueCount(0), + OutputDataType(Output_Data_type_t::Default), + PullUpOption(false), InverseLogicOption(false), FormulaOption(false), + Custom(false), SendDataOption(false), GlobalSyncOption(false), + TimerOption(false), TimerOptional(false), DecimalsOnly(false), + DuplicateDetection(false), ExitTaskBeforeSave(true), ErrorStateValues(false), + PluginStats(false), PluginLogsPeaks(false), PowerManager(false), + TaskLogsOwnPeaks(false), I2CNoDeviceCheck(false), + I2CMax100kHz(false), HasFormatUserVar(false) {} + +bool DeviceStruct::connectedToGPIOpins() const { + switch(Type) { + case DEVICE_TYPE_SINGLE: // Single GPIO + case DEVICE_TYPE_SPI: + case DEVICE_TYPE_CUSTOM1: + + case DEVICE_TYPE_DUAL: // Dual GPIOs + case DEVICE_TYPE_SERIAL: + case DEVICE_TYPE_SPI2: + case DEVICE_TYPE_CUSTOM2: + + case DEVICE_TYPE_TRIPLE: // Triple GPIOs + case DEVICE_TYPE_SERIAL_PLUS1: + case DEVICE_TYPE_SPI3: + case DEVICE_TYPE_CUSTOM3: + return true; + default: + return false; + } +} + +bool DeviceStruct::usesTaskDevicePin(int pin) const { + if (pin == 1) + return connectedToGPIOpins(); + if (pin == 2) + return connectedToGPIOpins() && + !(Type == DEVICE_TYPE_SINGLE || + Type == DEVICE_TYPE_SPI || + Type == DEVICE_TYPE_CUSTOM1); + if (pin == 3) + return Type == DEVICE_TYPE_TRIPLE || + Type == DEVICE_TYPE_SERIAL_PLUS1 || + Type == DEVICE_TYPE_SPI3 || + Type == DEVICE_TYPE_CUSTOM3; + return false; +} + +bool DeviceStruct::isSerial() const { + return (Type == DEVICE_TYPE_SERIAL) || + (Type == DEVICE_TYPE_SERIAL_PLUS1); +} + +bool DeviceStruct::isSPI() const { + return (Type == DEVICE_TYPE_SPI) || + (Type == DEVICE_TYPE_SPI2) || + (Type == DEVICE_TYPE_SPI3); +} + +bool DeviceStruct::isCustom() const { + return (Type == DEVICE_TYPE_CUSTOM0) || + (Type == DEVICE_TYPE_CUSTOM1) || + (Type == DEVICE_TYPE_CUSTOM2) || + (Type == DEVICE_TYPE_CUSTOM3); +} + diff --git a/src/src/DataStructs/DeviceStruct.h b/src/src/DataStructs/DeviceStruct.h index 45d05a47b..6d0d1da66 100644 --- a/src/src/DataStructs/DeviceStruct.h +++ b/src/src/DataStructs/DeviceStruct.h @@ -1,167 +1,169 @@ -#ifndef DATASTRUCTS_DEVICESTRUCTS_H -#define DATASTRUCTS_DEVICESTRUCTS_H - - -#include "../../ESPEasy_common.h" - -#include - -#include "../DataTypes/DeviceIndex.h" -#include "../DataTypes/PluginID.h" -#include "../DataTypes/SensorVType.h" - - -#define DEVICE_TYPE_SINGLE 1 // connected through 1 datapin -#define DEVICE_TYPE_DUAL 2 // connected through 2 datapins -#define DEVICE_TYPE_TRIPLE 3 // connected through 3 datapins -#define DEVICE_TYPE_ANALOG 10 // AIN/tout pin -#define DEVICE_TYPE_I2C 20 // connected through I2C -#define DEVICE_TYPE_SERIAL 21 // connected through UART/Serial -#define DEVICE_TYPE_SERIAL_PLUS1 22 // connected through UART/Serial + 1 extra signal pin -#define DEVICE_TYPE_SPI 23 // connected through SPI -#define DEVICE_TYPE_SPI2 24 // connected through SPI, 2 GPIOs -#define DEVICE_TYPE_SPI3 25 // connected through SPI, 3 GPIOs -#define DEVICE_TYPE_CUSTOM0 30 // Custom labels, Not using TaskDevicePin1 ... TaskDevicePin3 -#define DEVICE_TYPE_CUSTOM1 31 // Custom labels, 1 GPIO -#define DEVICE_TYPE_CUSTOM2 32 // Custom labels, 2 GPIOs -#define DEVICE_TYPE_CUSTOM3 33 // Custom labels, 3 GPIOs -#define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection - -#define I2C_MULTIPLEXER_NONE -1 // None selected -#define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, with reset, addresses 0x70-0x77 -#define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a or TCA9545a 4 channel I2C switch, with reset, addresses 0x70-0x77 (no interrupt - // support on TCA9545a) -#define I2C_MULTIPLEXER_TCA9543A 2 // TCA9543a 2 channel I2C switch, with reset, addresses 0x70-0x73 -#define I2C_MULTIPLEXER_PCA9540 3 // PCA9540 2 channel I2C switch, no reset, address 0x70, different channel addressing - -#define I2C_FLAGS_SLOW_SPEED 0 // Force slow speed when this flag is set -#define I2C_FLAGS_MUX_MULTICHANNEL 1 // Allow multiple multiplexer channels when set - - - -/*********************************************************************************************\ -* DeviceStruct -* Description of a plugin -\*********************************************************************************************/ -struct __attribute__((__packed__)) DeviceStruct -{ - DeviceStruct(); - - bool connectedToGPIOpins() const; - - bool usesTaskDevicePin(int pin) const; - - bool configurableDecimals() const - { - return FormulaOption || DecimalsOnly; - } - - bool isSerial() const; - - bool isSPI() const; - - bool isCustom() const; - - pluginID_t getPluginID() const - { - return pluginID_t::toPluginID(Number); - } - - - uint8_t Number; // Plugin ID number. (PLUGIN_ID_xxx) - uint8_t Type; // How the device is connected. e.g. DEVICE_TYPE_SINGLE => connected through 1 datapin - Sensor_VType VType; // Type of value the plugin will return. e.g. SENSOR_TYPE_STRING - uint8_t Ports; // Port to use when device has multiple I/O pins (N.B. not used much) - uint8_t ValueCount; // The number of output values of a plugin. The value should match the number of keys PLUGIN_VALUENAME1_xxx - Output_Data_type_t OutputDataType; // Subset of selectable output data types (Default = no selection) - - bool PullUpOption : 1; // Allow to set internal pull-up resistors. - bool InverseLogicOption : 1; // Allow to invert the boolean state (e.g. a switch) - bool FormulaOption : 1; // Allow to enter a formula to convert values during read. (not possible with Custom enabled) - bool Custom : 1; - bool SendDataOption : 1; // Allow to send data to a controller. - bool GlobalSyncOption : 1; // No longer used. Was used for ESPeasy values sync between nodes - bool TimerOption : 1; // Allow to set the "Interval" timer for the plugin. - bool TimerOptional : 1; // When taskdevice timer is not set and not optional, use default "Interval" delay (Settings.Delay) - bool DecimalsOnly : 1; // Allow to set the number of decimals (otherwise treated a 0 decimals) - bool DuplicateDetection : 1; // Some (typically receiving) plugins may receive the same data on multiple nodes. Such a plugin must help detect message duplicates. - bool ExitTaskBeforeSave : 1; // Optimization in memory usage, Do not exit when task data is needed during save. - bool ErrorStateValues : 1; // Support Error State Values, can be called to retrieve surrogate values when PLUGIN_READ returns false - bool PluginStats : 1; // Support for PluginStats to record last N task values, show charts etc. - bool PluginLogsPeaks : 1; // When PluginStats is enabled, a call to PLUGIN_READ will also check for peaks. With this enabled, the plugin must call to check for peaks itself. - bool PowerManager : 1; // Is a Power management controller (Power manager), that can be selected to be intialized *before* the SPI interface is started. - // (F.e.: M5Stack Core/Core2 needs to power the TFT before SPI can be started) - bool TaskLogsOwnPeaks : 1; // When PluginStats is enabled, a call to PLUGIN_READ will also check for peaks. With this enabled, the plugin must call to check for peaks itself. - bool I2CNoDeviceCheck : 1; // When enabled, NO I2C check will be done on the I2C address returned from PLUGIN_I2C_GET_ADDRESS function call - bool I2CMax100kHz : 1; // When enabled, the device is only able to handle 100 kHz bus-clock speed, shows warning and enables "Force Slow I2C speed" by default -}; - - -// Since Device[] is used in all plugins, creating a strict struct for it will increase build size by about 5k. -// So for ESP8266, which is severely build size constraint, we use a simple vector typedef. -// For ESP32, we use the more strictly typed struct to let the compiler find undesired use of this. -#ifdef ESP8266 -//typedef std::vector DeviceVector; -typedef DeviceStruct* DeviceVector; -#else - -// Specific struct used to only allow changing Device vector in the PLUGIN_ADD call -struct DeviceCount_t { - DeviceCount_t() = default; - - DeviceCount_t& operator++() { - // pre-increment, ++a - ++value; - return *this; - } - - // operator int() const { return value; } - - int value = -1; - -}; - -struct DeviceVector { - - // Regular access to DeviceStruct elements is 'const' - const DeviceStruct& operator[](deviceIndex_t index) const - { - return _vector[index.value]; - } - - - // Only 'write' access to DeviceStruct elements via DeviceCount_t type - // This should only be done during call to PLUGIN_ADD - DeviceStruct& operator[](DeviceCount_t index) - { - return _vector[index.value]; - } - - - // Should not change anything in the device vector except for the PLUGIN_ADD call - // Whichever calls this function should reconsider doing this - // FIXME TD-er: Fix whereever this is called. - DeviceStruct& getDeviceStructForEdit(deviceIndex_t index) - { - return _vector[index.value]; - } - - - size_t size() const - { - return _vector.size(); - } - - - void resize(size_t newSize) - { - _vector.resize(newSize); - } - -private: - std::vector _vector; -}; -#endif - - -#endif // DATASTRUCTS_DEVICESTRUCTS_H +#ifndef DATASTRUCTS_DEVICESTRUCTS_H +#define DATASTRUCTS_DEVICESTRUCTS_H + + +#include "../../ESPEasy_common.h" + +#include + +#include "../DataTypes/DeviceIndex.h" +#include "../DataTypes/PluginID.h" +#include "../DataTypes/SensorVType.h" + + +#define DEVICE_TYPE_SINGLE 1 // connected through 1 datapin +#define DEVICE_TYPE_DUAL 2 // connected through 2 datapins +#define DEVICE_TYPE_TRIPLE 3 // connected through 3 datapins +#define DEVICE_TYPE_ANALOG 10 // AIN/tout pin +#define DEVICE_TYPE_I2C 20 // connected through I2C +#define DEVICE_TYPE_SERIAL 21 // connected through UART/Serial +#define DEVICE_TYPE_SERIAL_PLUS1 22 // connected through UART/Serial + 1 extra signal pin +#define DEVICE_TYPE_SPI 23 // connected through SPI +#define DEVICE_TYPE_SPI2 24 // connected through SPI, 2 GPIOs +#define DEVICE_TYPE_SPI3 25 // connected through SPI, 3 GPIOs +#define DEVICE_TYPE_CUSTOM0 30 // Custom labels, Not using TaskDevicePin1 ... TaskDevicePin3 +#define DEVICE_TYPE_CUSTOM1 31 // Custom labels, 1 GPIO +#define DEVICE_TYPE_CUSTOM2 32 // Custom labels, 2 GPIOs +#define DEVICE_TYPE_CUSTOM3 33 // Custom labels, 3 GPIOs +#define DEVICE_TYPE_DUMMY 99 // Dummy device, has no physical connection + +#define I2C_MULTIPLEXER_NONE -1 // None selected +#define I2C_MULTIPLEXER_TCA9548A 0 // TCA9548a 8 channel I2C switch, with reset, addresses 0x70-0x77 +#define I2C_MULTIPLEXER_TCA9546A 1 // TCA9546a or TCA9545a 4 channel I2C switch, with reset, addresses 0x70-0x77 (no interrupt + // support on TCA9545a) +#define I2C_MULTIPLEXER_TCA9543A 2 // TCA9543a 2 channel I2C switch, with reset, addresses 0x70-0x73 +#define I2C_MULTIPLEXER_PCA9540 3 // PCA9540 2 channel I2C switch, no reset, address 0x70, different channel addressing + +#define I2C_FLAGS_SLOW_SPEED 0 // Force slow speed when this flag is set +#define I2C_FLAGS_MUX_MULTICHANNEL 1 // Allow multiple multiplexer channels when set + + + +/*********************************************************************************************\ +* DeviceStruct +* Description of a plugin +\*********************************************************************************************/ +struct __attribute__((__packed__)) DeviceStruct +{ + DeviceStruct(); + + bool connectedToGPIOpins() const; + + bool usesTaskDevicePin(int pin) const; + + bool configurableDecimals() const + { + return FormulaOption || DecimalsOnly; + } + + bool isSerial() const; + + bool isSPI() const; + + bool isCustom() const; + + pluginID_t getPluginID() const + { + return pluginID_t::toPluginID(Number); + } + + + uint8_t Number; // Plugin ID number. (PLUGIN_ID_xxx) + uint8_t Type; // How the device is connected. e.g. DEVICE_TYPE_SINGLE => connected through 1 datapin + Sensor_VType VType; // Type of value the plugin will return. e.g. SENSOR_TYPE_STRING + uint8_t Ports; // Port to use when device has multiple I/O pins (N.B. not used much) + uint8_t ValueCount; // The number of output values of a plugin. The value should match the number of keys PLUGIN_VALUENAME1_xxx + Output_Data_type_t OutputDataType; // Subset of selectable output data types (Default = no selection) + + bool PullUpOption : 1; // Allow to set internal pull-up resistors. + bool InverseLogicOption : 1; // Allow to invert the boolean state (e.g. a switch) + bool FormulaOption : 1; // Allow to enter a formula to convert values during read. (not possible with Custom enabled) + bool Custom : 1; + bool SendDataOption : 1; // Allow to send data to a controller. + bool GlobalSyncOption : 1; // No longer used. Was used for ESPeasy values sync between nodes + bool TimerOption : 1; // Allow to set the "Interval" timer for the plugin. + bool TimerOptional : 1; // When taskdevice timer is not set and not optional, use default "Interval" delay (Settings.Delay) + bool DecimalsOnly : 1; // Allow to set the number of decimals (otherwise treated a 0 decimals) + bool DuplicateDetection : 1; // Some (typically receiving) plugins may receive the same data on multiple nodes. Such a plugin must help detect message duplicates. + bool ExitTaskBeforeSave : 1; // Optimization in memory usage, Do not exit when task data is needed during save. + bool ErrorStateValues : 1; // Support Error State Values, can be called to retrieve surrogate values when PLUGIN_READ returns false + bool PluginStats : 1; // Support for PluginStats to record last N task values, show charts etc. + bool PluginLogsPeaks : 1; // When PluginStats is enabled, a call to PLUGIN_READ will also check for peaks. With this enabled, the plugin must call to check for peaks itself. + bool PowerManager : 1; // Is a Power management controller (Power manager), that can be selected to be intialized *before* the SPI interface is started. + // (F.e.: M5Stack Core/Core2 needs to power the TFT before SPI can be started) + bool TaskLogsOwnPeaks : 1; // When PluginStats is enabled, a call to PLUGIN_READ will also check for peaks. With this enabled, the plugin must call to check for peaks itself. + bool I2CNoDeviceCheck : 1; // When enabled, NO I2C check will be done on the I2C address returned from PLUGIN_I2C_GET_ADDRESS function call + bool I2CMax100kHz : 1; // When enabled, the device is only able to handle 100 kHz bus-clock speed, shows warning and enables "Force Slow I2C speed" by default + + bool HasFormatUserVar : 1; // Optimization to only call this when PLUGIN_FORMAT_USERVAR is implemented +}; + + +// Since Device[] is used in all plugins, creating a strict struct for it will increase build size by about 5k. +// So for ESP8266, which is severely build size constraint, we use a simple vector typedef. +// For ESP32, we use the more strictly typed struct to let the compiler find undesired use of this. +#ifdef ESP8266 +//typedef std::vector DeviceVector; +typedef DeviceStruct* DeviceVector; +#else + +// Specific struct used to only allow changing Device vector in the PLUGIN_ADD call +struct DeviceCount_t { + DeviceCount_t() = default; + + DeviceCount_t& operator++() { + // pre-increment, ++a + ++value; + return *this; + } + + // operator int() const { return value; } + + int value = -1; + +}; + +struct DeviceVector { + + // Regular access to DeviceStruct elements is 'const' + const DeviceStruct& operator[](deviceIndex_t index) const + { + return _vector[index.value]; + } + + + // Only 'write' access to DeviceStruct elements via DeviceCount_t type + // This should only be done during call to PLUGIN_ADD + DeviceStruct& operator[](DeviceCount_t index) + { + return _vector[index.value]; + } + + + // Should not change anything in the device vector except for the PLUGIN_ADD call + // Whichever calls this function should reconsider doing this + // FIXME TD-er: Fix whereever this is called. + DeviceStruct& getDeviceStructForEdit(deviceIndex_t index) + { + return _vector[index.value]; + } + + + size_t size() const + { + return _vector.size(); + } + + + void resize(size_t newSize) + { + _vector.resize(newSize); + } + +private: + std::vector _vector; +}; +#endif + + +#endif // DATASTRUCTS_DEVICESTRUCTS_H diff --git a/src/src/DataStructs/ESPEasy_EventStruct.cpp b/src/src/DataStructs/ESPEasy_EventStruct.cpp index 4e36d79f3..9d1cea003 100644 --- a/src/src/DataStructs/ESPEasy_EventStruct.cpp +++ b/src/src/DataStructs/ESPEasy_EventStruct.cpp @@ -1,50 +1,75 @@ -#include "../DataStructs/ESPEasy_EventStruct.h" - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataTypes/EventValueSource.h" -#include "../Globals/Plugins.h" -#include "../Globals/CPlugins.h" -#include "../Globals/NPlugins.h" - -#include "../../_Plugin_Helper.h" - -EventStruct::EventStruct(taskIndex_t taskIndex) : - TaskIndex(taskIndex), BaseVarIndex(taskIndex * VARS_PER_TASK) -{ - if (taskIndex >= INVALID_TASK_INDEX) { - BaseVarIndex = 0; - } -} - -void EventStruct::deep_copy(const struct EventStruct& other) { - this->operator=(other); -} - -void EventStruct::deep_copy(const struct EventStruct *other) { - if (other != nullptr) { - deep_copy(*other); - } -} - -void EventStruct::setTaskIndex(taskIndex_t taskIndex) { - TaskIndex = taskIndex; - - if (TaskIndex < INVALID_TASK_INDEX) { - BaseVarIndex = taskIndex * VARS_PER_TASK; - } - sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; -} - -void EventStruct::clear() { - *this = EventStruct(); -} - -Sensor_VType EventStruct::getSensorType() { - const int tmp_idx = idx; - - checkDeviceVTypeForTask(this); - idx = tmp_idx; - return sensorType; -} +#include "../DataStructs/ESPEasy_EventStruct.h" + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataTypes/EventValueSource.h" +#include "../Globals/Plugins.h" +#include "../Globals/CPlugins.h" +#include "../Globals/NPlugins.h" + +#include "../../_Plugin_Helper.h" + +EventStruct::EventStruct(taskIndex_t taskIndex) : + TaskIndex(taskIndex), BaseVarIndex(taskIndex * VARS_PER_TASK) +{ + if (taskIndex >= INVALID_TASK_INDEX) { + BaseVarIndex = 0; + } +} + +void EventStruct::deep_copy(const struct EventStruct& other) { + this->operator=(other); +} + +void EventStruct::deep_copy(const struct EventStruct *other) { + if (other != nullptr) { + deep_copy(*other); + } +} + +void EventStruct::setTaskIndex(taskIndex_t taskIndex) { + TaskIndex = taskIndex; + + if (TaskIndex < INVALID_TASK_INDEX) { + BaseVarIndex = taskIndex * VARS_PER_TASK; + } + sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; +} + +void EventStruct::clear() { + *this = EventStruct(); +} + +Sensor_VType EventStruct::getSensorType() { + const int tmp_idx = idx; + + checkDeviceVTypeForTask(this); + idx = tmp_idx; + return sensorType; +} + +int64_t EventStruct::getTimestamp_as_systemMicros() const +{ + if (timestamp_sec == 0) + return getMicros64(); + + // FIXME TD-er: What to do when system time has not been set? + int64_t res = node_time.Unixtime_to_systemMicros(timestamp_sec, timestamp_frac); + if (res < 0) { + // Unix time was from before we booted + // FIXME TD-er: What to do now? + return getMicros64(); + } + return res; +} + +void EventStruct::setUnixTimeTimestamp() +{ + timestamp_sec = node_time.getUnixTime(timestamp_frac); +} + +void EventStruct::setLocalTimeTimestamp() +{ + timestamp_sec = node_time.getLocalUnixTime(timestamp_frac); +} \ No newline at end of file diff --git a/src/src/DataStructs/ESPEasy_EventStruct.h b/src/src/DataStructs/ESPEasy_EventStruct.h index 6197d007d..97f36068c 100644 --- a/src/src/DataStructs/ESPEasy_EventStruct.h +++ b/src/src/DataStructs/ESPEasy_EventStruct.h @@ -1,74 +1,81 @@ -#ifndef DATASTRUCTS_ESPEASY_EVENTSTRUCT_H -#define DATASTRUCTS_ESPEASY_EVENTSTRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../DataTypes/ControllerIndex.h" -#include "../DataTypes/EventValueSource.h" -#include "../DataTypes/TaskIndex.h" -#include "../DataTypes/NotifierIndex.h" -#include "../DataStructs/DeviceStruct.h" - - -/*********************************************************************************************\ -* EventStruct -* This should not be copied, only moved. -* When copy is really needed, use deep_copy -\*********************************************************************************************/ -struct EventStruct -{ - EventStruct() = default; - // Delete the copy constructor - EventStruct(const struct EventStruct& event) = delete; -private: - // Hide the copy assignment operator by making it private - EventStruct& operator=(const EventStruct&) = default; - -public: - 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); - // explicit EventStruct(const struct EventStruct& event); - // EventStruct& operator=(const struct EventStruct& other); - - - void setTaskIndex(taskIndex_t taskIndex); - - void clear(); - - // Check (and update) sensorType if not set, plus return (corrected) sensorType - Sensor_VType getSensorType(); - - String String1; - String String2; - String String3; - String String4; - String String5; - unsigned long timestamp = 0u; - uint8_t *Data = nullptr; - int idx = 0; - int Par1 = 0; - int Par2 = 0; - int Par3 = 0; - int Par4 = 0; - int Par5 = 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 -#if FEATURE_NOTIFIER - 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; -}; - -#endif // DATASTRUCTS_ESPEASY_EVENTSTRUCT_H +#ifndef DATASTRUCTS_ESPEASY_EVENTSTRUCT_H +#define DATASTRUCTS_ESPEASY_EVENTSTRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../DataTypes/ControllerIndex.h" +#include "../DataTypes/EventValueSource.h" +#include "../DataTypes/TaskIndex.h" +#include "../DataTypes/NotifierIndex.h" +#include "../DataStructs/DeviceStruct.h" + + +/*********************************************************************************************\ +* EventStruct +* This should not be copied, only moved. +* When copy is really needed, use deep_copy +\*********************************************************************************************/ +struct EventStruct +{ + EventStruct() = default; + // Delete the copy constructor + EventStruct(const struct EventStruct& event) = delete; +private: + // Hide the copy assignment operator by making it private + EventStruct& operator=(const EventStruct&) = default; + +public: + 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); + // explicit EventStruct(const struct EventStruct& event); + // EventStruct& operator=(const struct EventStruct& other); + + + void setTaskIndex(taskIndex_t taskIndex); + + void clear(); + + // Check (and update) sensorType if not set, plus return (corrected) sensorType + Sensor_VType getSensorType(); + + int64_t getTimestamp_as_systemMicros() const; + void setUnixTimeTimestamp(); + void setLocalTimeTimestamp(); + + String String1; + String String2; + String String3; + String String4; + String String5; + + + uint32_t timestamp_sec = 0u; + uint32_t timestamp_frac = 0u; + uint8_t *Data = nullptr; + int idx = 0; + int Par1 = 0; + int Par2 = 0; + int Par3 = 0; + int Par4 = 0; + int Par5 = 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 +#if FEATURE_NOTIFIER + 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; +}; + +#endif // DATASTRUCTS_ESPEASY_EVENTSTRUCT_H diff --git a/src/src/DataStructs/EthernetEventData.cpp b/src/src/DataStructs/EthernetEventData.cpp index 36a40a93f..008320e9e 100644 --- a/src/src/DataStructs/EthernetEventData.cpp +++ b/src/src/DataStructs/EthernetEventData.cpp @@ -3,6 +3,7 @@ #if FEATURE_ETHERNET #include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/Settings.h" #include "../Helpers/Networking.h" #include @@ -12,6 +13,19 @@ #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()) { @@ -163,15 +177,23 @@ void EthernetEventData_t::markDisconnect() { } lastConnectMoment.clear(); processedDisconnect = false; +#if ESP_IDF_VERSION_MAJOR >= 5 + WiFi.STA.setDefault(); +#endif } void EthernetEventData_t::markConnected() { lastConnectMoment.setNow(); processedConnect = false; -#if FEATURE_USE_IPV6 - ETH.enableIpV6(); +#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 { diff --git a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp index 3162eb44e..3c42d964d 100644 --- a/src/src/DataStructs/ExtraTaskSettingsStruct.cpp +++ b/src/src/DataStructs/ExtraTaskSettingsStruct.cpp @@ -1,271 +1,272 @@ -#include "../DataStructs/ExtraTaskSettingsStruct.h" - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/PluginStats_Config.h" - -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_Plugin.h" - -#define EXTRA_TASK_SETTINGS_VERSION 1 - - -void ExtraTaskSettingsStruct::clear() { - // 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(ExtraTaskSettingsStruct)); - TaskIndex = INVALID_TASK_INDEX; - dummy1 = 0; - version = EXTRA_TASK_SETTINGS_VERSION; - for (int i = 0; i < VARS_PER_TASK; ++i) { - TaskDeviceValueDecimals[i] = 2; - } -} - -void ExtraTaskSettingsStruct::validate() { - ZERO_TERMINATE(TaskDeviceName); - - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - ZERO_TERMINATE(TaskDeviceFormula[i]); - ZERO_TERMINATE(TaskDeviceValueNames[i]); - } - - if (dummy1 != 0) { - // FIXME TD-er: This check was added to add the version for allowing to make transitions on the data. - // If we've been using this for a while, we no longer need to check for the value of this dummy and we can re-use it for something else. - dummy1 = 0; - version = 0; - } - - if (version != EXTRA_TASK_SETTINGS_VERSION) { - if (version < 1) { - // Need to initialize the newly added fields - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - setIgnoreRangeCheck(i); - TaskDeviceErrorValue[i] = 0.0f; - VariousBits[i] = 0u; - } - } - version = EXTRA_TASK_SETTINGS_VERSION; - } -} - -ChecksumType ExtraTaskSettingsStruct::computeChecksum() const { - return ChecksumType(reinterpret_cast(this), sizeof(ExtraTaskSettingsStruct)); -} - -bool ExtraTaskSettingsStruct::checkUniqueValueNames() const { - for (int i = 0; i < (VARS_PER_TASK - 1); ++i) { - for (int j = i; j < VARS_PER_TASK; ++j) { - if ((i != j) && (TaskDeviceValueNames[i][0] != 0)) { - if (strcasecmp(TaskDeviceValueNames[i], TaskDeviceValueNames[j]) == 0) { - return false; - } - } - } - } - return true; -} - -void ExtraTaskSettingsStruct::clearUnusedValueNames(uint8_t usedVars) { - for (uint8_t i = usedVars; i < VARS_PER_TASK; ++i) { - ZERO_FILL(TaskDeviceFormula[i]); - ZERO_FILL(TaskDeviceValueNames[i]); - TaskDeviceValueDecimals[i] = 2; - setIgnoreRangeCheck(i); - TaskDeviceErrorValue[i] = 0.0f; - VariousBits[i] = 0; - } -} - -bool ExtraTaskSettingsStruct::checkInvalidCharInNames(const char *name) const { - int pos = 0; - - while (*(name + pos) != 0) { - if (!validCharForNames(*(name + pos))) { return false; } - ++pos; - } - return true; -} - -bool ExtraTaskSettingsStruct::checkInvalidCharInNames() const { - if (!checkInvalidCharInNames(&TaskDeviceName[0])) { return false; } - - for (int i = 0; i < VARS_PER_TASK; ++i) { - if (!checkInvalidCharInNames(&TaskDeviceValueNames[i][0])) { return false; } - } - return true; -} - -String ExtraTaskSettingsStruct::getInvalidCharsForNames() { - return F(",-+/*=^%!#[]{}()"); -} - -bool ExtraTaskSettingsStruct::validCharForNames(char c) { - return c != ' ' && getInvalidCharsForNames().indexOf(c) == -1; -} - -void ExtraTaskSettingsStruct::setTaskDeviceValueName(taskVarIndex_t taskVarIndex, const String& str) -{ - if (validTaskVarIndex(taskVarIndex)) { - safe_strncpy( - TaskDeviceValueNames[taskVarIndex], - str, - sizeof(TaskDeviceValueNames[taskVarIndex])); - } -} - -void ExtraTaskSettingsStruct::setTaskDeviceValueName(taskVarIndex_t taskVarIndex, const __FlashStringHelper * str) -{ - setTaskDeviceValueName(taskVarIndex, String(str)); -} - -void ExtraTaskSettingsStruct::clearTaskDeviceValueName(taskVarIndex_t taskVarIndex) -{ - if (validTaskVarIndex(taskVarIndex)) { - ZERO_FILL(TaskDeviceValueNames[taskVarIndex]); - } -} - -void ExtraTaskSettingsStruct::clearDefaultTaskDeviceValueNames() -{ - for (int i = 0; i < VARS_PER_TASK; ++i) { - if (isDefaultTaskVarName(i)) { - clearTaskDeviceValueName(i); - } - } -} - -void ExtraTaskSettingsStruct::setAllowedRange(taskVarIndex_t taskVarIndex, const float& minValue, const float& maxValue) -{ - if (validTaskVarIndex(taskVarIndex)) { - if (minValue > maxValue) { - TaskDeviceMinValue[taskVarIndex] = maxValue; - TaskDeviceMaxValue[taskVarIndex] = minValue; - } else { - TaskDeviceMinValue[taskVarIndex] = minValue; - TaskDeviceMaxValue[taskVarIndex] = maxValue; - } - } -} - -void ExtraTaskSettingsStruct::setIgnoreRangeCheck(taskVarIndex_t taskVarIndex) -{ - if (validTaskVarIndex(taskVarIndex)) { - // Clear range to indicate no range check should be done. - TaskDeviceMinValue[taskVarIndex] = 0.0f; - TaskDeviceMaxValue[taskVarIndex] = 0.0f; - } -} - -bool ExtraTaskSettingsStruct::ignoreRangeCheck(taskVarIndex_t taskVarIndex) const -{ - if (validTaskVarIndex(taskVarIndex)) { - return essentiallyEqual(TaskDeviceMinValue[taskVarIndex], TaskDeviceMaxValue[taskVarIndex]); - } - return true; -} - -bool ExtraTaskSettingsStruct::valueInAllowedRange(taskVarIndex_t taskVarIndex, const float& value) const -{ - if (ignoreRangeCheck(taskVarIndex)) { return true; } - - if (validTaskVarIndex(taskVarIndex)) { - return definitelyLessThan(value, TaskDeviceMaxValue[taskVarIndex]) || - definitelyGreaterThan(value, TaskDeviceMinValue[taskVarIndex]); - } - #ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_ERROR, F("Programming error: invalid taskVarIndex")); - #endif // ifndef BUILD_NO_DEBUG - return false; -} - -float ExtraTaskSettingsStruct::checkAllowedRange(taskVarIndex_t taskVarIndex, const float& value) const -{ - if (!valueInAllowedRange(taskVarIndex, value)) { - if (validTaskVarIndex(taskVarIndex)) { - return TaskDeviceErrorValue[taskVarIndex]; - } - } - return value; -} - -#if FEATURE_PLUGIN_STATS - -// Plugin Stats is now only a single bit, but this may later changed into a combobox with some options. -// Thus leave 8 bits for the plugin stats options. - -bool ExtraTaskSettingsStruct::enabledPluginStats(taskVarIndex_t taskVarIndex) const -{ - if (!validTaskVarIndex(taskVarIndex)) { return false; } - return bitRead(VariousBits[taskVarIndex], 0); -} - -void ExtraTaskSettingsStruct::enablePluginStats(taskVarIndex_t taskVarIndex, bool enabled) -{ - if (validTaskVarIndex(taskVarIndex)) { - bitWrite(VariousBits[taskVarIndex], 0, enabled); - } -} - -bool ExtraTaskSettingsStruct::anyEnabledPluginStats() const -{ - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - if (enabledPluginStats(i)) { return true; } - } - return false; -} - -PluginStats_Config_t ExtraTaskSettingsStruct::getPluginStatsConfig(taskVarIndex_t taskVarIndex) const -{ - if (!validTaskVarIndex(taskVarIndex)) { return PluginStats_Config_t(); } - - PluginStats_Config_t res(get8BitFromUL(VariousBits[taskVarIndex], 0)); - return res; -} - -void ExtraTaskSettingsStruct::setPluginStatsConfig(taskVarIndex_t taskVarIndex, PluginStats_Config_t config) -{ - if (validTaskVarIndex(taskVarIndex)) { - uint8_t value = config.getStoredBits(); - bitWrite(value, 1, bitRead(VariousBits[taskVarIndex], 1)); - set8BitToUL(VariousBits[taskVarIndex], 0, value); - } -} - - -#endif // if FEATURE_PLUGIN_STATS - -bool ExtraTaskSettingsStruct::isDefaultTaskVarName(taskVarIndex_t taskVarIndex) const -{ - if (!validTaskVarIndex(taskVarIndex)) { return false; } - return bitRead(VariousBits[taskVarIndex], 1); -} - -void ExtraTaskSettingsStruct::isDefaultTaskVarName(taskVarIndex_t taskVarIndex, bool isDefault) -{ - if (validTaskVarIndex(taskVarIndex)) { - bitWrite(VariousBits[taskVarIndex], 1, isDefault); - } -} - - -void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq( - const __FlashStringHelper *valuename, - size_t nrValues, - uint8_t defaultDecimals, - bool displayString) -{ - for (byte i = 0; i < VARS_PER_TASK; ++i) { - if (i < nrValues) { - safe_strncpy( - TaskDeviceValueNames[i], - Plugin_valuename(valuename, i, displayString), - sizeof(TaskDeviceValueNames[i])); - TaskDeviceValueDecimals[i] = defaultDecimals; - } else { - ZERO_FILL(TaskDeviceValueNames[i]); - } - } -} +#include "../DataStructs/ExtraTaskSettingsStruct.h" + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/PluginStats_Config.h" + +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_Plugin.h" + +#define EXTRA_TASK_SETTINGS_VERSION 1 + + +void ExtraTaskSettingsStruct::clear() { + // 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(ExtraTaskSettingsStruct)); + TaskIndex = INVALID_TASK_INDEX; + dummy1 = 0; + version = EXTRA_TASK_SETTINGS_VERSION; + for (int i = 0; i < VARS_PER_TASK; ++i) { + TaskDeviceValueDecimals[i] = 2; + TaskDeviceErrorValue[i] = NAN; + } +} + +void ExtraTaskSettingsStruct::validate() { + ZERO_TERMINATE(TaskDeviceName); + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + ZERO_TERMINATE(TaskDeviceFormula[i]); + ZERO_TERMINATE(TaskDeviceValueNames[i]); + } + + if (dummy1 != 0) { + // FIXME TD-er: This check was added to add the version for allowing to make transitions on the data. + // If we've been using this for a while, we no longer need to check for the value of this dummy and we can re-use it for something else. + dummy1 = 0; + version = 0; + } + + if (version != EXTRA_TASK_SETTINGS_VERSION) { + if (version < 1) { + // Need to initialize the newly added fields + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + setIgnoreRangeCheck(i); + TaskDeviceErrorValue[i] = NAN; + VariousBits[i] = 0u; + } + } + version = EXTRA_TASK_SETTINGS_VERSION; + } +} + +ChecksumType ExtraTaskSettingsStruct::computeChecksum() const { + return ChecksumType(reinterpret_cast(this), sizeof(ExtraTaskSettingsStruct)); +} + +bool ExtraTaskSettingsStruct::checkUniqueValueNames() const { + for (int i = 0; i < (VARS_PER_TASK - 1); ++i) { + for (int j = i; j < VARS_PER_TASK; ++j) { + if ((i != j) && (TaskDeviceValueNames[i][0] != 0)) { + if (strcasecmp(TaskDeviceValueNames[i], TaskDeviceValueNames[j]) == 0) { + return false; + } + } + } + } + return true; +} + +void ExtraTaskSettingsStruct::clearUnusedValueNames(uint8_t usedVars) { + for (uint8_t i = usedVars; i < VARS_PER_TASK; ++i) { + ZERO_FILL(TaskDeviceFormula[i]); + ZERO_FILL(TaskDeviceValueNames[i]); + TaskDeviceValueDecimals[i] = 2; + setIgnoreRangeCheck(i); + TaskDeviceErrorValue[i] = NAN; + VariousBits[i] = 0; + } +} + +bool ExtraTaskSettingsStruct::checkInvalidCharInNames(const char *name) const { + int pos = 0; + + while (*(name + pos) != 0) { + if (!validCharForNames(*(name + pos))) { return false; } + ++pos; + } + return true; +} + +bool ExtraTaskSettingsStruct::checkInvalidCharInNames() const { + if (!checkInvalidCharInNames(&TaskDeviceName[0])) { return false; } + + for (int i = 0; i < VARS_PER_TASK; ++i) { + if (!checkInvalidCharInNames(&TaskDeviceValueNames[i][0])) { return false; } + } + return true; +} + +String ExtraTaskSettingsStruct::getInvalidCharsForNames() { + return F(",-+/*=^%!#[]{}()"); +} + +bool ExtraTaskSettingsStruct::validCharForNames(char c) { + return c != ' ' && getInvalidCharsForNames().indexOf(c) == -1; +} + +void ExtraTaskSettingsStruct::setTaskDeviceValueName(taskVarIndex_t taskVarIndex, const String& str) +{ + if (validTaskVarIndex(taskVarIndex)) { + safe_strncpy( + TaskDeviceValueNames[taskVarIndex], + str, + sizeof(TaskDeviceValueNames[taskVarIndex])); + } +} + +void ExtraTaskSettingsStruct::setTaskDeviceValueName(taskVarIndex_t taskVarIndex, const __FlashStringHelper * str) +{ + setTaskDeviceValueName(taskVarIndex, String(str)); +} + +void ExtraTaskSettingsStruct::clearTaskDeviceValueName(taskVarIndex_t taskVarIndex) +{ + if (validTaskVarIndex(taskVarIndex)) { + ZERO_FILL(TaskDeviceValueNames[taskVarIndex]); + } +} + +void ExtraTaskSettingsStruct::clearDefaultTaskDeviceValueNames() +{ + for (int i = 0; i < VARS_PER_TASK; ++i) { + if (isDefaultTaskVarName(i)) { + clearTaskDeviceValueName(i); + } + } +} + +void ExtraTaskSettingsStruct::setAllowedRange(taskVarIndex_t taskVarIndex, const float& minValue, const float& maxValue) +{ + if (validTaskVarIndex(taskVarIndex)) { + if (minValue > maxValue) { + TaskDeviceMinValue[taskVarIndex] = maxValue; + TaskDeviceMaxValue[taskVarIndex] = minValue; + } else { + TaskDeviceMinValue[taskVarIndex] = minValue; + TaskDeviceMaxValue[taskVarIndex] = maxValue; + } + } +} + +void ExtraTaskSettingsStruct::setIgnoreRangeCheck(taskVarIndex_t taskVarIndex) +{ + if (validTaskVarIndex(taskVarIndex)) { + // Clear range to indicate no range check should be done. + TaskDeviceMinValue[taskVarIndex] = 0.0f; + TaskDeviceMaxValue[taskVarIndex] = 0.0f; + } +} + +bool ExtraTaskSettingsStruct::ignoreRangeCheck(taskVarIndex_t taskVarIndex) const +{ + if (validTaskVarIndex(taskVarIndex)) { + return essentiallyEqual(TaskDeviceMinValue[taskVarIndex], TaskDeviceMaxValue[taskVarIndex]); + } + return true; +} + +bool ExtraTaskSettingsStruct::valueInAllowedRange(taskVarIndex_t taskVarIndex, const float& value) const +{ + if (ignoreRangeCheck(taskVarIndex)) { return true; } + + if (validTaskVarIndex(taskVarIndex)) { + return definitelyLessThan(value, TaskDeviceMaxValue[taskVarIndex]) || + definitelyGreaterThan(value, TaskDeviceMinValue[taskVarIndex]); + } + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_ERROR, F("Programming error: invalid taskVarIndex")); + #endif // ifndef BUILD_NO_DEBUG + return false; +} + +float ExtraTaskSettingsStruct::checkAllowedRange(taskVarIndex_t taskVarIndex, const float& value) const +{ + if (!valueInAllowedRange(taskVarIndex, value)) { + if (validTaskVarIndex(taskVarIndex)) { + return TaskDeviceErrorValue[taskVarIndex]; + } + } + return value; +} + +#if FEATURE_PLUGIN_STATS + +// Plugin Stats is now only a single bit, but this may later changed into a combobox with some options. +// Thus leave 8 bits for the plugin stats options. + +bool ExtraTaskSettingsStruct::enabledPluginStats(taskVarIndex_t taskVarIndex) const +{ + if (!validTaskVarIndex(taskVarIndex)) { return false; } + return bitRead(VariousBits[taskVarIndex], 0); +} + +void ExtraTaskSettingsStruct::enablePluginStats(taskVarIndex_t taskVarIndex, bool enabled) +{ + if (validTaskVarIndex(taskVarIndex)) { + bitWrite(VariousBits[taskVarIndex], 0, enabled); + } +} + +bool ExtraTaskSettingsStruct::anyEnabledPluginStats() const +{ + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + if (enabledPluginStats(i)) { return true; } + } + return false; +} + +PluginStats_Config_t ExtraTaskSettingsStruct::getPluginStatsConfig(taskVarIndex_t taskVarIndex) const +{ + if (!validTaskVarIndex(taskVarIndex)) { return PluginStats_Config_t(); } + + PluginStats_Config_t res(get8BitFromUL(VariousBits[taskVarIndex], 0)); + return res; +} + +void ExtraTaskSettingsStruct::setPluginStatsConfig(taskVarIndex_t taskVarIndex, PluginStats_Config_t config) +{ + if (validTaskVarIndex(taskVarIndex)) { + uint8_t value = config.getStoredBits(); + bitWrite(value, 1, bitRead(VariousBits[taskVarIndex], 1)); + set8BitToUL(VariousBits[taskVarIndex], 0, value); + } +} + + +#endif // if FEATURE_PLUGIN_STATS + +bool ExtraTaskSettingsStruct::isDefaultTaskVarName(taskVarIndex_t taskVarIndex) const +{ + if (!validTaskVarIndex(taskVarIndex)) { return false; } + return bitRead(VariousBits[taskVarIndex], 1); +} + +void ExtraTaskSettingsStruct::isDefaultTaskVarName(taskVarIndex_t taskVarIndex, bool isDefault) +{ + if (validTaskVarIndex(taskVarIndex)) { + bitWrite(VariousBits[taskVarIndex], 1, isDefault); + } +} + + +void ExtraTaskSettingsStruct::populateDeviceValueNamesSeq( + const __FlashStringHelper *valuename, + size_t nrValues, + uint8_t defaultDecimals, + bool displayString) +{ + for (byte i = 0; i < VARS_PER_TASK; ++i) { + if (i < nrValues) { + safe_strncpy( + TaskDeviceValueNames[i], + Plugin_valuename(valuename, i, displayString), + sizeof(TaskDeviceValueNames[i])); + TaskDeviceValueDecimals[i] = defaultDecimals; + } else { + ZERO_FILL(TaskDeviceValueNames[i]); + } + } +} diff --git a/src/src/DataStructs/FactoryDefaultPref.cpp b/src/src/DataStructs/FactoryDefaultPref.cpp index 8a2d41f8c..7f79ed212 100644 --- a/src/src/DataStructs/FactoryDefaultPref.cpp +++ b/src/src/DataStructs/FactoryDefaultPref.cpp @@ -18,12 +18,10 @@ void ResetFactoryDefaultPreference_struct::set(uint32_t preference) // Max. 15 char keys for ESPEasy Factory Default marked keys # define FACTORY_DEFAULT_NVS_PREF_KEY "FacDefPref" -bool ResetFactoryDefaultPreference_struct::init() +bool ResetFactoryDefaultPreference_struct::init(ESPEasy_NVS_Helper& preferences) { - ESPEasy_NVS_Helper nvs_helper; - - if (nvs_helper.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE))) { - return from_NVS(nvs_helper); + if (preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE))) { + return from_NVS(preferences); } return false; } diff --git a/src/src/DataStructs/FactoryDefaultPref.h b/src/src/DataStructs/FactoryDefaultPref.h index 16423d2d4..2188266af 100644 --- a/src/src/DataStructs/FactoryDefaultPref.h +++ b/src/src/DataStructs/FactoryDefaultPref.h @@ -18,7 +18,7 @@ struct ResetFactoryDefaultPreference_struct { void set(uint32_t preference); #ifdef ESP32 - bool init(); + bool init(ESPEasy_NVS_Helper& preferences); bool from_NVS(ESPEasy_NVS_Helper& preferences); void to_NVS(ESPEasy_NVS_Helper& preferences) const; diff --git a/src/src/DataStructs/FactoryDefault_Network_NVS.cpp b/src/src/DataStructs/FactoryDefault_Network_NVS.cpp index 97a65c443..97a725e8c 100644 --- a/src/src/DataStructs/FactoryDefault_Network_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_Network_NVS.cpp @@ -35,9 +35,9 @@ bool FactoryDefault_Network_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& pr if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_ETH_HW_CONF_KEY), ETH_HW_conf)) { Settings.ETH_Phy_Addr = bits.ETH_Phy_Addr; - Settings.ETH_Pin_mdc = bits.ETH_Pin_mdc; - Settings.ETH_Pin_mdio = bits.ETH_Pin_mdio; - Settings.ETH_Pin_power = bits.ETH_Pin_power; + 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); @@ -64,9 +64,9 @@ void FactoryDefault_Network_NVS::fromSettings_to_NVS(ESPEasy_NVS_Helper& prefere } { bits.ETH_Phy_Addr = Settings.ETH_Phy_Addr; - bits.ETH_Pin_mdc = Settings.ETH_Pin_mdc; - bits.ETH_Pin_mdio = Settings.ETH_Pin_mdio; - bits.ETH_Pin_power = Settings.ETH_Pin_power; + bits.ETH_Pin_mdc_cs = Settings.ETH_Pin_mdc_cs; + bits.ETH_Pin_mdio_irq = Settings.ETH_Pin_mdio_irq; + bits.ETH_Pin_power_rst = Settings.ETH_Pin_power_rst; bits.ETH_Phy_Type = static_cast(Settings.ETH_Phy_Type); bits.ETH_Clock_Mode = static_cast(Settings.ETH_Clock_Mode); bits.NetworkMedium = static_cast(Settings.NetworkMedium); diff --git a/src/src/DataStructs/FactoryDefault_Network_NVS.h b/src/src/DataStructs/FactoryDefault_Network_NVS.h index 7ed42dc24..1aa0b3bd6 100644 --- a/src/src/DataStructs/FactoryDefault_Network_NVS.h +++ b/src/src/DataStructs/FactoryDefault_Network_NVS.h @@ -29,9 +29,9 @@ private: union { struct { int8_t ETH_Phy_Addr; - int8_t ETH_Pin_mdc; - int8_t ETH_Pin_mdio; - int8_t ETH_Pin_power; + int8_t ETH_Pin_mdc_cs; + int8_t ETH_Pin_mdio_irq; + int8_t ETH_Pin_power_rst; uint8_t ETH_Phy_Type; uint8_t ETH_Clock_Mode; uint8_t NetworkMedium; diff --git a/src/src/DataStructs/FactoryDefault_UnitName_NVS.cpp b/src/src/DataStructs/FactoryDefault_UnitName_NVS.cpp index 08c67b7f9..4068dd1c5 100644 --- a/src/src/DataStructs/FactoryDefault_UnitName_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_UnitName_NVS.cpp @@ -13,12 +13,16 @@ void FactoryDefault_UnitName_NVS::fromSettings() { bitWrite(data[1], 0, Settings.appendUnitToHostname()); data[0] = Settings.Unit; memcpy((char *)(data + 2), Settings.Name, sizeof(Settings.Name)); + data[2 + sizeof(Settings.Name)] = Settings.UDPPort >> 8; + data[3 + sizeof(Settings.Name)] = Settings.UDPPort & 0xFF; } void FactoryDefault_UnitName_NVS::applyToSettings() const { Settings.appendUnitToHostname(bitRead(data[1], 0)); Settings.Unit = data[0]; memcpy(Settings.Name, (char *)(data + 2), sizeof(Settings.Name)); + + Settings.UDPPort = data[2 + sizeof(Settings.Name)] << 8 | data[3 + sizeof(Settings.Name)]; } bool FactoryDefault_UnitName_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences) { diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp index db31abf94..8cc486e54 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.cpp @@ -1,101 +1,111 @@ -#include "../DataStructs/FactoryDefault_WiFi_NVS.h" - -#ifdef ESP32 - -# include "../Globals/Settings.h" -# include "../Globals/SecuritySettings.h" -# include "../Helpers/StringConverter.h" - -// Max. 15 char keys for ESPEasy Factory Default marked keys -# define FACTORY_DEFAULT_NVS_SSID1_KEY "WIFI_SSID1" -# define FACTORY_DEFAULT_NVS_WPA_PASS1_KEY "WIFI_PASS1" -# define FACTORY_DEFAULT_NVS_SSID2_KEY "WIFI_SSID2" -# define FACTORY_DEFAULT_NVS_WPA_PASS2_KEY "WIFI_PASS2" -# define FACTORY_DEFAULT_NVS_AP_PASS_KEY "WIFI_AP_PASS" -# define FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY "WIFI_Flags" - - -void FactoryDefault_WiFi_NVS::fromSettings() { - bits.IncludeHiddenSSID = Settings.IncludeHiddenSSID(); - bits.ApDontForceSetup = Settings.ApDontForceSetup(); - bits.DoNotStartAP = Settings.DoNotStartAP(); - bits.ForceWiFi_bg_mode = Settings.ForceWiFi_bg_mode(); - bits.WiFiRestart_connection_lost = Settings.WiFiRestart_connection_lost(); - bits.WifiNoneSleep = Settings.WifiNoneSleep(); - bits.gratuitousARP = Settings.gratuitousARP(); - bits.UseMaxTXpowerForSending = Settings.UseMaxTXpowerForSending(); - bits.UseLastWiFiFromRTC = Settings.UseLastWiFiFromRTC(); - bits.WaitWiFiConnect = Settings.WaitWiFiConnect(); - bits.SDK_WiFi_autoreconnect = Settings.SDK_WiFi_autoreconnect(); - bits.HiddenSSID_SlowConnectPerBSSID = Settings.HiddenSSID_SlowConnectPerBSSID(); -} - -void FactoryDefault_WiFi_NVS::applyToSettings() const { - Settings.IncludeHiddenSSID(bits.IncludeHiddenSSID); - Settings.ApDontForceSetup(bits.ApDontForceSetup); - Settings.DoNotStartAP(bits.DoNotStartAP); - Settings.ForceWiFi_bg_mode(bits.ForceWiFi_bg_mode); - Settings.WiFiRestart_connection_lost(bits.WiFiRestart_connection_lost); - Settings.WifiNoneSleep(bits.WifiNoneSleep); - Settings.gratuitousARP(bits.gratuitousARP); - Settings.UseMaxTXpowerForSending(bits.UseMaxTXpowerForSending); - Settings.UseLastWiFiFromRTC(bits.UseLastWiFiFromRTC); - Settings.WaitWiFiConnect(bits.WaitWiFiConnect); - Settings.SDK_WiFi_autoreconnect(bits.SDK_WiFi_autoreconnect); - Settings.HiddenSSID_SlowConnectPerBSSID(bits.HiddenSSID_SlowConnectPerBSSID); -} - -bool FactoryDefault_WiFi_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences) { - String tmp; - - if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_SSID1_KEY), tmp)) { - safe_strncpy(SecuritySettings.WifiSSID, tmp, sizeof(SecuritySettings.WifiSSID)); - } - - if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY), tmp)) { - safe_strncpy(SecuritySettings.WifiKey, tmp, sizeof(SecuritySettings.WifiKey)); - } - - if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_SSID2_KEY), tmp)) { - safe_strncpy(SecuritySettings.WifiSSID2, tmp, sizeof(SecuritySettings.WifiSSID2)); - } - - if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY), tmp)) { - safe_strncpy(SecuritySettings.WifiKey2, tmp, sizeof(SecuritySettings.WifiKey2)); - } - - if (preferences.getPreference(F(FACTORY_DEFAULT_NVS_AP_PASS_KEY), tmp)) { - safe_strncpy(SecuritySettings.WifiAPKey, tmp, sizeof(SecuritySettings.WifiAPKey)); - } - - - if (!preferences.getPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data)) { - return false; - } - - applyToSettings(); - return true; -} - -void FactoryDefault_WiFi_NVS::fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences) { - fromSettings(); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data); - - // Store WiFi credentials - preferences.setPreference(F(FACTORY_DEFAULT_NVS_SSID1_KEY), String(SecuritySettings.WifiSSID)); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY), String(SecuritySettings.WifiKey)); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_SSID2_KEY), String(SecuritySettings.WifiSSID2)); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY), String(SecuritySettings.WifiKey2)); - preferences.setPreference(F(FACTORY_DEFAULT_NVS_AP_PASS_KEY), String(SecuritySettings.WifiAPKey)); -} - -void FactoryDefault_WiFi_NVS::clear_from_NVS(ESPEasy_NVS_Helper& preferences) { - preferences.remove(F(FACTORY_DEFAULT_NVS_SSID1_KEY)); - preferences.remove(F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY)); - preferences.remove(F(FACTORY_DEFAULT_NVS_SSID2_KEY)); - preferences.remove(F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY)); - preferences.remove(F(FACTORY_DEFAULT_NVS_AP_PASS_KEY)); - preferences.remove(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY)); -} - -#endif // ifdef ESP32 +#include "../DataStructs/FactoryDefault_WiFi_NVS.h" + +#ifdef ESP32 + +# include "../Globals/Settings.h" +# include "../Globals/SecuritySettings.h" +# include "../Helpers/StringConverter.h" + +// Max. 15 char keys for ESPEasy Factory Default marked keys +# define FACTORY_DEFAULT_NVS_SSID1_KEY "WIFI_SSID1" +# define FACTORY_DEFAULT_NVS_WPA_PASS1_KEY "WIFI_PASS1" +# define FACTORY_DEFAULT_NVS_SSID2_KEY "WIFI_SSID2" +# define FACTORY_DEFAULT_NVS_WPA_PASS2_KEY "WIFI_PASS2" +# define FACTORY_DEFAULT_NVS_AP_PASS_KEY "WIFI_AP_PASS" +# define FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY "WIFI_Flags" + + +void FactoryDefault_WiFi_NVS::fromSettings() { + bits.IncludeHiddenSSID = Settings.IncludeHiddenSSID(); + bits.ApDontForceSetup = Settings.ApDontForceSetup(); + bits.DoNotStartAP = Settings.DoNotStartAP(); + bits.ForceWiFi_bg_mode = Settings.ForceWiFi_bg_mode(); + bits.WiFiRestart_connection_lost = Settings.WiFiRestart_connection_lost(); + bits.WifiNoneSleep = Settings.WifiNoneSleep(); + bits.gratuitousARP = Settings.gratuitousARP(); + bits.UseMaxTXpowerForSending = Settings.UseMaxTXpowerForSending(); + bits.UseLastWiFiFromRTC = Settings.UseLastWiFiFromRTC(); + bits.WaitWiFiConnect = Settings.WaitWiFiConnect(); + bits.SDK_WiFi_autoreconnect = Settings.SDK_WiFi_autoreconnect(); + bits.HiddenSSID_SlowConnectPerBSSID = Settings.HiddenSSID_SlowConnectPerBSSID(); + bits.EnableIPv6 = Settings.EnableIPv6(); + bits.PassiveWiFiScan = Settings.PassiveWiFiScan(); +} + +void FactoryDefault_WiFi_NVS::applyToSettings() const { + Settings.IncludeHiddenSSID(bits.IncludeHiddenSSID); + Settings.ApDontForceSetup(bits.ApDontForceSetup); + Settings.DoNotStartAP(bits.DoNotStartAP); + Settings.ForceWiFi_bg_mode(bits.ForceWiFi_bg_mode); + Settings.WiFiRestart_connection_lost(bits.WiFiRestart_connection_lost); + Settings.WifiNoneSleep(bits.WifiNoneSleep); + Settings.gratuitousARP(bits.gratuitousARP); + Settings.UseMaxTXpowerForSending(bits.UseMaxTXpowerForSending); + Settings.UseLastWiFiFromRTC(bits.UseLastWiFiFromRTC); + Settings.WaitWiFiConnect(bits.WaitWiFiConnect); + Settings.SDK_WiFi_autoreconnect(bits.SDK_WiFi_autoreconnect); + Settings.HiddenSSID_SlowConnectPerBSSID(bits.HiddenSSID_SlowConnectPerBSSID); + Settings.EnableIPv6(bits.EnableIPv6); + Settings.PassiveWiFiScan(bits.PassiveWiFiScan); +} + +struct FactoryDefault_WiFi_NVS_securityPrefs { + FactoryDefault_WiFi_NVS_securityPrefs(const __FlashStringHelper *pref, + char *dest, + size_t size) + : _pref(pref), _dest(dest), _size(size) {} + + const __FlashStringHelper *_pref; + char *_dest; + size_t _size; +}; + +const FactoryDefault_WiFi_NVS_securityPrefs _WiFi_NVS_securityPrefs_values[] = { + { F(FACTORY_DEFAULT_NVS_SSID1_KEY), SecuritySettings.WifiSSID, sizeof(SecuritySettings.WifiSSID) }, + { F(FACTORY_DEFAULT_NVS_WPA_PASS1_KEY), SecuritySettings.WifiKey, sizeof(SecuritySettings.WifiKey) }, + { F(FACTORY_DEFAULT_NVS_SSID2_KEY), SecuritySettings.WifiSSID2, sizeof(SecuritySettings.WifiSSID2) }, + { F(FACTORY_DEFAULT_NVS_WPA_PASS2_KEY), SecuritySettings.WifiKey2, sizeof(SecuritySettings.WifiKey2) }, + { F(FACTORY_DEFAULT_NVS_AP_PASS_KEY), SecuritySettings.WifiAPKey, sizeof(SecuritySettings.WifiAPKey) } +}; + + +bool FactoryDefault_WiFi_NVS::applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences) { + String tmp; + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + if (preferences.getPreference(_WiFi_NVS_securityPrefs_values[i]._pref, tmp)) { + safe_strncpy(_WiFi_NVS_securityPrefs_values[i]._dest, tmp, _WiFi_NVS_securityPrefs_values[i]._size); + } + } + + if (!preferences.getPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data)) { + return false; + } + + applyToSettings(); + return true; +} + +void FactoryDefault_WiFi_NVS::fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences) { + fromSettings(); + preferences.setPreference(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY), data); + + // Store WiFi credentials + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + preferences.setPreference(_WiFi_NVS_securityPrefs_values[i]._pref, String(_WiFi_NVS_securityPrefs_values[i]._dest)); + } +} + +void FactoryDefault_WiFi_NVS::clear_from_NVS(ESPEasy_NVS_Helper& preferences) { + constexpr unsigned nr__WiFi_NVS_securityPrefs_values = NR_ELEMENTS(_WiFi_NVS_securityPrefs_values); + + for (unsigned i = 0; i < nr__WiFi_NVS_securityPrefs_values; ++i) { + preferences.remove(_WiFi_NVS_securityPrefs_values[i]._pref); + } + preferences.remove(F(FACTORY_DEFAULT_NVS_WIFI_FLAGS_KEY)); +} + +#endif // ifdef ESP32 diff --git a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h index f184abdd7..e3638b084 100644 --- a/src/src/DataStructs/FactoryDefault_WiFi_NVS.h +++ b/src/src/DataStructs/FactoryDefault_WiFi_NVS.h @@ -1,55 +1,57 @@ -#ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H -#define DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H - - -#include "../../ESPEasy_common.h" - -#ifdef ESP32 - -# include "../Helpers/ESPEasy_NVS_Helper.h" - - -class FactoryDefault_WiFi_NVS { -private: - - void fromSettings(); - - void applyToSettings() const; - -public: - - bool applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences); - - void fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences); - - void clear_from_NVS(ESPEasy_NVS_Helper& preferences); - -private: - - union { - struct { - uint64_t IncludeHiddenSSID : 1; - uint64_t ApDontForceSetup : 1; - uint64_t DoNotStartAP : 1; - uint64_t ForceWiFi_bg_mode : 1; - uint64_t WiFiRestart_connection_lost : 1; - uint64_t WifiNoneSleep : 1; - uint64_t gratuitousARP : 1; - uint64_t UseMaxTXpowerForSending : 1; - uint64_t UseLastWiFiFromRTC : 1; - uint64_t WaitWiFiConnect : 1; - uint64_t SDK_WiFi_autoreconnect : 1; - uint64_t HiddenSSID_SlowConnectPerBSSID : 1; - - uint64_t unused : 52; - } bits; - - uint64_t data{}; - }; -}; - - -#endif // ifdef ESP32 - - -#endif // ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H +#ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H +#define DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H + + +#include "../../ESPEasy_common.h" + +#ifdef ESP32 + +# include "../Helpers/ESPEasy_NVS_Helper.h" + + +class FactoryDefault_WiFi_NVS { +private: + + void fromSettings(); + + void applyToSettings() const; + +public: + + bool applyToSettings_from_NVS(ESPEasy_NVS_Helper& preferences); + + void fromSettings_to_NVS(ESPEasy_NVS_Helper& preferences); + + void clear_from_NVS(ESPEasy_NVS_Helper& preferences); + +private: + + union { + struct { + uint64_t IncludeHiddenSSID : 1; + uint64_t ApDontForceSetup : 1; + uint64_t DoNotStartAP : 1; + uint64_t ForceWiFi_bg_mode : 1; + uint64_t WiFiRestart_connection_lost : 1; + uint64_t WifiNoneSleep : 1; + uint64_t gratuitousARP : 1; + uint64_t UseMaxTXpowerForSending : 1; + uint64_t UseLastWiFiFromRTC : 1; + uint64_t WaitWiFiConnect : 1; + uint64_t SDK_WiFi_autoreconnect : 1; + uint64_t HiddenSSID_SlowConnectPerBSSID : 1; + uint64_t EnableIPv6 : 1; + uint64_t PassiveWiFiScan : 1; + + uint64_t unused : 50; + } bits; + + uint64_t data{}; + }; +}; + + +#endif // ifdef ESP32 + + +#endif // ifndef DATASTRUCTS_FACTORYDEFAULT_WIFI_NVS_H diff --git a/src/src/DataStructs/GpioFactorySettingsStruct.cpp b/src/src/DataStructs/GpioFactorySettingsStruct.cpp index fd0415f17..b0e6838a8 100644 --- a/src/src/DataStructs/GpioFactorySettingsStruct.cpp +++ b/src/src/DataStructs/GpioFactorySettingsStruct.cpp @@ -107,7 +107,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) #endif -#ifdef ESP32 +# if CONFIG_ETH_USE_ESP32_EMAC case DeviceModel::DeviceModel_Olimex_ESP32_PoE: button[0] = 34; // BUT1 Button relais[0] = -1; // No LED's or relays on board @@ -115,7 +115,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda = 13; i2c_scl = 16; eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8710; + eth_phytype = EthPhyType_t::LAN8720; eth_mdc = 23; eth_mdio = 18; eth_power = 12; @@ -131,7 +131,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda = 13; i2c_scl = 16; eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8710; + eth_phytype = EthPhyType_t::LAN8720; eth_mdc = 23; eth_mdio = 18; eth_power = -1; // No Ethernet power pin @@ -146,7 +146,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda = -1; i2c_scl = -1; eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8710; + eth_phytype = EthPhyType_t::LAN8720; eth_mdc = 23; eth_mdio = 18; eth_power = 5; @@ -178,7 +178,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda = 15; i2c_scl = 4; eth_phyaddr = 0; - eth_phytype = EthPhyType_t::LAN8710; + eth_phytype = EthPhyType_t::LAN8720; eth_mdc = 16; eth_mdio = 17; eth_power = -1; @@ -191,7 +191,7 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) i2c_sda = 21; i2c_scl = 22; eth_phyaddr = 1; - eth_phytype = EthPhyType_t::LAN8710; + 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 @@ -199,12 +199,6 @@ GpioFactorySettingsStruct::GpioFactorySettingsStruct(DeviceModel model) network_medium = NetworkMedium_t::Ethernet; break; - #else - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - case DeviceModel::DeviceModel_wESP32: - case DeviceModel::DeviceModel_WT32_ETH01: #endif case DeviceModel::DeviceModel_default: diff --git a/src/src/DataStructs/MAC_address.h b/src/src/DataStructs/MAC_address.h index 54ac8ffef..1f5ab6b3f 100644 --- a/src/src/DataStructs/MAC_address.h +++ b/src/src/DataStructs/MAC_address.h @@ -1,79 +1,79 @@ -#ifndef DATASTRUCTS_MAC_ADDRESS_H -#define DATASTRUCTS_MAC_ADDRESS_H - -#include -#include - -class __attribute__((__packed__)) MAC_address { -public: - - MAC_address() = default; - - MAC_address(const uint8_t new_mac[6]); - - MAC_address(const MAC_address& other); - - MAC_address& operator=(const MAC_address& other); - - bool operator==(const MAC_address& other) const { - return mac_addr_cmp(other.mac); - } - - bool operator!=(const MAC_address& other) const { - return !mac_addr_cmp(other.mac); - } - - bool operator==(const uint8_t other[6]) const { - return mac_addr_cmp(other); - } - - bool operator!=(const uint8_t other[6]) const { - return !mac_addr_cmp(other); - } - - // Parse string with MAC address. - // Returns false if the given string has no valid formatted mac address. - bool set(const char *string); - - void set(const uint8_t other[6]); - - void get(uint8_t mac_out[6]) const; - - bool all_zero() const; - - bool all_one() const; - - String toString() const; - - // An universally administered address (UAA) is uniquely assigned to a device by its manufacturer. - // The first three octets (in transmission order) identify the organization that issued - // the identifier and are known as the organizationally unique identifier (OUI) - bool isUniversal() const { - return (mac[0] & 2) == 0; - } - - // A locally administered address (LAA) is assigned to a device by a network administrator, overriding the burned-in address. - bool isLocal() const { - return !isUniversal(); - } - - // Unicast frames are meant to be received by a single network device. - // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast - bool isUnicast() const { - return (mac[0] & 1) == 0; - } - - // Multicast frames are meant to be received by multiple network devices - // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast - bool isMulticast() const { - return !isUnicast(); - } - - uint8_t mac[6] = { 0 }; - -private: - - bool mac_addr_cmp(const uint8_t other[6]) const; -}; - +#ifndef DATASTRUCTS_MAC_ADDRESS_H +#define DATASTRUCTS_MAC_ADDRESS_H + +#include +#include + +class __attribute__((__packed__)) MAC_address { +public: + + MAC_address() = default; + + MAC_address(const uint8_t new_mac[6]); + + MAC_address(const MAC_address& other); + + MAC_address& operator=(const MAC_address& other); + + bool operator==(const MAC_address& other) const { + return mac_addr_cmp(other.mac); + } + + bool operator!=(const MAC_address& other) const { + return !mac_addr_cmp(other.mac); + } + + bool operator==(const uint8_t other[6]) const { + return mac_addr_cmp(other); + } + + bool operator!=(const uint8_t other[6]) const { + return !mac_addr_cmp(other); + } + + // Parse string with MAC address. + // Returns false if the given string has no valid formatted mac address. + bool set(const char *string); + + void set(const uint8_t other[6]); + + void get(uint8_t mac_out[6]) const; + + bool all_zero() const; + + bool all_one() const; + + String toString() const; + + // An universally administered address (UAA) is uniquely assigned to a device by its manufacturer. + // The first three octets (in transmission order) identify the organization that issued + // the identifier and are known as the organizationally unique identifier (OUI) + bool isUniversal() const { + return (mac[0] & 2) == 0; + } + + // A locally administered address (LAA) is assigned to a device by a network administrator, overriding the burned-in address. + bool isLocal() const { + return !isUniversal(); + } + + // Unicast frames are meant to be received by a single network device. + // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + bool isUnicast() const { + return (mac[0] & 1) == 0; + } + + // Multicast frames are meant to be received by multiple network devices + // See: https://en.wikipedia.org/wiki/MAC_address#Unicast_vs._multicast + bool isMulticast() const { + return !isUnicast(); + } + + uint8_t mac[6] = { 0 }; + +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/src/DataStructs/NTP_candidate.cpp b/src/src/DataStructs/NTP_candidate.cpp index aa4070516..ede7ffe5e 100644 --- a/src/src/DataStructs/NTP_candidate.cpp +++ b/src/src/DataStructs/NTP_candidate.cpp @@ -1,90 +1,103 @@ -#include "../DataStructs/NTP_candidate.h" - -#if FEATURE_ESPEASY_P2P - -# include "../CustomBuild/CompiletimeDefines.h" -# include "../DataTypes/ESPEasyTimeSource.h" -# include "../Globals/Settings.h" -# include "../Helpers/ESPEasy_time_calc.h" -# include "../Helpers/StringConverter.h" - - -bool NTP_candidate_struct::set(const NodeStruct& node) -{ - if (node.unit == Settings.Unit) { return false; } - - if (node.unix_time_sec < get_build_unixtime()) { return false; } - const timeSource_t timeSource = static_cast(node.timeSource); - - if (timeSource == timeSource_t::No_time_source) { return false; } - - // Only allow time from p2p nodes who only got it via p2p themselves as "last resource" - const unsigned long p2p_source_penalty = - isExternalTimeSource(timeSource) ? 0 : 10000; - const unsigned long time_wander_other = - p2p_source_penalty + computeExpectedWander(timeSource, node.lastUpdated); - - if (timePassedSince(_received_moment) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC) { clear(); } - - if ((_time_wander < 0) || (time_wander_other < static_cast(_time_wander))) { - _time_wander = time_wander_other; - _unix_time_sec = node.unix_time_sec; - _unix_time_frac = node.unix_time_frac; - _received_moment = millis(); - _unit = node.unit; - - if (_first_received_moment == 0) { - _first_received_moment = _received_moment; - } - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("NTP : Time candidate: "); - log += node.getSummary(); - - log += concat(F(" time: "), _unix_time_sec); - log += ' '; - log += toString(timeSource); - log += concat(F(" est. wander: "), _time_wander); - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG - return true; - } - return false; -} - -void NTP_candidate_struct::clear() -{ - _unix_time_sec = 0; - _unix_time_frac = 0; - _time_wander = -1; - _received_moment = 0; - _first_received_moment = 0; -} - -bool NTP_candidate_struct::getUnixTime(double& unix_time_d, uint8_t& unit) const -{ - if ((_unix_time_sec == 0) || (_time_wander < 0) || (_received_moment == 0)) { - return false; - } - - if (timePassedSince(_first_received_moment) < 30000) { - // Make sure to allow for enough time to collect the "best" option. - return false; - } - - unit = _unit; - - unix_time_d = static_cast(_unix_time_sec); - - // Add fractional part. - unix_time_d += (static_cast(_unix_time_frac) / 4294967295.0); - - // Add time since it was received - unix_time_d += static_cast(timePassedSince(_received_moment)) / 1000.0; - - return true; -} - -#endif // if FEATURE_ESPEASY_P2P +#include "../DataStructs/NTP_candidate.h" + +#if FEATURE_ESPEASY_P2P + +# include "../CustomBuild/CompiletimeDefines.h" +# include "../DataTypes/ESPEasyTimeSource.h" +# include "../Globals/Settings.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + + +bool NTP_candidate_struct::set(const NodeStruct& node) +{ + if (node.unit == Settings.Unit) { return false; } + + if (node.unix_time_sec < get_build_unixtime()) { return false; } + const timeSource_t timeSource = static_cast(node.timeSource); + + if (timeSource == timeSource_t::No_time_source) { return false; } + + // Only allow time from p2p nodes who only got it via p2p themselves as "last resource" + const unsigned long p2p_source_penalty = + isExternalTimeSource(timeSource) ? 0 : 10000; + const unsigned long time_wander_other = + p2p_source_penalty + + computeExpectedWander(timeSource, node.lastUpdated); // node.lastUpdated is already set to "time passed since" when sent + + + if (timePassedSince(_received_moment) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC) { clear(); } + + if ((_time_wander < 0) || (time_wander_other < static_cast(_time_wander))) { + _time_wander = time_wander_other; + _unix_time_sec = node.unix_time_sec; + _unix_time_frac = node.unix_time_frac; + _received_moment = millis(); + _unit = node.unit; + _timeSource = timeSource; + + if (_first_received_moment == 0) { + _first_received_moment = _received_moment; + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("NTP : Time candidate: "); + log += node.getSummary(); + + log += concat(F(" time: "), _unix_time_sec); + log += ' '; + log += toString(timeSource); + log += concat(F(" est. wander: "), _time_wander); + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + return true; + } + return false; +} + +void NTP_candidate_struct::clear() +{ + _unix_time_sec = 0; + _unix_time_frac = 0; + _time_wander = -1; + _received_moment = 0; + _first_received_moment = 0; + _timeSource = timeSource_t::No_time_source; +} + +timeSource_t NTP_candidate_struct::getUnixTime( + double & unix_time_d, + int32_t& wander, + uint8_t& unit) const +{ + if ((_unix_time_sec == 0) || + (_time_wander < 0) || + (_received_moment == 0) || + (_timeSource == timeSource_t::No_time_source)) { + return timeSource_t::No_time_source; + } + + if (timePassedSince(_first_received_moment) < 30000) { + // Make sure to allow for enough time to collect the "best" option. + return timeSource_t::No_time_source; + } + + unit = _unit; + + const int32_t timePassed = timePassedSince(_received_moment); + + const int64_t unix_time_usec = + sec_time_frac_to_Micros(_unix_time_sec, _unix_time_frac) + + (static_cast(timePassed) * 1000ll); // Add time since it was received + + unix_time_d = static_cast(unix_time_usec) / 1000000.0; + + wander = updateExpectedWander(_time_wander, timePassed); + + // FIXME TD-er: Must somehow know whether the p2p node was seen via UDP or ESPEasy-NOW + return timeSource_t::ESPEASY_p2p_UDP; +} + +#endif // if FEATURE_ESPEASY_P2P diff --git a/src/src/DataStructs/NTP_candidate.h b/src/src/DataStructs/NTP_candidate.h index 8518c49f1..1c9f15bf6 100644 --- a/src/src/DataStructs/NTP_candidate.h +++ b/src/src/DataStructs/NTP_candidate.h @@ -1,23 +1,32 @@ -#ifndef DATASTRUCT_NTP_CANDIDATE_H -#define DATASTRUCT_NTP_CANDIDATE_H - -#include "../../ESPEasy_common.h" -#if FEATURE_ESPEASY_P2P -# include "../DataStructs/NodeStruct.h" - -struct NTP_candidate_struct { - bool set(const NodeStruct& node); - - void clear(); - - bool getUnixTime(double& unix_time_d, uint8_t& unit) const; - - uint32_t _unix_time_sec = 0; - uint32_t _unix_time_frac = 0; - int32_t _time_wander = -1; - uint32_t _received_moment = 0; - uint32_t _first_received_moment = 0; - uint8_t _unit = 0; -}; -#endif // if FEATURE_ESPEASY_P2P -#endif // ifndef DATASTRUCT_NTP_CANDIDATE_H +#ifndef DATASTRUCT_NTP_CANDIDATE_H +#define DATASTRUCT_NTP_CANDIDATE_H + +#include "../../ESPEasy_common.h" +#if FEATURE_ESPEASY_P2P +# include "../DataStructs/NodeStruct.h" + +struct NTP_candidate_struct { + bool set(const NodeStruct& node); + + void clear(); + + // Returns whether a node has reported to have its system time set. + // Time since first node reported its time should be at least 30 seconds + // to allow for the node with the most accurate time estimate to have + // reported its system time. + // Reported Unix time is compensated for the time passed since it was received. + timeSource_t getUnixTime( + double & unix_time_d, + int32_t& wander, + uint8_t& unit) const; + + uint32_t _unix_time_sec = 0; + uint32_t _unix_time_frac = 0; + int32_t _time_wander = -1; + uint32_t _received_moment = 0; + uint32_t _first_received_moment = 0; + uint8_t _unit = 0; + timeSource_t _timeSource = timeSource_t::No_time_source; +}; +#endif // if FEATURE_ESPEASY_P2P +#endif // ifndef DATASTRUCT_NTP_CANDIDATE_H diff --git a/src/src/DataStructs/NTP_packet.cpp b/src/src/DataStructs/NTP_packet.cpp new file mode 100644 index 000000000..fa094a1c1 --- /dev/null +++ b/src/src/DataStructs/NTP_packet.cpp @@ -0,0 +1,222 @@ +#include "../DataStructs/NTP_packet.h" + +#include "../Helpers/ESPEasy_time_calc.h" + +#include "../Helpers/StringConverter.h" + +NTP_packet::NTP_packet() +{ + // li, vn, and mode: + // - li. 2 bits. Leap indicator. + // 0 = no warning + // 1 = last minute of the day has 61 seconds + // 2 = last minute of the day has 59 seconds + // 3 = unknown (clock unsynchronized) + // - vn. 3 bits. Version number of the protocol. (0b100 = v4) + // - mode. 3 bits. Client will pick mode 3 for client. + // 0 = reserved + // 1 = symmetric active + // 2 = symmetric passive + // 3 = client + // 4 = server + // 5 = broadcast + // 6 = NTP control message + // 7 = reserved for private use + data[0] = 0b11100011; // Unsynchronized, V4, client mode + + // Stratum level of the local clock. + // 0 = unspecified or invalid + // 1 = primary server (e.g., equipped with a GPS receiver) + // 2-15 = secondary server (via NTP) + // 16 = unsynchronized + // 17-255 = reserved + data[1] = 0u; + + // Poll: 8-bit signed integer representing the maximum interval between + // successive messages, in log2 seconds. Suggested default limits for + // minimum and maximum poll intervals are 6 and 10, respectively. + data[2] = 6u; + + // Precision: 8-bit signed integer representing the precision of the + // system clock, in log2 seconds. For instance, a value of -18 + // corresponds to a precision of about one microsecond. The precision + // can be determined when the service first starts up as the minimum + // time of several iterations to read the system clock. + data[3] = 0xEC; // -20 -> 2^-20 sec -> microsec precision. + + //constexpr int8_t precision = 0xEC; + + // Reference clock identifier. ASCII: "1N14" + data[12] = 0x31; + data[13] = 0x4E; + data[14] = 0x31; + data[15] = 0x34; +} + +uint32_t NTP_packet::readWord(uint8_t startIndex) const +{ + uint32_t res{}; + + res = (uint32_t)data[startIndex] << 24; + res |= (uint32_t)data[startIndex + 1] << 16; + res |= (uint32_t)data[startIndex + 2] << 8; + res |= (uint32_t)data[startIndex + 3]; + return res; +} + +uint64_t NTP_packet::ntp_timestamp_to_Unix_time(uint8_t startIndex) const { + // Apply offset from 1900/01/01 to 1970/01/01 + constexpr uint64_t offset_since_1900 = 2208988800ULL * 1000000ull; + + const uint32_t Tm_s = readWord(startIndex); + const uint32_t Tm_f = readWord(startIndex + 4); + uint64_t usec_since_1900 = sec_time_frac_to_Micros(Tm_s, Tm_f); + + if (usec_since_1900 < offset_since_1900) { + // Fix overflow which will occur in 2036 + usec_since_1900 += (4294967296ull * 1000000ull); + } + return usec_since_1900 - offset_since_1900; +} + +void NTP_packet::writeWord(uint32_t value, uint8_t startIndex) +{ + data[startIndex] = (value >> 24) & 0xFF; + data[startIndex + 1] = (value >> 16) & 0xFF; + data[startIndex + 2] = (value >> 8) & 0xFF; + data[startIndex + 3] = (value) & 0xFF; +} + +bool NTP_packet::isUnsynchronized() const +{ + return (data[0] /*li_vn_mode*/ & 0b11000000) == 0b11000000; +} + +uint64_t NTP_packet::getReferenceTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(16); +} + +uint64_t NTP_packet::getOriginTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(24); +} + +uint64_t NTP_packet::getReceiveTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(32); +} + +uint64_t NTP_packet::getTransmitTimestamp_usec() const +{ + return ntp_timestamp_to_Unix_time(40); +} + +void NTP_packet::setTxTimestamp(uint64_t micros) +{ + constexpr uint64_t offset_since_1900 = 2208988800ULL * 1000000ull; + + micros += offset_since_1900; + uint32_t tmp_origTm_f{}; + + writeWord(micros_to_sec_time_frac(micros, tmp_origTm_f), 40); + writeWord(tmp_origTm_f, 44); +} + +bool NTP_packet::compute_usec( + uint64_t localTXTimestamp_usec, + uint64_t localRxTimestamp_usec, + int64_t& offset_usec, + int64_t& roundtripDelay_usec) const +{ + int64_t t1 = getOriginTimestamp_usec(); + + if (t1 == 0) { + t1 = localTXTimestamp_usec; + } + + const int64_t t2 = getReceiveTimestamp_usec(); + const int64_t t3 = getTransmitTimestamp_usec(); + + if ((t3 == 0) || (t3 < t2)) { + // No time stamp received + return false; + } + const int64_t t4 = localRxTimestamp_usec; + + offset_usec = (t2 - t1) + (t3 - t4); + offset_usec /= 2; + + roundtripDelay_usec = (t4 - t1) - (t3 - t2); + return true; +} + +String NTP_packet::getRefID_str(bool& isError) const +{ + String refID; + + const uint8_t stratum = data[1]; + + isError = false; + + if ((stratum == 0) || (stratum == 1)) { + refID = strformat(F("%c%c%c%c"), + static_cast(data[12] & 0x7F), + static_cast(data[13] & 0x7F), + static_cast(data[14] & 0x7F), + static_cast(data[15] & 0x7F)); + + if (stratum == 0) { + if (refID.equals(F("DENY")) || + refID.equals(F("RSTR"))) { + // For kiss codes DENY and RSTR, the client MUST + // demobilize any associations to that server and + // stop sending packets to that server; + // DENY = Access denied by remote server. + // RSTR = Access denied due to local policy. + isError = true; + } else if (refID.equals(F("RATE"))) { + // For kiss code RATE, the client MUST immediately reduce its + // polling interval to that server and continue to reduce it each + // time it receives a RATE kiss code. + } + } + } else { + const IPAddress addrv4(readWord(12)); + refID = addrv4.toString(); + } + return refID; +} + +#ifndef BUILD_NO_DEBUG +String NTP_packet::toDebugString() const +{ + const uint8_t li = (data[0] >> 6) & 0x3; // Leap Indicator + const uint8_t ver = (data[0] >> 3) & 0x7; // Version + const uint8_t mode = data[0] & 0x7; // Mode + + bool isError{}; + + return strformat( + F(" li: %u ver: %u mode: %u\n" + " strat: %u poll: %d prec: %d\n" + " del: %u disp: %u refID: '%s'\n" + " refTm_s : %u refTm_f : %u\n" + " origTm_s: %u origTm_f: %u\n" + " rxTm_s : %u rxTm_f : %u\n" + " txTm_s : %u txTm_f : %u\n"), + li, ver, mode, // li_vn_mode + data[1], // stratum, + (int8_t)(data[2]), // poll in log2 seconds + (int8_t)(data[3]), // precision in log2 seconds + readWord(4), readWord(8), // rootDelay, rootDispersion, + getRefID_str(isError).c_str(), + readWord(16), unix_time_frac_to_micros(readWord(20)), // refTm_s, unix_time_frac_to_micros(refTm_f), + readWord(24), unix_time_frac_to_micros(readWord(28)), // origTm_s, unix_time_frac_to_micros(origTm_f), + readWord(32), unix_time_frac_to_micros(readWord(36)), // rxTm_s, unix_time_frac_to_micros(rxTm_f), + readWord(40), unix_time_frac_to_micros(readWord(44)) // txTm_s, unix_time_frac_to_micros(txTm_f) + + ); +} + +#endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/NTP_packet.h b/src/src/DataStructs/NTP_packet.h new file mode 100644 index 000000000..9ce7009c1 --- /dev/null +++ b/src/src/DataStructs/NTP_packet.h @@ -0,0 +1,58 @@ +#ifndef DATASTRUCTS_NTP_PACKET_H +#define DATASTRUCTS_NTP_PACKET_H + +#include + +struct __attribute__((__packed__)) NTP_packet +{ + NTP_packet(); + bool isUnsynchronized() const; + + // Reference Timestamp: Time when the system clock was last set or corrected, in NTP timestamp format. + // Returned timestamp is Unixtime in microseconds + uint64_t getReferenceTimestamp_usec() const; + + // Origin Timestamp (org): Time at the client when the request departed for the server, in NTP timestamp format. + // Returned timestamp is Unixtime in microseconds + uint64_t getOriginTimestamp_usec() const; + + // Receive Timestamp (rec): Time at the server when the request arrived from the client, in NTP timestamp format. + // Returned timestamp is Unixtime in microseconds + uint64_t getReceiveTimestamp_usec() const; + + // Transmit Timestamp (xmt): Time at the server when the response left for the client, in NTP timestamp format. + // N.B. when requesting the time, the client should set its local system time here. + // In the reply packet, this will be moved to the origin timestamp field. + // Returned timestamp is Unixtime in microseconds + uint64_t getTransmitTimestamp_usec() const; + + + // Before sending, the TX-timestamp of the local machine must be set. + // This will be returned in the reply as "Origin" timestamp + void setTxTimestamp(uint64_t micros); + + // The "Offset", the time difference of the two computer clocks + // The "Delay", the time that was needed to transfer the packet in the network + bool compute_usec( + uint64_t localTXTimestamp_usec, + uint64_t localRxTimestamp_usec, + int64_t& offset_usec, + int64_t& roundtripDelay_usec) const; + + String getRefID_str(bool& isError) const; + +#ifndef BUILD_NO_DEBUG + String toDebugString() const; +#endif // ifndef BUILD_NO_DEBUG + + uint8_t data[48]{}; + +private: + + uint32_t readWord(uint8_t startIndex) const; + void writeWord(uint32_t value, + uint8_t startIndex); + uint64_t ntp_timestamp_to_Unix_time(uint8_t startIndex) const; +}; + +#endif // ifndef DATASTRUCTS_NTP_PACKET_H diff --git a/src/src/DataStructs/NodeStruct.cpp b/src/src/DataStructs/NodeStruct.cpp index e267fb996..70256c2fe 100644 --- a/src/src/DataStructs/NodeStruct.cpp +++ b/src/src/DataStructs/NodeStruct.cpp @@ -1,316 +1,327 @@ -#include "../DataStructs/NodeStruct.h" - -#if FEATURE_ESPEASY_P2P -#include "../../ESPEasy-Globals.h" -#include "../DataTypes/NodeTypeID.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_time_calc.h" - - -#define NODE_STRUCT_AGE_TIMEOUT 300000 // 5 minutes - -NodeStruct::NodeStruct() : - ESPEasyNowPeer(0), - useAP_ESPEasyNow(0), - scaled_rssi(0) -#if FEATURE_USE_IPV6 - ,hasIPv4(0) - ,hasIPv6_mac_based_link_local(0) - ,hasIPv6_mac_based_link_global(0) - ,unused(0) -#endif -{} - -bool NodeStruct::valid() const { - // FIXME TD-er: Must make some sanity checks to see if it is a valid message - return true; -} - -bool NodeStruct::validate(const IPAddress& remoteIP) { - if (build < 20107) { - // webserverPort introduced in 20107 - webgui_portnumber = 80; - for (uint8_t i = 0; i < 6; ++i) { - ap_mac[i] = 0; - } - load = 0; - distance = 255; - timeSource = static_cast(timeSource_t::No_time_source); - channel = 0; - ESPEasyNowPeer = 0; - useAP_ESPEasyNow = 0; - setRSSI(0); - lastUpdated = 0; - } - if (build < 20253) { - version = 0; -#if FEATURE_USE_IPV6 - hasIPv4 = 0; - hasIPv6_mac_based_link_local = 0; - hasIPv6_mac_based_link_global = 0; - - unused = 0; -#else - unused = 0; -#endif - - unix_time_frac = 0; - unix_time_sec = 0; - } - -#if FEATURE_USE_IPV6 - // Check if we're in the same global subnet - if (hasIPv6_mac_based_link_global && remoteIP.type() == IPv6) { - const IPAddress this_global = 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]) - hasIPv6_mac_based_link_global = false; - } - } -#endif - - // FIXME TD-er: Must make some sanity checks to see if it is a valid message - return valid(); -} - -bool NodeStruct::operator<(const NodeStruct &other) const { - const bool thisExpired = isExpired(); - if (thisExpired != other.isExpired()) { - return !thisExpired; - } - - const bool markedAsPriority = markedAsPriorityPeer(); - if (markedAsPriority != other.markedAsPriorityPeer()) { - return markedAsPriority; - } - - if (ESPEasyNowPeer != other.ESPEasyNowPeer) { - // One is confirmed, so prefer that one. - return ESPEasyNowPeer; - } - - const int8_t thisRssi = getRSSI(); - const int8_t otherRssi = other.getRSSI(); - - int score_this = getLoad(); - int score_other = other.getLoad(); - - if (distance != other.distance) { - if (!isExpired() && !other.isExpired()) { - // Distance is not the same, so take distance into account. - return distance < other.distance; -/* - int distance_penalty = distance - other.distance; - distance_penalty = distance_penalty * distance_penalty * 10; - if (distance > other.distance) { - score_this += distance_penalty; - } else { - score_other += distance_penalty; - } -*/ - } - } - - if (thisRssi >= 0 || otherRssi >= 0) { - // One or both have no RSSI, so cannot use RSSI in computing score - } else { - // RSSI value is negative, so subtract the value - // RSSI range from -38 ... 99 - // Shift RSSI and add a weighing factor to make sure - // A load of 100% with RSSI of -40 is preferred over a load of 20% with an RSSI of -80. - score_this -= (thisRssi + 38) * 2; - score_other -= (otherRssi + 38) * 2; - } - return score_this < score_other; -} - - -const __FlashStringHelper * NodeStruct::getNodeTypeDisplayString() const { - return toNodeTypeDisplayString(nodeType); -} - -String NodeStruct::getNodeName() const { - String res; - size_t length = strnlen(reinterpret_cast(nodeName), sizeof(nodeName)); - - res.reserve(length); - - for (size_t i = 0; i < length; ++i) { - res += static_cast(nodeName[i]); - } - return res; -} - -IPAddress NodeStruct::IP() const { - return IPAddress(ip[0], ip[1], ip[2], ip[3]); -} - -#if FEATURE_USE_IPV6 -IPAddress NodeStruct::IPv6_link_local() const -{ - if (hasIPv6_mac_based_link_local) { - // Base IPv6 on MAC address - IPAddress ipv6; - if (IPv6_link_local_from_MAC(sta_mac, ipv6)) { - return ipv6; - } - } - return IN6ADDR_ANY; -} - -IPAddress NodeStruct::IPv6_global() const -{ - if (hasIPv6_mac_based_link_global) { - // Base IPv6 on MAC address - IPAddress ipv6; - if (IPv6_global_from_MAC(sta_mac, ipv6)) { - return ipv6; - } - } - return IN6ADDR_ANY; -} -#endif - - -MAC_address NodeStruct::STA_MAC() const { - return MAC_address(sta_mac); -} - -MAC_address NodeStruct::ESPEasy_Now_MAC() const { - if (ESPEasyNowPeer == 0) return MAC_address(); - if (useAP_ESPEasyNow) { - return MAC_address(ap_mac); - } - return MAC_address(sta_mac); -} - -unsigned long NodeStruct::getAge() const { - return timePassedSince(lastUpdated); -} - -bool NodeStruct::isExpired() const { - return getAge() > NODE_STRUCT_AGE_TIMEOUT; -} - -float NodeStruct::getLoad() const { - return load / 2.55; -} - -String NodeStruct::getSummary() const { - String res; - - res.reserve(48); - res = F("Unit: "); - res += unit; - res += F(" \""); - res += getNodeName(); - res += '"'; - res += F(" load: "); - res += String(getLoad(), 1); - res += F(" RSSI: "); - res += getRSSI(); - res += F(" ch: "); - res += channel; - res += F(" dst: "); - res += distance; - return res; -} - -bool NodeStruct::setESPEasyNow_mac(const MAC_address& received_mac) -{ - if (received_mac.all_zero()) return false; - if (received_mac == sta_mac) { - ESPEasyNowPeer = 1; - useAP_ESPEasyNow = 0; - return true; - } - - if (received_mac == ap_mac) { - ESPEasyNowPeer = 1; - useAP_ESPEasyNow = 1; - return true; - } - return false; -} - -int8_t NodeStruct::getRSSI() const -{ - if (scaled_rssi == 0) { - return 0; // Not set - } - - if (scaled_rssi == 0x3F) { - return 31; // Error state - } - - // scaled_rssi = 1 ... 62 - // output = -38 ... -99 - int8_t rssi = scaled_rssi + 37; - return rssi * -1; -} - -void NodeStruct::setRSSI(int8_t rssi) -{ - if (rssi == 0) { - // Not set - scaled_rssi = 0; - return; - } - - if (rssi > 0) { - // Error state - scaled_rssi = 0x3F; - return; - } - rssi *= -1; - rssi -= 37; - - if (rssi < 1) { - scaled_rssi = 1; - return; - } - - if (rssi >= 0x3F) { - scaled_rssi = 0x3F - 1; - return; - } - scaled_rssi = rssi; -} - -bool NodeStruct::markedAsPriorityPeer() const -{ -#ifdef USES_ESPEASY_NOW - for (int i = 0; i < ESPEASY_NOW_PEER_MAX; ++i) { - if (SecuritySettings.peerMacSet(i)) { - if (match(SecuritySettings.EspEasyNowPeerMAC[i])) { - return true; - } - } - } -#endif - return false; -} - -bool NodeStruct::match(const MAC_address& mac) const -{ - return (mac == sta_mac || mac == ap_mac); -} - -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; - - return false; -} - -void NodeStruct::setAP_MAC(const MAC_address& mac) -{ - mac.get(ap_mac); -} - +#include "../DataStructs/NodeStruct.h" + +#if FEATURE_ESPEASY_P2P +#include "../../ESPEasy-Globals.h" +#include "../DataTypes/NodeTypeID.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_time_calc.h" + + +#define NODE_STRUCT_AGE_TIMEOUT 300000 // 5 minutes + +NodeStruct::NodeStruct() : + ESPEasyNowPeer(0), + useAP_ESPEasyNow(0), + scaled_rssi(0) +#if FEATURE_USE_IPV6 + ,hasIPv4(0) + ,hasIPv6_mac_based_link_local(0) + ,hasIPv6_mac_based_link_global(0) + ,unused(0) +#endif +{} + +bool NodeStruct::valid() const { + // FIXME TD-er: Must make some sanity checks to see if it is a valid message + return true; +} + +bool NodeStruct::validate(const IPAddress& remoteIP) { + if (build < 20107) { + // webserverPort introduced in 20107 + webgui_portnumber = 80; + for (uint8_t i = 0; i < 6; ++i) { + ap_mac[i] = 0; + } + load = 0; + distance = 255; + timeSource = static_cast(timeSource_t::No_time_source); + channel = 0; + ESPEasyNowPeer = 0; + useAP_ESPEasyNow = 0; + setRSSI(0); + lastUpdated = 0; + } + if (build < 20253) { + version = 0; +#if FEATURE_USE_IPV6 + hasIPv4 = 0; + hasIPv6_mac_based_link_local = 0; + hasIPv6_mac_based_link_global = 0; + + unused = 0; +#else + unused = 0; +#endif + + unix_time_frac = 0; + unix_time_sec = 0; + } + +#if FEATURE_USE_IPV6 + // Check if we're in the same global subnet + if (Settings.EnableIPv6() && + hasIPv6_mac_based_link_global && + remoteIP.type() == IPv6) { + const IPAddress this_global = 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]) + hasIPv6_mac_based_link_global = false; + } + } +#endif + + // FIXME TD-er: Must make some sanity checks to see if it is a valid message + return valid(); +} + +bool NodeStruct::operator<(const NodeStruct &other) const { + const bool thisExpired = isExpired(); + if (thisExpired != other.isExpired()) { + return !thisExpired; + } + + const bool markedAsPriority = markedAsPriorityPeer(); + if (markedAsPriority != other.markedAsPriorityPeer()) { + return markedAsPriority; + } + + if (ESPEasyNowPeer != other.ESPEasyNowPeer) { + // One is confirmed, so prefer that one. + return ESPEasyNowPeer; + } + + const int8_t thisRssi = getRSSI(); + const int8_t otherRssi = other.getRSSI(); + + int score_this = getLoad(); + int score_other = other.getLoad(); + + if (distance != other.distance) { + if (!isExpired() && !other.isExpired()) { + // Distance is not the same, so take distance into account. + return distance < other.distance; +/* + int distance_penalty = distance - other.distance; + distance_penalty = distance_penalty * distance_penalty * 10; + if (distance > other.distance) { + score_this += distance_penalty; + } else { + score_other += distance_penalty; + } +*/ + } + } + + if (thisRssi >= 0 || otherRssi >= 0) { + // One or both have no RSSI, so cannot use RSSI in computing score + } else { + // RSSI value is negative, so subtract the value + // RSSI range from -38 ... 99 + // Shift RSSI and add a weighing factor to make sure + // A load of 100% with RSSI of -40 is preferred over a load of 20% with an RSSI of -80. + score_this -= (thisRssi + 38) * 2; + score_other -= (otherRssi + 38) * 2; + } + return score_this < score_other; +} + + +const __FlashStringHelper * NodeStruct::getNodeTypeDisplayString() const { + return toNodeTypeDisplayString(nodeType); +} + +String NodeStruct::getNodeName() const { + String res; + size_t length = strnlen(reinterpret_cast(nodeName), sizeof(nodeName)); + + res.reserve(length); + + for (size_t i = 0; i < length; ++i) { + res += static_cast(nodeName[i]); + } + return res; +} + +IPAddress NodeStruct::IP() const { + return IPAddress(ip[0], ip[1], ip[2], ip[3]); +} + +#if FEATURE_USE_IPV6 +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 (stripZone) { + return IPAddress(IPv6, &ipv6[0], 0); + } + return ipv6; + } + } + return IN6ADDR_ANY; +} + +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)) { + return ipv6; + } + } + return IN6ADDR_ANY; +} + +bool NodeStruct::hasIPv6() const { + if (!Settings.EnableIPv6()) return false; + return hasIPv6_mac_based_link_local || + hasIPv6_mac_based_link_global; +} +#endif + + +MAC_address NodeStruct::STA_MAC() const { + return MAC_address(sta_mac); +} + +MAC_address NodeStruct::ESPEasy_Now_MAC() const { + if (ESPEasyNowPeer == 0) return MAC_address(); + if (useAP_ESPEasyNow) { + return MAC_address(ap_mac); + } + return MAC_address(sta_mac); +} + +unsigned long NodeStruct::getAge() const { + return timePassedSince(lastUpdated); +} + +bool NodeStruct::isExpired() const { + return getAge() > NODE_STRUCT_AGE_TIMEOUT; +} + +float NodeStruct::getLoad() const { + return load / 2.55; +} + +String NodeStruct::getSummary() const { + String res; + + res.reserve(48); + res = F("Unit: "); + res += unit; + res += F(" \""); + res += getNodeName(); + res += '"'; + res += F(" load: "); + res += String(getLoad(), 1); + res += F(" RSSI: "); + res += getRSSI(); + res += F(" ch: "); + res += channel; + res += F(" dst: "); + res += distance; + return res; +} + +bool NodeStruct::setESPEasyNow_mac(const MAC_address& received_mac) +{ + if (received_mac.all_zero()) return false; + if (received_mac == sta_mac) { + ESPEasyNowPeer = 1; + useAP_ESPEasyNow = 0; + return true; + } + + if (received_mac == ap_mac) { + ESPEasyNowPeer = 1; + useAP_ESPEasyNow = 1; + return true; + } + return false; +} + +int8_t NodeStruct::getRSSI() const +{ + if (scaled_rssi == 0) { + return 0; // Not set + } + + if (scaled_rssi == 0x3F) { + return 31; // Error state + } + + // scaled_rssi = 1 ... 62 + // output = -38 ... -99 + int8_t rssi = scaled_rssi + 37; + return rssi * -1; +} + +void NodeStruct::setRSSI(int8_t rssi) +{ + if (rssi == 0) { + // Not set + scaled_rssi = 0; + return; + } + + if (rssi > 0) { + // Error state + scaled_rssi = 0x3F; + return; + } + rssi *= -1; + rssi -= 37; + + if (rssi < 1) { + scaled_rssi = 1; + return; + } + + if (rssi >= 0x3F) { + scaled_rssi = 0x3F - 1; + return; + } + scaled_rssi = rssi; +} + +bool NodeStruct::markedAsPriorityPeer() const +{ +#ifdef USES_ESPEASY_NOW + for (int i = 0; i < ESPEASY_NOW_PEER_MAX; ++i) { + if (SecuritySettings.peerMacSet(i)) { + if (match(SecuritySettings.EspEasyNowPeerMAC[i])) { + return true; + } + } + } +#endif + return false; +} + +bool NodeStruct::match(const MAC_address& mac) const +{ + return (mac == sta_mac || mac == ap_mac); +} + +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; + + return false; +} + +void NodeStruct::setAP_MAC(const MAC_address& mac) +{ + mac.get(ap_mac); +} + #endif \ No newline at end of file diff --git a/src/src/DataStructs/NodeStruct.h b/src/src/DataStructs/NodeStruct.h index 8c839898d..26025676b 100644 --- a/src/src/DataStructs/NodeStruct.h +++ b/src/src/DataStructs/NodeStruct.h @@ -1,120 +1,122 @@ -#ifndef DATASTRUCTS_NODESTRUCT_H -#define DATASTRUCTS_NODESTRUCT_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_ESPEASY_P2P -#include "../Helpers/ESPEasy_time.h" -#include "../DataStructs/MAC_address.h" - -#include -#include - - -/*********************************************************************************************\ -* NodeStruct -\*********************************************************************************************/ -struct __attribute__((__packed__)) NodeStruct -{ - NodeStruct(); - - bool valid() const; - bool validate(const IPAddress& remoteIP); - - // Compare nodes. - // Return true when this node has better credentials to be used as ESPEasy-NOW neighbor - // - Shorter distance to a network connected gateway node. - // - confirmed ESPEasy-NOW peer - // - better RSSI - // - lower load (TODO TD-er) - bool operator<(const NodeStruct &other) const; - - const __FlashStringHelper * getNodeTypeDisplayString() const; - - String getNodeName() const; - - IPAddress IP() const; - - #if FEATURE_USE_IPV6 - IPAddress IPv6_link_local() const; - IPAddress IPv6_global() const; - #endif - - MAC_address STA_MAC() const; - - MAC_address ESPEasy_Now_MAC() const; - - unsigned long getAge() const; - - bool isExpired() const; - - float getLoad() const; - - String getSummary() const; - - bool setESPEasyNow_mac(const MAC_address& received_mac); - - int8_t getRSSI() const; - - void setRSSI(int8_t rssi); - - bool markedAsPriorityPeer() const; - - bool match(const MAC_address& mac) const; - - bool isThisNode() const; - - void setAP_MAC(const MAC_address& mac); - - - // Do not change the order of this data, as it is being sent via P2P UDP. - // 6 byte mac (STA or ETH interface) - // 4 byte ip - // 1 byte unit - // 2 byte build - // 25 char name - // 1 byte node type id - - // Added starting build '20107': - // 2 bytes webserver port - // 6 bytes AP MAC - // 1 byte system load - // 1 byte administrative distance - - - uint8_t sta_mac[6] = { 0 }; // STA mode MAC (or MAC from ETH device) - uint8_t ip[4] = { 0 }; - uint8_t unit = 0; - uint16_t build = 0; - uint8_t nodeName[25] = { 0 }; - uint8_t nodeType = 0; - uint16_t webgui_portnumber = 80; - uint8_t ap_mac[6] = { 0 }; // AP mode MAC - uint8_t load = 127; // Default to average load - uint8_t distance = 255; // Administrative distance for routing - uint8_t timeSource = static_cast(timeSource_t::No_time_source); - uint8_t channel = 0; // The WiFi channel used - uint8_t ESPEasyNowPeer : 1; // Signalling if the node is an ESPEasy-NOW peer - uint8_t useAP_ESPEasyNow : 1; // ESPEasy-NOW can either use STA or AP for communications. - uint8_t scaled_rssi : 6; // "shortened" RSSI value - - // 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); - uint8_t version = 1; - #if FEATURE_USE_IPV6 - uint8_t hasIPv4 : 1; - // Whether the IPv6 address can be derived from the given sta_mac member - uint8_t hasIPv6_mac_based_link_local : 1; - uint8_t hasIPv6_mac_based_link_global : 1; - - uint8_t unused : 5; - #else - uint8_t unused = 0; - #endif - uint32_t unix_time_sec = 0; - uint32_t unix_time_frac = 0; -}; -typedef std::map NodesMap; -#endif // if FEATURE_ESPEASY_P2P -#endif // DATASTRUCTS_NODESTRUCT_H +#ifndef DATASTRUCTS_NODESTRUCT_H +#define DATASTRUCTS_NODESTRUCT_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_P2P +#include "../Helpers/ESPEasy_time.h" +#include "../DataStructs/MAC_address.h" + +#include +#include + + +/*********************************************************************************************\ +* NodeStruct +\*********************************************************************************************/ +struct __attribute__((__packed__)) NodeStruct +{ + NodeStruct(); + + bool valid() const; + bool validate(const IPAddress& remoteIP); + + // Compare nodes. + // Return true when this node has better credentials to be used as ESPEasy-NOW neighbor + // - Shorter distance to a network connected gateway node. + // - confirmed ESPEasy-NOW peer + // - better RSSI + // - lower load (TODO TD-er) + bool operator<(const NodeStruct &other) const; + + const __FlashStringHelper * getNodeTypeDisplayString() const; + + String getNodeName() const; + + IPAddress IP() const; + + #if FEATURE_USE_IPV6 + IPAddress IPv6_link_local(bool stripZone = false) const; + IPAddress IPv6_global() const; + + bool hasIPv6() const; + #endif + + MAC_address STA_MAC() const; + + MAC_address ESPEasy_Now_MAC() const; + + unsigned long getAge() const; + + bool isExpired() const; + + float getLoad() const; + + String getSummary() const; + + bool setESPEasyNow_mac(const MAC_address& received_mac); + + int8_t getRSSI() const; + + void setRSSI(int8_t rssi); + + bool markedAsPriorityPeer() const; + + bool match(const MAC_address& mac) const; + + bool isThisNode() const; + + void setAP_MAC(const MAC_address& mac); + + + // Do not change the order of this data, as it is being sent via P2P UDP. + // 6 byte mac (STA or ETH interface) + // 4 byte ip + // 1 byte unit + // 2 byte build + // 25 char name + // 1 byte node type id + + // Added starting build '20107': + // 2 bytes webserver port + // 6 bytes AP MAC + // 1 byte system load + // 1 byte administrative distance + + + uint8_t sta_mac[6] = { 0 }; // STA mode MAC (or MAC from ETH device) + uint8_t ip[4] = { 0 }; + uint8_t unit = 0; + uint16_t build = 0; + uint8_t nodeName[25] = { 0 }; + uint8_t nodeType = 0; + uint16_t webgui_portnumber = 80; + uint8_t ap_mac[6] = { 0 }; // AP mode MAC + uint8_t load = 127; // Default to average load + uint8_t distance = 255; // Administrative distance for routing + uint8_t timeSource = static_cast(timeSource_t::No_time_source); + uint8_t channel = 0; // The WiFi channel used + uint8_t ESPEasyNowPeer : 1; // Signalling if the node is an ESPEasy-NOW peer + uint8_t useAP_ESPEasyNow : 1; // ESPEasy-NOW can either use STA or AP for communications. + uint8_t scaled_rssi : 6; // "shortened" RSSI value + + // 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); + uint8_t version = 1; + #if FEATURE_USE_IPV6 + uint8_t hasIPv4 : 1; + // Whether the IPv6 address can be derived from the given sta_mac member + uint8_t hasIPv6_mac_based_link_local : 1; + uint8_t hasIPv6_mac_based_link_global : 1; + + uint8_t unused : 5; + #else + uint8_t unused = 0; + #endif + uint32_t unix_time_sec = 0; + uint32_t unix_time_frac = 0; +}; +typedef std::map NodesMap; +#endif // if FEATURE_ESPEASY_P2P +#endif // DATASTRUCTS_NODESTRUCT_H diff --git a/src/src/DataStructs/NodesHandler.cpp b/src/src/DataStructs/NodesHandler.cpp index 50a01c44a..0c3bd500e 100644 --- a/src/src/DataStructs/NodesHandler.cpp +++ b/src/src/DataStructs/NodesHandler.cpp @@ -1,709 +1,730 @@ -#include "../DataStructs/NodesHandler.h" - -#include "../../ESPEasy_common.h" - -#if FEATURE_ESPEASY_P2P -#include "../../ESPEasy-Globals.h" - -#ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_peermanager.h" -#include "../Globals/ESPEasy_now_state.h" -#endif - -#include "../DataTypes/NodeTypeID.h" - -#if FEATURE_MQTT -#include "../ESPEasyCore/Controller.h" -#endif - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/MQTT.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/PeriodicalActions.h" - -#define ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE 35000 - -bool NodesHandler::addNode(const NodeStruct& node) -{ - int8_t rssi = 0; - MAC_address match_sta; - MAC_address match_ap; - MAC_address ESPEasy_NOW_MAC; - - bool isNewNode = true; - - // Erase any existing node with matching MAC address - for (auto it = _nodes.begin(); it != _nodes.end(); ) - { - const MAC_address sta = it->second.sta_mac; - const MAC_address ap = it->second.ap_mac; - if ((!sta.all_zero() && node.match(sta)) || (!ap.all_zero() && node.match(ap))) { - rssi = it->second.getRSSI(); - if (!sta.all_zero()) - match_sta = sta; - if (!ap.all_zero()) - match_ap = ap; - ESPEasy_NOW_MAC = it->second.ESPEasy_Now_MAC(); - - isNewNode = false; - { - _nodes_mutex.lock(); - it = _nodes.erase(it); - _nodes_mutex.unlock(); - } - } else { - ++it; - } - } - { - _nodes_mutex.lock(); - { - #ifdef USE_SECOND_HEAP - // FIXME TD-er: Must check whether this is working well as the NodesMap is a std::map - HeapSelectIram ephemeral; - #endif - _nodes[node.unit] = node; - } - _ntp_candidate.set(node); - _nodes[node.unit].lastUpdated = millis(); - if (node.getRSSI() >= 0 && rssi < 0) { - _nodes[node.unit].setRSSI(rssi); - } - const MAC_address node_ap(node.ap_mac); - if (node_ap.all_zero()) { - _nodes[node.unit].setAP_MAC(node_ap); - } - if (node.ESPEasy_Now_MAC().all_zero()) { - _nodes[node.unit].setESPEasyNow_mac(ESPEasy_NOW_MAC); - } - _nodes_mutex.unlock(); - } - - // Check whether the current time source is considered "worse" than received from p2p node. - if (!node_time.systemTimePresent() || - node_time.timeSource > timeSource_t::ESPEASY_p2p_UDP || - ((node_time.timeSource == timeSource_t::ESPEASY_p2p_UDP) && - (timePassedSince(node_time.lastSyncTime_ms) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC) )) { - double unixTime; - uint8_t unit; - if (_ntp_candidate.getUnixTime(unixTime, unit)) { - node_time.setExternalTimeSource(unixTime, timeSource_t::ESPEASY_p2p_UDP, unit); - } - } - - return isNewNode; -} - -#ifdef USES_ESPEASY_NOW -bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_struct& traceRoute) -{ - const bool isNewNode = addNode(node); - { - _nodeStats_mutex.lock(); - _nodeStats[node.unit].setDiscoveryRoute(node.unit, traceRoute); - _nodeStats_mutex.unlock(); - } - - ESPEasy_now_peermanager.addPeer(node.ESPEasy_Now_MAC(), node.channel); - - if (!node.isThisNode()) { - if (traceRoute.getDistance() != 255) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - if (log.reserve(80)) { - log = F(ESPEASY_NOW_NAME); - log += F(": Node: "); - log += String(node.unit); - log += F(" DiscoveryRoute received: "); - log += traceRoute.toString(); - addLog(LOG_LEVEL_INFO, log); - } - } - } else {} - } - return isNewNode; -} -#endif - -bool NodesHandler::hasNode(uint8_t unit_nr) const -{ - return _nodes.find(unit_nr) != _nodes.end(); -} - -bool NodesHandler::hasNode(const uint8_t *mac) const -{ - return getNodeByMac(mac) != nullptr; -} - -NodeStruct * NodesHandler::getNode(uint8_t unit_nr) -{ - auto it = _nodes.find(unit_nr); - - if (it == _nodes.end()) { - return nullptr; - } - return &(it->second); -} - -const NodeStruct * NodesHandler::getNode(uint8_t unit_nr) const -{ - auto it = _nodes.find(unit_nr); - - if (it == _nodes.end()) { - return nullptr; - } - return &(it->second); -} - -NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) -{ - if (mac.all_zero()) { - return nullptr; - } - delay(0); - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if (mac == it->second.sta_mac) { - return &(it->second); - } - - if (mac == it->second.ap_mac) { - return &(it->second); - } - } - return nullptr; -} - -const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) const -{ - bool match_STA; - - return getNodeByMac(mac, match_STA); -} - -const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac, bool& match_STA) const -{ - if (mac.all_zero()) { - return nullptr; - } - delay(0); - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if (mac == it->second.sta_mac) { - match_STA = true; - return &(it->second); - } - - if (mac == it->second.ap_mac) { - match_STA = false; - return &(it->second); - } - } - return nullptr; -} - -const NodeStruct * NodesHandler::getPreferredNode() const { - MAC_address dummy; - - return getPreferredNode_notMatching(dummy); -} - -const NodeStruct* NodesHandler::getPreferredNode_notMatching(uint8_t unit_nr) const { - MAC_address not_matching; - if (unit_nr != 0 && unit_nr != 255) { - const NodeStruct* node = getNode(unit_nr); - if (node != nullptr) { - not_matching = node->ESPEasy_Now_MAC(); - } - } - return getPreferredNode_notMatching(not_matching); -} - -const NodeStruct * NodesHandler::getPreferredNode_notMatching(const MAC_address& not_matching) const { - MAC_address this_mac; - - WiFi.macAddress(this_mac.mac); - const NodeStruct *thisNode = getNodeByMac(this_mac); - const NodeStruct *reject = getNodeByMac(not_matching); - - const NodeStruct *res = nullptr; - - for (auto it = _nodes.begin(); it != _nodes.end(); ++it) - { - if ((&(it->second) != reject) && (&(it->second) != thisNode)) { - bool mustSet = false; - if (res == nullptr) { - mustSet = true; - } else { - #ifdef USES_ESPEASY_NOW - - uint8_t distance_new, distance_res = 255; - - const int successRate_new = getRouteSuccessRate(it->second.unit, distance_new); - const int successRate_res = getRouteSuccessRate(res->unit, distance_res); - - if (successRate_new == 0 || successRate_res == 0) { - // One of the nodes does not (yet) have a route. - if (successRate_new == 0 && successRate_res == 0) { - distance_new = it->second.distance; - distance_res = res->distance; - } else if (successRate_res == 0) { - // The new one has a route, so must set the new one. - distance_res = res->distance; - if (distance_new < 255) { - mustSet = true; - } - } - } - - if (distance_new == distance_res) { - if (successRate_new > successRate_res && distance_new < 255) { - mustSet = true; - } - } else if (distance_new < distance_res) { - if (it->second.getAge() < ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE) { - // Only allow this new one if it was seen recently - // as it does not (yet) have a traceroute. - mustSet = true; - } - } - #else - if (it->second < *res) { - mustSet = true; - } - #endif - } - if (mustSet) { - #ifdef USES_ESPEASY_NOW - if (it->second.ESPEasyNowPeer && it->second.distance < 255) { - res = &(it->second); - } - #else - res = &(it->second); - #endif - } - } - } - -/* - #ifdef USES_ESPEASY_NOW - if (res != nullptr) - { - uint8_t distance_res = 255; - const int successRate_res = getRouteSuccessRate(res->unit, distance_res); - if (distance_res == 255) { - return nullptr; - } - } - #endif -*/ - - return res; -} - -#ifdef USES_ESPEASY_NOW -const ESPEasy_now_traceroute_struct* NodesHandler::getTraceRoute(uint8_t unit) const -{ - auto trace_it = _nodeStats.find(unit); - if (trace_it == _nodeStats.end()) { - return nullptr; - } - return trace_it->second.bestRoute(); -} - -const ESPEasy_now_traceroute_struct* NodesHandler::getDiscoveryRoute(uint8_t unit) const -{ - auto trace_it = _nodeStats.find(unit); - if (trace_it == _nodeStats.end()) { - return nullptr; - } - return &(trace_it->second.discoveryRoute()); -} - -void NodesHandler::setTraceRoute(const MAC_address& mac, const ESPEasy_now_traceroute_struct& traceRoute) -{ - if (traceRoute.computeSuccessRate() == 0) { - // No need to store traceroute with low success rate. - return; - } - NodeStruct* node = getNodeByMac(mac); - if (node != nullptr) { - auto trace_it = _nodeStats.find(node->unit); - if (trace_it != _nodeStats.end()) { - _lastTimeValidDistance = millis(); - trace_it->second.addRoute(node->unit, traceRoute); - } - } -} - -#endif - - -void NodesHandler::updateThisNode() { - NodeStruct thisNode; - - // Set local data - #if FEATURE_ETHERNET - { - MAC_address mac = NetworkMacAddress(); - mac.get(thisNode.sta_mac); - } - #else - WiFi.macAddress(thisNode.sta_mac); - #endif - WiFi.softAPmacAddress(thisNode.ap_mac); - { - const bool addIP = NetworkConnected(); - #ifdef USES_ESPEASY_NOW - if (use_EspEasy_now) { - thisNode.useAP_ESPEasyNow = 1; - } - #endif - if (addIP) { - const IPAddress localIP = NetworkLocalIP(); - - for (uint8_t i = 0; i < 4; ++i) { - thisNode.ip[i] = localIP[i]; - } - } - } - #ifdef USES_ESPEASY_NOW - thisNode.channel = getESPEasyNOW_channel(); - #else - thisNode.channel = WiFiEventData.usedChannel; - #endif - if (thisNode.channel == 0) { - thisNode.channel = WiFi.channel(); - } - - thisNode.unit = Settings.Unit; - thisNode.build = Settings.Build; - memcpy(thisNode.nodeName, Settings.getName().c_str(), 25); - thisNode.nodeType = NODE_TYPE_ID; - - thisNode.webgui_portnumber = Settings.WebserverPort; - const int load_int = getCPUload() * 2.55; - - if (load_int > 255) { - thisNode.load = 255; - } else { - thisNode.load = load_int; - } - thisNode.timeSource = static_cast(node_time.timeSource); - - switch (node_time.timeSource) { - case timeSource_t::No_time_source: - thisNode.lastUpdated = (1 << 30); - break; - default: - { - thisNode.lastUpdated = timePassedSince(node_time.lastSyncTime_ms); - break; - } - } - if (node_time.systemTimePresent()) { - // NodeStruct is a packed struct, so we cannot directly use its members as a reference. - uint32_t unix_time_frac = 0; - thisNode.unix_time_sec = node_time.getUnixTime(unix_time_frac); - thisNode.unix_time_frac = unix_time_frac; - } - #ifdef USES_ESPEASY_NOW - if (Settings.UseESPEasyNow()) { - thisNode.ESPEasyNowPeer = 1; - } - #endif - - const uint8_t lastDistance = _distance; - #ifdef USES_ESPEASY_NOW - ESPEasy_now_traceroute_struct thisTraceRoute; - #endif - if (isEndpoint()) { - _distance = 0; - _lastTimeValidDistance = millis(); - if (lastDistance != _distance) { - _recentlyBecameDistanceZero = true; - } - #ifdef USES_ESPEASY_NOW - thisNode.distance = _distance; - thisNode.setRSSI(WiFi.RSSI()); - thisTraceRoute.addUnit(thisNode.unit); - #endif - } else { - _distance = 255; - #ifdef USES_ESPEASY_NOW - const NodeStruct *preferred = getPreferredNode_notMatching(thisNode.sta_mac); - - if (preferred != nullptr) { - if (!preferred->isExpired()) { - // Only take the distance of another node if it is running a build which does not send out traceroute - // If it is a build sending traceroute, only consider having a distance if you know how to reach the gateway node - // This does impose an issue when a gateway node is running an older version, as the next hops never will have a traceroute too. - // Therefore the reported build for those units will be faked to be an older version. - if (preferred->build < 20113) { - if (preferred->distance != 255) { - _distance = preferred->distance + 1; - thisNode.build = 20112; - } - } else { - const ESPEasy_now_traceroute_struct* tracert_ptr = getTraceRoute(preferred->unit); - if (tracert_ptr != nullptr && tracert_ptr->getDistance() < 255) { - // Make a copy of the traceroute - thisTraceRoute = *tracert_ptr; - thisTraceRoute.addUnit(thisNode.unit); - if (preferred->distance != 255) { - // Traceroute is only updated when a node is connected. - // Thus the traceroute may be outdated, while the node info will already indicate if a node has lost its route to the gateway node. - // So we only must set the distance of this node if the preferred node has a distance. - _distance = thisTraceRoute.getDistance(); // This node is already included in the traceroute. - } - } - } - } - } - #endif - } - thisNode.distance = _distance; - - #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); - #endif - - #ifdef USES_ESPEASY_NOW - addNode(thisNode, thisTraceRoute); - if (thisNode.distance == 0) { - // Since we're the end node, claim highest success rate - updateSuccessRate(thisNode.unit, 255); - } - #else - addNode(thisNode); - #endif -} - -const NodeStruct * NodesHandler::getThisNode() { - node_time.now(); - updateThisNode(); - MAC_address this_mac; - WiFi.macAddress(this_mac.mac); - return getNodeByMac(this_mac.mac); -} - -uint8_t NodesHandler::getDistance() const { - // Perform extra check since _distance is only updated once every 30 seconds. - // And we don't want to tell other nodes we have distance 0 when we haven't. - if (isEndpoint()) return 0; - if (_distance == 0) { - // Outdated info, so return "we don't know" - return 255; - } - return _distance; -} - - -NodesMap::const_iterator NodesHandler::begin() const { - return _nodes.begin(); -} - -NodesMap::const_iterator NodesHandler::end() const { - return _nodes.end(); -} - -NodesMap::const_iterator NodesHandler::find(uint8_t unit_nr) const -{ - return _nodes.find(unit_nr); -} - -bool NodesHandler::refreshNodeList(unsigned long max_age_allowed, unsigned long& max_age) -{ - max_age = 0; - bool nodeRemoved = false; - - for (auto it = _nodes.begin(); it != _nodes.end();) { - unsigned long age = it->second.getAge(); - if (age > max_age_allowed) { - bool mustErase = true; - #ifdef USES_ESPEASY_NOW - auto route_it = _nodeStats.find(it->second.unit); - if (route_it != _nodeStats.end()) { - if (route_it->second.getAge() > max_age_allowed) { - _nodeStats_mutex.lock(); - _nodeStats.erase(route_it); - _nodeStats_mutex.unlock(); - } else { - mustErase = false; - } - } - #endif - if (mustErase) { - { - _nodes_mutex.lock(); - it = _nodes.erase(it); - _nodes_mutex.unlock(); - } - nodeRemoved = true; - } - } else { - ++it; - - if (age > max_age) { - max_age = age; - } - } - } - return nodeRemoved; -} - -// FIXME TD-er: should be a check per controller to see if it will accept messages -bool NodesHandler::isEndpoint() const -{ - // FIXME TD-er: Must check controller to see if it needs wifi (e.g. LoRa or cache controller do not need it) - #if FEATURE_MQTT - controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); - if (validControllerIndex(enabledMqttController)) { - // FIXME TD-er: Must call updateMQTTclient_connected() and see what effect - // the MQTTclient_connected state has when using ESPEasy-NOW. - return MQTTclient_connected; - } - #endif - - if (!NetworkConnected()) return false; - - return false; -} - -#ifdef USES_ESPEASY_NOW -uint8_t NodesHandler::getESPEasyNOW_channel() const -{ - if (active_network_medium == NetworkMedium_t::WIFI && NetworkConnected()) { - return WiFi.channel(); - } - if (Settings.ForceESPEasyNOWchannel > 0) { - return Settings.ForceESPEasyNOWchannel; - } - if (isEndpoint()) { - if (active_network_medium == NetworkMedium_t::WIFI) { - return WiFi.channel(); - } - } - const NodeStruct *preferred = getPreferredNode(); - if (preferred != nullptr) { - if (preferred->distance < 255) { - return preferred->channel; - } - } - return WiFiEventData.usedChannel; -} -#endif - -bool NodesHandler::recentlyBecameDistanceZero() { - if (!_recentlyBecameDistanceZero) { - return false; - } - _recentlyBecameDistanceZero = false; - return true; -} - -void NodesHandler::setRSSI(const MAC_address& mac, int rssi) -{ - setRSSI(getNodeByMac(mac), rssi); -} - -void NodesHandler::setRSSI(uint8_t unit, int rssi) -{ - setRSSI(getNode(unit), rssi); -} - -void NodesHandler::setRSSI(NodeStruct * node, int rssi) -{ - if (node != nullptr) { - node->setRSSI(rssi); - } -} - -bool NodesHandler::lastTimeValidDistanceExpired() const -{ -// if (_lastTimeValidDistance == 0) return false; - return timePassedSince(_lastTimeValidDistance) > 120000; // 2 minutes -} - -#ifdef USES_ESPEASY_NOW -void NodesHandler::updateSuccessRate(uint8_t unit, bool success) -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - it->second.updateSuccessRate(unit, success); - } -} - -void NodesHandler::updateSuccessRate(const MAC_address& mac, bool success) -{ - const NodeStruct * node = getNodeByMac(mac); - if (node == nullptr) { - return; - } - updateSuccessRate(node->unit, success); -} - -int NodesHandler::getRouteSuccessRate(uint8_t unit, uint8_t& distance) const -{ - distance = 255; - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - const ESPEasy_now_traceroute_struct* route = it->second.bestRoute(); - if (route != nullptr) { - distance = route->getDistance(); - return route->computeSuccessRate(); - } - } - return 0; -} - -uint8_t NodesHandler::getSuccessRate(uint8_t unit) const -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - return it->second.getNodeSuccessRate(); - } - return 127; -} - -ESPEasy_Now_MQTT_QueueCheckState::Enum NodesHandler::getMQTTQueueState(uint8_t unit) const -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - return it->second.getMQTTQueueState(); - } - return ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; - -} - -void NodesHandler::setMQTTQueueState(uint8_t unit, ESPEasy_Now_MQTT_QueueCheckState::Enum state) -{ - auto it = _nodeStats.find(unit); - if (it != _nodeStats.end()) { - it->second.setMQTTQueueState(state); - } -} - -void NodesHandler::setMQTTQueueState(const MAC_address& mac, ESPEasy_Now_MQTT_QueueCheckState::Enum state) -{ - const NodeStruct * node = getNodeByMac(mac); - if (node != nullptr) { - setMQTTQueueState(node->unit, state); - } -} - -#endif - +#include "../DataStructs/NodesHandler.h" + +#include "../../ESPEasy_common.h" + +#if FEATURE_ESPEASY_P2P +#include "../../ESPEasy-Globals.h" + +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_peermanager.h" +#include "../Globals/ESPEasy_now_state.h" +#endif + +#include "../Globals/EventQueue.h" + +#include "../DataTypes/NodeTypeID.h" + +#if FEATURE_MQTT +#include "../ESPEasyCore/Controller.h" +#endif + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/MQTT.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Misc.h" +#include "../Helpers/PeriodicalActions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_System.h" + +#define ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE 35000 + +bool NodesHandler::addNode(const NodeStruct& node) +{ + int8_t rssi = 0; + MAC_address match_sta; + MAC_address match_ap; + MAC_address ESPEasy_NOW_MAC; + + bool isNewNode = true; + + // Erase any existing node with matching MAC address + for (auto it = _nodes.begin(); it != _nodes.end(); ) + { + const MAC_address sta = it->second.sta_mac; + const MAC_address ap = it->second.ap_mac; + if ((!sta.all_zero() && node.match(sta)) || (!ap.all_zero() && node.match(ap))) { + rssi = it->second.getRSSI(); + if (!sta.all_zero()) + match_sta = sta; + if (!ap.all_zero()) + match_ap = ap; + ESPEasy_NOW_MAC = it->second.ESPEasy_Now_MAC(); + + isNewNode = false; + { + _nodes_mutex.lock(); + it = _nodes.erase(it); + _nodes_mutex.unlock(); + } + } else { + ++it; + } + } + { + _nodes_mutex.lock(); + { + #ifdef USE_SECOND_HEAP + // FIXME TD-er: Must check whether this is working well as the NodesMap is a std::map + HeapSelectIram ephemeral; + #endif + _nodes[node.unit] = node; + } + // Make sure to set first as NTP candidate, as it was set to time since + // last time sync by the sender node before sending. + _ntp_candidate.set(node); + // Now set lastUpdated so we can keep track of its age. + _nodes[node.unit].lastUpdated = millis(); + if (node.getRSSI() >= 0 && rssi < 0) { + _nodes[node.unit].setRSSI(rssi); + } + const MAC_address node_ap(node.ap_mac); + if (node_ap.all_zero()) { + _nodes[node.unit].setAP_MAC(node_ap); + } + if (node.ESPEasy_Now_MAC().all_zero()) { + _nodes[node.unit].setESPEasyNow_mac(ESPEasy_NOW_MAC); + } + _nodes_mutex.unlock(); + } + + // Check whether the current time source is considered "worse" than received from p2p node. + if (!node_time.systemTimePresent() || + (node_time.getTimeSource() > timeSource_t::ESPEASY_p2p_UDP) || + (timePassedSince(node_time.lastSyncTime_ms) > EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC)) { + double unixTime{}; + uint8_t unit = 0; + int32_t wander = -1; + const timeSource_t timeSource = _ntp_candidate.getUnixTime(unixTime, wander, unit); + + if (timeSource != timeSource_t::No_time_source) { + node_time.setExternalTimeSource_withTimeWander(unixTime, timeSource, wander, unit); + } + } + + if (isNewNode) { + if (Settings.UseRules && (node.unit != 0)) + { + // Generate event announcing new p2p node + // TODO TD-er: Maybe also add other info like ESP type, IP-address, etc? + eventQueue.addMove(strformat( + F("p2pNode#Connected=%d,'%s','%s'"), + node.unit, + node.getNodeName().c_str(), + formatSystemBuildNr(node.build).c_str() + )); + } + } + + return isNewNode; +} + +#ifdef USES_ESPEASY_NOW +bool NodesHandler::addNode(const NodeStruct& node, const ESPEasy_now_traceroute_struct& traceRoute) +{ + const bool isNewNode = addNode(node); + { + _nodeStats_mutex.lock(); + _nodeStats[node.unit].setDiscoveryRoute(node.unit, traceRoute); + _nodeStats_mutex.unlock(); + } + + ESPEasy_now_peermanager.addPeer(node.ESPEasy_Now_MAC(), node.channel); + + if (!node.isThisNode()) { + if (traceRoute.getDistance() != 255) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + if (log.reserve(80)) { + log = F(ESPEASY_NOW_NAME); + log += F(": Node: "); + log += String(node.unit); + log += F(" DiscoveryRoute received: "); + log += traceRoute.toString(); + addLog(LOG_LEVEL_INFO, log); + } + } + } else {} + } + return isNewNode; +} +#endif + +bool NodesHandler::hasNode(uint8_t unit_nr) const +{ + return _nodes.find(unit_nr) != _nodes.end(); +} + +bool NodesHandler::hasNode(const uint8_t *mac) const +{ + return getNodeByMac(mac) != nullptr; +} + +NodeStruct * NodesHandler::getNode(uint8_t unit_nr) +{ + auto it = _nodes.find(unit_nr); + + if (it == _nodes.end()) { + return nullptr; + } + return &(it->second); +} + +const NodeStruct * NodesHandler::getNode(uint8_t unit_nr) const +{ + auto it = _nodes.find(unit_nr); + + if (it == _nodes.end()) { + return nullptr; + } + return &(it->second); +} + +NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) +{ + if (mac.all_zero()) { + return nullptr; + } + delay(0); + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if (mac == it->second.sta_mac) { + return &(it->second); + } + + if (mac == it->second.ap_mac) { + return &(it->second); + } + } + return nullptr; +} + +const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac) const +{ + bool match_STA; + + return getNodeByMac(mac, match_STA); +} + +const NodeStruct * NodesHandler::getNodeByMac(const MAC_address& mac, bool& match_STA) const +{ + if (mac.all_zero()) { + return nullptr; + } + delay(0); + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if (mac == it->second.sta_mac) { + match_STA = true; + return &(it->second); + } + + if (mac == it->second.ap_mac) { + match_STA = false; + return &(it->second); + } + } + return nullptr; +} + +const NodeStruct * NodesHandler::getPreferredNode() const { + MAC_address dummy; + + return getPreferredNode_notMatching(dummy); +} + +const NodeStruct* NodesHandler::getPreferredNode_notMatching(uint8_t unit_nr) const { + MAC_address not_matching; + if (unit_nr != 0 && unit_nr != 255) { + const NodeStruct* node = getNode(unit_nr); + if (node != nullptr) { + not_matching = node->ESPEasy_Now_MAC(); + } + } + return getPreferredNode_notMatching(not_matching); +} + +const NodeStruct * NodesHandler::getPreferredNode_notMatching(const MAC_address& not_matching) const { + MAC_address this_mac = NetworkMacAddress(); + const NodeStruct *thisNode = getNodeByMac(this_mac); + const NodeStruct *reject = getNodeByMac(not_matching); + + const NodeStruct *res = nullptr; + + for (auto it = _nodes.begin(); it != _nodes.end(); ++it) + { + if ((&(it->second) != reject) && (&(it->second) != thisNode)) { + bool mustSet = false; + if (res == nullptr) { + mustSet = true; + } else { + #ifdef USES_ESPEASY_NOW + + uint8_t distance_new, distance_res = 255; + + const int successRate_new = getRouteSuccessRate(it->second.unit, distance_new); + const int successRate_res = getRouteSuccessRate(res->unit, distance_res); + + if (successRate_new == 0 || successRate_res == 0) { + // One of the nodes does not (yet) have a route. + if (successRate_new == 0 && successRate_res == 0) { + distance_new = it->second.distance; + distance_res = res->distance; + } else if (successRate_res == 0) { + // The new one has a route, so must set the new one. + distance_res = res->distance; + if (distance_new < 255) { + mustSet = true; + } + } + } + + if (distance_new == distance_res) { + if (successRate_new > successRate_res && distance_new < 255) { + mustSet = true; + } + } else if (distance_new < distance_res) { + if (it->second.getAge() < ESPEASY_NOW_ALLOWED_AGE_NO_TRACEROUTE) { + // Only allow this new one if it was seen recently + // as it does not (yet) have a traceroute. + mustSet = true; + } + } + #else + if (it->second < *res) { + mustSet = true; + } + #endif + } + if (mustSet) { + #ifdef USES_ESPEASY_NOW + if (it->second.ESPEasyNowPeer && it->second.distance < 255) { + res = &(it->second); + } + #else + res = &(it->second); + #endif + } + } + } + +/* + #ifdef USES_ESPEASY_NOW + if (res != nullptr) + { + uint8_t distance_res = 255; + const int successRate_res = getRouteSuccessRate(res->unit, distance_res); + if (distance_res == 255) { + return nullptr; + } + } + #endif +*/ + + return res; +} + +#ifdef USES_ESPEASY_NOW +const ESPEasy_now_traceroute_struct* NodesHandler::getTraceRoute(uint8_t unit) const +{ + auto trace_it = _nodeStats.find(unit); + if (trace_it == _nodeStats.end()) { + return nullptr; + } + return trace_it->second.bestRoute(); +} + +const ESPEasy_now_traceroute_struct* NodesHandler::getDiscoveryRoute(uint8_t unit) const +{ + auto trace_it = _nodeStats.find(unit); + if (trace_it == _nodeStats.end()) { + return nullptr; + } + return &(trace_it->second.discoveryRoute()); +} + +void NodesHandler::setTraceRoute(const MAC_address& mac, const ESPEasy_now_traceroute_struct& traceRoute) +{ + if (traceRoute.computeSuccessRate() == 0) { + // No need to store traceroute with low success rate. + return; + } + NodeStruct* node = getNodeByMac(mac); + if (node != nullptr) { + auto trace_it = _nodeStats.find(node->unit); + if (trace_it != _nodeStats.end()) { + _lastTimeValidDistance = millis(); + trace_it->second.addRoute(node->unit, traceRoute); + } + } +} + +#endif + + +void NodesHandler::updateThisNode() { + NodeStruct thisNode; + + // Set local data + { + MAC_address mac = NetworkMacAddress(); + mac.get(thisNode.sta_mac); + } + WiFi.softAPmacAddress(thisNode.ap_mac); + { + const bool addIP = NetworkConnected(); + #ifdef USES_ESPEASY_NOW + if (use_EspEasy_now) { + thisNode.useAP_ESPEasyNow = 1; + } + #endif + if (addIP) { + const IPAddress localIP = NetworkLocalIP(); + + for (uint8_t i = 0; i < 4; ++i) { + thisNode.ip[i] = localIP[i]; + } + } + } + #ifdef USES_ESPEASY_NOW + thisNode.channel = getESPEasyNOW_channel(); + #else + thisNode.channel = WiFiEventData.usedChannel; + #endif + if (thisNode.channel == 0) { + thisNode.channel = WiFi.channel(); + } + + thisNode.unit = Settings.Unit; + thisNode.build = Settings.Build; + memcpy(thisNode.nodeName, Settings.getName().c_str(), 25); + thisNode.nodeType = NODE_TYPE_ID; + + thisNode.webgui_portnumber = Settings.WebserverPort; + const int load_int = getCPUload() * 2.55; + + if (load_int > 255) { + thisNode.load = 255; + } else { + thisNode.load = load_int; + } + thisNode.timeSource = static_cast(node_time.getTimeSource()); + + switch (node_time.getTimeSource()) { + case timeSource_t::No_time_source: + thisNode.lastUpdated = (1 << 30); + break; + default: + { + thisNode.lastUpdated = timePassedSince(node_time.lastSyncTime_ms); + break; + } + } + if (node_time.systemTimePresent()) { + // NodeStruct is a packed struct, so we cannot directly use its members as a reference. + uint32_t unix_time_frac = 0; + thisNode.unix_time_sec = node_time.getUnixTime(unix_time_frac); + thisNode.unix_time_frac = unix_time_frac; + } + #ifdef USES_ESPEASY_NOW + if (Settings.UseESPEasyNow()) { + thisNode.ESPEasyNowPeer = 1; + } + #endif + + const uint8_t lastDistance = _distance; + #ifdef USES_ESPEASY_NOW + ESPEasy_now_traceroute_struct thisTraceRoute; + #endif + if (isEndpoint()) { + _distance = 0; + _lastTimeValidDistance = millis(); + if (lastDistance != _distance) { + _recentlyBecameDistanceZero = true; + } + #ifdef USES_ESPEASY_NOW + thisNode.distance = _distance; + thisNode.setRSSI(WiFi.RSSI()); + thisTraceRoute.addUnit(thisNode.unit); + #endif + } else { + _distance = 255; + #ifdef USES_ESPEASY_NOW + const NodeStruct *preferred = getPreferredNode_notMatching(thisNode.sta_mac); + + if (preferred != nullptr) { + if (!preferred->isExpired()) { + // Only take the distance of another node if it is running a build which does not send out traceroute + // If it is a build sending traceroute, only consider having a distance if you know how to reach the gateway node + // This does impose an issue when a gateway node is running an older version, as the next hops never will have a traceroute too. + // Therefore the reported build for those units will be faked to be an older version. + if (preferred->build < 20113) { + if (preferred->distance != 255) { + _distance = preferred->distance + 1; + thisNode.build = 20112; + } + } else { + const ESPEasy_now_traceroute_struct* tracert_ptr = getTraceRoute(preferred->unit); + if (tracert_ptr != nullptr && tracert_ptr->getDistance() < 255) { + // Make a copy of the traceroute + thisTraceRoute = *tracert_ptr; + thisTraceRoute.addUnit(thisNode.unit); + if (preferred->distance != 255) { + // Traceroute is only updated when a node is connected. + // Thus the traceroute may be outdated, while the node info will already indicate if a node has lost its route to the gateway node. + // So we only must set the distance of this node if the preferred node has a distance. + _distance = thisTraceRoute.getDistance(); // This node is already included in the traceroute. + } + } + } + } + } + #endif + } + thisNode.distance = _distance; + + #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); + #endif + + #ifdef USES_ESPEASY_NOW + addNode(thisNode, thisTraceRoute); + if (thisNode.distance == 0) { + // Since we're the end node, claim highest success rate + updateSuccessRate(thisNode.unit, 255); + } + #else + addNode(thisNode); + #endif +} + +const NodeStruct * NodesHandler::getThisNode() { +// node_time.now(); + updateThisNode(); + MAC_address this_mac = NetworkMacAddress(); + return getNodeByMac(this_mac.mac); +} + +uint8_t NodesHandler::getDistance() const { + // Perform extra check since _distance is only updated once every 30 seconds. + // And we don't want to tell other nodes we have distance 0 when we haven't. + if (isEndpoint()) return 0; + if (_distance == 0) { + // Outdated info, so return "we don't know" + return 255; + } + return _distance; +} + + +NodesMap::const_iterator NodesHandler::begin() const { + return _nodes.begin(); +} + +NodesMap::const_iterator NodesHandler::end() const { + return _nodes.end(); +} + +NodesMap::const_iterator NodesHandler::find(uint8_t unit_nr) const +{ + return _nodes.find(unit_nr); +} + +bool NodesHandler::refreshNodeList(unsigned long max_age_allowed, unsigned long& max_age) +{ + max_age = 0; + bool nodeRemoved = false; + + for (auto it = _nodes.begin(); it != _nodes.end();) { + unsigned long age = it->second.getAge(); + if (age > max_age_allowed) { + bool mustErase = true; + #ifdef USES_ESPEASY_NOW + auto route_it = _nodeStats.find(it->second.unit); + if (route_it != _nodeStats.end()) { + if (route_it->second.getAge() > max_age_allowed) { + _nodeStats_mutex.lock(); + _nodeStats.erase(route_it); + _nodeStats_mutex.unlock(); + } else { + mustErase = false; + } + } + #endif + if (mustErase) { + if (Settings.UseRules && it->second.unit != 0) + { + // Add event about removing node from nodeslist. + eventQueue.addMove(strformat(F("p2pNode#Disconnected=%d"), it->second.unit)); + } + { + _nodes_mutex.lock(); + it = _nodes.erase(it); + _nodes_mutex.unlock(); + } + nodeRemoved = true; + } + } else { + ++it; + + if (age > max_age) { + max_age = age; + } + } + } + return nodeRemoved; +} + +// FIXME TD-er: should be a check per controller to see if it will accept messages +bool NodesHandler::isEndpoint() const +{ + // FIXME TD-er: Must check controller to see if it needs wifi (e.g. LoRa or cache controller do not need it) + #if FEATURE_MQTT + controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); + if (validControllerIndex(enabledMqttController)) { + // FIXME TD-er: Must call updateMQTTclient_connected() and see what effect + // the MQTTclient_connected state has when using ESPEasy-NOW. + return MQTTclient_connected; + } + #endif + + if (!NetworkConnected()) return false; + + return false; +} + +#ifdef USES_ESPEASY_NOW +uint8_t NodesHandler::getESPEasyNOW_channel() const +{ + if (active_network_medium == NetworkMedium_t::WIFI && NetworkConnected()) { + return WiFi.channel(); + } + if (Settings.ForceESPEasyNOWchannel > 0) { + return Settings.ForceESPEasyNOWchannel; + } + if (isEndpoint()) { + if (active_network_medium == NetworkMedium_t::WIFI) { + return WiFi.channel(); + } + } + const NodeStruct *preferred = getPreferredNode(); + if (preferred != nullptr) { + if (preferred->distance < 255) { + return preferred->channel; + } + } + return WiFiEventData.usedChannel; +} +#endif + +bool NodesHandler::recentlyBecameDistanceZero() { + if (!_recentlyBecameDistanceZero) { + return false; + } + _recentlyBecameDistanceZero = false; + return true; +} + +void NodesHandler::setRSSI(const MAC_address& mac, int rssi) +{ + setRSSI(getNodeByMac(mac), rssi); +} + +void NodesHandler::setRSSI(uint8_t unit, int rssi) +{ + setRSSI(getNode(unit), rssi); +} + +void NodesHandler::setRSSI(NodeStruct * node, int rssi) +{ + if (node != nullptr) { + node->setRSSI(rssi); + } +} + +bool NodesHandler::lastTimeValidDistanceExpired() const +{ +// if (_lastTimeValidDistance == 0) return false; + return timePassedSince(_lastTimeValidDistance) > 120000; // 2 minutes +} + +#ifdef USES_ESPEASY_NOW +void NodesHandler::updateSuccessRate(uint8_t unit, bool success) +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + it->second.updateSuccessRate(unit, success); + } +} + +void NodesHandler::updateSuccessRate(const MAC_address& mac, bool success) +{ + const NodeStruct * node = getNodeByMac(mac); + if (node == nullptr) { + return; + } + updateSuccessRate(node->unit, success); +} + +int NodesHandler::getRouteSuccessRate(uint8_t unit, uint8_t& distance) const +{ + distance = 255; + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + const ESPEasy_now_traceroute_struct* route = it->second.bestRoute(); + if (route != nullptr) { + distance = route->getDistance(); + return route->computeSuccessRate(); + } + } + return 0; +} + +uint8_t NodesHandler::getSuccessRate(uint8_t unit) const +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + return it->second.getNodeSuccessRate(); + } + return 127; +} + +ESPEasy_Now_MQTT_QueueCheckState::Enum NodesHandler::getMQTTQueueState(uint8_t unit) const +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + return it->second.getMQTTQueueState(); + } + return ESPEasy_Now_MQTT_QueueCheckState::Enum::Unset; + +} + +void NodesHandler::setMQTTQueueState(uint8_t unit, ESPEasy_Now_MQTT_QueueCheckState::Enum state) +{ + auto it = _nodeStats.find(unit); + if (it != _nodeStats.end()) { + it->second.setMQTTQueueState(state); + } +} + +void NodesHandler::setMQTTQueueState(const MAC_address& mac, ESPEasy_Now_MQTT_QueueCheckState::Enum state) +{ + const NodeStruct * node = getNodeByMac(mac); + if (node != nullptr) { + setMQTTQueueState(node->unit, state); + } +} + +#endif + #endif \ No newline at end of file diff --git a/src/src/DataStructs/NodesHandler.h b/src/src/DataStructs/NodesHandler.h index d1eff1751..79c16b188 100644 --- a/src/src/DataStructs/NodesHandler.h +++ b/src/src/DataStructs/NodesHandler.h @@ -1,148 +1,148 @@ -#ifndef DATASTRUCTS_NODESHANDLER_H -#define DATASTRUCTS_NODESHANDLER_H - -#include "../../ESPEasy_common.h" -#if FEATURE_ESPEASY_P2P - -#include "../DataStructs/MAC_address.h" -#include "../DataStructs/NodeStruct.h" -#include "../DataStructs/NTP_candidate.h" - - -#ifdef USES_ESPEASY_NOW -# include "../DataStructs/ESPEasy_now_traceroute.h" -# include "../DataStructs/ESPEasy_now_Node_statistics.h" -# include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" -# include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" -# include "../Globals/ESPEasy_now_peermanager.h" -#endif // ifdef USES_ESPEASY_NOW - -#include "../Helpers/ESPEasyMutex.h" - - -class NodesHandler { -public: - - // Add node to the list of known nodes. - // @retval true when the node was not yet present in the list. - bool addNode(const NodeStruct& node); - -#ifdef USES_ESPEASY_NOW - bool addNode(const NodeStruct & node, - const ESPEasy_now_traceroute_struct& traceRoute); -#endif // ifdef USES_ESPEASY_NOW - - - bool hasNode(uint8_t unit_nr) const; - - bool hasNode(const uint8_t *mac) const; - - NodeStruct * getNode(uint8_t unit_nr); - const NodeStruct * getNode(uint8_t unit_nr) const; - - NodeStruct * getNodeByMac(const MAC_address& mac); - const NodeStruct * getNodeByMac(const MAC_address& mac) const; - const NodeStruct * getNodeByMac(const MAC_address& mac, - bool & match_STA) const; - - NodesMap::const_iterator begin() const; - NodesMap::const_iterator end() const; - NodesMap::const_iterator find(uint8_t unit_nr) const; - - // Remove nodes in list older than max_age_allowed (msec) - // Returns oldest age, max_age (msec) not removed from the list. - // Return true if a node has been removed. - bool refreshNodeList(unsigned long max_age_allowed, - unsigned long& max_age); - - - const NodeStruct * getPreferredNode() const; - const NodeStruct * getPreferredNode_notMatching(uint8_t unit_nr) const; - const NodeStruct * getPreferredNode_notMatching(const MAC_address& not_matching) const; - -#ifdef USES_ESPEASY_NOW - const ESPEasy_now_traceroute_struct* getTraceRoute(uint8_t unit) const; - const ESPEasy_now_traceroute_struct* getDiscoveryRoute(uint8_t unit) const; - - void setTraceRoute(const MAC_address & mac, - const ESPEasy_now_traceroute_struct& traceRoute); -#endif // ifdef USES_ESPEASY_NOW - - // Update the node referring to this unit with the most recent info. - void updateThisNode(); - - const NodeStruct* getThisNode(); - - uint8_t getDistance() const; - - bool lastTimeValidDistanceExpired() const; - - unsigned long get_lastTimeValidDistance() const { - return _lastTimeValidDistance; - } - - bool isEndpoint() const; - -#ifdef USES_ESPEASY_NOW - uint8_t getESPEasyNOW_channel() const; -#endif // ifdef USES_ESPEASY_NOW - - bool recentlyBecameDistanceZero(); - - void setRSSI(const MAC_address& mac, - int rssi); - - void setRSSI(uint8_t unit, - int rssi); - -#ifdef USES_ESPEASY_NOW - void updateSuccessRate(uint8_t unit, - bool success); - void updateSuccessRate(const MAC_address& mac, - bool success); - - int getRouteSuccessRate(uint8_t unit, - uint8_t& distance) const; - - uint8_t getSuccessRate(uint8_t unit) const; - - ESPEasy_Now_MQTT_QueueCheckState::Enum getMQTTQueueState(uint8_t unit) const; - - void setMQTTQueueState(uint8_t unit, - ESPEasy_Now_MQTT_QueueCheckState::Enum state); - void setMQTTQueueState(const MAC_address & mac, - ESPEasy_Now_MQTT_QueueCheckState::Enum state); - -#endif // ifdef USES_ESPEASY_NOW - - bool getUnixTime(double &unix_time, uint8_t& unit) const { - return _ntp_candidate.getUnixTime(unix_time, unit); - } - - -private: - - void setRSSI(NodeStruct *node, - int rssi); - - unsigned long _lastTimeValidDistance = 0; - - uint8_t _distance = 255; // Cached value - - NodesMap _nodes; - ESPEasy_Mutex _nodes_mutex; - - NTP_candidate_struct _ntp_candidate; - - -#ifdef USES_ESPEASY_NOW - ESPEasy_now_Node_statisticsMap _nodeStats; - ESPEasy_Mutex _nodeStats_mutex; -#endif // ifdef USES_ESPEASY_NOW - - bool _recentlyBecameDistanceZero = false; -}; - -#endif - +#ifndef DATASTRUCTS_NODESHANDLER_H +#define DATASTRUCTS_NODESHANDLER_H + +#include "../../ESPEasy_common.h" +#if FEATURE_ESPEASY_P2P + +#include "../DataStructs/MAC_address.h" +#include "../DataStructs/NodeStruct.h" +#include "../DataStructs/NTP_candidate.h" + + +#ifdef USES_ESPEASY_NOW +# include "../DataStructs/ESPEasy_now_traceroute.h" +# include "../DataStructs/ESPEasy_now_Node_statistics.h" +# include "../DataStructs/ESPEasy_Now_MQTT_queue_check_packet.h" +# include "../DataTypes/ESPEasy_Now_MQTT_queue_check_state.h" +# include "../Globals/ESPEasy_now_peermanager.h" +#endif // ifdef USES_ESPEASY_NOW + +#include "../Helpers/ESPEasyMutex.h" + + +class NodesHandler { +public: + + // Add node to the list of known nodes. + // @retval true when the node was not yet present in the list. + bool addNode(const NodeStruct& node); + +#ifdef USES_ESPEASY_NOW + bool addNode(const NodeStruct & node, + const ESPEasy_now_traceroute_struct& traceRoute); +#endif // ifdef USES_ESPEASY_NOW + + + bool hasNode(uint8_t unit_nr) const; + + bool hasNode(const uint8_t *mac) const; + + NodeStruct * getNode(uint8_t unit_nr); + const NodeStruct * getNode(uint8_t unit_nr) const; + + NodeStruct * getNodeByMac(const MAC_address& mac); + const NodeStruct * getNodeByMac(const MAC_address& mac) const; + const NodeStruct * getNodeByMac(const MAC_address& mac, + bool & match_STA) const; + + NodesMap::const_iterator begin() const; + NodesMap::const_iterator end() const; + NodesMap::const_iterator find(uint8_t unit_nr) const; + + // Remove nodes in list older than max_age_allowed (msec) + // Returns oldest age, max_age (msec) not removed from the list. + // Return true if a node has been removed. + bool refreshNodeList(unsigned long max_age_allowed, + unsigned long& max_age); + + + const NodeStruct * getPreferredNode() const; + const NodeStruct * getPreferredNode_notMatching(uint8_t unit_nr) const; + const NodeStruct * getPreferredNode_notMatching(const MAC_address& not_matching) const; + +#ifdef USES_ESPEASY_NOW + const ESPEasy_now_traceroute_struct* getTraceRoute(uint8_t unit) const; + const ESPEasy_now_traceroute_struct* getDiscoveryRoute(uint8_t unit) const; + + void setTraceRoute(const MAC_address & mac, + const ESPEasy_now_traceroute_struct& traceRoute); +#endif // ifdef USES_ESPEASY_NOW + + // Update the node referring to this unit with the most recent info. + void updateThisNode(); + + const NodeStruct* getThisNode(); + + uint8_t getDistance() const; + + bool lastTimeValidDistanceExpired() const; + + unsigned long get_lastTimeValidDistance() const { + return _lastTimeValidDistance; + } + + bool isEndpoint() const; + +#ifdef USES_ESPEASY_NOW + uint8_t getESPEasyNOW_channel() const; +#endif // ifdef USES_ESPEASY_NOW + + bool recentlyBecameDistanceZero(); + + void setRSSI(const MAC_address& mac, + int rssi); + + void setRSSI(uint8_t unit, + int rssi); + +#ifdef USES_ESPEASY_NOW + void updateSuccessRate(uint8_t unit, + bool success); + void updateSuccessRate(const MAC_address& mac, + bool success); + + int getRouteSuccessRate(uint8_t unit, + uint8_t& distance) const; + + uint8_t getSuccessRate(uint8_t unit) const; + + ESPEasy_Now_MQTT_QueueCheckState::Enum getMQTTQueueState(uint8_t unit) const; + + void setMQTTQueueState(uint8_t unit, + ESPEasy_Now_MQTT_QueueCheckState::Enum state); + void setMQTTQueueState(const MAC_address & mac, + ESPEasy_Now_MQTT_QueueCheckState::Enum state); + +#endif // ifdef USES_ESPEASY_NOW + + timeSource_t getUnixTime(double &unix_time, int32_t& wander, uint8_t& unit) const { + return _ntp_candidate.getUnixTime(unix_time, wander, unit); + } + + +private: + + void setRSSI(NodeStruct *node, + int rssi); + + unsigned long _lastTimeValidDistance = 0; + + uint8_t _distance = 255; // Cached value + + NodesMap _nodes; + ESPEasy_Mutex _nodes_mutex; + + NTP_candidate_struct _ntp_candidate; + + +#ifdef USES_ESPEASY_NOW + ESPEasy_now_Node_statisticsMap _nodeStats; + ESPEasy_Mutex _nodeStats_mutex; +#endif // ifdef USES_ESPEASY_NOW + + bool _recentlyBecameDistanceZero = false; +}; + +#endif + #endif // ifndef DATASTRUCTS_NODESHANDLER_H \ No newline at end of file diff --git a/src/src/DataStructs/NotificationSettingsStruct.h b/src/src/DataStructs/NotificationSettingsStruct.h index 01ddcb445..f4f23fc85 100644 --- a/src/src/DataStructs/NotificationSettingsStruct.h +++ b/src/src/DataStructs/NotificationSettingsStruct.h @@ -7,6 +7,10 @@ #include // For std::shared_ptr +# define NPLUGIN_001_DEF_TM 8000 // Email Server Default Response Time, in mS. +# define NPLUGIN_001_MIN_TM 5000 +# define NPLUGIN_001_MAX_TM 20000 + /*********************************************************************************************\ * NotificationSettingsStruct \*********************************************************************************************/ @@ -27,6 +31,7 @@ struct NotificationSettingsStruct int8_t Pin2; char User[49]; char Pass[33]; + unsigned int Timeout; //its safe to extend this struct, up to 4096 bytes, default values in config are 0 }; diff --git a/src/src/DataStructs/PluginStats.cpp b/src/src/DataStructs/PluginStats.cpp index f44a02817..6d0fb343a 100644 --- a/src/src/DataStructs/PluginStats.cpp +++ b/src/src/DataStructs/PluginStats.cpp @@ -1,707 +1,603 @@ -#include "../DataStructs/PluginStats.h" - -#if FEATURE_PLUGIN_STATS -# include "../../_Plugin_Helper.h" - -# include "../Helpers/ESPEasy_math.h" - -# include "../WebServer/Chart_JS.h" - -PluginStats::PluginStats(uint8_t nrDecimals, float errorValue) : - _errorValue(errorValue), - _nrDecimals(nrDecimals) - -{ - _errorValueIsNaN = isnan(_errorValue); - _minValue = std::numeric_limits::max(); - _maxValue = std::numeric_limits::lowest(); -} - -bool PluginStats::push(float value) -{ - return _samples.push(value); -} - -void PluginStats::trackPeak(float value) -{ - if (value > _maxValue) { _maxValue = value; } - - if (value < _minValue) { _minValue = value; } -} - -void PluginStats::resetPeaks() -{ - _minValue = std::numeric_limits::max(); - _maxValue = std::numeric_limits::lowest(); -} - -float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const -{ - if (_samples.size() == 0) { return _errorValue; } - float sum = 0.0f; - - PluginStatsBuffer_t::index_t i = 0; - - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; - } - PluginStatsBuffer_t::index_t samplesUsed = 0; - - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); - - if (usableValue(sample)) { - ++samplesUsed; - sum += sample; - } - } - - if (samplesUsed == 0) { return _errorValue; } - return sum / samplesUsed; -} - -float PluginStats::getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const -{ - float variance = 0.0f; - const float average = getSampleAvg(lastNrSamples); - - if (!usableValue(average)) { return 0.0f; } - - PluginStatsBuffer_t::index_t i = 0; - - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; - } - PluginStatsBuffer_t::index_t samplesUsed = 0; - - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); - - if (usableValue(sample)) { - ++samplesUsed; - const float diff = sample - average; - variance += diff * diff; - } - } - - if (samplesUsed < 2) { return 0.0f; } - - variance /= samplesUsed; - return sqrtf(variance); -} - -float PluginStats::getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, bool getMax) const -{ - if (_samples.size() == 0) { return _errorValue; } - - PluginStatsBuffer_t::index_t i = 0; - - if (lastNrSamples < _samples.size()) { - i = _samples.size() - lastNrSamples; - } - - bool changed = false; - - float res = getMax ? INT_MIN : INT_MAX; - - for (; i < _samples.size(); ++i) { - const float sample(_samples[i]); - - if (usableValue(sample)) { - if ((getMax && (sample > res)) || - (!getMax && (sample < res))) { - changed = true; - res = sample; - } - } - } - - if (!changed) { return _errorValue; } - - return res; -} - -float PluginStats::getSample(int lastNrSamples) const -{ - if ((_samples.size() == 0) || (_samples.size() < abs(lastNrSamples))) { return _errorValue; } - - PluginStatsBuffer_t::index_t i = 0; - - if (lastNrSamples > 0) { - i = _samples.size() - lastNrSamples; - } else if (lastNrSamples < 0) { - i = abs(lastNrSamples) - 1; - } - - if (i < _samples.size()) { - return _samples[i]; - } - return _errorValue; -} - -float PluginStats::operator[](PluginStatsBuffer_t::index_t index) const -{ - if (index < _samples.size()) { return _samples[index]; } - return _errorValue; -} - -bool PluginStats::matchedCommand(const String& command, const __FlashStringHelper *cmd_match, int& nrSamples) -{ - const String cmd_match_str(cmd_match); - - if (command.equals(cmd_match_str)) { - nrSamples = INT_MIN; - return true; - } - - if (command.startsWith(cmd_match_str)) { - nrSamples = 0; - - // FIXME TD-er: ESP_IDF 5.x needs strict matching thus int32_t != int - int32_t tmp{}; - - if (validIntFromString(command.substring(cmd_match_str.length()), tmp)) { - nrSamples = tmp; - return true; - } - } - return false; -} - -bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String& string) const -{ - // Full value name is something like "taskvaluename.avg" - const String fullValueName = parseString(string, 1); - const String command = parseString(fullValueName, 2, '.'); - - if (command.isEmpty()) { - return false; - } - - float value{}; - int nrSamples = 0; - bool success = false; - - switch (command[0]) - { - case 'a': - - if (matchedCommand(command, F("avg"), nrSamples)) { - success = nrSamples != 0; - - if (nrSamples < 0) { // [taskname#valuename.avg] Average value of the last N kept samples - value = getSampleAvg(); - } else { - // Check for "avgN", where N is the number of most recent samples to use. - if (nrSamples > 0) { - // [taskname#valuename.avgN] Average over N most recent samples - value = getSampleAvg(nrSamples); - } - } - } - break; - case 'm': - - if (matchedCommand(command, F("min"), nrSamples)) { - success = nrSamples != 0; - - if (nrSamples < 0) { // [taskname#valuename.min] Lowest value seen since value reset - value = getPeakLow(); - } else { // Check for "minN", where N is the number of most recent samples to use. - if (nrSamples > 0) { - value = getSampleExtreme(nrSamples, false); - } - } - } else if (matchedCommand(command, F("max"), nrSamples)) { - success = nrSamples != 0; - - if (nrSamples < 0) { // [taskname#valuename.max] Highest value seen since value reset - value = getPeakHigh(); - } else { // Check for "maxN", where N is the number of most recent samples to use. - if (nrSamples > 0) { - value = getSampleExtreme(nrSamples, true); - } - } - } - break; - case 's': - - if (matchedCommand(command, F("stddev"), nrSamples)) { - success = nrSamples != 0; - - if (nrSamples < 0) { // [taskname#valuename.stddev] Std deviation of the last N kept samples - value = getSampleStdDev(); - } else { - // Check for "stddevN", where N is the number of most recent samples to use. - if (nrSamples > 0) { - // [taskname#valuename.stddevN] Std. deviation over N most recent samples - value = getSampleStdDev(nrSamples); - } - } - } else if (matchedCommand(command, F("size"), nrSamples)) { - // [taskname#valuename.size] Number of samples in memory - value = _samples.size(); - success = true; - } else if (matchedCommand(command, F("sample"), nrSamples)) { - success = nrSamples != 0; - - if (nrSamples == INT_MIN) { - // [taskname#valuename.sample] Number of samples in memory. - value = _samples.size(); - success = true; - } else { - if (nrSamples != 0) { - // [taskname#valuename.sampleN] - // With sample N: - // N > 0: Return N'th most recent sample - // N < 0: Return abs(N)'th sample in memory, starting at the oldest one. - // abs(N) > [number of samples]: return error value - value = getSample(nrSamples); - } - } - } - break; - default: - return false; - } - - - if (success) { - string = toString(value, _nrDecimals); - } - return success; -} - -bool PluginStats::webformLoad_show_stats(struct EventStruct *event) const -{ - bool somethingAdded = false; - - if (webformLoad_show_avg(event)) { somethingAdded = true; } - - if (webformLoad_show_stdev(event)) { somethingAdded = true; } - - if (webformLoad_show_peaks(event)) { somethingAdded = true; } - - if (somethingAdded) { - addFormSeparator(4); - } - - return somethingAdded; -} - -bool PluginStats::webformLoad_show_avg(struct EventStruct *event) const -{ - if (getNrSamples() > 0) { - addRowLabel(concat(getLabel(), F(" Average"))); - addHtmlFloat(getSampleAvg(), _nrDecimals); - addHtml(strformat(F(" (%u samples)"), getNrSamples())); - return true; - } - return false; -} - -bool PluginStats::webformLoad_show_stdev(struct EventStruct *event) const -{ - const float stdDev = getSampleStdDev(); - - if (usableValue(stdDev) && (getNrSamples() > 1)) { - addRowLabel(concat(getLabel(), F(" std. dev"))); - addHtmlFloat(stdDev, _nrDecimals); - addHtml(strformat(F(" (%u samples)"), getNrSamples())); - return true; - } - return false; -} - -bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, bool include_peak_to_peak) const -{ - if (hasPeaks() && (getNrSamples() > 1)) { - addRowLabel(concat(getLabel(), F(" Peak Low/High"))); - addHtmlFloat(getPeakLow(), _nrDecimals); - addHtml('/'); - addHtmlFloat(getPeakHigh(), _nrDecimals); - - if (include_peak_to_peak) { - addRowLabel(concat(getLabel(), F(" Peak-to-peak"))); - addHtmlFloat(getPeakHigh() - getPeakLow(), _nrDecimals); - } - return true; - } - return false; -} - -void PluginStats::webformLoad_show_val( - struct EventStruct *event, - const String & label, - ESPEASY_RULES_FLOAT_TYPE value, - const String & unit) const -{ - addRowLabel(concat(getLabel(), label)); - addHtmlFloat(value, _nrDecimals); - - if (!unit.isEmpty()) { - addUnit(unit); - } -} - -# if FEATURE_CHART_JS -void PluginStats::plot_ChartJS_dataset() const -{ - add_ChartJS_dataset_header(_ChartJS_dataset_config); - - PluginStatsBuffer_t::index_t i = 0; - - for (; i < _samples.size(); ++i) { - if (i != 0) { - addHtml(','); - } - - if (!isnan(_samples[i])) { - addHtmlFloat(_samples[i], _nrDecimals); - } - else { - addHtml(F("null")); - } - } - add_ChartJS_dataset_footer(); -} - -# endif // if FEATURE_CHART_JS - -bool PluginStats::usableValue(float value) const -{ - if (!isnan(value)) { - if (_errorValueIsNaN || !essentiallyEqual(_errorValue, value)) { - return true; - } - } - return false; -} - -PluginStats_array::~PluginStats_array() -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - delete _plugin_stats[i]; - _plugin_stats[i] = nullptr; - } - } -} - -void PluginStats_array::initPluginStats(taskVarIndex_t taskVarIndex) -{ - if (taskVarIndex < VARS_PER_TASK) { - delete _plugin_stats[taskVarIndex]; - _plugin_stats[taskVarIndex] = nullptr; - - if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - _plugin_stats[taskVarIndex] = new (std::nothrow) PluginStats( - ExtraTaskSettings.TaskDeviceValueDecimals[taskVarIndex], - ExtraTaskSettings.TaskDeviceErrorValue[taskVarIndex]); - - if (_plugin_stats[taskVarIndex] != nullptr) { - _plugin_stats[taskVarIndex]->setLabel(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); - # 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); - # endif // if FEATURE_CHART_JS - } - } - } -} - -void PluginStats_array::clearPluginStats(taskVarIndex_t taskVarIndex) -{ - if (taskVarIndex < VARS_PER_TASK) { - if (_plugin_stats[taskVarIndex] != nullptr) { - delete _plugin_stats[taskVarIndex]; - _plugin_stats[taskVarIndex] = nullptr; - } - } -} - -bool PluginStats_array::hasStats() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { return true; } - } - return false; -} - -bool PluginStats_array::hasPeaks() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if ((_plugin_stats[i] != nullptr) && _plugin_stats[i]->hasPeaks()) { - return true; - } - } - return false; -} - -size_t PluginStats_array::nrSamplesPresent() const -{ - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - return _plugin_stats[i]->getNrSamples(); - } - } - return 0; -} - -size_t PluginStats_array::nrPluginStats() const -{ - size_t res{}; - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - ++res; - } - } - return res; -} - -void PluginStats_array::pushPluginStatsValues(struct EventStruct *event, bool trackPeaks) -{ - if (validTaskIndex(event->TaskIndex)) { - const uint8_t valueCount = getValueCountForTask(event->TaskIndex); - const Sensor_VType sensorType = event->getSensorType(); - - 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); - } - } - } - } -} - -bool PluginStats_array::plugin_get_config_value_base(struct EventStruct *event, - String & string) const -{ - // Full value name is something like "taskvaluename.avg" - const String fullValueName = parseString(string, 1); - const String valueName = parseString(fullValueName, 1, '.'); - - for (taskVarIndex_t i = 0; i < VARS_PER_TASK; i++) - { - if (_plugin_stats[i] != nullptr) { - // Check case insensitive, since the user entered value name can have any case. - if (valueName.equalsIgnoreCase(getTaskValueName(event->TaskIndex, i))) - { - return _plugin_stats[i]->plugin_get_config_value_base(event, string); - } - } - } - return false; -} - -bool PluginStats_array::plugin_write_base(struct EventStruct *event, const String& string) -{ - bool success = false; - const String cmd = parseString(string, 1); // command - - const bool resetPeaks = equals(cmd, F("resetpeaks")); // Command: "taskname.resetPeaks" - const bool clearSamples = equals(cmd, F("clearsamples")); // Command: "taskname.clearSamples" - - if (resetPeaks || clearSamples) { - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - if (resetPeaks) { - success = true; - _plugin_stats[i]->resetPeaks(); - } - - if (clearSamples) { - success = true; - _plugin_stats[i]->clearSamples(); - } - } - } - } - return success; -} - -bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event) const -{ - bool somethingAdded = false; - - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - if (_plugin_stats[i]->webformLoad_show_stats(event)) { - somethingAdded = true; - } - } - } - return somethingAdded; -} - -# if FEATURE_CHART_JS -void PluginStats_array::plot_ChartJS() const -{ - const size_t nrSamples = nrSamplesPresent(); - - if (nrSamples == 0) { return; } - - // Chart Header - { - ChartJS_options_scales scales; - scales.add({ F("x") }); - - 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(); - - add_ChartJS_chart_header( - F("line"), - F("TaskStatsChart"), - {}, - 500 + (70 * (scales.nr_Y_scales() - 1)), - 500, - scales.toString(), - nrSamples); - } - - - // Add labels - addHtml(F("labels:[")); - - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlInt(i); - } - addHtml(F("],datasets:[")); - - - // Data sets - for (size_t i = 0; i < VARS_PER_TASK; ++i) { - if (_plugin_stats[i] != nullptr) { - _plugin_stats[i]->plot_ChartJS_dataset(); - } - } - add_ChartJS_chart_footer(); -} - -void PluginStats_array::plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage, - const String & options) const -{ - const PluginStats *stats_X = getPluginStats(values_X_axis_index); - const PluginStats *stats_Y = getPluginStats(values_Y_axis_index); - - if ((stats_X == nullptr) || (stats_Y == nullptr)) { - return; - } - - if ((stats_X->getNrSamples() < 2) || (stats_Y->getNrSamples() < 2)) { - 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(); - - add_ChartJS_chart_header( - F("scatter"), - id, - chartTitle, - width, - height, - axisOptions, - nrSamples); - - // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. - addHtml(F("labels:[")); - - for (size_t i = 0; i < nrSamples; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlInt(i); - } - addHtml(F("],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 - 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(); -} - -# endif // if FEATURE_CHART_JS - - -PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) const -{ - if ((taskVarIndex < VARS_PER_TASK)) { - return _plugin_stats[taskVarIndex]; - } - return nullptr; -} - -PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) -{ - if ((taskVarIndex < VARS_PER_TASK)) { - return _plugin_stats[taskVarIndex]; - } - return nullptr; -} - -#endif // if FEATURE_PLUGIN_STATS +#include "../DataStructs/PluginStats.h" + +#if FEATURE_PLUGIN_STATS +# include "../../_Plugin_Helper.h" + +# include "../Globals/TimeZone.h" + +# include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Memory.h" + +# include "../WebServer/Chart_JS.h" + + +PluginStats::PluginStats(uint8_t nrDecimals, float errorValue) : + _errorValue(errorValue), + _nrDecimals(nrDecimals), + _plugin_stats_timestamps(nullptr) + +{ + // Try to allocate in PSRAM if possible + void *ptr = special_calloc(1, sizeof(PluginStatsBuffer_t)); + + if (ptr == nullptr) { _samples = nullptr; } + else { + _samples = new (ptr) PluginStatsBuffer_t(); + } + _errorValueIsNaN = isnan(_errorValue); + _minValue = std::numeric_limits::max(); + _maxValue = std::numeric_limits::lowest(); + _minValueTimestamp = 0; + _maxValueTimestamp = 0; +} + +PluginStats::~PluginStats() +{ + if (_samples != nullptr) { + free(_samples); + + // delete _samples; + } + _samples = nullptr; + _plugin_stats_timestamps = nullptr; +} + +void PluginStats::processTimeSet(const double& time_offset) +{ + // Check to see if there was a unix time set before the system time was set + // For example when receiving data from a p2p node + const int64_t cur_micros = getMicros64(); + const int64_t offset_micros = time_offset * 1000000ull; + + if ((_maxValueTimestamp > cur_micros) && (_maxValueTimestamp > offset_micros)) { + _maxValueTimestamp -= offset_micros; + } + + if ((_minValueTimestamp > cur_micros) && (_minValueTimestamp > offset_micros)) { + _minValueTimestamp -= offset_micros; + } +} + +bool PluginStats::push(float value) +{ + if (_samples == nullptr) { return false; } + return _samples->push(value); +} + +bool PluginStats::matchesLastTwoEntries(float value) const +{ + const size_t nrSamples = getNrSamples(); + + if (nrSamples < 2) { return false; } + + const float last = (*_samples)[nrSamples - 1]; + const float beforeLast = (*_samples)[nrSamples - 2]; + + const String value_str = toString(value, _nrDecimals); + + return + toString(last, _nrDecimals).equals(value_str) && + toString(beforeLast, _nrDecimals).equals(value_str); + + + /* + const bool value_valid = isValidFloat(value); + const bool last_valid = isValidFloat(last); + + if (value_valid != last_valid) { + return false; + } + const bool beforeLast_valid = isValidFloat(beforeLast); + + if (value_valid != beforeLast_valid) { + return false; + } + + if (value_valid) { + return + approximatelyEqual(value, last) && + approximatelyEqual(value, beforeLast); + } + return true; + */ +} + +void PluginStats::trackPeak(float value, int64_t timestamp) +{ + if ((value > _maxValue) || (value < _minValue)) { + if (timestamp == 0) { + // Make sure both extremes are flagged with the same timestamp. + timestamp = getMicros64(); + } + + if (value > _maxValue) { + _maxValueTimestamp = timestamp; + _maxValue = value; + } + + if (value < _minValue) { + _minValueTimestamp = timestamp; + _minValue = value; + } + } +} + +void PluginStats::resetPeaks() +{ + _minValue = std::numeric_limits::max(); + _maxValue = std::numeric_limits::lowest(); + _minValueTimestamp = 0; + _maxValueTimestamp = 0; +} + +void PluginStats::clearSamples() { + if (_samples != nullptr) { + _samples->clear(); + } +} + +size_t PluginStats::getNrSamples() const { + if (_samples == nullptr) { return 0u; } + return _samples->size(); +} + +float PluginStats::getSampleAvg() const { + return getSampleAvg(getNrSamples()); +} + +float PluginStats::getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const +{ + const size_t nrSamples = getNrSamples(); + + if (nrSamples == 0) { return _errorValue; } + float sum = 0.0f; + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; + } + PluginStatsBuffer_t::index_t samplesUsed = 0; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + + if (usableValue(sample)) { + ++samplesUsed; + sum += sample; + } + } + + if (samplesUsed == 0) { return _errorValue; } + return sum / samplesUsed; +} + +float PluginStats::getSampleAvg_time(PluginStatsBuffer_t::index_t lastNrSamples, uint64_t& totalDuration_usec) const +{ + const size_t nrSamples = getNrSamples(); + + totalDuration_usec = 0u; + + if ((nrSamples == 0) || (_plugin_stats_timestamps == nullptr)) { + return _errorValue; + } + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; + } + + int64_t lastTimestamp = 0; + float lastValue = 0.0f; + bool lastValueUsable = false; + float sum = 0.0f; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + const int64_t curTimestamp = (*_plugin_stats_timestamps)[i]; + const bool curValueUsable = usableValue(sample); + + if ((lastTimestamp != 0) && lastValueUsable) { + const int64_t duration_usec = abs(timeDiff64(lastTimestamp, curTimestamp)); + + if (curValueUsable) { + // Old and new value usable, take average of this period. + sum += ((lastValue + sample) / 2.0f) * duration_usec; + } else { + // New value is not usable, so just add the last value for the duration. + sum += lastValue * duration_usec; + } + totalDuration_usec += duration_usec; + } + + lastValueUsable = curValueUsable; + lastTimestamp = curTimestamp; + lastValue = sample; + } + + if (totalDuration_usec == 0) { return _errorValue; } + return sum / totalDuration_usec; +} + +float PluginStats::getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const +{ + const size_t nrSamples = getNrSamples(); + float variance = 0.0f; + const float average = getSampleAvg(lastNrSamples); + + if (!usableValue(average)) { return 0.0f; } + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; + } + PluginStatsBuffer_t::index_t samplesUsed = 0; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + + if (usableValue(sample)) { + ++samplesUsed; + const float diff = sample - average; + variance += diff * diff; + } + } + + if (samplesUsed < 2) { return 0.0f; } + + variance /= samplesUsed; + return sqrtf(variance); +} + +float PluginStats::getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, bool getMax) const +{ + const size_t nrSamples = getNrSamples(); + + if (nrSamples == 0) { return _errorValue; } + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples < nrSamples) { + i = nrSamples - lastNrSamples; + } + + bool changed = false; + + float res = getMax ? INT_MIN : INT_MAX; + + for (; i < nrSamples; ++i) { + const float sample((*_samples)[i]); + + if (usableValue(sample)) { + if ((getMax && (sample > res)) || + (!getMax && (sample < res))) { + changed = true; + res = sample; + } + } + } + + if (!changed) { return _errorValue; } + + return res; +} + +float PluginStats::getSample(int lastNrSamples) const +{ + const size_t nrSamples = getNrSamples(); + + if ((nrSamples == 0) || (nrSamples < abs(lastNrSamples))) { return _errorValue; } + + PluginStatsBuffer_t::index_t i = 0; + + if (lastNrSamples > 0) { + i = nrSamples - lastNrSamples; + } else if (lastNrSamples < 0) { + i = abs(lastNrSamples) - 1; + } + + if (i < nrSamples) { + return (*_samples)[i]; + } + return _errorValue; +} + +float PluginStats::operator[](PluginStatsBuffer_t::index_t index) const +{ + const size_t nrSamples = getNrSamples(); + + if (index < nrSamples) { return (*_samples)[index]; } + return _errorValue; +} + +bool PluginStats::matchedCommand(const String& command, const __FlashStringHelper *cmd_match, int& nrSamples) +{ + const String cmd_match_str(cmd_match); + + if (command.equals(cmd_match_str)) { + nrSamples = INT_MIN; + return true; + } + + if (command.startsWith(cmd_match_str)) { + nrSamples = 0; + + // FIXME TD-er: ESP_IDF 5.x needs strict matching thus int32_t != int + int32_t tmp{}; + + if (validIntFromString(command.substring(cmd_match_str.length()), tmp)) { + nrSamples = tmp; + return true; + } + } + return false; +} + +bool PluginStats::plugin_get_config_value_base(struct EventStruct *event, String& string) const +{ + // Full value name is something like "taskvaluename.avg" + const String fullValueName = parseString(string, 1); + const String command = parseString(fullValueName, 2, '.'); + + if (command.isEmpty()) { + return false; + } + + float value{}; + int nrSamples = 0; + bool success = false; + + switch (command[0]) + { + case 'a': + + if (matchedCommand(command, F("avg"), nrSamples)) { + success = nrSamples != 0; + + if (nrSamples < 0) { // [taskname#valuename.avg] Average value of the last N kept samples + value = getSampleAvg(); + } else { + // Check for "avgN", where N is the number of most recent samples to use. + if (nrSamples > 0) { + // [taskname#valuename.avgN] Average over N most recent samples + value = getSampleAvg(nrSamples); + } + } + } + break; + case 'm': + + if (matchedCommand(command, F("min"), nrSamples)) { + success = nrSamples != 0; + + if (nrSamples < 0) { // [taskname#valuename.min] Lowest value seen since value reset + value = getPeakLow(); + } else { // Check for "minN", where N is the number of most recent samples to use. + if (nrSamples > 0) { + value = getSampleExtreme(nrSamples, false); + } + } + } else if (matchedCommand(command, F("max"), nrSamples)) { + success = nrSamples != 0; + + if (nrSamples < 0) { // [taskname#valuename.max] Highest value seen since value reset + value = getPeakHigh(); + } else { // Check for "maxN", where N is the number of most recent samples to use. + if (nrSamples > 0) { + value = getSampleExtreme(nrSamples, true); + } + } + } + break; + case 's': + + if (matchedCommand(command, F("stddev"), nrSamples)) { + success = nrSamples != 0; + + if (nrSamples < 0) { // [taskname#valuename.stddev] Std deviation of the last N kept samples + value = getSampleStdDev(); + } else { + // Check for "stddevN", where N is the number of most recent samples to use. + if (nrSamples > 0) { + // [taskname#valuename.stddevN] Std. deviation over N most recent samples + value = getSampleStdDev(nrSamples); + } + } + } else if (matchedCommand(command, F("size"), nrSamples)) { + // [taskname#valuename.size] Number of samples in memory + value = getNrSamples(); + success = true; + } else if (matchedCommand(command, F("sample"), nrSamples)) { + success = nrSamples != 0; + + if (nrSamples == INT_MIN) { + // [taskname#valuename.sample] Number of samples in memory. + value = getNrSamples(); + success = true; + } else { + if (nrSamples != 0) { + // [taskname#valuename.sampleN] + // With sample N: + // N > 0: Return N'th most recent sample + // N < 0: Return abs(N)'th sample in memory, starting at the oldest one. + // abs(N) > [number of samples]: return error value + value = getSample(nrSamples); + } + } + } + break; + default: + return false; + } + + + if (success) { + string = toString(value, _nrDecimals); + } + return success; +} + +bool PluginStats::webformLoad_show_stats(struct EventStruct *event) const +{ + bool somethingAdded = false; + + if (webformLoad_show_avg(event)) { somethingAdded = true; } + + if (webformLoad_show_stdev(event)) { somethingAdded = true; } + + if (webformLoad_show_peaks(event)) { somethingAdded = true; } + + if (somethingAdded) { + addFormSeparator(4); + } + + return somethingAdded; +} + +bool PluginStats::webformLoad_show_avg(struct EventStruct *event) const +{ + if (getNrSamples() > 0) { + addRowLabel(concat(getLabel(), F(" Average / sample"))); + addHtmlFloat(getSampleAvg(), (_nrDecimals == 0) ? 1 : _nrDecimals); + addHtml(strformat(F(" (%u samples)"), getNrSamples())); + + if (_plugin_stats_timestamps != nullptr) { + uint64_t totalDuration_usec = 0u; + const float avg_per_sec = getSampleAvg_time(totalDuration_usec); + + if (totalDuration_usec > 0) { + addRowLabel(concat(getLabel(), F(" Average / sec"))); + addHtmlFloat(avg_per_sec, (_nrDecimals == 0) ? 1 : _nrDecimals); + addHtml(strformat(F(" (%s duration)"), secondsToDayHourMinuteSecond_ms(totalDuration_usec).c_str())); + } + } + return true; + } + return false; +} + +bool PluginStats::webformLoad_show_stdev(struct EventStruct *event) const +{ + const float stdDev = getSampleStdDev(); + + if (usableValue(stdDev) && (getNrSamples() > 1)) { + addRowLabel(concat(getLabel(), F(" std. dev"))); + addHtmlFloat(stdDev, (_nrDecimals == 0) ? 1 : _nrDecimals); + addHtml(strformat(F(" (%u samples)"), getNrSamples())); + return true; + } + return false; +} + +bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, bool include_peak_to_peak) const +{ + if (hasPeaks() && (getNrSamples() > 1)) { + return webformLoad_show_peaks( + event, + getLabel(), + toString(getPeakLow(), _nrDecimals), + toString(getPeakHigh(), _nrDecimals), + include_peak_to_peak); + } + return false; +} + +bool PluginStats::webformLoad_show_peaks(struct EventStruct *event, + const String & label, + const String & lowValue, + const String & highValue, + bool include_peak_to_peak) const +{ + if (hasPeaks() && (getNrSamples() > 1)) { + uint32_t peakLow_frac{}; + uint32_t peakHigh_frac{}; + const uint32_t peakLow = node_time.systemMicros_to_Unixtime(getPeakLowTimestamp(), peakLow_frac); + const uint32_t peakHigh = node_time.systemMicros_to_Unixtime(getPeakHighTimestamp(), peakHigh_frac); + const uint32_t current = node_time.getUnixTime(); + const bool useTimeOnly = (current - peakLow) < 86400 && (current - peakHigh) < 86400; + struct tm ts; + breakTime(time_zone.toLocal(peakLow), ts); + + + addRowLabel(concat(label, F(" Peak Low"))); + addHtml(strformat( + F("%s @ %s.%03u"), + lowValue.c_str(), + useTimeOnly + ? formatTimeString(ts).c_str() + : formatDateTimeString(ts).c_str(), + unix_time_frac_to_millis(peakLow_frac))); + + + breakTime(time_zone.toLocal(peakHigh), ts); + + addRowLabel(concat(label, F(" Peak High"))); + addHtml(strformat( + F("%s @ %s.%03u"), + highValue.c_str(), + useTimeOnly + ? formatTimeString(ts).c_str() + : formatDateTimeString(ts).c_str(), + unix_time_frac_to_millis(peakHigh_frac))); + + if (include_peak_to_peak) { + addRowLabel(concat(getLabel(), F(" Peak-to-peak"))); + addHtmlFloat(getPeakHigh() - getPeakLow(), _nrDecimals); + } + return true; + } + return false; +} + +void PluginStats::webformLoad_show_val( + struct EventStruct *event, + const String & label, + ESPEASY_RULES_FLOAT_TYPE value, + const String & unit) const +{ + addRowLabel(concat(getLabel(), label)); + addHtmlFloat(value, _nrDecimals); + + if (!unit.isEmpty()) { + addUnit(unit); + } +} + +# if FEATURE_CHART_JS +void PluginStats::plot_ChartJS_dataset() const +{ + add_ChartJS_dataset_header(_ChartJS_dataset_config); + + PluginStatsBuffer_t::index_t i = 0; + const size_t nrSamples = getNrSamples(); + + for (; i < nrSamples; ++i) { + if (i != 0) { + addHtml(','); + } + + if (!isnan((*_samples)[i])) { + addHtmlFloat((*_samples)[i], _nrDecimals); + } + else { + addHtml(F("null")); + } + } + add_ChartJS_dataset_footer(); +} + +# endif // if FEATURE_CHART_JS + +bool PluginStats::usableValue(float value) const +{ + if (!isnan(value)) { + if (_errorValueIsNaN || !essentiallyEqual(_errorValue, value)) { + return true; + } + } + return false; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats.h b/src/src/DataStructs/PluginStats.h index 10ab26c50..2a320eb80 100644 --- a/src/src/DataStructs/PluginStats.h +++ b/src/src/DataStructs/PluginStats.h @@ -1,229 +1,198 @@ -#ifndef HELPERS_PLUGINSTATS_H -#define HELPERS_PLUGINSTATS_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_PLUGIN_STATS - -# include "../DataStructs/ChartJS_dataset_config.h" -# include "../DataTypes/TaskIndex.h" - - -# if FEATURE_CHART_JS -# include "../WebServer/Chart_JS_title.h" -# endif // if FEATURE_CHART_JS - -# include - -# ifndef PLUGIN_STATS_NR_ELEMENTS -# ifdef ESP8266 -# ifdef USE_SECOND_HEAP -# define PLUGIN_STATS_NR_ELEMENTS 50 -#else -# define PLUGIN_STATS_NR_ELEMENTS 16 -#endif -# endif // ifdef ESP8266 -# ifdef ESP32 -# define PLUGIN_STATS_NR_ELEMENTS 250 -# endif // ifdef ESP32 -# endif // ifndef PLUGIN_STATS_NR_ELEMENTS - -class PluginStats { -public: - - typedef CircularBuffer PluginStatsBuffer_t; - - PluginStats() = delete; - PluginStats(uint8_t nrDecimals, - float errorValue); - - - // Add a sample to the _sample buffer - // This does not also track peaks as the peaks could be raw sensor data and the samples processed data. - bool push(float value); - - // Keep track of peaks. - // Use this for sensors that need to take several samples before actually output a task value. - // For example the ADC with oversampling - void trackPeak(float value); - - // Get lowest recorded value since reset - float getPeakLow() const { - return hasPeaks() ? _minValue : _errorValue; - } - - // Get highest recorded value since reset - float getPeakHigh() const { - return hasPeaks() ? _maxValue : _errorValue; - } - - bool hasPeaks() const { - return _maxValue >= _minValue; - } - - // Set the peaks to unset values - void resetPeaks(); - - void clearSamples() { - _samples.clear(); - } - - size_t getNrSamples() const { - return _samples.size(); - } - - // Compute average over all stored values - float getSampleAvg() const { - return getSampleAvg(_samples.size()); - } - - // Compute average over last N stored values - float getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const; - - // Compute the standard deviation over all stored values - float getSampleStdDev() const { - return getSampleStdDev(_samples.size()); - } - - // Compute the standard deviation over last N stored values - float getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const; - - // Compute min/max over last N stored values - float getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, - bool getMax) const; - - // Compute sample stored values - float getSample(int lastNrSamples) const; - - float operator[](PluginStatsBuffer_t::index_t index) const; - -private: - - static bool matchedCommand(const String & command, - const __FlashStringHelper *cmd_match, - int & nrSamples); - -public: - - // Support task value notation to 'get' statistics - // Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples. - bool plugin_get_config_value_base(struct EventStruct *event, - String & string) const; - - bool webformLoad_show_stats(struct EventStruct *event) const; - - bool webformLoad_show_avg(struct EventStruct *event) const; - bool webformLoad_show_stdev(struct EventStruct *event) const; - bool webformLoad_show_peaks(struct EventStruct *event, - bool include_peak_to_peak = true) const; - void webformLoad_show_val( - struct EventStruct *event, - const String & label, - ESPEASY_RULES_FLOAT_TYPE value, - const String & unit) const; - - - const String& getLabel() const { -# if FEATURE_CHART_JS - return _ChartJS_dataset_config.label; -# else // if FEATURE_CHART_JS - return _label; -# endif // if FEATURE_CHART_JS - } - - void setLabel(const String& label) { -# if FEATURE_CHART_JS - _ChartJS_dataset_config.label = label; -# else // if FEATURE_CHART_JS - _label = label; -# endif // if FEATURE_CHART_JS - } - -# if FEATURE_CHART_JS - void plot_ChartJS_dataset() const; -# endif // if FEATURE_CHART_JS - -# if FEATURE_CHART_JS - -public: - - ChartJS_dataset_config _ChartJS_dataset_config; -# else // if FEATURE_CHART_JS - -private: - - String _label; - -public: - -# endif // if FEATURE_CHART_JS - -private: - - bool usableValue(float value) const; - - float _minValue; - float _maxValue; - - PluginStatsBuffer_t _samples; - float _errorValue; - bool _errorValueIsNaN; - - uint8_t _nrDecimals = 3u; -}; - -class PluginStats_array { -public: - - PluginStats_array() = default; - ~PluginStats_array(); - - void initPluginStats(taskVarIndex_t taskVarIndex); - void clearPluginStats(taskVarIndex_t taskVarIndex); - - bool hasStats() const; - bool hasPeaks() const; - - size_t nrSamplesPresent() const; - size_t nrPluginStats() const; - - void pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks); - - bool plugin_get_config_value_base(struct EventStruct *event, - String & string) const; - - bool plugin_write_base(struct EventStruct *event, - const String & string); - - bool webformLoad_show_stats(struct EventStruct *event) const; - -# if FEATURE_CHART_JS - void plot_ChartJS() const; - - void plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage = true, - const String & options = EMPTY_STRING) const; - - -# endif // if FEATURE_CHART_JS - - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); - -private: - - PluginStats *_plugin_stats[VARS_PER_TASK] = {}; -}; - -#endif // if FEATURE_PLUGIN_STATS -#endif // ifndef HELPERS_PLUGINSTATS_H +#ifndef HELPERS_PLUGINSTATS_H +#define HELPERS_PLUGINSTATS_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include "../DataStructs/ChartJS_dataset_config.h" +# include "../DataStructs/PluginStats_size.h" +# include "../DataStructs/PluginStats_timestamp.h" +# include "../DataTypes/TaskIndex.h" + + +# if FEATURE_CHART_JS +# include "../WebServer/Chart_JS_title.h" +# endif // if FEATURE_CHART_JS + +class PluginStats { +public: + + typedef CircularBuffer PluginStatsBuffer_t; + + PluginStats() = delete; + PluginStats(uint8_t nrDecimals, + float errorValue); + + ~PluginStats(); + + void processTimeSet(const double& time_offset); + + void setPluginStats_timestamp(PluginStats_timestamp *plugin_stats_timestamps) + { + _plugin_stats_timestamps = plugin_stats_timestamps; + } + + // Add a sample to the _sample buffer + // This does not also track peaks as the peaks could be raw sensor data and the samples processed data. + bool push(float value); + + // When only updating the timestamp of the last entry, we should look at the last + bool matchesLastTwoEntries(float value) const; + + // Keep track of peaks. + // Use this for sensors that need to take several samples before actually output a task value. + // For example the ADC with oversampling + void trackPeak(float value, int64_t timestamp = 0u); + + // Get lowest recorded value since reset + float getPeakLow() const { + return hasPeaks() ? _minValue : _errorValue; + } + + // Get highest recorded value since reset + float getPeakHigh() const { + return hasPeaks() ? _maxValue : _errorValue; + } + + int64_t getPeakLowTimestamp() const { + return hasPeaks() ? _minValueTimestamp : 0; + } + + int64_t getPeakHighTimestamp() const { + return hasPeaks() ? _maxValueTimestamp : 0; + } + + bool hasPeaks() const { + return _maxValue >= _minValue; + } + + // Set the peaks to unset values + void resetPeaks(); + + void clearSamples(); + + size_t getNrSamples() const; + + // Compute average over all stored values + float getSampleAvg() const; + + // Compute average over last N stored values + float getSampleAvg(PluginStatsBuffer_t::index_t lastNrSamples) const; + + // Compute the standard deviation over all stored values + float getSampleStdDev() const { + return getSampleStdDev(getNrSamples()); + } + + // Compute average over all stored values, taking timestamp into account. + // Returns average per second. + float getSampleAvg_time(uint64_t& totalDuration_usec) const { + return getSampleAvg_time(getNrSamples(), totalDuration_usec); + } + + // Compute average over last N stored values, taking timestamp into account. + // Returns average per second. + float getSampleAvg_time(PluginStatsBuffer_t::index_t lastNrSamples, + uint64_t & totalDuration_usec) const; + + // Compute the standard deviation over last N stored values + float getSampleStdDev(PluginStatsBuffer_t::index_t lastNrSamples) const; + + // Compute min/max over last N stored values + float getSampleExtreme(PluginStatsBuffer_t::index_t lastNrSamples, + bool getMax) const; + + // Compute sample stored values + float getSample(int lastNrSamples) const; + + float operator[](PluginStatsBuffer_t::index_t index) const; + +private: + + static bool matchedCommand(const String & command, + const __FlashStringHelper *cmd_match, + int & nrSamples); + +public: + + // Support task value notation to 'get' statistics + // Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples. + bool plugin_get_config_value_base(struct EventStruct *event, + String & string) const; + + bool webformLoad_show_stats(struct EventStruct *event) const; + + bool webformLoad_show_avg(struct EventStruct *event) const; + bool webformLoad_show_stdev(struct EventStruct *event) const; + bool webformLoad_show_peaks(struct EventStruct *event, + bool include_peak_to_peak = true) const; + bool webformLoad_show_peaks(struct EventStruct *event, + const String& label, + const String& lowValue, + const String& highValue, + bool include_peak_to_peak = true) const; + + void webformLoad_show_val( + struct EventStruct *event, + const String & label, + ESPEASY_RULES_FLOAT_TYPE value, + const String & unit) const; + + + const String& getLabel() const { +# if FEATURE_CHART_JS + return _ChartJS_dataset_config.label; +# else // if FEATURE_CHART_JS + return _label; +# endif // if FEATURE_CHART_JS + } + + void setLabel(const String& label) { +# if FEATURE_CHART_JS + _ChartJS_dataset_config.label = label; +# else // if FEATURE_CHART_JS + _label = label; +# endif // if FEATURE_CHART_JS + } + +# if FEATURE_CHART_JS + void plot_ChartJS_dataset() const; +# endif // if FEATURE_CHART_JS + +# if FEATURE_CHART_JS + +public: + + ChartJS_dataset_config _ChartJS_dataset_config; +# else // if FEATURE_CHART_JS + +private: + + String _label; + +public: + +# endif // if FEATURE_CHART_JS + +private: + + bool usableValue(float value) const; + + float _minValue; + float _maxValue; + int64_t _minValueTimestamp; + int64_t _maxValueTimestamp; + + PluginStatsBuffer_t *_samples = nullptr; + float _errorValue; + bool _errorValueIsNaN; + + uint8_t _nrDecimals = 3u; + + PluginStats_timestamp *_plugin_stats_timestamps = nullptr; +}; + + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_H diff --git a/src/src/DataStructs/PluginStats_Config.cpp b/src/src/DataStructs/PluginStats_Config.cpp index 9df1b78d6..2e8fa78bd 100644 --- a/src/src/DataStructs/PluginStats_Config.cpp +++ b/src/src/DataStructs/PluginStats_Config.cpp @@ -6,7 +6,7 @@ PluginStats_Config_t & PluginStats_Config_t::operator=(const PluginStats_Config_t& other) { - stored = other.stored; + setStored(other.getStored()); return *this; } diff --git a/src/src/DataStructs/PluginStats_Config.h b/src/src/DataStructs/PluginStats_Config.h index 115bcf330..16dcff1d0 100644 --- a/src/src/DataStructs/PluginStats_Config.h +++ b/src/src/DataStructs/PluginStats_Config.h @@ -1,76 +1,90 @@ -#ifndef DATASTRUCTS_PLUGINSTATS_CONFIG_H -#define DATASTRUCTS_PLUGINSTATS_CONFIG_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_PLUGIN_STATS - -// Configuration of the plugin stats per task value -struct PluginStats_Config_t { - enum class AxisPosition { - Left, - Right - }; - - PluginStats_Config_t() : stored(0) {} - - PluginStats_Config_t(uint8_t stored_value) : stored(stored_value) {} - - PluginStats_Config_t& operator=(const PluginStats_Config_t& other); - - AxisPosition getAxisPosition() const { - return static_cast(bits.chartAxisPosition); - } - - bool isLeft() const { return AxisPosition::Left == getAxisPosition(); } - - void setAxisPosition(AxisPosition position) { - bits.chartAxisPosition = static_cast(position); - } - - uint8_t getAxisIndex() const { - return bits.chartAxisIndex; - } - - void setAxisIndex(uint8_t index) { - bits.chartAxisIndex = index; - } - - uint8_t getStoredBits() const { - return stored & ~0x02; // Mask unused_01 - } - - bool isEnabled() const { - return bits.enabled; - } - - void setEnabled(bool enable) { - bits.enabled = enable; - } - - bool showHidden() const { - return bits.hidden; - } - - void setHidden(bool enable) { - bits.hidden = enable; - } - -private: - - union { - struct { - uint8_t enabled : 1; // Bit 00 - uint8_t unused_01 : 1; // Bit 01 Used by isDefaultTaskVarName in ExtraTaskSettingsStruct - uint8_t hidden : 1; // Bit 02 Hidden/Displayed state on initial showing of the chart - uint8_t chartAxisIndex : 2; // Bit 03 ... 04 - uint8_t chartAxisPosition : 1; // Bit 05 - uint8_t unused_06 : 1; // Bit 06 - uint8_t unused_07 : 1; // Bit 07 - } bits; - uint8_t stored{}; - }; -}; - -#endif // if FEATURE_PLUGIN_STATS -#endif // ifndef DATASTRUCTS_PLUGINSTATS_CONFIG_H +#ifndef DATASTRUCTS_PLUGINSTATS_CONFIG_H +#define DATASTRUCTS_PLUGINSTATS_CONFIG_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +// Configuration of the plugin stats per task value +struct PluginStats_Config_t { + enum class AxisPosition { + Left, + Right + }; + + PluginStats_Config_t() { + setStored(0); + } + + PluginStats_Config_t(uint8_t stored_value) { + setStored(stored_value); + } + + PluginStats_Config_t& operator=(const PluginStats_Config_t& other); + + AxisPosition getAxisPosition() const { + return static_cast(bits.chartAxisPosition); + } + + bool isLeft() const { + return AxisPosition::Left == getAxisPosition(); + } + + void setAxisPosition(AxisPosition position) { + bits.chartAxisPosition = static_cast(position); + } + + uint8_t getAxisIndex() const { + return bits.chartAxisIndex; + } + + void setAxisIndex(uint8_t index) { + bits.chartAxisIndex = index; + } + + uint8_t getStoredBits() const { + return getStored() & ~0x02; // Mask unused_01 + } + + bool isEnabled() const { + return bits.enabled; + } + + void setEnabled(bool enable) { + bits.enabled = enable; + } + + bool showHidden() const { + return bits.hidden; + } + + void setHidden(bool enable) { + bits.hidden = enable; + } + +private: + + uint8_t getStored() const { + uint8_t res{}; + memcpy(&res, &bits, sizeof(uint8_t)); + return res; + } + + // Needs to be inline in the header file as it is used in the constructor + void setStored(uint8_t value) { + memcpy(&bits, &value, sizeof(uint8_t)); + } + + struct { + uint8_t enabled : 1; // Bit 00 + uint8_t unused_01 : 1; // Bit 01 Used by isDefaultTaskVarName in ExtraTaskSettingsStruct + uint8_t hidden : 1; // Bit 02 Hidden/Displayed state on initial showing of the chart + uint8_t chartAxisIndex : 2; // Bit 03 ... 04 + uint8_t chartAxisPosition : 1; // Bit 05 + uint8_t unused_06 : 1; // Bit 06 + uint8_t unused_07 : 1; // Bit 07 + } bits; +}; + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef DATASTRUCTS_PLUGINSTATS_CONFIG_H diff --git a/src/src/DataStructs/PluginStats_array.cpp b/src/src/DataStructs/PluginStats_array.cpp new file mode 100644 index 000000000..f23524f53 --- /dev/null +++ b/src/src/DataStructs/PluginStats_array.cpp @@ -0,0 +1,511 @@ +#include "../DataStructs/PluginStats_array.h" + +#if FEATURE_PLUGIN_STATS + +# include "../../_Plugin_Helper.h" + +# include "../Globals/TimeZone.h" + +# include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Memory.h" + +# include "../WebServer/Chart_JS.h" + +PluginStats_array::~PluginStats_array() +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + delete _plugin_stats[i]; + _plugin_stats[i] = nullptr; + } + } + + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } +} + +void PluginStats_array::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + delete _plugin_stats[taskVarIndex]; + _plugin_stats[taskVarIndex] = nullptr; + + if (!hasStats()) { + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } + } + + if (ExtraTaskSettings.enabledPluginStats(taskVarIndex)) { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + // Try to allocate in PSRAM 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]); + } + + + if (_plugin_stats[taskVarIndex] != nullptr) { + _plugin_stats[taskVarIndex]->setLabel(ExtraTaskSettings.TaskDeviceValueNames[taskVarIndex]); + # 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); + # endif // if FEATURE_CHART_JS + + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats[taskVarIndex]->setPluginStats_timestamp(_plugin_stats_timestamps); + } + } + } + } + + if (hasStats()) { + if (_plugin_stats_timestamps == nullptr) { + // Try to allocate in PSRAM if possible + constexpr unsigned size = sizeof(PluginStats_timestamp); + void *ptr = special_calloc(1, size); + + if (ptr != nullptr) { + // TODO TD-er: Let the task decide whether we need 1/50 sec resolution or 1/10 + // 1/10 sec resolution allows for ~13.6 years without overflow + _plugin_stats_timestamps = new (ptr) PluginStats_timestamp(true); + } + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + _plugin_stats[taskVarIndex]->setPluginStats_timestamp(_plugin_stats_timestamps); + } + } + } +} + +void PluginStats_array::clearPluginStats(taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + if (_plugin_stats[taskVarIndex] != nullptr) { + delete _plugin_stats[taskVarIndex]; + _plugin_stats[taskVarIndex] = nullptr; + } + } + + if (!hasStats()) { + if (_plugin_stats_timestamps != nullptr) { + free(_plugin_stats_timestamps); + _plugin_stats_timestamps = nullptr; + } + } +} + +void PluginStats_array::processTimeSet(const double& time_offset) +{ + if (_plugin_stats_timestamps != nullptr) { + _plugin_stats_timestamps->processTimeSet(time_offset); + } + + // Also update timestamps of peaks + for (taskVarIndex_t taskVarIndex = 0; taskVarIndex < VARS_PER_TASK; ++taskVarIndex) { + PluginStats *stats = getPluginStats(taskVarIndex); + + if (stats != nullptr) { + stats->processTimeSet(time_offset); + } + } +} + +bool PluginStats_array::hasStats() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { return true; } + } + return false; +} + +bool PluginStats_array::hasPeaks() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if ((_plugin_stats[i] != nullptr) && _plugin_stats[i]->hasPeaks()) { + return true; + } + } + return false; +} + +size_t PluginStats_array::nrSamplesPresent() const +{ + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + return _plugin_stats[i]->getNrSamples(); + } + } + return 0; +} + +size_t PluginStats_array::nrPluginStats() const +{ + size_t res{}; + + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + ++res; + } + } + return res; +} + +uint32_t PluginStats_array::getFullPeriodInSec(uint32_t& time_frac) const +{ + if (_plugin_stats_timestamps == nullptr) { + time_frac = 0u; + return 0u; + } + return _plugin_stats_timestamps->getFullPeriodInSec(time_frac); +} + +void PluginStats_array::pushPluginStatsValues( + struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) +{ + if (validTaskIndex(event->TaskIndex)) { + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + 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); + } + + 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); + } + } + } + } + } +} + +bool PluginStats_array::plugin_get_config_value_base(struct EventStruct *event, + String & string) const +{ + // Full value name is something like "taskvaluename.avg" + const String fullValueName = parseString(string, 1); + const String valueName = parseString(fullValueName, 1, '.'); + + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + + for (taskVarIndex_t i = 0; i < valueCount; i++) + { + if (_plugin_stats[i] != nullptr) { + // Check case insensitive, since the user entered value name can have any case. + if (valueName.equalsIgnoreCase(Cache.getTaskDeviceValueName(event->TaskIndex, i))) + { + return _plugin_stats[i]->plugin_get_config_value_base(event, string); + } + } + } + return false; +} + +bool PluginStats_array::plugin_write_base(struct EventStruct *event, const String& string) +{ + bool success = false; + const String cmd = parseString(string, 1); // command + + const bool resetPeaks = equals(cmd, F("resetpeaks")); // Command: "taskname.resetPeaks" + const bool clearSamples = equals(cmd, F("clearsamples")); // Command: "taskname.clearSamples" + + if (resetPeaks || clearSamples) { + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + if (resetPeaks) { + success = true; + _plugin_stats[i]->resetPeaks(); + } + + if (clearSamples) { + success = true; + _plugin_stats[i]->clearSamples(); + } + } + } + } + return success; +} + +bool PluginStats_array::webformLoad_show_stats(struct EventStruct *event, bool showTaskValues) const +{ + bool somethingAdded = false; + + uint32_t time_frac{}; + const uint32_t duration = getFullPeriodInSec(time_frac); + const uint32_t nrSamples = nrSamplesPresent(); + + if ((duration > 0) && (nrSamples > 1)) { + const uint32_t duration_millis = unix_time_frac_to_millis(time_frac); + addRowLabel(F("Total Duration")); + addHtml(strformat( + F("%s.%03u (%u.%03u sec)"), + secondsToDayHourMinuteSecond(duration).c_str(), + duration_millis, + duration, + duration_millis)); + const float duration_f = static_cast(duration) + (duration_millis / 1000.0f); + addRowLabel(F("Total Nr Samples")); + addHtmlInt(nrSamples); + addRowLabel(F("Avg Rate")); + addHtmlFloat(duration_f / static_cast(nrSamples - 1), 2); + addUnit(F("sec/sample")); + addFormSeparator(4); + somethingAdded = true; + } + + if (showTaskValues) { + for (size_t i = 0; i < VARS_PER_TASK; ++i) { + if (_plugin_stats[i] != nullptr) { + if (_plugin_stats[i]->webformLoad_show_stats(event)) { + somethingAdded = true; + } + } + } + } + return somethingAdded; +} + +# if FEATURE_CHART_JS +void PluginStats_array::plot_ChartJS(bool onlyJSON) const +{ + const size_t nrSamples = nrSamplesPresent(); + + if (nrSamples == 0) { return; } + + // Chart Header + { + 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(','); + } + + if (_plugin_stats_timestamps != nullptr) { + struct tm ts; + uint32_t unix_time_frac{}; + const uint32_t uinxtime_sec = node_time.systemMicros_to_Unixtime((*_plugin_stats_timestamps)[i], unix_time_frac); + const uint32_t local_timestamp = time_zone.toLocal(uinxtime_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); + } + } + 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(','); + } + first = false; + _plugin_stats[i]->plot_ChartJS_dataset(); + } + } + add_ChartJS_chart_footer(onlyJSON); +} + +void PluginStats_array::plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + int width, + int height, + bool showAverage, + const String & options, + bool onlyJSON) const +{ + const PluginStats *stats_X = getPluginStats(values_X_axis_index); + const PluginStats *stats_Y = getPluginStats(values_Y_axis_index); + + if ((stats_X == nullptr) || (stats_Y == nullptr)) { + return; + } + + if ((stats_X->getNrSamples() < 2) || (stats_Y->getNrSamples() < 2)) { + 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; + + add_ChartJS_chart_header( + F("scatter"), + id, + chartTitle, + width, + height, + axisOptions, + enableZoom, + nrSamples, + onlyJSON); + + // Add labels, which will be shown in a tooltip when hovering with the mouse over a point. + addHtml(F("\"labels\":[")); + + for (size_t i = 0; i < nrSamples; ++i) { + if (i != 0) { + addHtml(','); + } + 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)) { + return _plugin_stats[taskVarIndex]; + } + return nullptr; +} + +PluginStats * PluginStats_array::getPluginStats(taskVarIndex_t taskVarIndex) +{ + if ((taskVarIndex < VARS_PER_TASK)) { + return _plugin_stats[taskVarIndex]; + } + return nullptr; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats_array.h b/src/src/DataStructs/PluginStats_array.h new file mode 100644 index 000000000..ec3e1df71 --- /dev/null +++ b/src/src/DataStructs/PluginStats_array.h @@ -0,0 +1,86 @@ +#ifndef HELPERS_PLUGINSTATS_ARRAY_H +#define HELPERS_PLUGINSTATS_ARRAY_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include "../DataStructs/PluginStats.h" +# include "../DataStructs/PluginStats_timestamp.h" + +# include "../DataStructs/ChartJS_dataset_config.h" +# include "../DataTypes/TaskIndex.h" + + +# if FEATURE_CHART_JS +# include "../WebServer/Chart_JS_title.h" +# endif // if FEATURE_CHART_JS + + +class PluginStats_array { +public: + + PluginStats_array() = default; + ~PluginStats_array(); + + void initPluginStats(taskIndex_t taskIndex, + taskVarIndex_t taskVarIndex); + void clearPluginStats(taskVarIndex_t taskVarIndex); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + + bool hasStats() const; + bool hasPeaks() const; + + size_t nrSamplesPresent() const; + size_t nrPluginStats() const; + + // Compute the duration between first and last sample in seconds + // For 0 or 1 samples, the period will be 0 seconds. + uint32_t getFullPeriodInSec(uint32_t& time_frac) const; + + void pushPluginStatsValues(struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); + + bool plugin_get_config_value_base(struct EventStruct *event, + String & string) const; + + bool plugin_write_base(struct EventStruct *event, + const String & string); + + bool webformLoad_show_stats(struct EventStruct *event, + bool showTaskValues = true) const; + +# if FEATURE_CHART_JS + void plot_ChartJS(bool onlyJSON = false) const; + + void plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + 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; + + +# endif // if FEATURE_CHART_JS + + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); + +private: + + PluginStats *_plugin_stats[VARS_PER_TASK] = {}; + PluginStats_timestamp *_plugin_stats_timestamps = nullptr; +}; + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_ARRAY_H diff --git a/src/src/DataStructs/PluginStats_size.h b/src/src/DataStructs/PluginStats_size.h new file mode 100644 index 000000000..b501d7fde --- /dev/null +++ b/src/src/DataStructs/PluginStats_size.h @@ -0,0 +1,25 @@ +#ifndef HELPERS_PLUGINSTATS_SIZE_H +#define HELPERS_PLUGINSTATS_SIZE_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include + +# ifndef PLUGIN_STATS_NR_ELEMENTS +# ifdef ESP8266 +# ifdef USE_SECOND_HEAP +# define PLUGIN_STATS_NR_ELEMENTS 50 +# else // ifdef USE_SECOND_HEAP +# define PLUGIN_STATS_NR_ELEMENTS 16 +# endif // ifdef USE_SECOND_HEAP +# endif // ifdef ESP8266 +# ifdef ESP32 +# define PLUGIN_STATS_NR_ELEMENTS 250 +# endif // ifdef ESP32 +# endif // ifndef PLUGIN_STATS_NR_ELEMENTS + + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_SIZE_H diff --git a/src/src/DataStructs/PluginStats_timestamp.cpp b/src/src/DataStructs/PluginStats_timestamp.cpp new file mode 100644 index 000000000..f71f05873 --- /dev/null +++ b/src/src/DataStructs/PluginStats_timestamp.cpp @@ -0,0 +1,120 @@ +#include "../DataStructs/PluginStats_timestamp.h" + +#if FEATURE_PLUGIN_STATS + +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/ESPEasy_time_calc.h" + +PluginStats_timestamp::PluginStats_timestamp(bool useHighRes) + : _internal_to_micros_ratio(useHighRes +? 20000ull // 1/50 sec resolution +: 100000ull) // 1/10 sec resolution +{} + +PluginStats_timestamp::~PluginStats_timestamp() +{} + +bool PluginStats_timestamp::push(const int64_t& timestamp_sysmicros) +{ + return _timestamps.push(systemMicros_to_internalTimestamp(timestamp_sysmicros)); +} + +bool PluginStats_timestamp::updateLast(const int64_t& timestamp_sysmicros) +{ + const size_t nrElements = _timestamps.size(); + + if (nrElements == 0) { return false; } + return _timestamps.set(nrElements - 1, systemMicros_to_internalTimestamp(timestamp_sysmicros)); +} + +void PluginStats_timestamp::clear() +{ + _timestamps.clear(); +} + +void PluginStats_timestamp::processTimeSet(const double& time_offset) +{ + // Check to see if there was a unix time set before the system time was set + // For example when receiving data from a p2p node + + /* + const uint64_t cur_micros = getMicros64(); + const uint64_t offset_micros = time_offset * 1000000ull; + const size_t nrSamples = _timestamps.size(); + + // GMT Wed Jan 01 2020 00:00:00 GMT+0000 + const int64_t unixTime_20200101 = 1577836800ll * _internal_to_micros_ratio; + + for (PluginStatsTimestamps_t::index_t i = 0; i < nrSamples; ++i) { + if (_timestamps[i] < unixTime_20200101) { + _timestamps.set(i, _timestamps[i] + time_offset); + } + } + */ +} + +int64_t PluginStats_timestamp::getTimestamp(int lastNrSamples) const +{ + if ((_timestamps.size() == 0) || (_timestamps.size() < abs(lastNrSamples))) { return 0u; } + + PluginStatsTimestamps_t::index_t i = 0; + + if (lastNrSamples > 0) { + i = _timestamps.size() - lastNrSamples; + } else if (lastNrSamples < 0) { + i = abs(lastNrSamples) - 1; + } + + if (i < _timestamps.size()) { + return internalTimestamp_to_systemMicros(_timestamps[i]); + } + return 0u; +} + +uint32_t PluginStats_timestamp::getFullPeriodInSec(uint32_t& time_frac) const +{ + const size_t nrSamples = _timestamps.size(); + + time_frac = 0u; + + if (nrSamples <= 1) { + return 0u; + } + + const int64_t start = internalTimestamp_to_systemMicros(_timestamps[0]); + const int64_t end = internalTimestamp_to_systemMicros(_timestamps[nrSamples - 1]); + + const int64_t period_usec = (end < start) ? (start - end) : (end - start); + + return micros_to_sec_time_frac(period_usec, time_frac); +} + +int64_t PluginStats_timestamp::operator[](PluginStatsTimestamps_t::index_t index) const +{ + if (index < _timestamps.size()) { + return internalTimestamp_to_systemMicros(_timestamps[index]); + } + return 0u; +} + +uint32_t PluginStats_timestamp::systemMicros_to_internalTimestamp(const int64_t& timestamp_sysmicros) const +{ + return static_cast(timestamp_sysmicros / _internal_to_micros_ratio); +} + +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; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginStats_timestamp.h b/src/src/DataStructs/PluginStats_timestamp.h new file mode 100644 index 000000000..96e97457b --- /dev/null +++ b/src/src/DataStructs/PluginStats_timestamp.h @@ -0,0 +1,56 @@ +#ifndef HELPERS_PLUGINSTATS_TIMESTAMP_H +#define HELPERS_PLUGINSTATS_TIMESTAMP_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_PLUGIN_STATS + +# include "../DataStructs/PluginStats_size.h" + +// When using 'high res', the timestamps are stored internally +// with 0.02 sec resolution. (1/50 sec) Default is 0.1 sec resolution +// Stored timestamp will be based on the system micros +// This also implies there might be overflow issues when the +// full period exceeds (2^32 / 50) seconds (~ 1000 days) +// or (2^32 / 10) seconds (~13.6 years) +class PluginStats_timestamp { +public: + + typedef CircularBuffer PluginStatsTimestamps_t; + + PluginStats_timestamp() = delete; + + PluginStats_timestamp(bool useHighRes); + ~PluginStats_timestamp(); + + bool push(const int64_t& timestamp_sysmicros); + + bool updateLast(const int64_t& timestamp_sysmicros); + + void clear(); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + + int64_t getTimestamp(int lastNrSamples) const; + + // Compute the duration between first and last sample in seconds + // For 0 or 1 samples, the period will be 0 seconds. + uint32_t getFullPeriodInSec(uint32_t& time_frac) const; + + int64_t operator[](PluginStatsTimestamps_t::index_t index) const; + +private: + + // Conversion from system micros to internal timestamp + uint32_t systemMicros_to_internalTimestamp(const int64_t& timestamp_sysmicros) const; + + // Conversion from internal timestamp to system micros + int64_t internalTimestamp_to_systemMicros(const uint32_t& internalTimestamp) const; + + PluginStatsTimestamps_t _timestamps; + const uint32_t _internal_to_micros_ratio = 20000ul; // Default to 1/50 sec +}; + +#endif // if FEATURE_PLUGIN_STATS +#endif // ifndef HELPERS_PLUGINSTATS_TIMESTAMP_H diff --git a/src/src/DataStructs/PluginTaskData_base.cpp b/src/src/DataStructs/PluginTaskData_base.cpp index 215dabf7b..28428100f 100644 --- a/src/src/DataStructs/PluginTaskData_base.cpp +++ b/src/src/DataStructs/PluginTaskData_base.cpp @@ -1,182 +1,192 @@ -#include "../DataStructs/PluginTaskData_base.h" - -#include "../DataStructs/ESPEasy_EventStruct.h" - -#include "../Globals/RuntimeData.h" - -#include "../Helpers/StringConverter.h" - -#include "../WebServer/Chart_JS.h" -#include "../WebServer/HTML_wrappers.h" - - -PluginTaskData_base::PluginTaskData_base() - : _taskdata_pluginID(INVALID_PLUGIN_ID) -#if FEATURE_PLUGIN_STATS - , _plugin_stats_array(nullptr) -#endif // if FEATURE_PLUGIN_STATS -{} - - -PluginTaskData_base::~PluginTaskData_base() { -#if FEATURE_PLUGIN_STATS - delete _plugin_stats_array; - _plugin_stats_array = nullptr; -#endif // if FEATURE_PLUGIN_STATS -} - -bool PluginTaskData_base::hasPluginStats() const { -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->hasStats(); - } -#endif // if FEATURE_PLUGIN_STATS - return false; -} - -bool PluginTaskData_base::hasPeaks() const { -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->hasPeaks(); - } -#endif // if FEATURE_PLUGIN_STATS - return false; -} - -size_t PluginTaskData_base::nrSamplesPresent() const { -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->nrSamplesPresent(); - } -#endif // if FEATURE_PLUGIN_STATS - return 0; -} - -#if FEATURE_PLUGIN_STATS -void PluginTaskData_base::initPluginStats(taskVarIndex_t taskVarIndex) -{ - if (taskVarIndex < VARS_PER_TASK) { - if (_plugin_stats_array == nullptr) { - _plugin_stats_array = new (std::nothrow) PluginStats_array(); - } - - if (_plugin_stats_array != nullptr) { - _plugin_stats_array->initPluginStats(taskVarIndex); - } - } -} - -void PluginTaskData_base::clearPluginStats(taskVarIndex_t taskVarIndex) -{ - if ((taskVarIndex < VARS_PER_TASK) && _plugin_stats_array) { - _plugin_stats_array->clearPluginStats(taskVarIndex); - - if (!_plugin_stats_array->hasStats()) { - delete _plugin_stats_array; - _plugin_stats_array = nullptr; - } - } -} - -#endif // if FEATURE_PLUGIN_STATS - -void PluginTaskData_base::pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks) -{ -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - _plugin_stats_array->pushPluginStatsValues(event, trackPeaks); - } -#endif // if FEATURE_PLUGIN_STATS -} - -bool PluginTaskData_base::plugin_get_config_value_base(struct EventStruct *event, - String & string) const -{ -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->plugin_get_config_value_base(event, string); - } -#endif // if FEATURE_PLUGIN_STATS - return false; -} - -bool PluginTaskData_base::plugin_write_base(struct EventStruct *event, - const String & string) -{ -#if FEATURE_PLUGIN_STATS - - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->plugin_write_base(event, string); - } -#endif // if FEATURE_PLUGIN_STATS - return false; -} - -#if FEATURE_PLUGIN_STATS -bool PluginTaskData_base::webformLoad_show_stats(struct EventStruct *event) const -{ - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->webformLoad_show_stats(event); - } - return false; -} - -# if FEATURE_CHART_JS -void PluginTaskData_base::plot_ChartJS() const -{ - if (_plugin_stats_array != nullptr) { - _plugin_stats_array->plot_ChartJS(); - } -} - -void PluginTaskData_base::plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage, - const String & options) const -{ - if (_plugin_stats_array != nullptr) { - _plugin_stats_array->plot_ChartJS_scatter( - values_X_axis_index, - values_Y_axis_index, - id, - chartTitle, - datasetConfig, - width, - height, - showAverage, - options); - } -} - -# endif // if FEATURE_CHART_JS - - -PluginStats * PluginTaskData_base::getPluginStats(taskVarIndex_t taskVarIndex) const -{ - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->getPluginStats(taskVarIndex); - } - return nullptr; -} - -PluginStats * PluginTaskData_base::getPluginStats(taskVarIndex_t taskVarIndex) -{ - if (_plugin_stats_array != nullptr) { - return _plugin_stats_array->getPluginStats(taskVarIndex); - } - return nullptr; -} - -#endif // if FEATURE_PLUGIN_STATS +#include "../DataStructs/PluginTaskData_base.h" + +#include "../DataStructs/ESPEasy_EventStruct.h" + +#include "../Globals/RuntimeData.h" + +#include "../Helpers/StringConverter.h" + +#include "../WebServer/Chart_JS.h" +#include "../WebServer/HTML_wrappers.h" + + +PluginTaskData_base::PluginTaskData_base() + : _taskdata_pluginID(INVALID_PLUGIN_ID) +#if FEATURE_PLUGIN_STATS + , _plugin_stats_array(nullptr) +#endif // if FEATURE_PLUGIN_STATS +{} + + +PluginTaskData_base::~PluginTaskData_base() { +#if FEATURE_PLUGIN_STATS + delete _plugin_stats_array; + _plugin_stats_array = nullptr; +#endif // if FEATURE_PLUGIN_STATS +} + +bool PluginTaskData_base::hasPluginStats() const { +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->hasStats(); + } +#endif // if FEATURE_PLUGIN_STATS + return false; +} + +bool PluginTaskData_base::hasPeaks() const { +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->hasPeaks(); + } +#endif // if FEATURE_PLUGIN_STATS + return false; +} + +size_t PluginTaskData_base::nrSamplesPresent() const { +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->nrSamplesPresent(); + } +#endif // if FEATURE_PLUGIN_STATS + return 0; +} + +#if FEATURE_PLUGIN_STATS +void PluginTaskData_base::initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex) +{ + if (taskVarIndex < VARS_PER_TASK) { + if (_plugin_stats_array == nullptr) { + _plugin_stats_array = new (std::nothrow) PluginStats_array(); + } + + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->initPluginStats(taskIndex, taskVarIndex); + } + } +} + +void PluginTaskData_base::clearPluginStats(taskVarIndex_t taskVarIndex) +{ + if ((taskVarIndex < VARS_PER_TASK) && _plugin_stats_array) { + _plugin_stats_array->clearPluginStats(taskVarIndex); + + if (!_plugin_stats_array->hasStats()) { + delete _plugin_stats_array; + _plugin_stats_array = nullptr; + } + } +} + +void PluginTaskData_base::processTimeSet(const double& time_offset) +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->processTimeSet(time_offset); + } +} + +#endif // if FEATURE_PLUGIN_STATS + +void PluginTaskData_base::pushPluginStatsValues(struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame) +{ +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->pushPluginStatsValues(event, trackPeaks, onlyUpdateTimestampWhenSame); + } +#endif // if FEATURE_PLUGIN_STATS +} + +bool PluginTaskData_base::plugin_get_config_value_base(struct EventStruct *event, + String & string) const +{ +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->plugin_get_config_value_base(event, string); + } +#endif // if FEATURE_PLUGIN_STATS + return false; +} + +bool PluginTaskData_base::plugin_write_base(struct EventStruct *event, + const String & string) +{ +#if FEATURE_PLUGIN_STATS + + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->plugin_write_base(event, string); + } +#endif // if FEATURE_PLUGIN_STATS + return false; +} + +#if FEATURE_PLUGIN_STATS +bool PluginTaskData_base::webformLoad_show_stats(struct EventStruct *event) const +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->webformLoad_show_stats(event); + } + return false; +} + +# if FEATURE_CHART_JS +void PluginTaskData_base::plot_ChartJS(bool onlyJSON) const +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->plot_ChartJS(onlyJSON); + } +} + +void PluginTaskData_base::plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + const ChartJS_dataset_config& datasetConfig, + int width, + int height, + bool showAverage, + const String & options, + bool onlyJSON) const +{ + if (_plugin_stats_array != nullptr) { + _plugin_stats_array->plot_ChartJS_scatter( + values_X_axis_index, + values_Y_axis_index, + id, + chartTitle, + datasetConfig, + width, + height, + showAverage, + options, + onlyJSON); + } +} + +# endif // if FEATURE_CHART_JS + + +PluginStats * PluginTaskData_base::getPluginStats(taskVarIndex_t taskVarIndex) const +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->getPluginStats(taskVarIndex); + } + return nullptr; +} + +PluginStats * PluginTaskData_base::getPluginStats(taskVarIndex_t taskVarIndex) +{ + if (_plugin_stats_array != nullptr) { + return _plugin_stats_array->getPluginStats(taskVarIndex); + } + return nullptr; +} + +#endif // if FEATURE_PLUGIN_STATS diff --git a/src/src/DataStructs/PluginTaskData_base.h b/src/src/DataStructs/PluginTaskData_base.h index 0714331dc..f8fa2f287 100644 --- a/src/src/DataStructs/PluginTaskData_base.h +++ b/src/src/DataStructs/PluginTaskData_base.h @@ -1,93 +1,98 @@ -#ifndef DATASTRUCTS_PLUGINTASKDATA_BASE_H -#define DATASTRUCTS_PLUGINTASKDATA_BASE_H - - -#include "../../ESPEasy_common.h" - -#include "../DataStructs/PluginStats.h" - -#include "../DataTypes/PluginID.h" -#include "../DataTypes/TaskIndex.h" - -// ============================================== -// Data used by instances of 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 PluginTaskData_base { - PluginTaskData_base(); - - virtual ~PluginTaskData_base(); - - bool baseClassOnly() const { - return _baseClassOnly; - } - - bool hasPluginStats() const; - - bool hasPeaks() const; - - size_t nrSamplesPresent() const; - - #if FEATURE_PLUGIN_STATS - void initPluginStats(taskVarIndex_t taskVarIndex); - void clearPluginStats(taskVarIndex_t taskVarIndex); - #endif // if FEATURE_PLUGIN_STATS - - // Called right after successful PLUGIN_READ to store task values - void pushPluginStatsValues(struct EventStruct *event, - bool trackPeaks); - - // Support task value notation to 'get' statistics - // Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples. - bool plugin_get_config_value_base(struct EventStruct *event, - String & string) const; - - bool plugin_write_base(struct EventStruct *event, - const String & string); - -#if FEATURE_PLUGIN_STATS - bool webformLoad_show_stats(struct EventStruct *event) const; - -# if FEATURE_CHART_JS - void plot_ChartJS() const; - - void plot_ChartJS_scatter( - taskVarIndex_t values_X_axis_index, - taskVarIndex_t values_Y_axis_index, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - const ChartJS_dataset_config& datasetConfig, - int width, - int height, - bool showAverage = true, - const String & options = EMPTY_STRING) const; - -# endif // if FEATURE_CHART_JS -#endif // if FEATURE_PLUGIN_STATS - - // 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. - pluginID_t _taskdata_pluginID = INVALID_PLUGIN_ID; -#if FEATURE_PLUGIN_STATS - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; - - PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); - -private: - - // Array of pointers to PluginStats. One per task value. - PluginStats_array *_plugin_stats_array = nullptr; -#endif // if FEATURE_PLUGIN_STATS - -protected: - - bool _baseClassOnly = false; -}; - -#endif // ifndef DATASTRUCTS_PLUGINTASKDATA_BASE_H +#ifndef DATASTRUCTS_PLUGINTASKDATA_BASE_H +#define DATASTRUCTS_PLUGINTASKDATA_BASE_H + + +#include "../../ESPEasy_common.h" + +#include "../DataStructs/PluginStats_array.h" + +#include "../DataTypes/PluginID.h" +#include "../DataTypes/TaskIndex.h" + +// ============================================== +// Data used by instances of 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 PluginTaskData_base { + PluginTaskData_base(); + + virtual ~PluginTaskData_base(); + + bool baseClassOnly() const { + return _baseClassOnly; + } + + bool hasPluginStats() const; + + bool hasPeaks() const; + + size_t nrSamplesPresent() const; + + #if FEATURE_PLUGIN_STATS + void initPluginStats(taskIndex_t taskIndex, taskVarIndex_t taskVarIndex); + void clearPluginStats(taskVarIndex_t taskVarIndex); + + // Update any logged timestamp with this newly set system time. + void processTimeSet(const double& time_offset); + #endif // if FEATURE_PLUGIN_STATS + + // Called right after successful PLUGIN_READ to store task values + void pushPluginStatsValues(struct EventStruct *event, + bool trackPeaks, + bool onlyUpdateTimestampWhenSame); + + // Support task value notation to 'get' statistics + // Notations like [taskname#taskvalue.avg] can then be used to compute the average over a number of samples. + bool plugin_get_config_value_base(struct EventStruct *event, + String & string) const; + + bool plugin_write_base(struct EventStruct *event, + const String & string); + +#if FEATURE_PLUGIN_STATS + bool webformLoad_show_stats(struct EventStruct *event) const; + +# if FEATURE_CHART_JS + void plot_ChartJS(bool onlyJSON = false) const; + + void plot_ChartJS_scatter( + taskVarIndex_t values_X_axis_index, + taskVarIndex_t values_Y_axis_index, + 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; + +# endif // if FEATURE_CHART_JS +#endif // if FEATURE_PLUGIN_STATS + + // 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. + pluginID_t _taskdata_pluginID = INVALID_PLUGIN_ID; +#if FEATURE_PLUGIN_STATS + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex) const; + + PluginStats* getPluginStats(taskVarIndex_t taskVarIndex); + +protected: + + // Array of pointers to PluginStats. One per task value. + PluginStats_array *_plugin_stats_array = nullptr; +#endif // if FEATURE_PLUGIN_STATS + +protected: + + bool _baseClassOnly = false; +}; + +#endif // ifndef DATASTRUCTS_PLUGINTASKDATA_BASE_H diff --git a/src/src/DataStructs/ProtocolStruct.h b/src/src/DataStructs/ProtocolStruct.h index 53df95a0e..f6f8ed0d9 100644 --- a/src/src/DataStructs/ProtocolStruct.h +++ b/src/src/DataStructs/ProtocolStruct.h @@ -21,27 +21,23 @@ struct ProtocolStruct } uint16_t defaultPort{}; - union { - struct { - uint16_t usesMQTT : 1; - uint16_t usesAccount : 1; - uint16_t usesPassword : 1; - uint16_t usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics - uint16_t usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data - uint16_t Custom : 1; // When set, the controller has to define all parameters on the controller setup page - uint16_t usesHost : 1; - uint16_t usesPort : 1; - uint16_t usesQueue : 1; - uint16_t usesCheckReply : 1; - uint16_t usesTimeout : 1; - uint16_t usesSampleSets : 1; - uint16_t usesExtCreds : 1; - uint16_t needsNetwork : 1; - uint16_t allowsExpire : 1; - uint16_t allowLocalSystemTime : 1; - }; - uint16_t bits{}; - + struct { + uint16_t usesMQTT : 1; + uint16_t usesAccount : 1; + uint16_t usesPassword : 1; + uint16_t usesTemplate : 1; // When set, the protocol will pre-load some templates like default MQTT topics + uint16_t usesID : 1; // Whether a controller supports sending an IDX value sent along with plugin data + uint16_t Custom : 1; // When set, the controller has to define all parameters on the controller setup page + uint16_t usesHost : 1; + uint16_t usesPort : 1; + uint16_t usesQueue : 1; + uint16_t usesCheckReply : 1; + uint16_t usesTimeout : 1; + uint16_t usesSampleSets : 1; + uint16_t usesExtCreds : 1; + uint16_t needsNetwork : 1; + uint16_t allowsExpire : 1; + uint16_t allowLocalSystemTime : 1; }; #if FEATURE_MQTT_TLS bool usesTLS : 1; // May offer TLS related settings and options diff --git a/src/src/DataStructs/ProvisioningStruct.h b/src/src/DataStructs/ProvisioningStruct.h index ba8007b30..25a959721 100644 --- a/src/src/DataStructs/ProvisioningStruct.h +++ b/src/src/DataStructs/ProvisioningStruct.h @@ -39,21 +39,16 @@ struct ProvisioningStruct char pass[64] = { 0 }; char url[128] = { 0 }; - union { - uint16_t allowed{}; - struct { - uint16_t allowFetchFirmware :1; - uint16_t allowFetchConfigDat :1; - uint16_t allowFetchSecurityDat :1; - uint16_t allowFetchNotificationDat :1; - uint16_t allowFetchProvisioningDat :1; - uint16_t allowFetchRules :4; - - uint16_t unused :7; // Add to use full 16 bit. - } allowedFlags; - }; - + struct { + uint16_t allowFetchFirmware :1; + uint16_t allowFetchConfigDat :1; + uint16_t allowFetchSecurityDat :1; + uint16_t allowFetchNotificationDat :1; + uint16_t allowFetchProvisioningDat :1; + uint16_t allowFetchRules :4; + uint16_t unused :7; // Add to use full 16 bit. + } allowedFlags; }; typedef std::shared_ptr ProvisioningStruct_ptr_type; diff --git a/src/src/DataStructs/SchedulerTimerID.cpp b/src/src/DataStructs/SchedulerTimerID.cpp index 142406103..9cacd4c2b 100644 --- a/src/src/DataStructs/SchedulerTimerID.cpp +++ b/src/src/DataStructs/SchedulerTimerID.cpp @@ -1,6 +1,23 @@ #include "../DataStructs/SchedulerTimerID.h" +#include "../Helpers/Misc.h" + SchedulerTimerID::SchedulerTimerID(SchedulerTimerType_e timerType) { - timer_type = static_cast(timerType); + set4BitToUL(mixed_id, 0, static_cast(timerType)); +} + +void SchedulerTimerID::setTimerType(SchedulerTimerType_e timerType) +{ + set4BitToUL(mixed_id, 0, static_cast(timerType)); +} + +SchedulerTimerType_e SchedulerTimerID::getTimerType() const +{ + return static_cast(get4BitFromUL(mixed_id, 0)); +} + +uint32_t SchedulerTimerID::getId() const +{ + return mixed_id >> 4; } diff --git a/src/src/DataStructs/SchedulerTimerID.h b/src/src/DataStructs/SchedulerTimerID.h index 42d61fceb..c1cd05dae 100644 --- a/src/src/DataStructs/SchedulerTimerID.h +++ b/src/src/DataStructs/SchedulerTimerID.h @@ -11,24 +11,22 @@ struct SchedulerTimerID { explicit SchedulerTimerID(uint32_t mixedID) : mixed_id(mixedID) {} - union { - struct { - uint32_t id : 28; - uint32_t timer_type : 4; // Change this when SchedulerTimerType_e needs more bits - }; + virtual ~SchedulerTimerID() {} - uint32_t mixed_id{}; - }; + void setTimerType(SchedulerTimerType_e timerType); + SchedulerTimerType_e getTimerType() const; - void setTimerType(SchedulerTimerType_e timerType) + + uint32_t getId() const; + + // Have setId in the header file as it is used in the constructor of derived classes + // Thus it should be inline as we otherwise cannot call member functions of base class in the constructor in a derived class + void setId(uint32_t id) { - timer_type = static_cast(timerType); + mixed_id = (id << 4) | (mixed_id & 0x0f); } - SchedulerTimerType_e getTimerType() const - { - return static_cast(timer_type); - } + uint32_t mixed_id{}; }; diff --git a/src/src/DataStructs/Scheduler_ConstIntervalTimerID.h b/src/src/DataStructs/Scheduler_ConstIntervalTimerID.h index a5eb92b94..17d175c6e 100644 --- a/src/src/DataStructs/Scheduler_ConstIntervalTimerID.h +++ b/src/src/DataStructs/Scheduler_ConstIntervalTimerID.h @@ -9,12 +9,12 @@ struct ConstIntervalTimerID : SchedulerTimerID { ConstIntervalTimerID(SchedulerIntervalTimer_e timer) : SchedulerTimerID(SchedulerTimerType_e::ConstIntervalTimer) { - id = static_cast(timer); + setId(static_cast(timer)); } SchedulerIntervalTimer_e getIntervalTimer() const { - return static_cast(id); + return static_cast(getId()); } #ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_GPIOTimerID.cpp b/src/src/DataStructs/Scheduler_GPIOTimerID.cpp index 0900ebb90..10416ecf5 100644 --- a/src/src/DataStructs/Scheduler_GPIOTimerID.cpp +++ b/src/src/DataStructs/Scheduler_GPIOTimerID.cpp @@ -5,7 +5,7 @@ GPIOTimerID::GPIOTimerID(uint8_t GPIOType, uint8_t pinNumber, int Par1) : SchedulerTimerID(SchedulerTimerType_e::GPIO_timer) { - id = (Par1 << 16) + (pinNumber << 8) + GPIOType; + setId((Par1 << 16) + (pinNumber << 8) + GPIOType); } diff --git a/src/src/DataStructs/Scheduler_GPIOTimerID.h b/src/src/DataStructs/Scheduler_GPIOTimerID.h index 588617fa9..41f5fd169 100644 --- a/src/src/DataStructs/Scheduler_GPIOTimerID.h +++ b/src/src/DataStructs/Scheduler_GPIOTimerID.h @@ -8,18 +8,20 @@ * Special timer to handle timed GPIO actions \*********************************************************************************************/ struct GPIOTimerID : SchedulerTimerID { - GPIOTimerID(uint8_t GPIOType, uint8_t pinNumber, int Par1); + GPIOTimerID(uint8_t GPIOType, + uint8_t pinNumber, + int Par1); uint8_t getGPIO_type() const { - return static_cast((id) & 0xFF); + return static_cast((getId()) & 0xFF); } uint8_t getPinNumber() const { - return static_cast((id >> 8) & 0xFF); + return static_cast((getId() >> 8) & 0xFF); } uint8_t getPinStateValue() const { - return static_cast((id >> 16) & 0xFF); + return static_cast((getId() >> 16) & 0xFF); } #ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_IntendedRebootTimerID.cpp b/src/src/DataStructs/Scheduler_IntendedRebootTimerID.cpp index 8013bde6e..2611b3168 100644 --- a/src/src/DataStructs/Scheduler_IntendedRebootTimerID.cpp +++ b/src/src/DataStructs/Scheduler_IntendedRebootTimerID.cpp @@ -4,6 +4,6 @@ IntendedRebootTimerID::IntendedRebootTimerID(IntendedRebootReason_e reason) : SchedulerTimerID(SchedulerTimerType_e::IntendedReboot) { - id = static_cast(reason); + setId(static_cast(reason)); } diff --git a/src/src/DataStructs/Scheduler_IntendedRebootTimerID.h b/src/src/DataStructs/Scheduler_IntendedRebootTimerID.h index afffb312c..8df8ddd88 100644 --- a/src/src/DataStructs/Scheduler_IntendedRebootTimerID.h +++ b/src/src/DataStructs/Scheduler_IntendedRebootTimerID.h @@ -10,7 +10,7 @@ struct IntendedRebootTimerID : SchedulerTimerID { IntendedRebootReason_e getReason() const { - return static_cast(id); + return static_cast(getId()); } #ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_PluginDeviceTimerID.cpp b/src/src/DataStructs/Scheduler_PluginDeviceTimerID.cpp index 019677eb9..979dba5dd 100644 --- a/src/src/DataStructs/Scheduler_PluginDeviceTimerID.cpp +++ b/src/src/DataStructs/Scheduler_PluginDeviceTimerID.cpp @@ -14,7 +14,7 @@ PluginDeviceTimerID::PluginDeviceTimerID(pluginID_t pluginID, int Par1) : // FIXME TD-er: Must add a constexpr function with nr of included plugins. const unsigned nrBits = getNrBitsDeviceIndex(); const unsigned mask = MASK_BITS(nrBits); - id = (deviceIndex.value & mask) | (Par1 << nrBits); + setId((deviceIndex.value & mask) | (Par1 << nrBits)); } } @@ -22,7 +22,7 @@ deviceIndex_t PluginDeviceTimerID::get_deviceIndex() const { const unsigned nrBits = getNrBitsDeviceIndex(); const unsigned mask = MASK_BITS(nrBits); - return deviceIndex_t::toDeviceIndex(id & mask); + return deviceIndex_t::toDeviceIndex(getId() & mask); } #ifndef BUILD_NO_DEBUG @@ -33,7 +33,7 @@ String PluginDeviceTimerID::decode() const if (validDeviceIndex(deviceIndex)) { return getPluginNameFromDeviceIndex(deviceIndex); } - return String(id); + return String(getId()); } #endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_PluginTaskTimerID.cpp b/src/src/DataStructs/Scheduler_PluginTaskTimerID.cpp index c0500ede3..5f0c3885e 100644 --- a/src/src/DataStructs/Scheduler_PluginTaskTimerID.cpp +++ b/src/src/DataStructs/Scheduler_PluginTaskTimerID.cpp @@ -14,9 +14,9 @@ PluginTaskTimerID::PluginTaskTimerID(taskIndex_t taskIndex, constexpr unsigned mask_function = MASK_BITS(nrBitsPluginFunction); if (validTaskIndex(taskIndex)) { - id = (taskIndex & mask_taskIndex) | + setId((taskIndex & mask_taskIndex) | ((function & mask_function) << nrBitsTaskIndex) | - (Par1 << (nrBitsTaskIndex + nrBitsPluginFunction)); + (Par1 << (nrBitsTaskIndex + nrBitsPluginFunction))); } } @@ -25,7 +25,7 @@ taskIndex_t PluginTaskTimerID::getTaskIndex() const constexpr unsigned nrBitsTaskIndex = NR_BITS(TASKS_MAX); constexpr unsigned mask_taskIndex = MASK_BITS(nrBitsTaskIndex); - return static_cast(id & mask_taskIndex); + return static_cast(getId() & mask_taskIndex); } PluginFunctions_e PluginTaskTimerID::getFunction() const @@ -34,7 +34,7 @@ PluginFunctions_e PluginTaskTimerID::getFunction() const constexpr unsigned nrBitsPluginFunction = NrBitsPluginFunctions; constexpr unsigned mask_function = MASK_BITS(nrBitsPluginFunction); - return static_cast((id >> nrBitsTaskIndex) & mask_function); + return static_cast((getId() >> nrBitsTaskIndex) & mask_function); } #ifndef BUILD_NO_DEBUG @@ -45,7 +45,7 @@ String PluginTaskTimerID::decode() const if (validTaskIndex(taskIndex)) { return getTaskDeviceName(taskIndex); } - return String(id); + return String(getId()); } #endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_RulesTimerID.cpp b/src/src/DataStructs/Scheduler_RulesTimerID.cpp index 5ee8dfc72..b5f3c2929 100644 --- a/src/src/DataStructs/Scheduler_RulesTimerID.cpp +++ b/src/src/DataStructs/Scheduler_RulesTimerID.cpp @@ -5,13 +5,13 @@ RulesTimerID::RulesTimerID(unsigned int timerIndex) : SchedulerTimerID(SchedulerTimerType_e::RulesTimer) { - id = timerIndex; + setId(timerIndex); } #ifndef BUILD_NO_DEBUG String RulesTimerID::decode() const { - return concat(F("Rules#Timer="), id); + return concat(F("Rules#Timer="), getId()); } #endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp index e111b545f..a241920c4 100644 --- a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp +++ b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.cpp @@ -7,9 +7,9 @@ SystemEventQueueTimerID::SystemEventQueueTimerID(SchedulerPluginPtrType_e ptr_type, uint8_t Index, uint8_t Function) : SchedulerTimerID(SchedulerTimerType_e::SystemEventQueue) { - id = (static_cast(ptr_type) << 16) + + setId((static_cast(ptr_type) << 16) + (Index << 8) + - Function; + Function); } diff --git a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.h b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.h index 7dff58ada..10ceafed4 100644 --- a/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.h +++ b/src/src/DataStructs/Scheduler_SystemEventQueueTimerID.h @@ -16,15 +16,15 @@ struct SystemEventQueueTimerID : SchedulerTimerID { uint8_t Function); uint8_t getFunction() const { - return static_cast((id) & 0xFF); + return static_cast((getId()) & 0xFF); } uint8_t getIndex() const { - return static_cast((id >> 8) & 0xFF); + return static_cast((getId() >> 8) & 0xFF); } SchedulerPluginPtrType_e getPtrType() const { - return static_cast((id >> 16) & 0xFF); + return static_cast((getId() >> 16) & 0xFF); } #ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_TaskDeviceTimerID.cpp b/src/src/DataStructs/Scheduler_TaskDeviceTimerID.cpp index 3363fef4f..fa12d1e36 100644 --- a/src/src/DataStructs/Scheduler_TaskDeviceTimerID.cpp +++ b/src/src/DataStructs/Scheduler_TaskDeviceTimerID.cpp @@ -6,7 +6,7 @@ TaskDeviceTimerID::TaskDeviceTimerID(taskIndex_t taskIndex) : SchedulerTimerID(SchedulerTimerType_e::TaskDeviceTimer) { - id = static_cast(taskIndex); + setId(static_cast(taskIndex)); } #ifndef BUILD_NO_DEBUG @@ -16,7 +16,7 @@ String TaskDeviceTimerID::decode() const return concat(F("Task "), validTaskIndex(taskIndex) ? getTaskDeviceName(taskIndex) - : String(id)); + : String(getId())); } #endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/Scheduler_TaskDeviceTimerID.h b/src/src/DataStructs/Scheduler_TaskDeviceTimerID.h index 6da5407bf..6f539f07d 100644 --- a/src/src/DataStructs/Scheduler_TaskDeviceTimerID.h +++ b/src/src/DataStructs/Scheduler_TaskDeviceTimerID.h @@ -14,7 +14,7 @@ struct TaskDeviceTimerID : SchedulerTimerID { taskIndex_t getTaskIndex() const { - return static_cast(id); + return static_cast(getId()); } #ifndef BUILD_NO_DEBUG diff --git a/src/src/DataStructs/SecurityStruct.cpp b/src/src/DataStructs/SecurityStruct.cpp index 238ef5d3c..506cd5197 100644 --- a/src/src/DataStructs/SecurityStruct.cpp +++ b/src/src/DataStructs/SecurityStruct.cpp @@ -1,101 +1,101 @@ -#include "../DataStructs/SecurityStruct.h" - -#include "../../ESPEasy_common.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/CPlugins.h" - -SecurityStruct::SecurityStruct() { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - ZERO_FILL(WifiAPKey); - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - ZERO_FILL(ControllerUser[i]); - ZERO_FILL(ControllerPassword[i]); - } - ZERO_FILL(Password); -} - -ChecksumType SecurityStruct::computeChecksum() const { - constexpr size_t len_upto_md5 = offsetof(SecurityStruct, md5); - return ChecksumType( - reinterpret_cast(this), - sizeof(SecurityStruct), - len_upto_md5); -} - -bool SecurityStruct::checksumMatch() const { - return computeChecksum().matchChecksum(md5); -} - -bool SecurityStruct::updateChecksum() { - const ChecksumType checksum = computeChecksum(); - if (checksum.matchChecksum(md5)) { - return false; - } - checksum.getChecksum(md5); - return true; -} - -void SecurityStruct::validate() { - ZERO_TERMINATE(WifiSSID); - ZERO_TERMINATE(WifiKey); - ZERO_TERMINATE(WifiSSID2); - ZERO_TERMINATE(WifiKey2); - ZERO_TERMINATE(WifiAPKey); - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - ZERO_TERMINATE(ControllerUser[i]); - ZERO_TERMINATE(ControllerPassword[i]); - } - ZERO_TERMINATE(Password); -} - -void SecurityStruct::forceSave() { - memset(md5, 0, 16); -} - -void SecurityStruct::clearWiFiCredentials() { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - addLog(LOG_LEVEL_INFO, F("WiFi : Clear WiFi credentials from settings")); -} - -void SecurityStruct::clearWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) { - if (slot == SecurityStruct::WiFiCredentialsSlot::first) { - ZERO_FILL(WifiSSID); - ZERO_FILL(WifiKey); - } else if (slot == SecurityStruct::WiFiCredentialsSlot::second) { - ZERO_FILL(WifiSSID2); - ZERO_FILL(WifiKey2); - } -} - -bool SecurityStruct::hasWiFiCredentials() const { - return hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first) || - hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second); -} - -bool SecurityStruct::hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) const { - if (slot == SecurityStruct::WiFiCredentialsSlot::first) - return (WifiSSID[0] != 0 && !String(WifiSSID).equalsIgnoreCase(F("ssid"))); - if (slot == SecurityStruct::WiFiCredentialsSlot::second) - return (WifiSSID2[0] != 0 && !String(WifiSSID2).equalsIgnoreCase(F("ssid"))); - - return false; -} - -String SecurityStruct::getPassword() const { - String res; - const size_t passLength = strnlen(Password, sizeof(Password)); - res.reserve(passLength); - for (size_t i = 0; i < passLength; ++i) { - res += Password[i]; - } - return res; +#include "../DataStructs/SecurityStruct.h" + +#include "../../ESPEasy_common.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/CPlugins.h" + +SecurityStruct::SecurityStruct() { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + ZERO_FILL(WifiAPKey); + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + ZERO_FILL(ControllerUser[i]); + ZERO_FILL(ControllerPassword[i]); + } + ZERO_FILL(Password); +} + +ChecksumType SecurityStruct::computeChecksum() const { + constexpr size_t len_upto_md5 = offsetof(SecurityStruct, md5); + return ChecksumType( + reinterpret_cast(this), + sizeof(SecurityStruct), + len_upto_md5); +} + +bool SecurityStruct::checksumMatch() const { + return computeChecksum().matchChecksum(md5); +} + +bool SecurityStruct::updateChecksum() { + const ChecksumType checksum = computeChecksum(); + if (checksum.matchChecksum(md5)) { + return false; + } + checksum.getChecksum(md5); + return true; +} + +void SecurityStruct::validate() { + ZERO_TERMINATE(WifiSSID); + ZERO_TERMINATE(WifiKey); + ZERO_TERMINATE(WifiSSID2); + ZERO_TERMINATE(WifiKey2); + ZERO_TERMINATE(WifiAPKey); + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + ZERO_TERMINATE(ControllerUser[i]); + ZERO_TERMINATE(ControllerPassword[i]); + } + ZERO_TERMINATE(Password); +} + +void SecurityStruct::forceSave() { + memset(md5, 0, 16); +} + +void SecurityStruct::clearWiFiCredentials() { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + addLog(LOG_LEVEL_INFO, F("WiFi : Clear WiFi credentials from settings")); +} + +void SecurityStruct::clearWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) { + if (slot == SecurityStruct::WiFiCredentialsSlot::first) { + ZERO_FILL(WifiSSID); + ZERO_FILL(WifiKey); + } else if (slot == SecurityStruct::WiFiCredentialsSlot::second) { + ZERO_FILL(WifiSSID2); + ZERO_FILL(WifiKey2); + } +} + +bool SecurityStruct::hasWiFiCredentials() const { + return hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first) || + hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second); +} + +bool SecurityStruct::hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot slot) const { + if (slot == SecurityStruct::WiFiCredentialsSlot::first) + return (WifiSSID[0] != 0 && !String(WifiSSID).equalsIgnoreCase(F("ssid"))); + if (slot == SecurityStruct::WiFiCredentialsSlot::second) + return (WifiSSID2[0] != 0 && !String(WifiSSID2).equalsIgnoreCase(F("ssid"))); + + return false; +} + +String SecurityStruct::getPassword() const { + String res; + const size_t passLength = strnlen(Password, sizeof(Password)); + res.reserve(passLength); + for (size_t i = 0; i < passLength; ++i) { + res += Password[i]; + } + return res; } \ No newline at end of file diff --git a/src/src/DataStructs/SecurityStruct.h b/src/src/DataStructs/SecurityStruct.h index 755aace56..68bdb12c7 100644 --- a/src/src/DataStructs/SecurityStruct.h +++ b/src/src/DataStructs/SecurityStruct.h @@ -1,63 +1,63 @@ -#ifndef DATASTRUCTS_SECURITYSTRUCT_H -#define DATASTRUCTS_SECURITYSTRUCT_H - -#include "../../ESPEasy_common.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ChecksumType.h" - -/*********************************************************************************************\ - * SecurityStruct -\*********************************************************************************************/ -struct SecurityStruct -{ - enum class WiFiCredentialsSlot { - first, - second - }; - - - SecurityStruct(); - - ChecksumType computeChecksum() const; - - // Return true when stored checksum matches. - bool checksumMatch() const; - - // Check and update checksum when content was changed. - // Return true when stored checksum is updated. - bool updateChecksum(); - - void validate(); - - // Clear the checksum to make sure file will be saved - void forceSave(); - - void clearWiFiCredentials(); - - void clearWiFiCredentials(WiFiCredentialsSlot slot); - - bool hasWiFiCredentials() const; - - bool hasWiFiCredentials(WiFiCredentialsSlot slot) const; - - String getPassword() const; - - char WifiSSID[32]; - char WifiKey[64]; - char WifiSSID2[32]; - char WifiKey2[64]; - char WifiAPKey[64]; - char ControllerUser[CONTROLLER_MAX][26]; - char ControllerPassword[CONTROLLER_MAX][64]; - char Password[26]; - uint8_t AllowedIPrangeLow[4] = {0}; // TD-er: Use these - uint8_t AllowedIPrangeHigh[4] = {0}; - uint8_t IPblockLevel = 0; - - //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}; -}; - - -#endif // DATASTRUCTS_SECURITYSTRUCT_H +#ifndef DATASTRUCTS_SECURITYSTRUCT_H +#define DATASTRUCTS_SECURITYSTRUCT_H + +#include "../../ESPEasy_common.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ChecksumType.h" + +/*********************************************************************************************\ + * SecurityStruct +\*********************************************************************************************/ +struct SecurityStruct +{ + enum class WiFiCredentialsSlot { + first, + second + }; + + + SecurityStruct(); + + ChecksumType computeChecksum() const; + + // Return true when stored checksum matches. + bool checksumMatch() const; + + // Check and update checksum when content was changed. + // Return true when stored checksum is updated. + bool updateChecksum(); + + void validate(); + + // Clear the checksum to make sure file will be saved + void forceSave(); + + void clearWiFiCredentials(); + + void clearWiFiCredentials(WiFiCredentialsSlot slot); + + bool hasWiFiCredentials() const; + + bool hasWiFiCredentials(WiFiCredentialsSlot slot) const; + + String getPassword() const; + + char WifiSSID[32]; + char WifiKey[64]; + char WifiSSID2[32]; + char WifiKey2[64]; + char WifiAPKey[64]; + char ControllerUser[CONTROLLER_MAX][26]; + char ControllerPassword[CONTROLLER_MAX][64]; + char Password[26]; + uint8_t AllowedIPrangeLow[4] = {0}; // TD-er: Use these + uint8_t AllowedIPrangeHigh[4] = {0}; + uint8_t IPblockLevel = 0; + + //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}; +}; + + +#endif // DATASTRUCTS_SECURITYSTRUCT_H diff --git a/src/src/DataStructs/SettingsStruct.h b/src/src/DataStructs/SettingsStruct.h index c01ba255d..45fade5f8 100644 --- a/src/src/DataStructs/SettingsStruct.h +++ b/src/src/DataStructs/SettingsStruct.h @@ -1,542 +1,583 @@ - -#ifndef DATASTRUCTS_SETTINGSSTRUCT_H -#define DATASTRUCTS_SETTINGSSTRUCT_H - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ChecksumType.h" -#include "../DataStructs/DeviceStruct.h" -#include "../DataTypes/EthernetParameters.h" -#include "../DataTypes/NetworkMedium.h" -#include "../DataTypes/NPluginID.h" -#include "../DataTypes/PluginID.h" -#include "../DataTypes/TaskEnabledState.h" -#include "../DataTypes/TimeSource.h" -#include "../Globals/Plugins.h" - - -//we disable SPI if not defined -#ifndef DEFAULT_SPI - #define DEFAULT_SPI 0 -#endif - - -// FIXME TD-er: Move this PinBootState to DataTypes folder - -// State is stored, so don't change order -enum class PinBootState { - Default_state = 0, - Output_low = 1, - Output_high = 2, - Input_pullup = 3, - Input_pulldown = 4, // Only on ESP32 and GPIO16 on ESP82xx - Input = 5, - - // Options for later: - // ANALOG (only on ESP32) - // WAKEUP_PULLUP (only on ESP8266) - // WAKEUP_PULLDOWN (only on ESP8266) - // SPECIAL - // FUNCTION_0 (only on ESP8266) - // FUNCTION_1 - // FUNCTION_2 - // FUNCTION_3 - // FUNCTION_4 - // FUNCTION_5 (only on ESP32) - // FUNCTION_6 (only on ESP32) - -}; - - - - -/*********************************************************************************************\ - * SettingsStruct -\*********************************************************************************************/ -template -class SettingsStruct_tmpl -{ - public: - -// 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;} - - bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } - void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } - - bool OldRulesEngine() const { -#ifdef WEBSERVER_NEW_RULES - return !VariousBits_1.OldRulesEngine; -#else - return true; -#endif - } - 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; } - - bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } - 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; } - - bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } - 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; } - - // 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; } - - // 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; } - - // Enable/disable ESPEasyNow protocol - bool UseESPEasyNow() const { -#ifdef USES_ESPEASY_NOW - return VariousBits_1.UseESPEasyNow; -#else - return false; -#endif - } - 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; } - - // 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; } - - // 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 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; } - - // 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; } - - // Allow to actively reset I2C bus if it appears to be hanging. - bool EnableClearHangingI2Cbus() const { -#if FEATURE_CLEAR_I2C_STUCK - return VariousBits_1.EnableClearHangingI2Cbus; -#else - return false; -#endif -} - 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; } - - // Enable caching of rules, to speed up rules processing - bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } - 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; } - - // 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; } - - // 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; } - - #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; } - #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; } - - // 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; } - - // 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; } - - #if FEATURE_RULES_EASY_COLOR_CODE - // Inhibit RulesCodeCompletion - bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } - void DisableRulesCodeCompletion(bool value) { VariousBits_2.DisableRulesCodeCompletion = value; } - #endif // if FEATURE_RULES_EASY_COLOR_CODE - - - // 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); - - bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; } - void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; } - - bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } - void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } - - bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } - void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } - - ExtTimeSource_e ExtTimeSource() const; - void ExtTimeSource(ExtTimeSource_e value); - - bool UseNTP() const; - void UseNTP(bool value); - - bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } - 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; } - #endif // FEATURE_AUTO_DARK_MODE - - bool isTaskEnableReadonly(taskIndex_t taskIndex) const; - void setTaskEnableReadonly(taskIndex_t taskIndex, bool value); - - #if FEATURE_PLUGIN_PRIORITY - bool isPowerManagerTask(taskIndex_t taskIndex) const; - void setPowerManagerTask(taskIndex_t taskIndex, bool value); - - bool isPriorityTask(taskIndex_t taskIndex) const; - #endif // if FEATURE_PLUGIN_PRIORITY - - void validate(); - - bool networkSettingsEmpty() const; - - void clearNetworkSettings(); - - void clearTimeSettings(); - - void clearNotifications(); - - void clearControllers(); - - void clearTasks(); - - void clearLogSettings(); - - void clearUnitNameSettings(); - - void clearMisc(); - - void clearTask(taskIndex_t task); - - // Return hostname + unit when selected to add unit. - String getHostname() const; - - // Return hostname with explicit set append unit. - String getHostname(bool appendUnit) const; - - // Return the name of the unit, without unitnr appended, with template parsing applied, replacement for Settings.Name in most places - String getName() const; - -private: - - // Compute the index in either - // - PinBootStates array (index_low) or - // - PinBootStates_ESP32 (index_high) - // Returns whether it is a valid index - bool getPinBootStateIndex( - int8_t gpio_pin, - int8_t& index_low - #ifdef ESP32 - , int8_t& index_high - #endif - ) const; - -public: - - PinBootState getPinBootState(int8_t gpio_pin) const; - void setPinBootState(int8_t gpio_pin, PinBootState state); - - bool getSPI_pins(int8_t spi_gpios[3]) const; - - // Return true when pin is one of the SPI pins and SPI is enabled - bool isSPI_pin(int8_t pin) const; - - // Return true when SPI enabled and opt. user defined pins valid. - bool isSPI_valid() const; - - // Return true when pin is one of the configured I2C pins. - bool isI2C_pin(int8_t pin) const; - - // Return true if I2C settings are correct - bool isI2CEnabled() const; - - // Return true when pin is one of the fixed Ethernet pins and Ethernet is enabled - bool isEthernetPin(int8_t pin) const; - - // Return true when pin is one of the optional Ethernet pins and Ethernet is enabled - bool isEthernetPinOptional(int8_t pin) const; - - // Access to TaskDevicePin1 ... TaskDevicePin3 - // @param pinnr 1 = TaskDevicePin1, ..., 3 = TaskDevicePin3 - int8_t getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const; - - float getWiFi_TX_power() const; - void setWiFi_TX_power(float dBm); - - pluginID_t getPluginID_for_task(taskIndex_t taskIndex) const; - - void forceSave() { memset(md5, 0, 16); } - - - unsigned long PID = 0; - int Version = 0; - int16_t Build = 0; - uint8_t IP[4] = {0}; - uint8_t Gateway[4] = {0}; - uint8_t Subnet[4] = {0}; - uint8_t DNS[4] = {0}; - uint8_t IP_Octet = 0; - uint8_t Unit = 0; - char Name[26] = {0}; - char NTPHost[64] = {0}; - // FIXME TD-er: Issue #2690 - unsigned long 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; - uint8_t SyslogLevel = 0; - uint8_t SerialLogLevel = 0; - 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. - uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds - boolean CustomCSS = false; - boolean DST = false; - uint8_t WDI2CAddress = 0; - boolean UseRules = false; - boolean UseSerial = false; - boolean UseSSDP = false; - uint8_t ExternalTimeSource = 0; - unsigned long WireClockStretchLimit = 0; - boolean GlobalSync = false; - unsigned long 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 - // FIXME TD-er: Must change to cpluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible - uint8_t Protocol[CONTROLLER_MAX] = {0}; - uint8_t Notification[NOTIFICATION_MAX] = {0}; //notifications, point to a NPLUGIN id - // FIXME TD-er: Must change to pluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible - uint8_t TaskDeviceNumber[N_TASKS] = {0}; // The "plugin number" set at as task (e.g. 4 for P004_dallas) - unsigned int OLD_TaskDeviceID[N_TASKS] = {0}; //UNUSED: this can be reused - union { - struct { - int8_t TaskDevicePin1[N_TASKS]; - int8_t TaskDevicePin2[N_TASKS]; - int8_t TaskDevicePin3[N_TASKS]; - uint8_t TaskDevicePort[N_TASKS]; - }; - int8_t TaskDevicePin[4][N_TASKS]{}; - }; - boolean TaskDevicePin1PullUp[N_TASKS] = {0}; - int16_t TaskDevicePluginConfig[N_TASKS][PLUGIN_CONFIGVAR_MAX]{}; - boolean TaskDevicePin1Inversed[N_TASKS] = {0}; - float TaskDevicePluginConfigFloat[N_TASKS][PLUGIN_CONFIGFLOATVAR_MAX]{}; - union { - int32_t TaskDevicePluginConfigLong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]; - uint32_t TaskDevicePluginConfigULong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]{}; - }; - 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}; - 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) - boolean TaskDeviceSendData[CONTROLLER_MAX][N_TASKS]{}; - boolean Pin_status_led_Inversed = false; - boolean deepSleepOnFail = false; - boolean UseValueLogger = false; - boolean ArduinoOTAEnable = false; - uint16_t DST_Start = 0; - uint16_t DST_End = 0; - boolean UseRTOSMultitasking = false; - int8_t Pin_Reset = -1; - uint8_t SyslogFacility = 0; - uint32_t StructSize = 0; // Forced to be 32 bit, to make sure alignment is clear. - boolean MQTTUseUnitNameAsClientId_unused = false; - - //its safe to extend this struct, up to several bytes, default values in config are 0 - //look in misc.ino how config.dat is used because also other stuff is stored in it at different offsets. - //TODO: document config.dat somewhere here - float Latitude = 0.0f; - float Longitude = 0.0f; - union { - // VariousBits1 defaults to 0, keep in mind when adding bit lookups. - struct { - uint32_t unused_00 : 1; // Bit 00 - uint32_t appendUnitToHostname : 1; // Bit 01 Inverted - uint32_t unused_02 : 1; // Bit 02 uniqueMQTTclientIdReconnect_unused - uint32_t OldRulesEngine : 1; // Bit 03 Inverted - uint32_t ForceWiFi_bg_mode : 1; // Bit 04 - uint32_t WiFiRestart_connection_lost : 1; // Bit 05 - uint32_t EcoPowerMode : 1; // Bit 06 - uint32_t WifiNoneSleep : 1; // Bit 07 - uint32_t gratuitousARP : 1; // Bit 08 Inverted - uint32_t TolerantLastArgParse : 1; // Bit 09 - uint32_t SendToHttp_ack : 1; // Bit 10 - 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 unused_15 : 1; // Bit 15 was used by PeriodicalScanWiFi - uint32_t JSONBoolWithoutQuotes : 1; // Bit 16 - uint32_t DoNotStartAP : 1; // Bit 17 - uint32_t UseAlternativeDeepSleep : 1; // Bit 18 - uint32_t UseLastWiFiFromRTC : 1; // Bit 19 - uint32_t EnableTimingStats : 1; // Bit 20 - uint32_t AllowTaskValueSetAllPlugins : 1; // Bit 21 - uint32_t EnableClearHangingI2Cbus : 1; // Bit 22 - uint32_t EnableRAMTracking : 1; // Bit 23 - uint32_t EnableRulesCaching : 1; // Bit 24 Inverted - uint32_t EnableRulesEventReorder : 1; // Bit 25 Inverted - uint32_t AllowOTAUnlimited : 1; // Bit 26 - uint32_t SendToHTTP_follow_redirects : 1; // Bit 27 - uint32_t CssMode : 2; // Bit 28 -// uint32_t unused_29 : 1; // Bit 29 - uint32_t CheckI2Cdevice : 1; // Bit 30 Inverted - uint32_t DoNotUse_31 : 1; // Bit 31 Was used to detect whether various bits were even set - - } VariousBits_1; - uint32_t VariousBits1 = 0; - }; - - uint32_t ResetFactoryDefaultPreference = 0; // Do not clear this one in the clearAll() - uint32_t I2C_clockSpeed = 400000; - uint16_t WebserverPort = 80; - uint16_t SyslogPort = DEFAULT_SYSLOG_PORT; - - int8_t ETH_Phy_Addr = -1; - int8_t ETH_Pin_mdc = -1; - int8_t ETH_Pin_mdio = -1; - int8_t ETH_Pin_power = -1; - EthPhyType_t ETH_Phy_Type = EthPhyType_t::LAN8710; - EthClockMode_t ETH_Clock_Mode = EthClockMode_t::Ext_crystal_osc; - 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; - int8_t I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; - int8_t I2C_Multiplexer_Addr = -1; - int8_t I2C_Multiplexer_Channel[N_TASKS]{}; - uint8_t I2C_Flags[N_TASKS] = {0}; - uint32_t I2C_clockSpeed_Slow = 100000; - int8_t I2C_Multiplexer_ResetPin = -1; - - #ifdef ESP32 - 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 SPI_SCLK_pin = -1; - int8_t SPI_MISO_pin = -1; - int8_t SPI_MOSI_pin = -1; - int8_t ForceESPEasyNOWchannel = 0; - - // Do not rename or move this checksum. - // Checksum calculation will work "around" this - uint8_t md5[16]{}; // Store checksum of the settings. - union { - // VariousBits2 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 unused_04 : 1; // Bit 04 - uint32_t unused_05 : 1; // Bit 05 - uint32_t unused_06 : 1; // Bit 06 - uint32_t unused_07 : 1; // Bit 07 - 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 - - } VariousBits_2; - uint32_t VariousBits2 = 0; - }; - - - uint8_t console_serial_port = DEFAULT_CONSOLE_PORT; - int8_t console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - int8_t console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - uint8_t console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - - // Try to extend settings to make the checksum 4-uint8_t aligned. -}; - -/* -SettingsStruct* SettingsStruct_ptr = new (std::nothrow) SettingsStruct; -SettingsStruct& Settings = *SettingsStruct_ptr; -*/ - - - -typedef SettingsStruct_tmpl SettingsStruct; - -#endif // DATASTRUCTS_SETTINGSSTRUCT_H + +#ifndef DATASTRUCTS_SETTINGSSTRUCT_H +#define DATASTRUCTS_SETTINGSSTRUCT_H + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ChecksumType.h" +#include "../DataStructs/DeviceStruct.h" +#include "../DataTypes/EthernetParameters.h" +#include "../DataTypes/NetworkMedium.h" +#include "../DataTypes/NPluginID.h" +#include "../DataTypes/PluginID.h" +//#include "../DataTypes/TaskEnabledState.h" +#include "../DataTypes/TimeSource.h" +#include "../Globals/Plugins.h" + +#ifdef ESP32 +#include +#endif + +//we disable SPI if not defined +#ifndef DEFAULT_SPI + #define DEFAULT_SPI 0 +#endif + + +// FIXME TD-er: Move this PinBootState to DataTypes folder + +// State is stored, so don't change order +enum class PinBootState { + Default_state = 0, + Output_low = 1, + Output_high = 2, + Input_pullup = 3, + Input_pulldown = 4, // Only on ESP32 and GPIO16 on ESP82xx + Input = 5, + + // Options for later: + // ANALOG (only on ESP32) + // WAKEUP_PULLUP (only on ESP8266) + // WAKEUP_PULLDOWN (only on ESP8266) + // SPECIAL + // FUNCTION_0 (only on ESP8266) + // FUNCTION_1 + // FUNCTION_2 + // FUNCTION_3 + // FUNCTION_4 + // FUNCTION_5 (only on ESP32) + // FUNCTION_6 (only on ESP32) + +}; + + + + +/*********************************************************************************************\ + * SettingsStruct +\*********************************************************************************************/ +template +class SettingsStruct_tmpl +{ + public: + +// 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;} + + bool uniqueMQTTclientIdReconnect_unused() const { return VariousBits_1.unused_02; } + void uniqueMQTTclientIdReconnect_unused(bool value) { VariousBits_1.unused_02 = value; } + + bool OldRulesEngine() const { +#ifdef WEBSERVER_NEW_RULES + return !VariousBits_1.OldRulesEngine; +#else + return true; +#endif + } + 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; } + + bool WiFiRestart_connection_lost() const { return VariousBits_1.WiFiRestart_connection_lost; } + 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; } + + bool WifiNoneSleep() const { return VariousBits_1.WifiNoneSleep; } + 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; } + + // 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; } + + // 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; } + + // Enable/disable ESPEasyNow protocol + bool UseESPEasyNow() const { +#ifdef USES_ESPEASY_NOW + return VariousBits_1.UseESPEasyNow; +#else + return false; +#endif + } + 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; } + + // 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; } + + // 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 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; } + + // 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; } + + // Allow to actively reset I2C bus if it appears to be hanging. + bool EnableClearHangingI2Cbus() const { +#if FEATURE_CLEAR_I2C_STUCK + return VariousBits_1.EnableClearHangingI2Cbus; +#else + return false; +#endif +} + 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; } + + // Enable caching of rules, to speed up rules processing + bool EnableRulesCaching() const { return !VariousBits_1.EnableRulesCaching; } + 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; } + + // 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; } + + // 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; } + + #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; } + #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; } + +#ifdef ESP32 + // Toggle between passive/active WiFi scan. + bool PassiveWiFiScan() const { return !VariousBits_2.PassiveWiFiScan; } + 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; } + + bool EnableIPv6() const { return !VariousBits_2.EnableIPv6; } + 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; } + + #if FEATURE_RULES_EASY_COLOR_CODE + // Inhibit RulesCodeCompletion + bool DisableRulesCodeCompletion() const { return VariousBits_2.DisableRulesCodeCompletion; } + 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; } + #endif // if FEATURE_TARSTREAM_SUPPORT + + // 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); + + bool DoNotStartAP() const { return VariousBits_1.DoNotStartAP; } + void DoNotStartAP(bool value) { VariousBits_1.DoNotStartAP = value; } + + bool UseAlternativeDeepSleep() const { return VariousBits_1.UseAlternativeDeepSleep; } + void UseAlternativeDeepSleep(bool value) { VariousBits_1.UseAlternativeDeepSleep = value; } + + bool UseLastWiFiFromRTC() const { return VariousBits_1.UseLastWiFiFromRTC; } + void UseLastWiFiFromRTC(bool value) { VariousBits_1.UseLastWiFiFromRTC = value; } + + ExtTimeSource_e ExtTimeSource() const; + void ExtTimeSource(ExtTimeSource_e value); + + bool UseNTP() const; + void UseNTP(bool value); + + bool AllowTaskValueSetAllPlugins() const { return VariousBits_1.AllowTaskValueSetAllPlugins; } + 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; } + #endif // FEATURE_AUTO_DARK_MODE + + bool isTaskEnableReadonly(taskIndex_t taskIndex) const; + void setTaskEnableReadonly(taskIndex_t taskIndex, bool value); + + #if FEATURE_PLUGIN_PRIORITY + bool isPowerManagerTask(taskIndex_t taskIndex) const; + void setPowerManagerTask(taskIndex_t taskIndex, bool value); + + bool isPriorityTask(taskIndex_t taskIndex) const; + #endif // if FEATURE_PLUGIN_PRIORITY + + void validate(); + + bool networkSettingsEmpty() const; + + void clearNetworkSettings(); + + void clearTimeSettings(); + + void clearNotifications(); + + void clearControllers(); + + void clearTasks(); + + void clearLogSettings(); + + void clearUnitNameSettings(); + + void clearMisc(); + + void clearTask(taskIndex_t task); + + // Return hostname + unit when selected to add unit. + String getHostname() const; + + // Return hostname with explicit set append unit. + String getHostname(bool appendUnit) const; + + // Return the name of the unit, without unitnr appended, with template parsing applied, replacement for Settings.Name in most places + String getName() const; + +private: + + // Compute the index in either + // - PinBootStates array (index_low) or + // - PinBootStates_ESP32 (index_high) + // Returns whether it is a valid index + bool getPinBootStateIndex( + int8_t gpio_pin, + int8_t& index_low + #ifdef ESP32 + , int8_t& index_high + #endif + ) const; + +public: + + PinBootState getPinBootState(int8_t gpio_pin) const; + void setPinBootState(int8_t gpio_pin, PinBootState state); + + bool getSPI_pins(int8_t spi_gpios[3]) const; + + #ifdef ESP32 + spi_host_device_t getSPI_host() const; + #endif + + // Return true when pin is one of the SPI pins and SPI is enabled + bool isSPI_pin(int8_t pin) const; + + // Return true when SPI enabled and opt. user defined pins valid. + bool isSPI_valid() const; + + // Return true when pin is one of the configured I2C pins. + bool isI2C_pin(int8_t pin) const; + + // Return true if I2C settings are correct + bool isI2CEnabled() const; + + // Return true when pin is one of the fixed Ethernet pins and Ethernet is enabled + bool isEthernetPin(int8_t pin) const; + + // Return true when pin is one of the optional Ethernet pins and Ethernet is enabled + bool isEthernetPinOptional(int8_t pin) const; + + // Access to TaskDevicePin1 ... TaskDevicePin3 + // @param pinnr 1 = TaskDevicePin1, ..., 3 = TaskDevicePin3 + int8_t getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const; + + float getWiFi_TX_power() const; + void setWiFi_TX_power(float dBm); + + pluginID_t getPluginID_for_task(taskIndex_t taskIndex) const; + + void forceSave() { memset(md5, 0, 16); } + + uint32_t getVariousBits1() const { + uint32_t res; + memcpy(&res, &VariousBits_1, sizeof(VariousBits_1)); + return res; + } + + void setVariousBits1(uint32_t value) { + memcpy(&VariousBits_1, &value, sizeof(VariousBits_1)); + } + + uint32_t getVariousBits2() const { + uint32_t res; + memcpy(&res, &VariousBits_2, sizeof(VariousBits_2)); + return res; + } + + void setVariousBits2(uint32_t value) { + memcpy(&VariousBits_2, &value, sizeof(VariousBits_2)); + } + + + unsigned long PID = 0; + int Version = 0; + int16_t Build = 0; + uint8_t IP[4] = {0}; + uint8_t Gateway[4] = {0}; + uint8_t Subnet[4] = {0}; + uint8_t DNS[4] = {0}; + uint8_t IP_Octet = 0; + uint8_t Unit = 0; + char Name[26] = {0}; + char NTPHost[64] = {0}; + // FIXME TD-er: Issue #2690 + unsigned long 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; + uint8_t SyslogLevel = 0; + uint8_t SerialLogLevel = 0; + 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. + uint8_t deepSleep_wakeTime = 0; // 0 = Sleep Disabled, else time awake from sleep in seconds + boolean CustomCSS = false; + boolean DST = false; + uint8_t WDI2CAddress = 0; + boolean UseRules = false; + boolean UseSerial = false; + boolean UseSSDP = false; + uint8_t ExternalTimeSource = 0; + unsigned long WireClockStretchLimit = 0; + boolean GlobalSync = false; + unsigned long 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 + // FIXME TD-er: Must change to cpluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible + uint8_t Protocol[CONTROLLER_MAX] = {0}; + uint8_t Notification[NOTIFICATION_MAX] = {0}; //notifications, point to a NPLUGIN id + // FIXME TD-er: Must change to pluginID_t, but then also another check must be added since changing the pluginID_t will also render settings incompatible + uint8_t TaskDeviceNumber[N_TASKS] = {0}; // The "plugin number" set at as task (e.g. 4 for P004_dallas) + unsigned int OLD_TaskDeviceID[N_TASKS] = {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. + union { + struct { + int8_t TaskDevicePin1[N_TASKS]; + int8_t TaskDevicePin2[N_TASKS]; + int8_t TaskDevicePin3[N_TASKS]; + uint8_t TaskDevicePort[N_TASKS]; + }; + int8_t TaskDevicePin[4][N_TASKS]{}; + }; + boolean TaskDevicePin1PullUp[N_TASKS] = {0}; + int16_t TaskDevicePluginConfig[N_TASKS][PLUGIN_CONFIGVAR_MAX]{}; + boolean TaskDevicePin1Inversed[N_TASKS] = {0}; + float TaskDevicePluginConfigFloat[N_TASKS][PLUGIN_CONFIGFLOATVAR_MAX]{}; + + // FIXME TD-er: When used on ESP8266, this conversion union may not work + // It might work as it is 32-bit in size. + union { + int32_t TaskDevicePluginConfigLong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]; + uint32_t TaskDevicePluginConfigULong[N_TASKS][PLUGIN_CONFIGLONGVAR_MAX]{}; + }; + 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}; + 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) + boolean TaskDeviceSendData[CONTROLLER_MAX][N_TASKS]{}; + boolean Pin_status_led_Inversed = false; + boolean deepSleepOnFail = false; + boolean UseValueLogger = false; + boolean ArduinoOTAEnable = false; + uint16_t DST_Start = 0; + uint16_t DST_End = 0; + boolean UseRTOSMultitasking = false; + int8_t Pin_Reset = -1; + uint8_t SyslogFacility = 0; + uint32_t StructSize = 0; // Forced to be 32 bit, to make sure alignment is clear. + boolean MQTTUseUnitNameAsClientId_unused = false; + + //its safe to extend this struct, up to several bytes, default values in config are 0 + //look in misc.ino how config.dat is used because also other stuff is stored in it at different offsets. + //TODO: document config.dat somewhere here + float Latitude = 0.0f; + float Longitude = 0.0f; + + // VariousBits_1 defaults to 0, keep in mind when adding bit lookups. + struct { + uint32_t unused_00 : 1; // Bit 00 + uint32_t appendUnitToHostname : 1; // Bit 01 Inverted + uint32_t unused_02 : 1; // Bit 02 uniqueMQTTclientIdReconnect_unused + uint32_t OldRulesEngine : 1; // Bit 03 Inverted + uint32_t ForceWiFi_bg_mode : 1; // Bit 04 + uint32_t WiFiRestart_connection_lost : 1; // Bit 05 + uint32_t EcoPowerMode : 1; // Bit 06 + uint32_t WifiNoneSleep : 1; // Bit 07 + uint32_t gratuitousARP : 1; // Bit 08 Inverted + uint32_t TolerantLastArgParse : 1; // Bit 09 + uint32_t SendToHttp_ack : 1; // Bit 10 + 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 unused_15 : 1; // Bit 15 was used by PeriodicalScanWiFi + uint32_t JSONBoolWithoutQuotes : 1; // Bit 16 + uint32_t DoNotStartAP : 1; // Bit 17 + uint32_t UseAlternativeDeepSleep : 1; // Bit 18 + uint32_t UseLastWiFiFromRTC : 1; // Bit 19 + uint32_t EnableTimingStats : 1; // Bit 20 + uint32_t AllowTaskValueSetAllPlugins : 1; // Bit 21 + uint32_t EnableClearHangingI2Cbus : 1; // Bit 22 + uint32_t EnableRAMTracking : 1; // Bit 23 + uint32_t EnableRulesCaching : 1; // Bit 24 Inverted + uint32_t EnableRulesEventReorder : 1; // Bit 25 Inverted + uint32_t AllowOTAUnlimited : 1; // Bit 26 + uint32_t SendToHTTP_follow_redirects : 1; // Bit 27 + uint32_t CssMode : 2; // Bit 28 +// uint32_t unused_29 : 1; // Bit 29 + uint32_t CheckI2Cdevice : 1; // Bit 30 Inverted + uint32_t DoNotUse_31 : 1; // Bit 31 Was used to detect whether various bits were even set + + } VariousBits_1; + + uint32_t ResetFactoryDefaultPreference = 0; // Do not clear this one in the clearAll() + uint32_t I2C_clockSpeed = 400000; + uint16_t WebserverPort = 80; + uint16_t SyslogPort = DEFAULT_SYSLOG_PORT; + + int8_t ETH_Phy_Addr = -1; + 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; + 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; + int8_t I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; + int8_t I2C_Multiplexer_Addr = -1; + int8_t I2C_Multiplexer_Channel[N_TASKS]{}; + uint8_t I2C_Flags[N_TASKS] = {0}; + uint32_t I2C_clockSpeed_Slow = 100000; + int8_t I2C_Multiplexer_ResetPin = -1; + + #ifdef ESP32 + 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 SPI_SCLK_pin = -1; + int8_t SPI_MISO_pin = -1; + int8_t SPI_MOSI_pin = -1; + int8_t ForceESPEasyNOWchannel = 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 unused_07 : 1; // Bit 07 + 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 + + } VariousBits_2; + + uint8_t console_serial_port = DEFAULT_CONSOLE_PORT; + int8_t console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + int8_t console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + uint8_t console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + + // Try to extend settings to make the checksum 4-uint8_t aligned. +}; + +/* +SettingsStruct* SettingsStruct_ptr = new (std::nothrow) SettingsStruct; +SettingsStruct& Settings = *SettingsStruct_ptr; +*/ + + + +typedef SettingsStruct_tmpl SettingsStruct; + +#endif // DATASTRUCTS_SETTINGSSTRUCT_H diff --git a/src/src/DataStructs/ShortChecksumType.cpp b/src/src/DataStructs/ShortChecksumType.cpp new file mode 100644 index 000000000..b605779d7 --- /dev/null +++ b/src/src/DataStructs/ShortChecksumType.cpp @@ -0,0 +1,137 @@ +#include "../DataStructs/ShortChecksumType.h" + +#include "../Helpers/StringConverter.h" + +#include + +void ShortChecksumType::md5sumToShortChecksum(const uint8_t md5[16], uint8_t shortChecksum[4]) +{ + memset(shortChecksum, 0, 4); + + for (uint8_t i = 0; i < 16; ++i) { + // shortChecksum is XOR per 32 bit + shortChecksum[i % 4] ^= md5[i]; + } +} + +ShortChecksumType::ShortChecksumType(const ShortChecksumType& rhs) +{ + memcpy(_checksum, rhs._checksum, 4); +} + +ShortChecksumType::ShortChecksumType(uint8_t checksum[4]) +{ + memcpy(_checksum, checksum, 4); +} + +ShortChecksumType::ShortChecksumType(const uint8_t *data, + size_t data_length) +{ + computeChecksum(_checksum, data, data_length, data_length, true); +} + +ShortChecksumType::ShortChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_checksum) +{ + computeChecksum(_checksum, data, data_length, len_upto_checksum, true); +} + +ShortChecksumType::ShortChecksumType(const String strings[], size_t nrStrings) +{ + MD5Builder md5; + + md5.begin(); + + for (size_t i = 0; i < nrStrings; ++i) { + md5.add(strings[i].c_str()); + } + md5.calculate(); + uint8_t tmp_md5[16] = { 0 }; + + md5.getBytes(tmp_md5); + md5sumToShortChecksum(tmp_md5, _checksum); +} + +bool ShortChecksumType::computeChecksum( + uint8_t checksum[4], + const uint8_t *data, + size_t data_length, + size_t len_upto_checksum, + bool updateChecksum) +{ + if (len_upto_checksum > data_length) { len_upto_checksum = data_length; } + MD5Builder md5; + + md5.begin(); + + if (len_upto_checksum > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_upto_checksum); + } + + if ((len_upto_checksum + 4) < data_length) { + data += len_upto_checksum + 4; + const int len_after_checksum = data_length - 4 - len_upto_checksum; + + if (len_after_checksum > 0) { + // MD5Builder::add has non-const argument + md5.add(const_cast(data), len_after_checksum); + } + } + md5.calculate(); + uint8_t tmp_checksum[4] = { 0 }; + + { + uint8_t tmp_md5[16] = { 0 }; + md5.getBytes(tmp_md5); + md5sumToShortChecksum(tmp_md5, tmp_checksum); + } + + if (memcmp(tmp_checksum, checksum, 4) != 0) { + // Data has changed, copy computed checksum + if (updateChecksum) { + memcpy(checksum, tmp_checksum, 4); + } + return false; + } + return true; +} + +void ShortChecksumType::getChecksum(uint8_t checksum[4]) const { + memcpy(checksum, _checksum, 4); +} + +void ShortChecksumType::setChecksum(const uint8_t checksum[4]) { + memcpy(_checksum, checksum, 4); +} + +bool ShortChecksumType::matchChecksum(const uint8_t checksum[4]) const { + return memcmp(_checksum, checksum, 4) == 0; +} + +bool ShortChecksumType::operator==(const ShortChecksumType& rhs) const { + return memcmp(_checksum, rhs._checksum, 4) == 0; +} + +ShortChecksumType& ShortChecksumType::operator=(const ShortChecksumType& rhs) { + memcpy(_checksum, rhs._checksum, 4); + return *this; +} + +String ShortChecksumType::toString() const { + return formatToHex_array(_checksum, 4); +} + +bool ShortChecksumType::isSet() const { + return + _checksum[0] != 0 || + _checksum[1] != 0 || + _checksum[2] != 0 || + _checksum[3] != 0; +} + +void ShortChecksumType::clear() +{ + memset(_checksum, 0, 4); +} diff --git a/src/src/DataStructs/ShortChecksumType.h b/src/src/DataStructs/ShortChecksumType.h new file mode 100644 index 000000000..cae852604 --- /dev/null +++ b/src/src/DataStructs/ShortChecksumType.h @@ -0,0 +1,57 @@ +#ifndef DATASTRUCTS_SHORTCHECKSUMTYPE_H +#define DATASTRUCTS_SHORTCHECKSUMTYPE_H + +#include "../../ESPEasy_common.h" + +// Short (4 byte) version of ChecksumType +struct __attribute__((__packed__)) ShortChecksumType { + // Empty checksum + ShortChecksumType() = default; + + ShortChecksumType(const ShortChecksumType& rhs); + + ShortChecksumType(uint8_t checksum[4]); + + // Construct with checksum over entire range of given data + ShortChecksumType(const uint8_t *data, + size_t data_length); + + ShortChecksumType(const uint8_t *data, + size_t data_length, + size_t len_upto_checksum); + + ShortChecksumType(const String strings[], + size_t nrStrings); + + // Compute checksum of the data. + // Skip the part where the checksum may be located in the data + // @param checksum The expected checksum. Will contain checksum after call finished. + // @retval true when checksum matches + static bool computeChecksum( + uint8_t checksum[4], + const uint8_t *data, + size_t data_length, + size_t len_upto_checksum, + bool updateChecksum = true); + + void getChecksum(uint8_t checksum[4]) const; + void setChecksum(const uint8_t checksum[4]); + bool matchChecksum(const uint8_t checksum[4]) const; + bool operator==(const ShortChecksumType& rhs) const; + ShortChecksumType& operator=(const ShortChecksumType& rhs); + + String toString() const; + + bool isSet() const; + + void clear(); + +private: + + static void md5sumToShortChecksum(const uint8_t md5[16], + uint8_t shortChecksum[4]); + + uint8_t _checksum[4] = { 0 }; +}; + +#endif // ifndef DATASTRUCTS_SHORTCHECKSUMTYPE_H diff --git a/src/src/DataStructs/TimingStats.cpp b/src/src/DataStructs/TimingStats.cpp index b4a4be220..fdf943119 100644 --- a/src/src/DataStructs/TimingStats.cpp +++ b/src/src/DataStructs/TimingStats.cpp @@ -1,333 +1,344 @@ -#include "../DataStructs/TimingStats.h" - -#if FEATURE_TIMING_STATS - -# include "../DataTypes/ESPEasy_plugin_functions.h" -# include "../Globals/CPlugins.h" -# include "../Helpers/_CPlugin_Helper.h" -# include "../Helpers/StringConverter.h" - -std::map pluginStats; -std::map controllerStats; -std::map miscStats; -unsigned long timingstats_last_reset(0); - - -TimingStats::TimingStats() : _timeTotal(0.0f), _count(0), _maxVal(0), _minVal(4294967295) {} - -void TimingStats::add(int64_t time) { - _timeTotal += static_cast(time); - ++_count; - - if (time > static_cast(_maxVal)) { _maxVal = time; } - - if (time < static_cast(_minVal)) { _minVal = time; } -} - -void TimingStats::reset() { - _timeTotal = 0.0f; - _count = 0; - _maxVal = 0; - _minVal = 4294967295; -} - -bool TimingStats::isEmpty() const { - return _count == 0; -} - -float TimingStats::getAvg() const { - if (_count == 0) { return 0.0f; } - return _timeTotal / static_cast(_count); -} - -uint32_t TimingStats::getMinMax(uint64_t& minVal, uint64_t& maxVal) const { - if (_count == 0) { - minVal = 0; - maxVal = 0; - return 0; - } - minVal = _minVal; - maxVal = _maxVal; - return _count; -} - -bool TimingStats::thresholdExceeded(const uint64_t& threshold) const { - if (_count == 0) { - return false; - } - return _maxVal > threshold; -} - -/********************************************************************************************\ - Functions used for displaying timing stats - \*********************************************************************************************/ -const __FlashStringHelper* getPluginFunctionName(int function) { - switch (function) { - case PLUGIN_INIT_ALL: return F("INIT_ALL"); - case PLUGIN_INIT: return F("INIT"); - case PLUGIN_READ: return F("READ"); - case PLUGIN_ONCE_A_SECOND: return F("ONCE_A_SECOND"); - case PLUGIN_TEN_PER_SECOND: return F("TEN_PER_SECOND"); - case PLUGIN_DEVICE_ADD: return F("DEVICE_ADD"); - case PLUGIN_EVENTLIST_ADD: return F("EVENTLIST_ADD"); - case PLUGIN_WEBFORM_SAVE: return F("WEBFORM_SAVE"); - case PLUGIN_WEBFORM_LOAD: return F("WEBFORM_LOAD"); - case PLUGIN_WEBFORM_SHOW_VALUES: return F("WEBFORM_SHOW_VALUES"); - case PLUGIN_FORMAT_USERVAR: return F("FORMAT_USERVAR"); - case PLUGIN_GET_DEVICENAME: return F("GET_DEVICENAME"); - case PLUGIN_GET_DEVICEVALUENAMES: return F("GET_DEVICEVALUENAMES"); - case PLUGIN_GET_DEVICEVALUECOUNT: return F("GET_DEVICEVALUECOUNT"); - case PLUGIN_GET_DEVICEVTYPE: return F("GET_DEVICEVTYPE"); - case PLUGIN_WRITE: return F("WRITE"); - case PLUGIN_WEBFORM_SHOW_CONFIG: return F("WEBFORM_SHOW_CONFIG"); - #if FEATURE_PLUGIN_STATS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: return F("WEBFORM_LOAD_SHOW_STATS"); - #endif - case PLUGIN_SERIAL_IN: return F("SERIAL_IN"); - case PLUGIN_UDP_IN: return F("UDP_IN"); - case PLUGIN_CLOCK_IN: return F("CLOCK_IN"); - case PLUGIN_TASKTIMER_IN: return F("TASKTIMER_IN"); - case PLUGIN_FIFTY_PER_SECOND: return F("FIFTY_PER_SECOND"); - case PLUGIN_SET_CONFIG: return F("SET_CONFIG"); - case PLUGIN_GET_DEVICEGPIONAMES: return F("GET_DEVICEGPIONAMES"); - case PLUGIN_EXIT: return F("EXIT"); - case PLUGIN_GET_CONFIG_VALUE: return F("GET_CONFIG"); -// case PLUGIN_UNCONDITIONAL_POLL: return F("UNCONDITIONAL_POLL"); - case PLUGIN_REQUEST: return F("REQUEST"); - case PLUGIN_PROCESS_CONTROLLER_DATA: return F("PROCESS_CONTROLLER_DATA"); - case PLUGIN_I2C_GET_ADDRESS: return F("I2C_CHECK_DEVICE"); - } - return F("Unknown"); -} - -bool mustLogFunction(int function) { - if (!Settings.EnableTimingStats()) { return false; } - - switch (function) { -// case PLUGIN_INIT_ALL: return false; -// case PLUGIN_INIT: return false; - case PLUGIN_READ: return true; - case PLUGIN_ONCE_A_SECOND: return true; - case PLUGIN_TEN_PER_SECOND: return true; -// case PLUGIN_DEVICE_ADD: return false; -// case PLUGIN_EVENTLIST_ADD: return false; -// case PLUGIN_WEBFORM_SAVE: return false; -// case PLUGIN_WEBFORM_LOAD: return false; -// case PLUGIN_WEBFORM_SHOW_VALUES: return false; - case PLUGIN_FORMAT_USERVAR: return true; - case PLUGIN_GET_DEVICENAME: return true; -// case PLUGIN_GET_DEVICEVALUENAMES: return false; -// case PLUGIN_GET_DEVICEVALUECOUNT: return false; -// case PLUGIN_GET_DEVICEVTYPE: return false; - case PLUGIN_WRITE: return true; -// case PLUGIN_WEBFORM_SHOW_CONFIG: return false; - case PLUGIN_SERIAL_IN: return true; -// case PLUGIN_UDP_IN: return false; -// case PLUGIN_CLOCK_IN: return false; - case PLUGIN_TASKTIMER_IN: return true; - case PLUGIN_FIFTY_PER_SECOND: return true; -// case PLUGIN_SET_CONFIG: return false; -// case PLUGIN_GET_DEVICEGPIONAMES: return false; -// case PLUGIN_EXIT: return false; -// case PLUGIN_GET_CONFIG_VALUE: return false; -// case PLUGIN_UNCONDITIONAL_POLL: return false; - case PLUGIN_REQUEST: return true; - case PLUGIN_I2C_GET_ADDRESS: return true; - case PLUGIN_PROCESS_CONTROLLER_DATA: return true; - } - return false; -} - -const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { - switch (function) { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return F("CPLUGIN_PROTOCOL_ADD"); - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return F("CPLUGIN_PROTOCOL_TEMPLATE"); - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return F("CPLUGIN_PROTOCOL_SEND"); - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return F("CPLUGIN_PROTOCOL_RECV"); - case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return F("CPLUGIN_GET_DEVICENAME"); - case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return F("CPLUGIN_WEBFORM_SAVE"); - case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return F("CPLUGIN_WEBFORM_LOAD"); - case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return F("CPLUGIN_GET_PROTOCOL_DISPLAY_NAME"); - case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return F("CPLUGIN_TASK_CHANGE_NOTIFICATION"); - case CPlugin::Function::CPLUGIN_INIT: return F("CPLUGIN_INIT"); - case CPlugin::Function::CPLUGIN_UDP_IN: return F("CPLUGIN_UDP_IN"); - 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"); - - case CPlugin::Function::CPLUGIN_GOT_CONNECTED: - case CPlugin::Function::CPLUGIN_GOT_INVALID: - case CPlugin::Function::CPLUGIN_INTERVAL: - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - break; - } - return F("Unknown"); -} - -bool mustLogCFunction(CPlugin::Function function) { - if (!Settings.EnableTimingStats()) { return false; } - - switch (function) { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return false; - case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return false; - case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return true; - case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return true; - 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; - - case CPlugin::Function::CPLUGIN_GOT_CONNECTED: - case CPlugin::Function::CPLUGIN_GOT_INVALID: - case CPlugin::Function::CPLUGIN_INTERVAL: - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: - case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: - break; - } - return false; -} - -// Return flash string type to reduce bin size -const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { - switch (stat) { - case TimingStatsElements::LOADFILE_STATS: return F("Load File"); - case TimingStatsElements::SAVEFILE_STATS: return F("Save File"); - case TimingStatsElements::LOOP_STATS: return F("Loop"); - case TimingStatsElements::PLUGIN_CALL_50PS: return F("Plugin call 50 p/s"); - case TimingStatsElements::PLUGIN_CALL_10PS: return F("Plugin call 10 p/s"); - case TimingStatsElements::PLUGIN_CALL_10PSU: return F("Plugin call 10 p/s U"); - 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::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"); - case TimingStatsElements::CONSOLE_LOOP: return F("Console loop()"); - case TimingStatsElements::CONSOLE_WRITE_SERIAL: return F("Console out"); - case TimingStatsElements::SEND_DATA_STATS: return F("sendData()"); - case TimingStatsElements::COMPUTE_FORMULA_STATS: return F("Compute formula"); - case TimingStatsElements::COMPUTE_STATS: return F("Compute()"); - case TimingStatsElements::PLUGIN_CALL_DEVICETIMER_IN: return F("PLUGIN_DEVICETIMER_IN"); - case TimingStatsElements::SET_NEW_TIMER: return F("setNewTimerAt()"); - case TimingStatsElements::MQTT_DELAY_QUEUE: return F("Delay queue MQTT"); - case TimingStatsElements::TRY_CONNECT_HOST_TCP: return F("try_connect_host() (TCP)"); - case TimingStatsElements::TRY_CONNECT_HOST_UDP: return F("try_connect_host() (UDP)"); - case TimingStatsElements::HOST_BY_NAME_STATS: return F("hostByName()"); - case TimingStatsElements::CONNECT_CLIENT_STATS: return F("connectClient()"); - case TimingStatsElements::LOAD_CUSTOM_TASK_STATS: return F("LoadCustomTaskSettings()"); - case TimingStatsElements::WIFI_ISCONNECTED_STATS: return F("WiFi.isConnected()"); - case TimingStatsElements::WIFI_NOTCONNECTED_STATS: return F("WiFi.isConnected() (fail)"); - case TimingStatsElements::LOAD_TASK_SETTINGS: return F("LoadTaskSettings()"); - case TimingStatsElements::SAVE_TASK_SETTINGS: return F("SaveTaskSettings()"); - case TimingStatsElements::LOAD_CONTROLLER_SETTINGS: return F("LoadControllerSettings()"); - #ifdef ESP32 - case TimingStatsElements::LOAD_CONTROLLER_SETTINGS_C: return F("LoadControllerSettings() (cached)"); - #endif - case TimingStatsElements::SAVE_CONTROLLER_SETTINGS: return F("SaveControllerSettings()"); - case TimingStatsElements::TRY_OPEN_FILE: return F("TryOpenFile()"); - case TimingStatsElements::FS_GC_SUCCESS: return F("ESPEASY_FS GC success"); - case TimingStatsElements::FS_GC_FAIL: return F("ESPEASY_FS GC fail"); - case TimingStatsElements::RULES_PROCESSING: return F("rulesProcessing()"); - case TimingStatsElements::RULES_PARSE_LINE: return F("parseCompleteNonCommentLine()"); - case TimingStatsElements::RULES_PROCESS_MATCHED: return F("processMatchedRule()"); - case TimingStatsElements::RULES_MATCH: return F("rulesMatch()"); - case TimingStatsElements::GRAT_ARP_STATS: return F("sendGratuitousARP()"); - case TimingStatsElements::SAVE_TO_RTC: return F("saveToRTC()"); - case TimingStatsElements::BACKGROUND_TASKS: return F("backgroundtasks()"); - case TimingStatsElements::PROCESS_SYSTEM_EVENT_QUEUE: return F("process_system_event_queue()"); - case TimingStatsElements::FORMAT_USER_VAR: return F("doFormatUserVar()"); - case TimingStatsElements::IS_NUMERICAL: return F("isNumerical()"); - case TimingStatsElements::GET_TASKVALUE_AS_STRING: return F("TaskValueGetAsString()"); - case TimingStatsElements::HANDLE_SCHEDULER_IDLE: return F("handle_schedule() idle"); - case TimingStatsElements::HANDLE_SCHEDULER_TASK: return F("handle_schedule() task"); - 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"); - case TimingStatsElements::HANDLE_SERVING_WEBPAGE: return F("handle webpage"); - case TimingStatsElements::HANDLE_SERVING_WEBPAGE_JSON: return F("handle webpage JSON"); - case TimingStatsElements::WIFI_SCAN_ASYNC: return F("WiFi Scan Async"); - case TimingStatsElements::WIFI_SCAN_SYNC: return F("WiFi Scan Sync (blocking)"); - case TimingStatsElements::NTP_SUCCESS: return F("NTP Success"); - 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"); -#ifdef LIMIT_BUILD_SIZE - default: break; -#else - // Include all elements of the enum, to allow the compiler to check if we missed some - case TimingStatsElements::C001_DELAY_QUEUE: - case TimingStatsElements::C002_DELAY_QUEUE: - case TimingStatsElements::C003_DELAY_QUEUE: - case TimingStatsElements::C004_DELAY_QUEUE: - case TimingStatsElements::C005_DELAY_QUEUE: - case TimingStatsElements::C006_DELAY_QUEUE: - case TimingStatsElements::C007_DELAY_QUEUE: - case TimingStatsElements::C008_DELAY_QUEUE: - case TimingStatsElements::C009_DELAY_QUEUE: - case TimingStatsElements::C010_DELAY_QUEUE: - case TimingStatsElements::C011_DELAY_QUEUE: - case TimingStatsElements::C012_DELAY_QUEUE: - case TimingStatsElements::C013_DELAY_QUEUE: - case TimingStatsElements::C014_DELAY_QUEUE: - case TimingStatsElements::C015_DELAY_QUEUE: - case TimingStatsElements::C016_DELAY_QUEUE: - case TimingStatsElements::C017_DELAY_QUEUE: - case TimingStatsElements::C018_DELAY_QUEUE: - case TimingStatsElements::C019_DELAY_QUEUE: - case TimingStatsElements::C020_DELAY_QUEUE: - case TimingStatsElements::C021_DELAY_QUEUE: - case TimingStatsElements::C022_DELAY_QUEUE: - case TimingStatsElements::C023_DELAY_QUEUE: - case TimingStatsElements::C024_DELAY_QUEUE: - case TimingStatsElements::C025_DELAY_QUEUE: - break; - -#endif - } - return F("Unknown"); -} - -String getMiscStatsName(TimingStatsElements stat) { - if ((stat >= TimingStatsElements::C001_DELAY_QUEUE) && - (stat <= TimingStatsElements::C025_DELAY_QUEUE)) { - return concat( - F("Delay queue "), - get_formatted_Controller_number(static_cast(static_cast(stat) - static_cast(TimingStatsElements::C001_DELAY_QUEUE) + 1))); - } - return getMiscStatsName_F(static_cast(stat)); -} - -void stopTimerTask(deviceIndex_t T, int F, uint64_t statisticsTimerStart) -{ - if (mustLogFunction(F)) { pluginStats[static_cast(T.value) * 256 + (F)].add(usecPassedSince(statisticsTimerStart)); } -} - -void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint64_t statisticsTimerStart) -{ - if (mustLogCFunction(F)) { controllerStats[static_cast(T) * 256 + static_cast(F)].add(usecPassedSince(statisticsTimerStart)); } -} - -void stopTimer(TimingStatsElements L, uint64_t statisticsTimerStart) -{ - if (Settings.EnableTimingStats()) { miscStats[L].add(usecPassedSince(statisticsTimerStart)); } -} - -void addMiscTimerStat(TimingStatsElements L, int64_t T) -{ - if (Settings.EnableTimingStats()) { miscStats[L].add(T); } -} - -#endif // if FEATURE_TIMING_STATS +#include "../DataStructs/TimingStats.h" + +#if FEATURE_TIMING_STATS + +# include "../DataTypes/ESPEasy_plugin_functions.h" +# include "../Globals/CPlugins.h" +# include "../Helpers/_CPlugin_Helper.h" +# include "../Helpers/StringConverter.h" + +std::map pluginStats; +std::map controllerStats; +std::map miscStats; +unsigned long timingstats_last_reset(0); + + +TimingStats::TimingStats() : _timeTotal(0.0f), _count(0), _maxVal(0), _minVal(4294967295) {} + +void TimingStats::add(int64_t time) { + _timeTotal += static_cast(time); + ++_count; + + if (time > static_cast(_maxVal)) { _maxVal = time; } + + if (time < static_cast(_minVal)) { _minVal = time; } +} + +void TimingStats::reset() { + _timeTotal = 0.0f; + _count = 0; + _maxVal = 0; + _minVal = 4294967295; +} + +bool TimingStats::isEmpty() const { + return _count == 0; +} + +float TimingStats::getAvg() const { + if (_count == 0) { return 0.0f; } + return _timeTotal / static_cast(_count); +} + +uint32_t TimingStats::getMinMax(uint64_t& minVal, uint64_t& maxVal) const { + if (_count == 0) { + minVal = 0; + maxVal = 0; + return 0; + } + minVal = _minVal; + maxVal = _maxVal; + return _count; +} + +bool TimingStats::thresholdExceeded(const uint64_t& threshold) const { + if (_count == 0) { + return false; + } + return _maxVal > threshold; +} + +/********************************************************************************************\ + Functions used for displaying timing stats + \*********************************************************************************************/ +const __FlashStringHelper* getPluginFunctionName(int function) { + switch (function) { + case PLUGIN_INIT_ALL: return F("INIT_ALL"); + case PLUGIN_INIT: return F("INIT"); + case PLUGIN_READ: return F("READ"); + case PLUGIN_ONCE_A_SECOND: return F("ONCE_A_SECOND"); + case PLUGIN_TEN_PER_SECOND: return F("TEN_PER_SECOND"); + case PLUGIN_DEVICE_ADD: return F("DEVICE_ADD"); + case PLUGIN_EVENTLIST_ADD: return F("EVENTLIST_ADD"); + case PLUGIN_WEBFORM_SAVE: return F("WEBFORM_SAVE"); + case PLUGIN_WEBFORM_LOAD: return F("WEBFORM_LOAD"); + case PLUGIN_WEBFORM_SHOW_VALUES: return F("WEBFORM_SHOW_VALUES"); + case PLUGIN_FORMAT_USERVAR: return F("FORMAT_USERVAR"); + case PLUGIN_GET_DEVICENAME: return F("GET_DEVICENAME"); + case PLUGIN_GET_DEVICEVALUENAMES: return F("GET_DEVICEVALUENAMES"); + case PLUGIN_GET_DEVICEVALUECOUNT: return F("GET_DEVICEVALUECOUNT"); + case PLUGIN_GET_DEVICEVTYPE: return F("GET_DEVICEVTYPE"); + case PLUGIN_WRITE: return F("WRITE"); + case PLUGIN_WEBFORM_SHOW_CONFIG: return F("WEBFORM_SHOW_CONFIG"); + #if FEATURE_PLUGIN_STATS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: return F("WEBFORM_LOAD_SHOW_STATS"); + #endif + case PLUGIN_SERIAL_IN: return F("SERIAL_IN"); + case PLUGIN_UDP_IN: return F("UDP_IN"); + case PLUGIN_CLOCK_IN: return F("CLOCK_IN"); + case PLUGIN_TASKTIMER_IN: return F("TASKTIMER_IN"); + case PLUGIN_FIFTY_PER_SECOND: return F("FIFTY_PER_SECOND"); + case PLUGIN_SET_CONFIG: return F("SET_CONFIG"); + case PLUGIN_GET_DEVICEGPIONAMES: return F("GET_DEVICEGPIONAMES"); + case PLUGIN_EXIT: return F("EXIT"); + case PLUGIN_GET_CONFIG_VALUE: return F("GET_CONFIG"); +// case PLUGIN_UNCONDITIONAL_POLL: return F("UNCONDITIONAL_POLL"); + case PLUGIN_REQUEST: return F("REQUEST"); + case PLUGIN_PROCESS_CONTROLLER_DATA: return F("PROCESS_CONTROLLER_DATA"); + case PLUGIN_I2C_GET_ADDRESS: return F("I2C_CHECK_DEVICE"); + case PLUGIN_READ_ERROR_OCCURED: return F("PLUGIN_READ_ERROR_OCCURED"); + } + return F("Unknown"); +} + +bool mustLogFunction(int function) { + if (!Settings.EnableTimingStats()) { return false; } + + switch (function) { +// case PLUGIN_INIT_ALL: return false; +// case PLUGIN_INIT: return false; + case PLUGIN_READ: return true; + case PLUGIN_ONCE_A_SECOND: return true; + case PLUGIN_TEN_PER_SECOND: return true; +// case PLUGIN_DEVICE_ADD: return false; +// case PLUGIN_EVENTLIST_ADD: return false; +// case PLUGIN_WEBFORM_SAVE: return false; +// case PLUGIN_WEBFORM_LOAD: return false; +// case PLUGIN_WEBFORM_SHOW_VALUES: return false; + case PLUGIN_FORMAT_USERVAR: return true; + case PLUGIN_GET_DEVICENAME: return true; +// case PLUGIN_GET_DEVICEVALUENAMES: return false; +// case PLUGIN_GET_DEVICEVALUECOUNT: return true; +// case PLUGIN_GET_DEVICEVTYPE: return true; + case PLUGIN_WRITE: return true; +// case PLUGIN_WEBFORM_SHOW_CONFIG: return false; + case PLUGIN_SERIAL_IN: return true; +// case PLUGIN_UDP_IN: return false; +// case PLUGIN_CLOCK_IN: return false; + case PLUGIN_TASKTIMER_IN: return true; + case PLUGIN_FIFTY_PER_SECOND: return true; +// case PLUGIN_SET_CONFIG: return false; +// case PLUGIN_GET_DEVICEGPIONAMES: return false; +// case PLUGIN_EXIT: return false; +// case PLUGIN_GET_CONFIG_VALUE: return false; +// case PLUGIN_UNCONDITIONAL_POLL: return false; + case PLUGIN_REQUEST: return true; + case PLUGIN_I2C_GET_ADDRESS: return true; + case PLUGIN_PROCESS_CONTROLLER_DATA: return true; + case PLUGIN_READ_ERROR_OCCURED: return true; + } + return false; +} + +const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function) { + switch (function) { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return F("CPLUGIN_PROTOCOL_ADD"); + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return F("CPLUGIN_CONNECT_SUCCESS"); + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return F("CPLUGIN_CONNECT_FAIL"); + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return F("CPLUGIN_PROTOCOL_TEMPLATE"); + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return F("CPLUGIN_PROTOCOL_SEND"); + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return F("CPLUGIN_PROTOCOL_RECV"); + case CPlugin::Function::CPLUGIN_GET_DEVICENAME: return F("CPLUGIN_GET_DEVICENAME"); + case CPlugin::Function::CPLUGIN_WEBFORM_SAVE: return F("CPLUGIN_WEBFORM_SAVE"); + case CPlugin::Function::CPLUGIN_WEBFORM_LOAD: return F("CPLUGIN_WEBFORM_LOAD"); + case CPlugin::Function::CPLUGIN_GET_PROTOCOL_DISPLAY_NAME: return F("CPLUGIN_GET_PROTOCOL_DISPLAY_NAME"); + case CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION: return F("CPLUGIN_TASK_CHANGE_NOTIFICATION"); + case CPlugin::Function::CPLUGIN_INIT: return F("CPLUGIN_INIT"); + case CPlugin::Function::CPLUGIN_UDP_IN: return F("CPLUGIN_UDP_IN"); + 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"); + + case CPlugin::Function::CPLUGIN_GOT_CONNECTED: + case CPlugin::Function::CPLUGIN_GOT_INVALID: + case CPlugin::Function::CPLUGIN_INTERVAL: + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + break; + } + return F("Unknown"); +} + +bool mustLogCFunction(CPlugin::Function function) { + if (!Settings.EnableTimingStats()) { return false; } + + switch (function) { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: return false; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: return true; + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: return true; + case CPlugin::Function::CPLUGIN_PROTOCOL_TEMPLATE: return false; + case CPlugin::Function::CPLUGIN_PROTOCOL_SEND: return true; + case CPlugin::Function::CPLUGIN_PROTOCOL_RECV: return true; + 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; + + case CPlugin::Function::CPLUGIN_GOT_CONNECTED: + case CPlugin::Function::CPLUGIN_GOT_INVALID: + case CPlugin::Function::CPLUGIN_INTERVAL: + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: + case CPlugin::Function::CPLUGIN_WEBFORM_SHOW_HOST_CONFIG: + break; + } + return false; +} + +// Return flash string type to reduce bin size +const __FlashStringHelper* getMiscStatsName_F(TimingStatsElements stat) { + switch (stat) { + case TimingStatsElements::LOADFILE_STATS: return F("Load File"); + case TimingStatsElements::SAVEFILE_STATS: return F("Save File"); + case TimingStatsElements::LOOP_STATS: return F("Loop"); + case TimingStatsElements::PLUGIN_CALL_50PS: return F("Plugin call 50 p/s"); + case TimingStatsElements::PLUGIN_CALL_10PS: return F("Plugin call 10 p/s"); + case TimingStatsElements::PLUGIN_CALL_10PSU: return F("Plugin call 10 p/s U"); + 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::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"); + case TimingStatsElements::CONSOLE_LOOP: return F("Console loop()"); + case TimingStatsElements::CONSOLE_WRITE_SERIAL: return F("Console out"); + case TimingStatsElements::SEND_DATA_STATS: return F("sendData()"); + case TimingStatsElements::COMPUTE_FORMULA_STATS: return F("Compute formula"); + case TimingStatsElements::COMPUTE_STATS: return F("Compute()"); + case TimingStatsElements::PLUGIN_CALL_DEVICETIMER_IN: return F("PLUGIN_DEVICETIMER_IN"); + case TimingStatsElements::SET_NEW_TIMER: return F("setNewTimerAt()"); + case TimingStatsElements::MQTT_DELAY_QUEUE: return F("Delay queue MQTT"); + case TimingStatsElements::TRY_CONNECT_HOST_TCP: return F("try_connect_host() (TCP)"); + case TimingStatsElements::TRY_CONNECT_HOST_UDP: return F("try_connect_host() (UDP)"); + case TimingStatsElements::HOST_BY_NAME_STATS: return F("hostByName()"); + case TimingStatsElements::CONNECT_CLIENT_STATS: return F("connectClient()"); + case TimingStatsElements::LOAD_CUSTOM_TASK_STATS: return F("LoadCustomTaskSettings()"); + case TimingStatsElements::WIFI_ISCONNECTED_STATS: return F("WiFi.isConnected()"); + case TimingStatsElements::WIFI_NOTCONNECTED_STATS: return F("WiFi.isConnected() (fail)"); + case TimingStatsElements::LOAD_TASK_SETTINGS: return F("LoadTaskSettings()"); + case TimingStatsElements::SAVE_TASK_SETTINGS: return F("SaveTaskSettings()"); + case TimingStatsElements::LOAD_CONTROLLER_SETTINGS: return F("LoadControllerSettings()"); + #ifdef ESP32 + case TimingStatsElements::LOAD_CONTROLLER_SETTINGS_C: return F("LoadControllerSettings() (cached)"); + #endif + case TimingStatsElements::SAVE_CONTROLLER_SETTINGS: return F("SaveControllerSettings()"); + case TimingStatsElements::TRY_OPEN_FILE: return F("TryOpenFile()"); + case TimingStatsElements::FS_GC_SUCCESS: return F("ESPEASY_FS GC success"); + case TimingStatsElements::FS_GC_FAIL: return F("ESPEASY_FS GC fail"); + case TimingStatsElements::RULES_PROCESSING: return F("rulesProcessing()"); + case TimingStatsElements::RULES_PARSE_LINE: return F("parseCompleteNonCommentLine()"); + case TimingStatsElements::RULES_PROCESS_MATCHED: return F("processMatchedRule()"); + case TimingStatsElements::RULES_MATCH: return F("rulesMatch()"); + case TimingStatsElements::GRAT_ARP_STATS: return F("sendGratuitousARP()"); + case TimingStatsElements::SAVE_TO_RTC: return F("saveToRTC()"); + case TimingStatsElements::BACKGROUND_TASKS: return F("backgroundtasks()"); + case TimingStatsElements::UPDATE_RTTTL: return F("update_rtttl()"); + case TimingStatsElements::CHECK_UDP: return F("checkUDP()"); + case TimingStatsElements::C013_SEND_UDP: return F("C013_sendUDP() SUCCESS"); + case TimingStatsElements::C013_SEND_UDP_FAIL: return F("C013_sendUDP() FAIL"); + case TimingStatsElements::C013_RECEIVE_SENSOR_DATA: return F("C013 Receive sensor data"); + case TimingStatsElements::WEBSERVER_HANDLE_CLIENT: return F("web_server.handleClient()"); + case TimingStatsElements::PROCESS_SYSTEM_EVENT_QUEUE: return F("process_system_event_queue()"); + case TimingStatsElements::FORMAT_USER_VAR: return F("doFormatUserVar()"); + 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"); + 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"); + case TimingStatsElements::HANDLE_SERVING_WEBPAGE: return F("handle webpage"); + case TimingStatsElements::HANDLE_SERVING_WEBPAGE_JSON: return F("handle webpage JSON"); + case TimingStatsElements::WIFI_SCAN_ASYNC: return F("WiFi Scan Async"); + case TimingStatsElements::WIFI_SCAN_SYNC: return F("WiFi Scan Sync (blocking)"); + case TimingStatsElements::NTP_SUCCESS: return F("NTP Success"); + 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"); +#ifdef LIMIT_BUILD_SIZE + default: break; +#else + // Include all elements of the enum, to allow the compiler to check if we missed some + case TimingStatsElements::C001_DELAY_QUEUE: + case TimingStatsElements::C002_DELAY_QUEUE: + case TimingStatsElements::C003_DELAY_QUEUE: + case TimingStatsElements::C004_DELAY_QUEUE: + case TimingStatsElements::C005_DELAY_QUEUE: + case TimingStatsElements::C006_DELAY_QUEUE: + case TimingStatsElements::C007_DELAY_QUEUE: + case TimingStatsElements::C008_DELAY_QUEUE: + case TimingStatsElements::C009_DELAY_QUEUE: + case TimingStatsElements::C010_DELAY_QUEUE: + case TimingStatsElements::C011_DELAY_QUEUE: + case TimingStatsElements::C012_DELAY_QUEUE: + case TimingStatsElements::C013_DELAY_QUEUE: + case TimingStatsElements::C014_DELAY_QUEUE: + case TimingStatsElements::C015_DELAY_QUEUE: + case TimingStatsElements::C016_DELAY_QUEUE: + case TimingStatsElements::C017_DELAY_QUEUE: + case TimingStatsElements::C018_DELAY_QUEUE: + case TimingStatsElements::C019_DELAY_QUEUE: + case TimingStatsElements::C020_DELAY_QUEUE: + case TimingStatsElements::C021_DELAY_QUEUE: + case TimingStatsElements::C022_DELAY_QUEUE: + case TimingStatsElements::C023_DELAY_QUEUE: + case TimingStatsElements::C024_DELAY_QUEUE: + case TimingStatsElements::C025_DELAY_QUEUE: + break; + +#endif + } + return F("Unknown"); +} + +String getMiscStatsName(TimingStatsElements stat) { + if ((stat >= TimingStatsElements::C001_DELAY_QUEUE) && + (stat <= TimingStatsElements::C025_DELAY_QUEUE)) { + return concat( + F("Delay queue "), + get_formatted_Controller_number(static_cast(static_cast(stat) - static_cast(TimingStatsElements::C001_DELAY_QUEUE) + 1))); + } + return getMiscStatsName_F(static_cast(stat)); +} + +void stopTimerTask(deviceIndex_t T, int F, uint64_t statisticsTimerStart) +{ + if (mustLogFunction(F)) { pluginStats[static_cast(T.value) * 256 + (F)].add(usecPassedSince(statisticsTimerStart)); } +} + +void stopTimerController(protocolIndex_t T, CPlugin::Function F, uint64_t statisticsTimerStart) +{ + if (mustLogCFunction(F)) { controllerStats[static_cast(T) * 256 + static_cast(F)].add(usecPassedSince(statisticsTimerStart)); } +} + +void stopTimer(TimingStatsElements L, uint64_t statisticsTimerStart) +{ + if (Settings.EnableTimingStats()) { miscStats[L].add(usecPassedSince(statisticsTimerStart)); } +} + +void addMiscTimerStat(TimingStatsElements L, int64_t T) +{ + if (Settings.EnableTimingStats()) { miscStats[L].add(T); } +} + +#endif // if FEATURE_TIMING_STATS diff --git a/src/src/DataStructs/TimingStats.h b/src/src/DataStructs/TimingStats.h index f7cf26915..41126efef 100644 --- a/src/src/DataStructs/TimingStats.h +++ b/src/src/DataStructs/TimingStats.h @@ -1,208 +1,213 @@ -#ifndef DATASTRUCTS_TIMINGSTATS_H -#define DATASTRUCTS_TIMINGSTATS_H - -#include "../../ESPEasy_common.h" - -#if FEATURE_TIMING_STATS - -# include "../DataTypes/DeviceIndex.h" -# include "../DataTypes/ESPEasy_plugin_functions.h" -# include "../DataTypes/ProtocolIndex.h" -# include "../Globals/Settings.h" -# include "../Helpers/ESPEasy_time_calc.h" - -# include -#endif // if FEATURE_TIMING_STATS - - -/*********************************************************************************************\ -* TimingStats -\*********************************************************************************************/ - -// These TimingStatsElements must not be excluded when FEATURE_TIMING_STATS is not defined. -// The Cxxx_DELAY_QUEUE defines are used in the macros to process the controller queues. -enum class TimingStatsElements { - - // Controller queue - MQTT_DELAY_QUEUE, - - // Do not interrupt this sequence of Cxxx_DELAY_QUEUE - // as its order is used to generate MiscStatsName - C001_DELAY_QUEUE, - C002_DELAY_QUEUE, - C003_DELAY_QUEUE, - C004_DELAY_QUEUE, - C005_DELAY_QUEUE, - C006_DELAY_QUEUE, - C007_DELAY_QUEUE, - C008_DELAY_QUEUE, - C009_DELAY_QUEUE, - C010_DELAY_QUEUE, - C011_DELAY_QUEUE, - C012_DELAY_QUEUE, - C013_DELAY_QUEUE, - C014_DELAY_QUEUE, - C015_DELAY_QUEUE, - C016_DELAY_QUEUE, - C017_DELAY_QUEUE, - C018_DELAY_QUEUE, - C019_DELAY_QUEUE, - C020_DELAY_QUEUE, - C021_DELAY_QUEUE, - C022_DELAY_QUEUE, - C023_DELAY_QUEUE, - C024_DELAY_QUEUE, - C025_DELAY_QUEUE, - - // Controller specific timing stats - C018_AIR_TIME, - - - // Related to Task runs & sending data + rules - PLUGIN_CALL_50PS, - PLUGIN_CALL_10PS, - PLUGIN_CALL_10PSU, - PLUGIN_CALL_1PS, - CPLUGIN_CALL_10PS, - CPLUGIN_CALL_50PS, - SENSOR_SEND_TASK, - SEND_DATA_STATS, - COMPUTE_FORMULA_STATS, - COMPUTE_STATS, - PARSE_SYSVAR, - PARSE_SYSVAR_NOCHANGE, - PARSE_TEMPLATE_PADDED, - IS_NUMERICAL, - GET_TASKVALUE_AS_STRING, - FORMAT_USER_VAR, - PROCESS_SYSTEM_EVENT_QUEUE, - RULES_MATCH, - RULES_PROCESSING, - RULES_PROCESS_MATCHED, - RULES_PARSE_LINE, - COMMAND_EXEC_INTERNAL, - COMMAND_DECODE_INTERNAL, - CONSOLE_LOOP, - CONSOLE_WRITE_SERIAL, - - // Related to file access - LOADFILE_STATS, - LOAD_TASK_SETTINGS, - LOAD_CUSTOM_TASK_STATS, - LOAD_CONTROLLER_SETTINGS, - #ifdef ESP32 - LOAD_CONTROLLER_SETTINGS_C, - #endif - SAVEFILE_STATS, - SAVE_TASK_SETTINGS, - SAVE_CONTROLLER_SETTINGS, - TRY_OPEN_FILE, - FS_GC_SUCCESS, - FS_GC_FAIL, - - // Scheduler related - SAVE_TO_RTC, - PLUGIN_CALL_DEVICETIMER_IN, - SET_NEW_TIMER, - HANDLE_SCHEDULER_TASK, - HANDLE_SCHEDULER_IDLE, - BACKGROUND_TASKS, - - // Web serving - HANDLE_SERVING_WEBPAGE, - HANDLE_SERVING_WEBPAGE_JSON, - - // Network related - TRY_CONNECT_HOST_TCP, - TRY_CONNECT_HOST_UDP, - HOST_BY_NAME_STATS, - GRAT_ARP_STATS, - WIFI_ISCONNECTED_STATS, - WIFI_NOTCONNECTED_STATS, - CONNECT_CLIENT_STATS, - WIFI_SCAN_ASYNC, - WIFI_SCAN_SYNC, - - // Time sync (also network related) - NTP_SUCCESS, - NTP_FAIL, - SYSTIME_UPDATED, - - // Close to the lifetime stats shown on the timing stats page - LOOP_STATS -}; - -#if FEATURE_TIMING_STATS - -class TimingStats { -public: - - TimingStats(); - - void add(int64_t time); - void reset(); - bool isEmpty() const; - float getAvg() const; - uint32_t getMinMax(uint64_t& minVal, - uint64_t& maxVal) const; - bool thresholdExceeded(const uint64_t& threshold) const; - -private: - - float _timeTotal; - uint32_t _count; - uint64_t _maxVal; - uint64_t _minVal; -}; - - -const __FlashStringHelper* getPluginFunctionName(int function); -bool mustLogFunction(int function); -const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); -bool mustLogCFunction(CPlugin::Function function); -String getMiscStatsName(TimingStatsElements stat); - -void stopTimerTask(deviceIndex_t T, - int F, - uint64_t statisticsTimerStart); -void stopTimerController(protocolIndex_t T, - CPlugin::Function F, - uint64_t statisticsTimerStart); -void stopTimer(TimingStatsElements L, - uint64_t statisticsTimerStart); -void addMiscTimerStat(TimingStatsElements L, - int64_t T); - -extern std::map pluginStats; -extern std::map controllerStats; -extern std::map miscStats; -extern unsigned long timingstats_last_reset; - -# define START_TIMER const uint64_t statisticsTimerStart(getMicros64()); -# define STOP_TIMER_TASK(T, F) stopTimerTask(T, F, statisticsTimerStart); -# define STOP_TIMER_CONTROLLER(T, F) stopTimerController(T, F, statisticsTimerStart); - -// #define STOP_TIMER_LOADFILE miscStats[LOADFILE_STATS].add(usecPassedSince(statisticsTimerStart)); -# define STOP_TIMER(L) stopTimer(TimingStatsElements::L, statisticsTimerStart); -# define STOP_TIMER_VAR(L) stopTimer(L, statisticsTimerStart); - -// Add a timer statistic value in usec. -# define ADD_TIMER_STAT(L, T) addMiscTimerStat(TimingStatsElements::L, T); - -#else // if FEATURE_TIMING_STATS - -# define START_TIMER ; -# define STOP_TIMER_TASK(T, F) ; -# define STOP_TIMER_CONTROLLER(T, F) ; -# define STOP_TIMER(L) ; -# define ADD_TIMER_STAT(L, T) ; - - -// FIXME TD-er: This class is used as a parameter in functions defined in .ino files. -// The Arduino build process tries to forward declare all functions it can find, regardless of defines. -// Meaning we must make sure the forward declaration of the TimingStats class is made, since it is used as an argument in some function. -class TimingStats; - -#endif // if FEATURE_TIMING_STATS - -#endif // DATASTRUCTS_TIMINGSTATS_H +#ifndef DATASTRUCTS_TIMINGSTATS_H +#define DATASTRUCTS_TIMINGSTATS_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_TIMING_STATS + +# include "../DataTypes/DeviceIndex.h" +# include "../DataTypes/ESPEasy_plugin_functions.h" +# include "../DataTypes/ProtocolIndex.h" +# include "../Globals/Settings.h" +# include "../Helpers/ESPEasy_time_calc.h" + +# include +#endif // if FEATURE_TIMING_STATS + + +/*********************************************************************************************\ +* TimingStats +\*********************************************************************************************/ + +// These TimingStatsElements must not be excluded when FEATURE_TIMING_STATS is not defined. +// The Cxxx_DELAY_QUEUE defines are used in the macros to process the controller queues. +enum class TimingStatsElements { + + // Controller queue + MQTT_DELAY_QUEUE, + + // Do not interrupt this sequence of Cxxx_DELAY_QUEUE + // as its order is used to generate MiscStatsName + C001_DELAY_QUEUE, + C002_DELAY_QUEUE, + C003_DELAY_QUEUE, + C004_DELAY_QUEUE, + C005_DELAY_QUEUE, + C006_DELAY_QUEUE, + C007_DELAY_QUEUE, + C008_DELAY_QUEUE, + C009_DELAY_QUEUE, + C010_DELAY_QUEUE, + C011_DELAY_QUEUE, + C012_DELAY_QUEUE, + C013_DELAY_QUEUE, + C014_DELAY_QUEUE, + C015_DELAY_QUEUE, + C016_DELAY_QUEUE, + C017_DELAY_QUEUE, + C018_DELAY_QUEUE, + C019_DELAY_QUEUE, + C020_DELAY_QUEUE, + C021_DELAY_QUEUE, + C022_DELAY_QUEUE, + C023_DELAY_QUEUE, + C024_DELAY_QUEUE, + C025_DELAY_QUEUE, + + // Controller specific timing stats + C018_AIR_TIME, + + + // Related to Task runs & sending data + rules + PLUGIN_CALL_50PS, + PLUGIN_CALL_10PS, + PLUGIN_CALL_10PSU, + PLUGIN_CALL_1PS, + CPLUGIN_CALL_10PS, + CPLUGIN_CALL_50PS, + SENSOR_SEND_TASK, + SEND_DATA_STATS, + COMPUTE_FORMULA_STATS, + COMPUTE_STATS, + PARSE_SYSVAR, + PARSE_SYSVAR_NOCHANGE, + PARSE_TEMPLATE_PADDED, + IS_NUMERICAL, + FORMAT_USER_VAR, + PROCESS_SYSTEM_EVENT_QUEUE, + RULES_MATCH, + RULES_PROCESSING, + RULES_PROCESS_MATCHED, + RULES_PARSE_LINE, + COMMAND_EXEC_INTERNAL, + COMMAND_DECODE_INTERNAL, + CONSOLE_LOOP, + CONSOLE_WRITE_SERIAL, + + // Related to file access + LOADFILE_STATS, + LOAD_TASK_SETTINGS, + LOAD_CUSTOM_TASK_STATS, + LOAD_CONTROLLER_SETTINGS, + #ifdef ESP32 + LOAD_CONTROLLER_SETTINGS_C, + #endif + SAVEFILE_STATS, + SAVE_TASK_SETTINGS, + SAVE_CONTROLLER_SETTINGS, + TRY_OPEN_FILE, + FS_GC_SUCCESS, + FS_GC_FAIL, + + // Scheduler related + SAVE_TO_RTC, + PLUGIN_CALL_DEVICETIMER_IN, + SET_NEW_TIMER, + HANDLE_SCHEDULER_TASK, + HANDLE_SCHEDULER_IDLE, + BACKGROUND_TASKS, + CHECK_UDP, + C013_SEND_UDP, + C013_SEND_UDP_FAIL, + C013_RECEIVE_SENSOR_DATA, + WEBSERVER_HANDLE_CLIENT, + UPDATE_RTTTL, + + // Web serving + HANDLE_SERVING_WEBPAGE, + HANDLE_SERVING_WEBPAGE_JSON, + + // Network related + TRY_CONNECT_HOST_TCP, + TRY_CONNECT_HOST_UDP, + HOST_BY_NAME_STATS, + GRAT_ARP_STATS, + WIFI_ISCONNECTED_STATS, + WIFI_NOTCONNECTED_STATS, + CONNECT_CLIENT_STATS, + WIFI_SCAN_ASYNC, + WIFI_SCAN_SYNC, + + // Time sync (also network related) + NTP_SUCCESS, + NTP_FAIL, + SYSTIME_UPDATED, + + // Close to the lifetime stats shown on the timing stats page + LOOP_STATS +}; + +#if FEATURE_TIMING_STATS + +class TimingStats { +public: + + TimingStats(); + + void add(int64_t time); + void reset(); + bool isEmpty() const; + float getAvg() const; + uint32_t getMinMax(uint64_t& minVal, + uint64_t& maxVal) const; + bool thresholdExceeded(const uint64_t& threshold) const; + +private: + + float _timeTotal; + uint32_t _count; + uint64_t _maxVal; + uint64_t _minVal; +}; + + +const __FlashStringHelper* getPluginFunctionName(int function); +bool mustLogFunction(int function); +const __FlashStringHelper* getCPluginCFunctionName(CPlugin::Function function); +bool mustLogCFunction(CPlugin::Function function); +String getMiscStatsName(TimingStatsElements stat); + +void stopTimerTask(deviceIndex_t T, + int F, + uint64_t statisticsTimerStart); +void stopTimerController(protocolIndex_t T, + CPlugin::Function F, + uint64_t statisticsTimerStart); +void stopTimer(TimingStatsElements L, + uint64_t statisticsTimerStart); +void addMiscTimerStat(TimingStatsElements L, + int64_t T); + +extern std::map pluginStats; +extern std::map controllerStats; +extern std::map miscStats; +extern unsigned long timingstats_last_reset; + +# define START_TIMER const uint64_t statisticsTimerStart(getMicros64()); +# define STOP_TIMER_TASK(T, F) stopTimerTask(T, F, statisticsTimerStart); +# define STOP_TIMER_CONTROLLER(T, F) stopTimerController(T, F, statisticsTimerStart); + +// #define STOP_TIMER_LOADFILE miscStats[LOADFILE_STATS].add(usecPassedSince(statisticsTimerStart)); +# define STOP_TIMER(L) stopTimer(TimingStatsElements::L, statisticsTimerStart); +# define STOP_TIMER_VAR(L) stopTimer(L, statisticsTimerStart); + +// Add a timer statistic value in usec. +# define ADD_TIMER_STAT(L, T) addMiscTimerStat(TimingStatsElements::L, T); + +#else // if FEATURE_TIMING_STATS + +# define START_TIMER ; +# define STOP_TIMER_TASK(T, F) ; +# define STOP_TIMER_CONTROLLER(T, F) ; +# define STOP_TIMER(L) ; +# define ADD_TIMER_STAT(L, T) ; + + +// FIXME TD-er: This class is used as a parameter in functions defined in .ino files. +// The Arduino build process tries to forward declare all functions it can find, regardless of defines. +// Meaning we must make sure the forward declaration of the TimingStats class is made, since it is used as an argument in some function. +class TimingStats; + +#endif // if FEATURE_TIMING_STATS + +#endif // DATASTRUCTS_TIMINGSTATS_H diff --git a/src/src/DataStructs/UnitMessageCount.cpp b/src/src/DataStructs/UnitMessageCount.cpp index 7ceaf696b..32dc853a9 100644 --- a/src/src/DataStructs/UnitMessageCount.cpp +++ b/src/src/DataStructs/UnitMessageCount.cpp @@ -1,19 +1,19 @@ -#include "../DataStructs/UnitMessageCount.h" - -bool UnitLastMessageCount_map::isNew(const UnitMessageCount_t *count) const { - if (count == nullptr) { return true; } - auto it = _map.find(count->unit); - - if (it != _map.end()) { - return it->second != count->count; - } - return true; -} - -void UnitLastMessageCount_map::add(const UnitMessageCount_t *count) { - if (count == nullptr) { return; } - - if ((count->unit != 0) && (count->unit != 255)) { - _map[count->unit] = count->count; - } -} +#include "../DataStructs/UnitMessageCount.h" + +bool UnitLastMessageCount_map::isNew(const UnitMessageCount_t *count) const { + if (count == nullptr) { return true; } + auto it = _map.find(count->unit); + + if (it != _map.end()) { + return it->second != count->count; + } + return true; +} + +void UnitLastMessageCount_map::add(const UnitMessageCount_t *count) { + if (count == nullptr) { return; } + + if ((count->unit != 0) && (count->unit != 255)) { + _map[count->unit] = count->count; + } +} diff --git a/src/src/DataStructs/UnitMessageCount.h b/src/src/DataStructs/UnitMessageCount.h index de2550510..50f804455 100644 --- a/src/src/DataStructs/UnitMessageCount.h +++ b/src/src/DataStructs/UnitMessageCount.h @@ -1,30 +1,30 @@ -#ifndef DATASTRUCTS_UNITMESSAGECOUNT_H -#define DATASTRUCTS_UNITMESSAGECOUNT_H - -#include "../../ESPEasy_common.h" - -#include - -// For deduplication, some controllers may add a unit ID and current counter. -// This count will wrap around, so it is just to detect if a message is received multiple times. -// The unit ID is the unit where the message originates from and thus should be kept along when forwarding. -struct UnitMessageCount_t { - UnitMessageCount_t() {} - - UnitMessageCount_t(uint8_t unitnr, uint8_t messageCount) : unit(unitnr), count(messageCount) {} - - uint8_t unit = 0; // Initialize to "not set" - uint8_t count = 0; -}; - -struct UnitLastMessageCount_map { - bool isNew(const UnitMessageCount_t *count) const; - - void add(const UnitMessageCount_t *count); - -private: - - std::map_map; -}; - -#endif // ifndef DATASTRUCTS_UNITMESSAGECOUNT_H +#ifndef DATASTRUCTS_UNITMESSAGECOUNT_H +#define DATASTRUCTS_UNITMESSAGECOUNT_H + +#include "../../ESPEasy_common.h" + +#include + +// For deduplication, some controllers may add a unit ID and current counter. +// This count will wrap around, so it is just to detect if a message is received multiple times. +// The unit ID is the unit where the message originates from and thus should be kept along when forwarding. +struct UnitMessageCount_t { + UnitMessageCount_t() {} + + UnitMessageCount_t(uint8_t unitnr, uint8_t messageCount) : unit(unitnr), count(messageCount) {} + + uint8_t unit = 0; // Initialize to "not set" + uint8_t count = 0; +}; + +struct UnitLastMessageCount_map { + bool isNew(const UnitMessageCount_t *count) const; + + void add(const UnitMessageCount_t *count); + +private: + + std::map_map; +}; + +#endif // ifndef DATASTRUCTS_UNITMESSAGECOUNT_H diff --git a/src/src/DataStructs/UserVarStruct.cpp b/src/src/DataStructs/UserVarStruct.cpp index f44869f65..1c97650ad 100644 --- a/src/src/DataStructs/UserVarStruct.cpp +++ b/src/src/DataStructs/UserVarStruct.cpp @@ -1,513 +1,535 @@ -#include "../DataStructs/UserVarStruct.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/Cache.h" -#include "../Globals/Plugins.h" -#include "../Globals/RulesCalculate.h" -#include "../Helpers/_Plugin_SensorTypeHelper.h" -#include "../Helpers/CRC_functions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - - - -void UserVarStruct::clear() -{ - for (size_t i = 0; i < TASKS_MAX; ++i) { - _rawData[i].clear(); - } - _computed.clear(); -#ifndef LIMIT_BUILD_SIZE - _preprocessedFormula.clear(); -#endif // ifndef LIMIT_BUILD_SIZE - _prevValue.clear(); -} - -float UserVarStruct::operator[](unsigned int index) const -{ - const unsigned int taskIndex = index / VARS_PER_TASK; - const unsigned int varNr = index % VARS_PER_TASK; - - constexpr bool raw = false; - - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_QUAD, raw); - - if (data != nullptr) { - return data->getFloat(varNr); - } else { - static float errorvalue = NAN; -#ifndef LIMIT_BUILD_SIZE - addLog(LOG_LEVEL_ERROR, F("UserVar index out of range")); -#endif - return errorvalue; - } -} - -unsigned long UserVarStruct::getSensorTypeLong(taskIndex_t taskIndex, bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, 0, Sensor_VType::SENSOR_TYPE_ULONG, raw); - - if (data != nullptr) { - return data->getSensorTypeLong(); - } - return 0u; -} - -void UserVarStruct::setSensorTypeLong(taskIndex_t taskIndex, unsigned long value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, 0)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - applyFormulaAndSet(taskIndex, 0, tmp, Sensor_VType::SENSOR_TYPE_ULONG); - } else { - _rawData[taskIndex].setSensorTypeLong(value); - } - } -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - -int32_t UserVarStruct::getInt32(taskIndex_t taskIndex, - taskVarIndex_t varNr, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_INT32_QUAD, raw); - - if (data != nullptr) { - return data->getInt32(varNr); - } - return 0; -} - -void UserVarStruct::setInt32(taskIndex_t taskIndex, - taskVarIndex_t varNr, - int32_t value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, varNr)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT32_QUAD); - } else { - _rawData[taskIndex].setInt32(varNr, value); - } - } -} - -#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES - -uint32_t UserVarStruct::getUint32(taskIndex_t taskIndex, taskVarIndex_t varNr, bool raw) const -{ -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_UINT32_QUAD, raw); -#else // if FEATURE_EXTENDED_TASK_VALUE_TYPES - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_NOT_SET, true); -#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES - - if (data != nullptr) { - return data->getUint32(varNr); - } - return 0u; -} - -void UserVarStruct::setUint32(taskIndex_t taskIndex, taskVarIndex_t varNr, uint32_t value) -{ - if (validTaskIndex(taskIndex)) { - // setUInt32 is used to read taskvalues back from RTC - // If FEATURE_EXTENDED_TASK_VALUE_TYPES is not enabled, this function will never be used for anything else -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - - if (Cache.hasFormula(taskIndex, varNr)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT32_QUAD); - } else -#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES - { - _rawData[taskIndex].setUint32(varNr, value); - } - } -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - -int64_t UserVarStruct::getInt64(taskIndex_t taskIndex, - taskVarIndex_t varNr, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_INT64_DUAL, raw); - - if (data != nullptr) { - return data->getInt64(varNr); - } - return 0; -} - -void UserVarStruct::setInt64(taskIndex_t taskIndex, - taskVarIndex_t varNr, - int64_t value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, varNr)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - - if (applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT64_DUAL)) { - // Apply anyway so we don't loose resolution in the raw value - _rawData[taskIndex].setInt64(varNr, value); - } - } else { - _rawData[taskIndex].setInt64(varNr, value); - } - } -} - -uint64_t UserVarStruct::getUint64(taskIndex_t taskIndex, - taskVarIndex_t varNr, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_UINT64_DUAL, raw); - - if (data != nullptr) { - return data->getUint64(varNr); - } - return 0u; -} - -void UserVarStruct::setUint64(taskIndex_t taskIndex, - taskVarIndex_t varNr, - uint64_t value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, varNr)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - - if (applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT64_DUAL)) { - // Apply anyway so we don't loose resolution in the raw value - _rawData[taskIndex].setUint64(varNr, value); - } - } else { - _rawData[taskIndex].setUint64(varNr, value); - } - } -} - -#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES - -float UserVarStruct::getFloat(taskIndex_t taskIndex, - taskVarIndex_t varNr, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_QUAD, raw); - - if (data != nullptr) { - return data->getFloat(varNr); - } - return 0.0f; -} - -void UserVarStruct::setFloat(taskIndex_t taskIndex, - taskVarIndex_t varNr, - float value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, varNr)) { - const ESPEASY_RULES_FLOAT_TYPE tmp = value; - applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_QUAD); - } else { - _rawData[taskIndex].setFloat(varNr, value); - } - } -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -double UserVarStruct::getDouble(taskIndex_t taskIndex, - taskVarIndex_t varNr, bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_DOUBLE_DUAL, raw); - - if (data != nullptr) { - return data->getDouble(varNr); - } - return 0.0; -} - -void UserVarStruct::setDouble(taskIndex_t taskIndex, - taskVarIndex_t varNr, - double value) -{ - if (validTaskIndex(taskIndex)) { - if (Cache.hasFormula(taskIndex, varNr)) { - applyFormulaAndSet(taskIndex, varNr, value, Sensor_VType::SENSOR_TYPE_DOUBLE_DUAL); - } else { - _rawData[taskIndex].setDouble(varNr, value); - } - } -} - -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES - -ESPEASY_RULES_FLOAT_TYPE UserVarStruct::getAsDouble(taskIndex_t taskIndex, - taskVarIndex_t varNr, - Sensor_VType sensorType, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); - - if (data != nullptr) { - return data->getAsDouble(varNr, sensorType); - } - return 0.0; -} - -String UserVarStruct::getAsString(taskIndex_t taskIndex, taskVarIndex_t varNr, Sensor_VType sensorType, uint8_t nrDecimals, bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); - - if (data != nullptr) { - if (nrDecimals == 255) { - // TD-er: Should we use the set nr of decimals here, or not round at all? - // See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437 - nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); - } - - return data->getAsString(varNr, sensorType, nrDecimals); - } - return EMPTY_STRING; -} - -void UserVarStruct::set(taskIndex_t taskIndex, taskVarIndex_t varNr, const ESPEASY_RULES_FLOAT_TYPE& value, Sensor_VType sensorType) -{ - applyFormulaAndSet(taskIndex, varNr, value, sensorType); -} - -bool UserVarStruct::isValid(taskIndex_t taskIndex, - taskVarIndex_t varNr, - Sensor_VType sensorType, - bool raw) const -{ - const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); - - if (data != nullptr) { - return data->isValid(varNr, sensorType); - } - return false; -} - -uint8_t * UserVarStruct::get(size_t& sizeInBytes) -{ - constexpr size_t size_rawData = TASKS_MAX * sizeof(TaskValues_Data_t); - - sizeInBytes = size_rawData; - return reinterpret_cast(&_rawData[0]); -} - -const TaskValues_Data_t * UserVarStruct::getRawTaskValues_Data(taskIndex_t taskIndex) const -{ - if (validTaskIndex(taskIndex)) { - return &_rawData[taskIndex]; - } - return nullptr; -} - -TaskValues_Data_t * UserVarStruct::getRawTaskValues_Data(taskIndex_t taskIndex) -{ - if (validTaskIndex(taskIndex)) { - return &_rawData[taskIndex]; - } - return nullptr; -} - -uint32_t UserVarStruct::compute_CRC32() const -{ - const uint8_t *buffer = reinterpret_cast(&_rawData[0]); - constexpr size_t size_rawData = TASKS_MAX * sizeof(TaskValues_Data_t); - - return calc_CRC32(buffer, size_rawData); -} - -void UserVarStruct::clear_computed(taskIndex_t taskIndex) -{ - if (!Cache.hasFormula(taskIndex)) { - auto it = _computed.find(taskIndex); - - if (it != _computed.end()) { - _computed.erase(it); - } - } -#ifndef LIMIT_BUILD_SIZE - - for (taskVarIndex_t varNr = 0; validTaskVarIndex(varNr); ++varNr) { - const uint16_t key = makeWord(taskIndex, varNr); - auto it = _preprocessedFormula.find(key); - - if (it != _preprocessedFormula.end()) { - _preprocessedFormula.erase(it); - } - } -#endif // ifndef LIMIT_BUILD_SIZE -} - -void UserVarStruct::markPluginRead(taskIndex_t taskIndex) -{ - for (taskVarIndex_t varNr = 0; validTaskVarIndex(varNr); ++varNr) { - if (Cache.hasFormula_with_prevValue(taskIndex, varNr)) { - const uint16_t key = makeWord(taskIndex, varNr); - _prevValue[key] = formatUserVarNoCheck(taskIndex, varNr); - } - } -} - -const TaskValues_Data_t * UserVarStruct::getRawOrComputed( - taskIndex_t taskIndex, - taskVarIndex_t varNr, - Sensor_VType sensorType, - bool raw) const -{ - if (!raw && Cache.hasFormula(taskIndex, varNr)) { - auto it = _computed.find(taskIndex); - - if ((it == _computed.end()) || !it->second.isSet(varNr)) { - // Try to compute values which do have a formula but not yet a 'computed' value cached. - // FIXME TD-er: This may yield unexpected results when formula contains references to %pvalue% - const int nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); - const String value = getAsString(taskIndex, varNr, sensorType, nrDecimals, true); - - constexpr bool applyNow = true; - - if (applyFormula(taskIndex, varNr, value, sensorType, applyNow)) { - it = _computed.find(taskIndex); - } - } - - if (it != _computed.end()) { - if (it->second.isSet(varNr)) { - return &(it->second.values); - } - } - } - return getRawTaskValues_Data(taskIndex); -} - -bool UserVarStruct::applyFormula(taskIndex_t taskIndex, - taskVarIndex_t varNr, - const String & value, - Sensor_VType sensorType, - bool applyNow) const -{ - if (!validTaskIndex(taskIndex) || - !validTaskVarIndex(varNr) || - (sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET)) - { - return false; - } - - if (!applyNow && !Cache.hasFormula_with_prevValue(taskIndex, varNr)) { - // Must check whether we can delay calculations until it is read for the first time. - auto it = _computed.find(taskIndex); - - if (it != _computed.end()) { - // Make sure it will apply formula when the value is actually read - it->second.clear(varNr); - } - return true; - } - - - String formula = getPreprocessedFormula(taskIndex, varNr); - bool res = true; - - if (!formula.isEmpty()) - { - START_TIMER; - - // TD-er: Should we use the set nr of decimals here, or not round at all? - // See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437 - if (formula.indexOf(F("%pvalue%")) != -1) { - const String prev_str = getPreviousValue(taskIndex, varNr, sensorType); - formula.replace(F("%pvalue%"), prev_str.isEmpty() ? value : prev_str); - } - - formula.replace(F("%value%"), value); - - ESPEASY_RULES_FLOAT_TYPE result{}; - - if (!isError(Calculate_preProcessed(parseTemplate(formula), result))) { - _computed[taskIndex].set(varNr, result, sensorType); - } else { - // FIXME TD-er: What to do now? Just copy the raw value, set error value or don't update? - res = false; - } - - STOP_TIMER(COMPUTE_FORMULA_STATS); - } - return res; -} - -bool UserVarStruct::applyFormulaAndSet(taskIndex_t taskIndex, - taskVarIndex_t varNr, - const ESPEASY_RULES_FLOAT_TYPE& value, - Sensor_VType sensorType) -{ - if (!Cache.hasFormula(taskIndex, varNr)) { - _rawData[taskIndex].set(varNr, value, sensorType); - return true; - } - - // Use a temporary TaskValues_Data_t object to have uniform formatting - TaskValues_Data_t tmp; - - tmp.set(varNr, value, sensorType); - const uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); - const String value_str = tmp.getAsString(varNr, sensorType, nrDecimals); - - constexpr bool applyNow = false; - - if (applyFormula(taskIndex, varNr, value_str, sensorType, applyNow)) { - _rawData[taskIndex].set(varNr, value, sensorType); - return true; - } - return false; -} - -String UserVarStruct::getPreprocessedFormula(taskIndex_t taskIndex, taskVarIndex_t varNr) const -{ - if (!Cache.hasFormula(taskIndex, varNr)) { - return EMPTY_STRING; - } - -#ifndef LIMIT_BUILD_SIZE - const uint16_t key = makeWord(taskIndex, varNr); - auto it = _preprocessedFormula.find(key); - - if (it == _preprocessedFormula.end()) { - _preprocessedFormula[key] = RulesCalculate_t::preProces(Cache.getTaskDeviceFormula(taskIndex, varNr)); - } - return _preprocessedFormula[key]; -#else // ifndef LIMIT_BUILD_SIZE - return RulesCalculate_t::preProces(Cache.getTaskDeviceFormula(taskIndex, varNr)); -#endif // ifndef LIMIT_BUILD_SIZE -} - -String UserVarStruct::getPreviousValue(taskIndex_t taskIndex, taskVarIndex_t varNr, Sensor_VType sensorType) const -{ - /* - if (!Cache.hasFormula_with_prevValue(taskIndex, varNr)) { - // Should not happen. - - } - */ - - const uint16_t key = makeWord(taskIndex, varNr); - auto it = _prevValue.find(key); - - if (it != _prevValue.end()) { - return it->second; - } - - // Probably the first run, so just return the current value - - // Do not call getAsString here as this will result in stack overflow. - return EMPTY_STRING; -} +#include "../DataStructs/UserVarStruct.h" + +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/Cache.h" +#include "../Globals/Plugins.h" +#include "../Globals/RulesCalculate.h" +#include "../Helpers/_Plugin_SensorTypeHelper.h" +#include "../Helpers/CRC_functions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + + + +void UserVarStruct::clear() +{ + for (size_t i = 0; i < TASKS_MAX; ++i) { + _rawData[i].clear(); + } + _computed.clear(); +#ifndef LIMIT_BUILD_SIZE + _preprocessedFormula.clear(); +#endif // ifndef LIMIT_BUILD_SIZE + _prevValue.clear(); +} + +float UserVarStruct::operator[](unsigned int index) const +{ + const unsigned int taskIndex = index / VARS_PER_TASK; + const unsigned int varNr = index % VARS_PER_TASK; + + constexpr bool raw = false; + + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_QUAD, raw); + + if (data != nullptr) { + return data->getFloat(varNr); + } else { + static float errorvalue = NAN; +#ifndef LIMIT_BUILD_SIZE + addLog(LOG_LEVEL_ERROR, F("UserVar index out of range")); +#endif + return errorvalue; + } +} + +unsigned long UserVarStruct::getSensorTypeLong(taskIndex_t taskIndex, bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, 0, Sensor_VType::SENSOR_TYPE_ULONG, raw); + + if (data != nullptr) { + return data->getSensorTypeLong(); + } + return 0u; +} + +void UserVarStruct::setSensorTypeLong(taskIndex_t taskIndex, unsigned long value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, 0)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + applyFormulaAndSet(taskIndex, 0, tmp, Sensor_VType::SENSOR_TYPE_ULONG); + } else { + _rawData[taskIndex].setSensorTypeLong(value); + } + } +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + +int32_t UserVarStruct::getInt32(taskIndex_t taskIndex, + taskVarIndex_t varNr, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_INT32_QUAD, raw); + + if (data != nullptr) { + return data->getInt32(varNr); + } + return 0; +} + +void UserVarStruct::setInt32(taskIndex_t taskIndex, + taskVarIndex_t varNr, + int32_t value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, varNr)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT32_QUAD); + } else { + _rawData[taskIndex].setInt32(varNr, value); + } + } +} + +#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + +uint32_t UserVarStruct::getUint32(taskIndex_t taskIndex, taskVarIndex_t varNr, bool raw) const +{ +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_UINT32_QUAD, raw); +#else // if FEATURE_EXTENDED_TASK_VALUE_TYPES + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_NOT_SET, true); +#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + + if (data != nullptr) { + return data->getUint32(varNr); + } + return 0u; +} + +void UserVarStruct::setUint32(taskIndex_t taskIndex, taskVarIndex_t varNr, uint32_t value) +{ + if (validTaskIndex(taskIndex)) { + // setUInt32 is used to read taskvalues back from RTC + // If FEATURE_EXTENDED_TASK_VALUE_TYPES is not enabled, this function will never be used for anything else +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + + if (Cache.hasFormula(taskIndex, varNr)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT32_QUAD); + } else +#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + { + _rawData[taskIndex].setUint32(varNr, value); + } + } +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + +int64_t UserVarStruct::getInt64(taskIndex_t taskIndex, + taskVarIndex_t varNr, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_INT64_DUAL, raw); + + if (data != nullptr) { + return data->getInt64(varNr); + } + return 0; +} + +void UserVarStruct::setInt64(taskIndex_t taskIndex, + taskVarIndex_t varNr, + int64_t value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, varNr)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + + if (applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_INT64_DUAL)) { + // Apply anyway so we don't loose resolution in the raw value + _rawData[taskIndex].setInt64(varNr, value); + } + } else { + _rawData[taskIndex].setInt64(varNr, value); + } + } +} + +uint64_t UserVarStruct::getUint64(taskIndex_t taskIndex, + taskVarIndex_t varNr, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_UINT64_DUAL, raw); + + if (data != nullptr) { + return data->getUint64(varNr); + } + return 0u; +} + +void UserVarStruct::setUint64(taskIndex_t taskIndex, + taskVarIndex_t varNr, + uint64_t value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, varNr)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + + if (applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_UINT64_DUAL)) { + // Apply anyway so we don't loose resolution in the raw value + _rawData[taskIndex].setUint64(varNr, value); + } + } else { + _rawData[taskIndex].setUint64(varNr, value); + } + } +} + +#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + +float UserVarStruct::getFloat(taskIndex_t taskIndex, + taskVarIndex_t varNr, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_QUAD, raw); + + if (data != nullptr) { + return data->getFloat(varNr); + } + return 0.0f; +} + +void UserVarStruct::setFloat(taskIndex_t taskIndex, + taskVarIndex_t varNr, + float value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, varNr)) { + const ESPEASY_RULES_FLOAT_TYPE tmp = value; + applyFormulaAndSet(taskIndex, varNr, tmp, Sensor_VType::SENSOR_TYPE_QUAD); + } else { + _rawData[taskIndex].setFloat(varNr, value); + } + } +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +double UserVarStruct::getDouble(taskIndex_t taskIndex, + taskVarIndex_t varNr, bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, Sensor_VType::SENSOR_TYPE_DOUBLE_DUAL, raw); + + if (data != nullptr) { + return data->getDouble(varNr); + } + return 0.0; +} + +void UserVarStruct::setDouble(taskIndex_t taskIndex, + taskVarIndex_t varNr, + double value) +{ + if (validTaskIndex(taskIndex)) { + if (Cache.hasFormula(taskIndex, varNr)) { + applyFormulaAndSet(taskIndex, varNr, value, Sensor_VType::SENSOR_TYPE_DOUBLE_DUAL); + } else { + _rawData[taskIndex].setDouble(varNr, value); + } + } +} + +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +#endif // if FEATURE_EXTENDED_TASK_VALUE_TYPES + +ESPEASY_RULES_FLOAT_TYPE UserVarStruct::getAsDouble(taskIndex_t taskIndex, + taskVarIndex_t varNr, + Sensor_VType sensorType, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); + + if (data != nullptr) { + return data->getAsDouble(varNr, sensorType); + } + return 0.0; +} + +String UserVarStruct::getAsString(taskIndex_t taskIndex, taskVarIndex_t varNr, Sensor_VType sensorType, uint8_t nrDecimals, bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); + + if (data != nullptr) { + if (nrDecimals == 255) { + // TD-er: Should we use the set nr of decimals here, or not round at all? + // See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437 + nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); + } + + return data->getAsString(varNr, sensorType, nrDecimals); + } + return EMPTY_STRING; +} + +void UserVarStruct::set(taskIndex_t taskIndex, taskVarIndex_t varNr, const ESPEASY_RULES_FLOAT_TYPE& value, Sensor_VType sensorType) +{ + applyFormulaAndSet(taskIndex, varNr, value, sensorType); +} + +bool UserVarStruct::isValid(taskIndex_t taskIndex, + taskVarIndex_t varNr, + Sensor_VType sensorType, + bool raw) const +{ + const TaskValues_Data_t *data = getRawOrComputed(taskIndex, varNr, sensorType, raw); + + if (data != nullptr) { + return data->isValid(varNr, sensorType); + } + return false; +} + +uint8_t * UserVarStruct::get(size_t& sizeInBytes) +{ + constexpr size_t size_rawData = TASKS_MAX * sizeof(TaskValues_Data_t); + + sizeInBytes = size_rawData; + return reinterpret_cast(&_rawData[0]); +} + +const TaskValues_Data_t * UserVarStruct::getRawTaskValues_Data(taskIndex_t taskIndex) const +{ + if (validTaskIndex(taskIndex)) { + return &_rawData[taskIndex]; + } + return nullptr; +} + +TaskValues_Data_t * UserVarStruct::getRawTaskValues_Data(taskIndex_t taskIndex) +{ + if (validTaskIndex(taskIndex)) { + return &_rawData[taskIndex]; + } + return nullptr; +} + +uint32_t UserVarStruct::compute_CRC32() const +{ + const uint8_t *buffer = reinterpret_cast(&_rawData[0]); + constexpr size_t size_rawData = TASKS_MAX * sizeof(TaskValues_Data_t); + + return calc_CRC32(buffer, size_rawData); +} + +void UserVarStruct::clear_computed(taskIndex_t taskIndex) +{ + auto it = _computed.find(taskIndex); + + if (it != _computed.end()) { + _computed.erase(it); + } + + for (taskVarIndex_t varNr = 0; validTaskVarIndex(varNr); ++varNr) { + const uint16_t key = makeWord(taskIndex, varNr); +#ifndef LIMIT_BUILD_SIZE + { + auto it = _preprocessedFormula.find(key); + + if (it != _preprocessedFormula.end()) { + _preprocessedFormula.erase(it); + } + } +#endif // ifndef LIMIT_BUILD_SIZE + { + auto it = _prevValue.find(key); + + if (it != _prevValue.end()) { + _prevValue.erase(it); + } + } + } +} + +void UserVarStruct::markPluginRead(taskIndex_t taskIndex) +{ + struct EventStruct TempEvent(taskIndex); + for (taskVarIndex_t varNr = 0; validTaskVarIndex(varNr); ++varNr) { + if (Cache.hasFormula_with_prevValue(taskIndex, varNr)) { + const uint16_t key = makeWord(taskIndex, varNr); + _prevValue[key] = formatUserVarNoCheck(&TempEvent, varNr); + } + } +} + +const TaskValues_Data_t * UserVarStruct::getRawOrComputed( + taskIndex_t taskIndex, + taskVarIndex_t varNr, + Sensor_VType sensorType, + bool raw) const +{ + if (!raw && Cache.hasFormula(taskIndex, varNr)) { + auto it = _computed.find(taskIndex); + + if ((it == _computed.end()) || !it->second.isSet(varNr)) { + // Try to compute values which do have a formula but not yet a 'computed' value cached. + // FIXME TD-er: This may yield unexpected results when formula contains references to %pvalue% + const int nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); + const String value = getAsString(taskIndex, varNr, sensorType, nrDecimals, true); + + constexpr bool applyNow = true; + + if (applyFormula(taskIndex, varNr, value, sensorType, applyNow)) { + it = _computed.find(taskIndex); + } + } + + if (it != _computed.end()) { + if (it->second.isSet(varNr)) { + return &(it->second.values); + } + } + } + return getRawTaskValues_Data(taskIndex); +} + +bool UserVarStruct::applyFormula(taskIndex_t taskIndex, + taskVarIndex_t varNr, + const String & value, + Sensor_VType sensorType, + bool applyNow) const +{ + if (!validTaskIndex(taskIndex) || + !validTaskVarIndex(varNr) || + (sensorType == Sensor_VType::SENSOR_TYPE_NOT_SET)) + { + return false; + } + + const bool formula_has_prevvalue = Cache.hasFormula_with_prevValue(taskIndex, varNr); + + if (!applyNow && !formula_has_prevvalue) { + // Must check whether we can delay calculations until it is read for the first time. + auto it = _computed.find(taskIndex); + + if (it != _computed.end()) { + // Make sure it will apply formula when the value is actually read + it->second.clear(varNr); + } + return true; + } + + + String formula = getPreprocessedFormula(taskIndex, varNr); + bool res = true; + + if (!formula.isEmpty()) + { + START_TIMER; + + formula.replace(F("%value%"), value); + + // TD-er: Should we use the set nr of decimals here, or not round at all? + // See: https://github.com/letscontrolit/ESPEasy/issues/3721#issuecomment-889649437 + if (formula_has_prevvalue) { + const String prev_str = getPreviousValue(taskIndex, varNr, sensorType); + formula.replace(F("%pvalue%"), prev_str.isEmpty() ? value : prev_str); + /* + addLog(LOG_LEVEL_INFO, + strformat( + F("pvalue: %s, value: %s, formula: %s"), + prev_str.c_str(), + value.c_str(), + formula.c_str())); + */ + } + + ESPEASY_RULES_FLOAT_TYPE result{}; + + if (!isError(Calculate_preProcessed(parseTemplate(formula), result))) { + _computed[taskIndex].set(varNr, result, sensorType); + } else { + // FIXME TD-er: What to do now? Just copy the raw value, set error value or don't update? + res = false; + } + + STOP_TIMER(COMPUTE_FORMULA_STATS); + } + return res; +} + +bool UserVarStruct::applyFormulaAndSet(taskIndex_t taskIndex, + taskVarIndex_t varNr, + const ESPEASY_RULES_FLOAT_TYPE& value, + Sensor_VType sensorType) +{ + if (!Cache.hasFormula(taskIndex, varNr)) { + _rawData[taskIndex].set(varNr, value, sensorType); + return true; + } + + // Use a temporary TaskValues_Data_t object to have uniform formatting + TaskValues_Data_t tmp; + + tmp.set(varNr, value, sensorType); + const uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(taskIndex, varNr); + const String value_str = tmp.getAsString(varNr, sensorType, nrDecimals); + + constexpr bool applyNow = false; + + if (applyFormula(taskIndex, varNr, value_str, sensorType, applyNow)) { + _rawData[taskIndex].set(varNr, value, sensorType); + return true; + } + return false; +} + +String UserVarStruct::getPreprocessedFormula(taskIndex_t taskIndex, taskVarIndex_t varNr) const +{ + if (!Cache.hasFormula(taskIndex, varNr)) { + return EMPTY_STRING; + } + +#ifndef LIMIT_BUILD_SIZE + const uint16_t key = makeWord(taskIndex, varNr); + auto it = _preprocessedFormula.find(key); + + if (it == _preprocessedFormula.end()) { + _preprocessedFormula.emplace( + std::make_pair( + key, + RulesCalculate_t::preProces(Cache.getTaskDeviceFormula(taskIndex, varNr)) + )); + } + return _preprocessedFormula[key]; +#else // ifndef LIMIT_BUILD_SIZE + return RulesCalculate_t::preProces(Cache.getTaskDeviceFormula(taskIndex, varNr)); +#endif // ifndef LIMIT_BUILD_SIZE +} + +String UserVarStruct::getPreviousValue(taskIndex_t taskIndex, taskVarIndex_t varNr, Sensor_VType sensorType) const +{ + /* + if (!Cache.hasFormula_with_prevValue(taskIndex, varNr)) { + // Should not happen. + + } + */ + + const uint16_t key = makeWord(taskIndex, varNr); + auto it = _prevValue.find(key); + + if (it != _prevValue.end()) { + return it->second; + } + + // Probably the first run, so just return the current value + + // Do not call getAsString here as this will result in stack overflow. + return EMPTY_STRING; +} diff --git a/src/src/DataStructs/UserVarStruct.h b/src/src/DataStructs/UserVarStruct.h index 71d0eb58c..2c8568f25 100644 --- a/src/src/DataStructs/UserVarStruct.h +++ b/src/src/DataStructs/UserVarStruct.h @@ -29,7 +29,7 @@ struct TaskValues_Data_cache { } TaskValues_Data_t values{}; - uint8_t values_set_map{}; + uint32_t values_set_map{}; }; struct UserVarStruct { diff --git a/src/src/DataStructs/Web_StreamingBuffer.cpp b/src/src/DataStructs/Web_StreamingBuffer.cpp index ef27cd2d3..565a4e130 100644 --- a/src/src/DataStructs/Web_StreamingBuffer.cpp +++ b/src/src/DataStructs/Web_StreamingBuffer.cpp @@ -1,409 +1,408 @@ -#include "../DataStructs/Web_StreamingBuffer.h" - -#include "../DataStructs/tcp_cleanup.h" -#include "../DataTypes/ESPEasyTimeSource.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -// FIXME TD-er: Should keep a pointer to the webserver as a member, not use the global defined one. -#include "../Globals/Services.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Convert.h" -#include "../Helpers/StringConverter.h" - -#include "../../ESPEasy_common.h" - -#ifdef ESP8266 -#define CHUNKED_BUFFER_SIZE 512 -#else -#define CHUNKED_BUFFER_SIZE 1400 -#endif - -Web_StreamingBuffer::Web_StreamingBuffer(void) : lowMemorySkip(false), - initialRam(0), beforeTXRam(0), duringTXRam(0), finalRam(0), maxCoreUsage(0), - maxServerUsage(0), sentBytes(0), flashStringCalls(0), flashStringData(0) -{ - // Make sure this is allocated on the DRAM since access to primary heap is faster - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - buf.reserve(CHUNKED_BUFFER_SIZE + 50); - buf.clear(); -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(char a) { - if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { - flush(); - } - this->buf += a; - return *this; -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(uint64_t a) { - return addString(ull2String(a)); -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(int64_t a) { - return addString(ll2String(a)); -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const float& a) { - return addString(toString(a, 2)); -} - -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const double& a) { - return addString(doubleToString(a)); -} -#endif - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const String& a) { - return addString(a); -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(PGM_P str) { - return addFlashString(str); -} - -Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const __FlashStringHelper* str) { - return addFlashString((PGM_P)str); -} - -Web_StreamingBuffer& Web_StreamingBuffer::addFlashString(PGM_P str, int length) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - - if (!str) { - return *this; // return if the pointer is void - } - - #ifdef USE_SECOND_HEAP - if (mmu_is_iram(str)) { - // Have to copy the string using mmu_get functions - // This is not a flash string. - bool done = false; - const char* cur_char = str; - while (!done) { - const uint8_t ch = mmu_get_uint8(cur_char++); - if (ch == 0) return *this; - if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { - flush(); - } - this->buf += (char)ch; - } - } - #endif - - ++flashStringCalls; - - if (lowMemorySkip) { return *this; } - - checkFull(); - - int flush_step = CHUNKED_BUFFER_SIZE - this->buf.length(); - if (flush_step < 1) { flush_step = 0; } - - { - // Copy to internal buffer and send in chunks - PGM_P pos = str; - while (length != 0) { - if (flush_step == 0) { - flush(); - flush_step = CHUNKED_BUFFER_SIZE; - } - const char c = (char)pgm_read_byte(pos); - if (c == '\0' && length < 0) { - // Only check for \0 when length was given (e.g. binary data) - return *this; - } - this->buf += c; - ++flashStringData; - ++pos; - --length; - --flush_step; - } - } - return *this; -} - -Web_StreamingBuffer& Web_StreamingBuffer::addString(const String& a) { - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - if (lowMemorySkip) { return *this; } - const unsigned int length = a.length(); - if (length == 0) { return *this; } - - checkFull(); - int flush_step = CHUNKED_BUFFER_SIZE - this->buf.length(); - - if (flush_step < 1) { flush_step = 0; } - - if (length < static_cast(flush_step)) { - // Just use the faster String operator to copy flash strings. - this->buf += a; - return *this; - } - - unsigned int pos = 0; - while (pos < length) { - if (flush_step <= 0) { - flush(); - flush_step = CHUNKED_BUFFER_SIZE; - } else { - const int remaining = length - pos; - const int fetchLength = flush_step >= remaining ? remaining : flush_step; - const char* ch = a.begin() + pos; - this->buf.concat(ch, static_cast(fetchLength)); - pos += fetchLength; - flush_step -= fetchLength; - } - } - return *this; -} - -void Web_StreamingBuffer::flush() { - if (lowMemorySkip) { - this->buf.clear(); - } else { - if (this->buf.length() > 0) { - sendContentBlocking(this->buf); - } - } -} - -void Web_StreamingBuffer::checkFull() { - if (lowMemorySkip) { this->buf.clear(); } - - if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { - trackTotalMem(); - flush(); - } -} - -void Web_StreamingBuffer::startStream(int httpCode) { - startStream(false, F("text/html"), F(""), httpCode); -} - -void Web_StreamingBuffer::startStream(const __FlashStringHelper * origin, int httpCode) { - startStream(false, F("text/html"), origin, httpCode); -} - -void Web_StreamingBuffer::startStream(const __FlashStringHelper * content_type, - const __FlashStringHelper * origin, - int httpCode, - bool cacheable) { - startStream(false, content_type, origin, httpCode, cacheable); -} - - -void Web_StreamingBuffer::startJsonStream() { - startStream(true, F("application/json"), F("*")); -} - -void Web_StreamingBuffer::startStream(bool allowOriginAll, - const __FlashStringHelper * content_type, - const __FlashStringHelper * origin, - int httpCode, - bool cacheable) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - maxCoreUsage = maxServerUsage = 0; - initialRam = ESP.getFreeHeap(); - beforeTXRam = initialRam; - sentBytes = 0; - buf.clear(); - buf.reserve(CHUNKED_BUFFER_SIZE); - - if (beforeTXRam < 3000) { - lowMemorySkip = true; - web_server.send_P(200, (PGM_P)F("text/plain"), (PGM_P)F("Low memory. Cannot display webpage :-(")); - #if defined(ESP8266) - tcpCleanup(); - #endif // if defined(ESP8266) - return; - } else { - sendHeaderBlocking(allowOriginAll, content_type, origin, httpCode, cacheable); - } -} - -void Web_StreamingBuffer::trackTotalMem() { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - beforeTXRam = ESP.getFreeHeap(); - - if ((initialRam - beforeTXRam) > maxServerUsage) { - maxServerUsage = initialRam - beforeTXRam; - } -} - -void Web_StreamingBuffer::trackCoreMem() { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - duringTXRam = ESP.getFreeHeap(); - - if ((initialRam - duringTXRam) > maxCoreUsage) { - maxCoreUsage = (initialRam - duringTXRam); - } -} - -void Web_StreamingBuffer::endStream() { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - if (!lowMemorySkip) { - if (buf.length() > 0) { sendContentBlocking(buf); } - buf.clear(); - sendContentBlocking(buf); - - web_server.client().flush(); - - finalRam = ESP.getFreeHeap(); - -/* -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = String("Ram usage: Webserver only: ") + maxServerUsage + - " including Core: " + maxCoreUsage + - " flashStringCalls: " + flashStringCalls + - " flashStringData: " + flashStringData; - addLog(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG -*/ - - } else { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) - addLog(LOG_LEVEL_ERROR, concat("Webpage skipped: low memory: ", finalRam)); - lowMemorySkip = false; - } -} - - -void Web_StreamingBuffer::sendContentBlocking(String& data) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - delay(0); // Try to prevent WDT reboots - - const uint32_t length = data.length(); -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - addLogMove(LOG_LEVEL_DEBUG_DEV, strformat( - F("sendcontent free: %u chunk size: %u"), - ESP.getFreeHeap(), - length)); - } -#endif // ifndef BUILD_NO_DEBUG - const uint32_t freeBeforeSend = ESP.getFreeHeap(); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("sendContentBlocking")); - #endif - - if (beforeTXRam > freeBeforeSend) { - beforeTXRam = freeBeforeSend; - } - duringTXRam = freeBeforeSend; - -#if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) - String size = formatToHex(length) + "\r\n"; - - // do chunked transfer encoding ourselves (WebServer doesn't support it) - web_server.sendContent(size); - - if (length > 0) { web_server.sendContent(data); } - web_server.sendContent("\r\n"); -#else // ESP8266 2.4.0rc2 and higher and the ESP32 webserver supports chunked http transfer - unsigned int timeout = 100; - - web_server.sendContent(data); - - if (data.length() > (CHUNKED_BUFFER_SIZE + 1)) { - data = String(); // Clear also allocated memory - } else { - data.clear(); - } - - const uint32_t beginWait = millis(); - while ((!data.reserve(CHUNKED_BUFFER_SIZE) || (ESP.getFreeHeap() < 4000 /*freeBeforeSend*/ )) && - !timeOutReached(beginWait + timeout)) { - if (ESP.getFreeHeap() < duringTXRam) { - duringTXRam = ESP.getFreeHeap(); - } - trackCoreMem(); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("duringDataTX")); - #endif - - delay(1); - } -#endif // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) - - sentBytes += length; - delay(0); -} - -void Web_StreamingBuffer::sendHeaderBlocking(bool allowOriginAll, - const String& content_type, - const String& origin, - int httpCode, - bool cacheable) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("sendHeaderBlocking")); - #endif - - web_server.client().flush(); - -#if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) - web_server.setContentLength(CONTENT_LENGTH_UNKNOWN); - sendHeader(F("Accept-Ranges"), F("none")); - sendHeader(F("Cache-Control"), F("no-cache")); - sendHeader(F("Transfer-Encoding"), F("chunked")); - - if (allowOriginAll) { - sendHeader(F("Access-Control-Allow-Origin"), "*"); - } - web_server.send(httpCode, content_type, EMPTY_STRING); -#else // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) - unsigned int timeout = 100; - const uint32_t freeBeforeSend = ESP.getFreeHeap(); - - const uint32_t beginWait = millis(); - - web_server.setContentLength(CONTENT_LENGTH_UNKNOWN); - if (!cacheable) - web_server.sendHeader(F("Cache-Control"), F("no-cache")); - - if (origin.length() > 0) { - web_server.sendHeader(F("Access-Control-Allow-Origin"), origin); - } - web_server.send(httpCode, content_type, EMPTY_STRING); - - // dont wait on 2.3.0. Memory returns just too slow. - while ((ESP.getFreeHeap() < freeBeforeSend) && - !timeOutReached(beginWait + timeout)) { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("duringHeaderTX")); - #endif - delay(1); - } -#endif // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) - delay(0); -} +#include "../DataStructs/Web_StreamingBuffer.h" + +#include "../DataStructs/tcp_cleanup.h" +#include "../DataTypes/ESPEasyTimeSource.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +// FIXME TD-er: Should keep a pointer to the webserver as a member, not use the global defined one. +#include "../Globals/Services.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Convert.h" +#include "../Helpers/StringConverter.h" + +#include "../../ESPEasy_common.h" + +#ifdef ESP8266 +#define CHUNKED_BUFFER_SIZE 512 +#else +#define CHUNKED_BUFFER_SIZE 1200 +#endif + +Web_StreamingBuffer::Web_StreamingBuffer(void) : lowMemorySkip(false), + initialRam(0), beforeTXRam(0), duringTXRam(0), finalRam(0), maxCoreUsage(0), + maxServerUsage(0), sentBytes(0), flashStringCalls(0), flashStringData(0) +{ + // Make sure this is allocated on the DRAM since access to primary heap is faster + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + buf.reserve(CHUNKED_BUFFER_SIZE + 50); + buf.clear(); +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(char a) { + if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { + flush(); + } + this->buf += a; + return *this; +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(uint64_t a) { + return addString(ull2String(a)); +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(int64_t a) { + return addString(ll2String(a)); +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const float& a) { + return addString(toString(a, 2)); +} + +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const double& a) { + return addString(doubleToString(a)); +} +#endif + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const String& a) { + return addString(a); +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(PGM_P str) { + return addFlashString(str); +} + +Web_StreamingBuffer& Web_StreamingBuffer::operator+=(const __FlashStringHelper* str) { + return addFlashString((PGM_P)str); +} + +Web_StreamingBuffer& Web_StreamingBuffer::addFlashString(PGM_P str, int length) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + + if (!str) { + return *this; // return if the pointer is void + } + + #ifdef USE_SECOND_HEAP + if (mmu_is_iram(str)) { + // Have to copy the string using mmu_get functions + // This is not a flash string. + bool done = false; + const char* cur_char = str; + while (!done) { + const uint8_t ch = mmu_get_uint8(cur_char++); + if (ch == 0) return *this; + if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { + flush(); + } + this->buf += (char)ch; + } + } + #endif + + ++flashStringCalls; + + if (lowMemorySkip) { return *this; } + + checkFull(); + + int flush_step = CHUNKED_BUFFER_SIZE - this->buf.length(); + if (flush_step < 1) { flush_step = 0; } + + { + // Copy to internal buffer and send in chunks + PGM_P pos = str; + while (length != 0) { + if (flush_step == 0) { + flush(); + flush_step = CHUNKED_BUFFER_SIZE; + } + const char c = (char)pgm_read_byte(pos); + if (c == '\0' && length < 0) { + // Only check for \0 when length was given (e.g. binary data) + return *this; + } + this->buf += c; + ++flashStringData; + ++pos; + --length; + --flush_step; + } + } + return *this; +} + +Web_StreamingBuffer& Web_StreamingBuffer::addString(const String& a) { + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + if (lowMemorySkip) { return *this; } + const unsigned int length = a.length(); + if (length == 0) { return *this; } + + checkFull(); + int flush_step = CHUNKED_BUFFER_SIZE - this->buf.length(); + + if (flush_step < 1) { flush_step = 0; } + + if (length < static_cast(flush_step)) { + // Just use the faster String operator to copy flash strings. + this->buf += a; + return *this; + } + + unsigned int pos = 0; + while (pos < length) { + if (flush_step <= 0) { + flush(); + flush_step = CHUNKED_BUFFER_SIZE; + } else { + const int remaining = length - pos; + const int fetchLength = flush_step >= remaining ? remaining : flush_step; + const char* ch = a.begin() + pos; + this->buf.concat(ch, static_cast(fetchLength)); + pos += fetchLength; + flush_step -= fetchLength; + } + } + return *this; +} + +void Web_StreamingBuffer::flush() { + if (lowMemorySkip) { + this->buf.clear(); + } else { + if (this->buf.length() > 0) { + sendContentBlocking(this->buf); + } + } +} + +void Web_StreamingBuffer::checkFull() { + if (lowMemorySkip) { this->buf.clear(); } + + if (this->buf.length() >= CHUNKED_BUFFER_SIZE) { + trackTotalMem(); + flush(); + } +} + +void Web_StreamingBuffer::startStream(int httpCode) { + startStream(false, F("text/html"), F(""), httpCode); +} + +void Web_StreamingBuffer::startStream(const __FlashStringHelper * origin, int httpCode) { + startStream(false, F("text/html"), origin, httpCode); +} + +void Web_StreamingBuffer::startStream(const __FlashStringHelper * content_type, + const __FlashStringHelper * origin, + int httpCode, + bool cacheable) { + startStream(false, content_type, origin, httpCode, cacheable); +} + + +void Web_StreamingBuffer::startJsonStream() { + startStream(true, F("application/json"), F("*")); +} + +void Web_StreamingBuffer::startStream(bool allowOriginAll, + const __FlashStringHelper * content_type, + const __FlashStringHelper * origin, + int httpCode, + bool cacheable) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + maxCoreUsage = maxServerUsage = 0; + initialRam = ESP.getFreeHeap(); + beforeTXRam = initialRam; + sentBytes = 0; + buf.clear(); + buf.reserve(CHUNKED_BUFFER_SIZE); + + if (beforeTXRam < 3000) { + lowMemorySkip = true; + web_server.send_P(200, (PGM_P)F("text/plain"), (PGM_P)F("Low memory. Cannot display webpage :-(")); + #if defined(ESP8266) + tcpCleanup(); + #endif // if defined(ESP8266) + return; + } else { + sendHeaderBlocking(allowOriginAll, content_type, origin, httpCode, cacheable); + } +} + +void Web_StreamingBuffer::trackTotalMem() { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + beforeTXRam = ESP.getFreeHeap(); + + if ((initialRam - beforeTXRam) > maxServerUsage) { + maxServerUsage = initialRam - beforeTXRam; + } +} + +void Web_StreamingBuffer::trackCoreMem() { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + duringTXRam = ESP.getFreeHeap(); + + if ((initialRam - duringTXRam) > maxCoreUsage) { + maxCoreUsage = (initialRam - duringTXRam); + } +} + +void Web_StreamingBuffer::endStream() { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + if (!lowMemorySkip) { + if (buf.length() > 0) { sendContentBlocking(buf); } + buf.clear(); + sendContentBlocking(buf); + + web_server.client().flush(); + + finalRam = ESP.getFreeHeap(); + +/* +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = String("Ram usage: Webserver only: ") + maxServerUsage + + " including Core: " + maxCoreUsage + + " flashStringCalls: " + flashStringCalls + + " flashStringData: " + flashStringData; + addLog(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG +*/ + + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) + addLog(LOG_LEVEL_ERROR, concat("Webpage skipped: low memory: ", finalRam)); + lowMemorySkip = false; + } + delay(5); +} + + +void Web_StreamingBuffer::sendContentBlocking(String& data) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + delay(0); // Try to prevent WDT reboots + + const uint32_t length = data.length(); +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + addLogMove(LOG_LEVEL_DEBUG_DEV, strformat( + F("sendcontent free: %u chunk size: %u"), + ESP.getFreeHeap(), + length)); + } +#endif // ifndef BUILD_NO_DEBUG + const uint32_t freeBeforeSend = ESP.getFreeHeap(); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("sendContentBlocking")); + #endif + + if (beforeTXRam > freeBeforeSend) { + beforeTXRam = freeBeforeSend; + } + duringTXRam = freeBeforeSend; + +#if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) + String size = formatToHex(length) + "\r\n"; + + // do chunked transfer encoding ourselves (WebServer doesn't support it) + web_server.sendContent(size); + + if (length > 0) { web_server.sendContent(data); } + web_server.sendContent("\r\n"); +#else // ESP8266 2.4.0rc2 and higher and the ESP32 webserver supports chunked http transfer + web_server.sendContent(data); + + if (data.length() > (CHUNKED_BUFFER_SIZE + 1)) { + data = String(); // Clear also allocated memory + } else { + data.clear(); + } + + const uint32_t timeout = millis() + 100; + while ((!data.reserve(CHUNKED_BUFFER_SIZE) || (ESP.getFreeHeap() < 4000 /*freeBeforeSend*/ )) && + !timeOutReached(timeout)) { + if (ESP.getFreeHeap() < duringTXRam) { + duringTXRam = ESP.getFreeHeap(); + } + trackCoreMem(); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("duringDataTX")); + #endif + + delay(1); + } +#endif // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) + + sentBytes += length; + delay(1); +} + +void Web_StreamingBuffer::sendHeaderBlocking(bool allowOriginAll, + const String& content_type, + const String& origin, + int httpCode, + bool cacheable) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("sendHeaderBlocking")); + #endif + + web_server.client().flush(); + +#if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) + web_server.setContentLength(CONTENT_LENGTH_UNKNOWN); + sendHeader(F("Accept-Ranges"), F("none")); + sendHeader(F("Cache-Control"), F("no-cache")); + sendHeader(F("Transfer-Encoding"), F("chunked")); + + if (allowOriginAll) { + sendHeader(F("Access-Control-Allow-Origin"), "*"); + } + web_server.send(httpCode, content_type, EMPTY_STRING); +#else // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) + unsigned int timeout = 100; + const uint32_t freeBeforeSend = ESP.getFreeHeap(); + + const uint32_t beginWait = millis(); + + web_server.setContentLength(CONTENT_LENGTH_UNKNOWN); + if (!cacheable) + web_server.sendHeader(F("Cache-Control"), F("no-cache")); + + if (origin.length() > 0) { + web_server.sendHeader(F("Access-Control-Allow-Origin"), origin); + } + web_server.send(httpCode, content_type, EMPTY_STRING); + + // dont wait on 2.3.0. Memory returns just too slow. + while ((ESP.getFreeHeap() < freeBeforeSend) && + !timeOutReached(beginWait + timeout)) { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("duringHeaderTX")); + #endif + delay(1); + } +#endif // if defined(ESP8266) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) + delay(0); +} diff --git a/src/src/DataStructs/WiFiEventData.cpp b/src/src/DataStructs/WiFiEventData.cpp index 59326fb7a..f34da0d7e 100644 --- a/src/src/DataStructs/WiFiEventData.cpp +++ b/src/src/DataStructs/WiFiEventData.cpp @@ -1,265 +1,284 @@ -#include "../DataStructs/WiFiEventData.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/RTC.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 - -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; -} - -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); - } -} - -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]; - } - } -} - -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); +#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; +} + +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); + } +} + +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 index bbf7c5409..1dbd20fdb 100644 --- a/src/src/DataStructs/WiFiEventData.h +++ b/src/src/DataStructs/WiFiEventData.h @@ -1,163 +1,163 @@ -#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 +#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_Candidate.cpp b/src/src/DataStructs/WiFi_AP_Candidate.cpp index b6fe85306..ddb96812c 100644 --- a/src/src/DataStructs/WiFi_AP_Candidate.cpp +++ b/src/src/DataStructs/WiFi_AP_Candidate.cpp @@ -1,196 +1,259 @@ -#include "../DataStructs/WiFi_AP_Candidate.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" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -#define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec - - -WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t index_c, const String& ssid_c) : - last_seen(0), rssi(0), channel(0), index(index_c), flags(0) -{ - const size_t ssid_length = ssid_c.length(); - - if ((ssid_length == 0) || equals(ssid_c, F("ssid"))) { - return; - } - - if (ssid_length > 32) { return; } - - ssid = ssid_c; -} - -WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0), flags(0) { - ssid = WiFi.SSID(networkItem); - rssi = WiFi.RSSI(networkItem); - channel = WiFi.channel(networkItem); - bssid = WiFi.BSSID(networkItem); - enc_type = WiFi.encryptionType(networkItem); - #ifdef ESP8266 - isHidden = WiFi.isHidden(networkItem); - #ifdef CORE_POST_3_0_0 - const bss_info* it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); - if (it) { - phy_11b = it->phy_11b; - phy_11g = it->phy_11g; - phy_11n = it->phy_11n; - wps = it->wps; - } - #endif - #endif // ifdef ESP8266 - #ifdef ESP32 - isHidden = ssid.isEmpty(); - wifi_ap_record_t* it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); - if (it) { - phy_11b = it->phy_11b; - phy_11g = it->phy_11g; - phy_11n = it->phy_11n; - phy_lr = it->phy_lr; -#if ESP_IDF_VERSION_MAJOR >= 5 - phy_11ax = it->phy_11ax; - ftm_initiator = it->ftm_initiator; - ftm_responder = it->ftm_responder; -#endif - wps = it->wps; - // FIXME TD-er: Maybe also add other info like 2nd channel, ftm and phy_lr support? - } - #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) -{ - 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 -#endif - - -bool WiFi_AP_Candidate::operator<(const WiFi_AP_Candidate& other) const { - if (isEmergencyFallback != other.isEmergencyFallback) { - return isEmergencyFallback; - } - if (lowPriority != other.lowPriority) { - return !lowPriority; - } - // Prefer non hidden over hidden. - if (isHidden != other.isHidden) { - return !isHidden; - } - - // RSSI values >= 0 are invalid - if (rssi >= 0) { return false; } - - if (other.rssi >= 0) { return true; } - - // RSSI values are negative, so the larger value is the better one. - return rssi > other.rssi; -} - -bool WiFi_AP_Candidate::usable() const { - // Allow for empty pass - // if (key.isEmpty()) return false; - if (isEmergencyFallback) { - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() > allowedUptimeMinutes || - !SecuritySettings.hasWiFiCredentials() || - WiFiEventData.performedClearWiFiCredentials || - lastBootCause != BOOT_CAUSE_COLD_BOOT) { - return false; - } - } - if (!isHidden && (ssid.isEmpty())) { return false; } - return !expired(); -} - -bool WiFi_AP_Candidate::expired() const { - if (last_seen == 0) { - // Not set, so cannot expire - return false; - } - return timePassedSince(last_seen) > WIFI_AP_CANDIDATE_MAX_AGE; -} - - -String WiFi_AP_Candidate::toString(const String& separator) const { - String result = ssid; - - htmlEscape(result); - if (isHidden) { - result += F("#Hidden#"); - } - result += strformat( - F("%s%s%sCh:%u"), - separator.c_str(), - bssid.toString().c_str(), - separator.c_str(), - channel); - - if (rssi == -1) { - result += F(" (RTC) "); - } else { - result += strformat(F(" (%ddBm)"), rssi); - } - - result += encryption_type(); - if (phy_known()) { - String phy_str; - - if (phy_11b) phy_str += 'b'; - if (phy_11g) phy_str += 'g'; - if (phy_11n) phy_str += 'n'; -#ifdef ESP32 - if (phy_11ax) phy_str += F("/ax"); - if (phy_lr) phy_str += F("/lr"); - if (ftm_initiator) phy_str += F("/FTM_i"); - if (ftm_responder) phy_str += F("/FTM_r"); -#endif - - if (phy_str.length()) { - result += strformat(F(" (%s)"), phy_str.c_str()); - } - } - return result; -} - -String WiFi_AP_Candidate::encryption_type() const { - return WiFi_encryptionType(enc_type); -} +#include "../DataStructs/WiFi_AP_Candidate.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" + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +#define WIFI_AP_CANDIDATE_MAX_AGE 300000 // 5 minutes in msec + + +WiFi_AP_Candidate::WiFi_AP_Candidate() : +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 +country({ + .cc = "01", + .schan = 1, + .nchan = 11, + .policy = WIFI_COUNTRY_POLICY_AUTO, +}), +#endif +#endif + last_seen(0), rssi(0), channel(0), index(0), enc_type(0) +{ + memset(&bits, 0, sizeof(bits)); +} + +WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t index_c, const String& ssid_c) : + last_seen(0), rssi(0), channel(0), index(index_c), enc_type(0) +{ + memset(&bits, 0, sizeof(bits)); + + const size_t ssid_length = ssid_c.length(); + + if ((ssid_length == 0) || equals(ssid_c, F("ssid"))) { + return; + } + + if (ssid_length > 32) { return; } + + ssid = ssid_c; +} + +WiFi_AP_Candidate::WiFi_AP_Candidate(uint8_t networkItem) : index(0) { + // Need to make sure the phy isn't known as we can't get this information from the AP + // See: https://github.com/letscontrolit/ESPEasy/issues/4996 + // Not sure why this makes any difference as the flags should already have been set to 0. + memset(&bits, 0, sizeof(bits)); + + ssid = WiFi.SSID(networkItem); + rssi = WiFi.RSSI(networkItem); + channel = WiFi.channel(networkItem); + bssid = WiFi.BSSID(networkItem); + enc_type = WiFi.encryptionType(networkItem); + #ifdef ESP8266 + bits.isHidden = WiFi.isHidden(networkItem); + # ifdef CORE_POST_3_0_0 + const bss_info *it = reinterpret_cast(WiFi.getScanInfoByIndex(networkItem)); + + if (it) { + bits.phy_11b = it->phy_11b; + bits.phy_11g = it->phy_11g; + bits.phy_11n = it->phy_11n; + bits.wps = it->wps; + } + # 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)); + + if (it) { + bits.phy_11b = it->phy_11b; + bits.phy_11g = it->phy_11g; + bits.phy_11n = it->phy_11n; + bits.phy_lr = it->phy_lr; +# 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 + 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 + memcpy(&country, &(it->country), sizeof(wifi_country_t)); +#endif + } + #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) +{ + memset(&bits, 0, sizeof(bits)); + + 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) { + return bits.isEmergencyFallback; + } + + if (bits.lowPriority != other.bits.lowPriority) { + return !bits.lowPriority; + } + + // Prefer non hidden over hidden. + if (bits.isHidden != other.bits.isHidden) { + return !bits.isHidden; + } + + // RSSI values >= 0 are invalid + if (rssi >= 0) { return false; } + + if (other.rssi >= 0) { return true; } + + // RSSI values are negative, so the larger value is the better one. + return rssi > other.rssi; +} + +bool WiFi_AP_Candidate::usable() const { + // Allow for empty pass + // if (key.isEmpty()) return false; + if (bits.isEmergencyFallback) { + int allowedUptimeMinutes = 10; + #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; + #endif // ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + + if ((getUptimeMinutes() > allowedUptimeMinutes) || + !SecuritySettings.hasWiFiCredentials() || + WiFiEventData.performedClearWiFiCredentials || + (lastBootCause != BOOT_CAUSE_COLD_BOOT)) { + return false; + } + } + + if (!bits.isHidden && (ssid.isEmpty())) { return false; } + return !expired(); +} + +bool WiFi_AP_Candidate::expired() const { + if (last_seen == 0) { + // Not set, so cannot expire + return false; + } + return timePassedSince(last_seen) > WIFI_AP_CANDIDATE_MAX_AGE; +} + +String WiFi_AP_Candidate::toString(const String& separator) const { + String result = ssid; + + htmlEscape(result); + + if (bits.isHidden) { + result += F("#Hidden#"); + } + result += strformat( + F("%s%s%sCh:%u"), + separator.c_str(), + bssid.toString().c_str(), + separator.c_str(), + channel); + + if (rssi == -1) { + result += F(" (RTC) "); + } else { + result += strformat(F(" (%ddBm) "), rssi); + } + + result += encryption_type(); + +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR >= 5 + // Country code string + if (country.cc[0] != '\0' && country.cc[1] != '\0') { + result += strformat(F(" '%c%c'"), country.cc[0], country.cc[1]); + switch (country.cc[2]) { + case 'O': // Outdoor + case 'I': // Indoor + case 'X': // "non-country" + result += strformat(F("(%c)"), country.cc[2]); + break; + } + } + if (country.nchan > 0) { + result += strformat(F(" ch: %d..%d"), country.schan, country.schan + country.nchan - 1); + } +#endif +#endif + + if (phy_known()) { + String phy_str; + + if (bits.phy_11b) { phy_str += 'b'; } + + if (bits.phy_11g) { phy_str += 'g'; } + + if (bits.phy_11n) { phy_str += 'n'; } +#ifdef ESP32 + + if (bits.phy_11ax) { phy_str += F("/ax"); } + + if (bits.phy_lr) { phy_str += F("/lr"); } + + if (bits.ftm_initiator) { phy_str += F("/FTM_i"); } + + if (bits.ftm_responder) { phy_str += F("/FTM_r"); } +#endif // ifdef ESP32 + + if (phy_str.length()) { + result += strformat(F(" (%s)"), phy_str.c_str()); + } + } + return result; +} + +String WiFi_AP_Candidate::encryption_type() const { + return WiFi_encryptionType(enc_type); +} diff --git a/src/src/DataStructs/WiFi_AP_Candidate.h b/src/src/DataStructs/WiFi_AP_Candidate.h index ce93d4cbf..bec19eeb9 100644 --- a/src/src/DataStructs/WiFi_AP_Candidate.h +++ b/src/src/DataStructs/WiFi_AP_Candidate.h @@ -5,32 +5,32 @@ #include "../DataStructs/MAC_address.h" struct WiFi_AP_Candidate { + WiFi_AP_Candidate(); + WiFi_AP_Candidate(const WiFi_AP_Candidate& other) = default; + + // Construct from stored credentials // @param index The index of the stored credentials // @param ssid_c SSID of the credentials // @param pass Password/key of the credentials - WiFi_AP_Candidate(uint8_t index, + WiFi_AP_Candidate(uint8_t index, const String& ssid_c); // Construct using index from WiFi scan result WiFi_AP_Candidate(uint8_t networkItem); #ifdef ESP8266 - #if FEATURE_ESP8266_DIRECT_WIFI_SCAN + # if FEATURE_ESP8266_DIRECT_WIFI_SCAN WiFi_AP_Candidate(const bss_info& ap); - #endif - #endif - - - WiFi_AP_Candidate() = default; - WiFi_AP_Candidate(const WiFi_AP_Candidate& other) = default; + # 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; + bool operator<(const WiFi_AP_Candidate& other) const; - bool operator==(const WiFi_AP_Candidate& other) const { - return bssid_match(other.bssid) && ssid.equals(other.ssid);// && key.equals(other.key); + bool operator==(const WiFi_AP_Candidate& other) const { + return bssid_match(other.bssid) && ssid.equals(other.ssid); // && key.equals(other.key); } WiFi_AP_Candidate& operator=(const WiFi_AP_Candidate& other) = default; @@ -42,50 +42,63 @@ struct WiFi_AP_Candidate { bool expired() const; // For quick connection the channel and BSSID are needed - bool allowQuickConnect() const { return (channel != 0) && bssid_set(); } + bool allowQuickConnect() const { + return (channel != 0) && bssid_set(); + } // Check to see if the BSSID is set - bool bssid_set() const { return !bssid.all_zero(); } + bool bssid_set() const { + return !bssid.all_zero(); + } - bool bssid_match(const uint8_t bssid_c[6]) const { return bssid == bssid_c; } - bool bssid_match(const MAC_address& other) const { return bssid == other; } + bool bssid_match(const uint8_t bssid_c[6]) const { + return bssid == bssid_c; + } + + bool bssid_match(const MAC_address& other) const { + return bssid == other; + } // Create a formatted string - String toString(const String& separator = " ") const; + String toString(const String& separator = " ") const; - String encryption_type() const; + String encryption_type() const; - bool phy_known() const { return phy_11b || phy_11g || phy_11n; } + bool phy_known() const { + return bits.phy_11b || bits.phy_11g || bits.phy_11n; + } - String ssid; -// String key; + String ssid; + + // String key; + + #ifdef ESP32 + # if ESP_IDF_VERSION_MAJOR >= 5 + wifi_country_t country; + #endif + #endif 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) - union - { - struct { - uint16_t isHidden:1; // Hidden SSID - uint16_t lowPriority:1; // Try as last attempt - uint16_t isEmergencyFallback:1; - uint16_t phy_11b:1; - uint16_t phy_11g:1; - uint16_t phy_11n:1; - uint16_t phy_lr:1; - uint16_t phy_11ax:1; - uint16_t wps:1; - uint16_t ftm_responder:1; - uint16_t ftm_initiator:1; + uint8_t index{}; // Index of the matching credentials + uint8_t enc_type{}; // Encryption used (e.g. WPA2) + struct { + uint16_t isHidden : 1; // Hidden SSID + uint16_t lowPriority : 1; // Try as last attempt + uint16_t isEmergencyFallback : 1; + uint16_t phy_11b : 1; + uint16_t phy_11g : 1; + uint16_t phy_11n : 1; + uint16_t phy_lr : 1; + uint16_t phy_11ax : 1; + uint16_t wps : 1; + uint16_t ftm_responder : 1; + uint16_t ftm_initiator : 1; - uint16_t unused:5; - }; - uint16_t flags{}; - }; - + uint16_t unused : 5; + } bits; }; #endif // ifndef DATASTRUCTS_WIFI_AP_CANDIDATES_H diff --git a/src/src/DataStructs/mBusPacket.cpp b/src/src/DataStructs/mBusPacket.cpp new file mode 100644 index 000000000..4e0a5537a --- /dev/null +++ b/src/src/DataStructs/mBusPacket.cpp @@ -0,0 +1,428 @@ +#include "../DataStructs/mBusPacket.h" + +#include "../Helpers/CRC_functions.h" +#include "../Helpers/StringConverter.h" + +#define FRAME_FORMAT_A_FIRST_BLOCK_LENGTH 10 +#define FRAME_FORMAT_A_OTHER_BLOCK_LENGTH 16 + + +mBusPacket_header_t::mBusPacket_header_t() +{ + _manufacturer = mBus_packet_wildcard_manufacturer; + _meterType = mBus_packet_wildcard_metertype; + _serialNr = mBus_packet_wildcard_serial; + _length = 0u; +} + +String mBusPacket_header_t::decodeManufacturerID(int id) +{ + String res; + int shift = 15; + + for (int i = 0; i < 3; ++i) { + shift -= 5; + res += static_cast(((id >> shift) & 0x1f) + 64); + } + return res; +} + +int mBusPacket_header_t::encodeManufacturerID(const String& id_str) +{ + int res = 0; + int nrChars = id_str.length(); + + if (nrChars > 3) { nrChars = 3; } + + int i = 0; + + while (i < nrChars) { + res <<= 5; + const int c = static_cast(toUpperCase(id_str[i])) - 64; + + if (c >= 0) { + res += c & 0x1f; + } + ++i; + } + return res; +} + +String mBusPacket_header_t::getManufacturerId() const +{ + return decodeManufacturerID(_manufacturer); +} + +String mBusPacket_header_t::toString() const +{ + String res = decodeManufacturerID(_manufacturer); + + res += '.'; + res += formatToHex_no_prefix(_meterType, 2); + res += '.'; + res += formatToHex_no_prefix(_serialNr, 8); + return res; +} + +uint64_t mBusPacket_header_t::encode_toUInt64() const +{ + if (!isValid()) { return 0ull; } + mBusPacket_header_t tmp(*this); + + tmp._length = 0; + return tmp._encodedValue; +} + +void mBusPacket_header_t::decode_fromUint64(uint64_t encodedValue) +{ + _encodedValue = encodedValue; + _length = 1; // To pass isValid() check +} + +bool mBusPacket_header_t::isValid() const +{ + return + _manufacturer != mBus_packet_wildcard_manufacturer && + _meterType != mBus_packet_wildcard_metertype && + _serialNr != mBus_packet_wildcard_serial && + _length > 0; +} + +void mBusPacket_header_t::clear() +{ + _manufacturer = mBus_packet_wildcard_manufacturer; + _meterType = mBus_packet_wildcard_metertype; + _serialNr = mBus_packet_wildcard_serial; + _length = 0; +} + +bool mBusPacket_header_t::matchSerial(uint32_t serialNr) const +{ + return isValid() && (_serialNr == serialNr); +} + +const mBusPacket_header_t * mBusPacket_t::getDeviceHeader() const +{ + // FIXME TD-er: Which deviceID is the device and which the wrapper? + if (_deviceId1.isValid()) { return &_deviceId1; } + + if (_deviceId2.isValid()) { return &_deviceId2; } + + return nullptr; +} + +uint32_t mBusPacket_t::getDeviceSerial() const +{ + const mBusPacket_header_t *header = getDeviceHeader(); + + if (header == nullptr) { return 0u; } + return header->_serialNr; +} + +uint32_t mBusPacket_t::deviceID_to_map_key() const +{ + return deviceID_to_map_key(_deviceId1._encodedValue, _deviceId2._encodedValue); +} + +uint32_t mBusPacket_t::deviceID_to_map_key_no_length() const { + return deviceID_to_map_key(_deviceId1.encode_toUInt64(), _deviceId2.encode_toUInt64()); +} + +uint32_t mBusPacket_t::deviceID_to_map_key(uint64_t id1, uint64_t id2) +{ + uint32_t res = 0; + + if (id1 != 0ull) { + res ^= calc_CRC32((const uint8_t *)(&id1), sizeof(uint64_t)); + } + + if (id2 != 0ull) { + // There is a forwarding device. + // To prevent issues when the forwarding device is the same as the forwarded device, alter the already existing checksum. + res ^= calc_CRC32((const uint8_t *)(&res), sizeof(res)); + res ^= calc_CRC32((const uint8_t *)(&id2), sizeof(uint64_t)); + } + + return res; +} + +bool mBusPacket_t::parse(const String& payload) +{ + if (payload[0] != 'b') { return false; } + + _checksum = 0; + mBusPacket_data payloadWithoutChecksums; + + if (payload[1] == 'Y') { + // Start with "bY" + payloadWithoutChecksums = removeChecksumsFrameB(payload, _checksum); + } else { + payloadWithoutChecksums = removeChecksumsFrameA(payload, _checksum); + } + + if (payloadWithoutChecksums.size() < 10) { return false; } + + int pos_semicolon = payload.indexOf(';'); + + if (pos_semicolon == -1) { pos_semicolon = payload.length(); } + + _lqi_rssi = hexToUL(payload, pos_semicolon - 4, 4); + return parseHeaders(payloadWithoutChecksums); +} + +int16_t mBusPacket_t::decode_LQI_RSSI(uint16_t lqi_rssi, uint8_t& LQI) +{ + LQI = (lqi_rssi >> 8) & 0x7f; // Bit 7 = CRC OK Bit + + int rssi = lqi_rssi & 0xFF; + + if (rssi >= 128) { + rssi -= 256; // 2-complement + } + return (rssi / 2) - 74; +} + +bool mBusPacket_t::matchSerial(uint32_t serialNr) const +{ + return _deviceId1.matchSerial(serialNr) || _deviceId2.matchSerial(serialNr); +} + +bool mBusPacket_t::parseHeaders(const mBusPacket_data& payloadWithoutChecksums) +{ + const int payloadSize = payloadWithoutChecksums.size(); + + _deviceId1.clear(); + _deviceId2.clear(); + + if (payloadSize < 10) { return false; } + int offset = 0; + + // 1st block is a static DataLinkLayer of 10 bytes + { + _deviceId1._manufacturer = makeWord(payloadWithoutChecksums[offset + 3], payloadWithoutChecksums[offset + 2]); + + // Type (offset + 9; convert to hex) + _deviceId1._meterType = payloadWithoutChecksums[offset + 9]; + + // Serial (offset + 4; 4 Bytes; least significant first; converted to hex) + _deviceId1._serialNr = 0; + + for (int i = 0; i < 4; ++i) { + const uint32_t val = payloadWithoutChecksums[offset + 4 + i]; + _deviceId1._serialNr += val << (i * 8); + } + offset += 10; + _deviceId1._length = payloadWithoutChecksums[0]; + } + + // next blocks can be anything. we skip known blocks of no interest, parse known blocks if interest and stop on onknown blocks + while (offset < payloadSize) { + switch (static_cast(payloadWithoutChecksums[offset])) { + case 0x8C: // ELL short + offset += 3; // fixed length + _deviceId1._length = payloadSize - offset; + break; + case 0x90: // AFL + offset++; + offset += (payloadWithoutChecksums[offset] & 0xff); // dynamic length with length in 1st byte + offset++; // length byte + _deviceId1._length = payloadSize - offset; + break; + case 0x72: // TPL_RESPONSE_MBUS_LONG_HEADER + _deviceId2 = _deviceId1; + + // note that serial/manufacturer are swapped !! + + _deviceId1._manufacturer = makeWord(payloadWithoutChecksums[offset + 6], payloadWithoutChecksums[offset + 5]); + + // Type (offset + 9; convert to hex) + _deviceId1._meterType = payloadWithoutChecksums[offset + 8]; + + // Serial (offset + 4; 4 Bytes; least significant first; converted to hex) + _deviceId1._serialNr = 0; + + + for (int i = 0; i < 4; ++i) { + const uint32_t val = payloadWithoutChecksums[offset + 1 + i]; + _deviceId1._serialNr += val << (i * 8); + } + + // We're done + offset = payloadSize; + break; + default: + // We're done + // addLog(LOG_LEVEL_ERROR, concat(F("CUL : offset "), offset) + F(" Data: ") + formatToHex(payloadWithoutChecksums[offset])); + offset = payloadSize; + break; + } + } + + if (_deviceId1.isValid() && _deviceId2.isValid() && _deviceId1.toString().startsWith(F("ITW.30."))) { + // ITW does not follow the spec and puts the redio converter behind the actual meter. Need to swap both + std::swap(_deviceId1, _deviceId2); + } + + return _deviceId1.isValid(); +} + +String mBusPacket_t::toString() const +{ + static size_t expectedSize = 96; + String res; + + if (res.reserve(expectedSize)) { + if (_deviceId1.isValid()) { + res += F(" deviceId1: "); + res += _deviceId1.toString(); + res += '('; + res += static_cast(_deviceId1._length); + res += ')'; + } + + if (_deviceId2.isValid()) { + res += F(" deviceId2: "); + res += _deviceId2.toString(); + res += '('; + res += static_cast(_deviceId2._length); + res += ')'; + } + res += F(" chksum: "); + res += formatToHex(_checksum, 8); + + uint8_t LQI = 0; + const int16_t rssi = decode_LQI_RSSI(_lqi_rssi, LQI); + res += F(" LQI: "); + res += LQI; + res += F(" RSSI: "); + res += rssi; + } + + if (res.length() > expectedSize) { expectedSize = res.length(); } + + return res; +} + +uint8_t mBusPacket_t::hexToByte(const String& str, size_t index) +{ + // Need to have at least 2 HEX nibbles + if ((index + 1) >= str.length()) { return 0; } + return hexToUL(str, index, 2); +} + +/** + * Format: + * [10 bytes message] + [2 bytes CRC] + * [16 bytes message] + [2 bytes CRC] + * [16 bytes message] + [2 bytes CRC] + * ... + * (last block can be < 16 bytes) + */ +mBusPacket_data mBusPacket_t::removeChecksumsFrameA(const String& payload, uint32_t& checksum) +{ + mBusPacket_data result; + const int payloadLength = payload.length(); + + if (payloadLength < 4) { return result; } + + int sourceIndex = 1; // Starts with "b" + int targetIndex = 0; + + // 1st byte contains length of data (excuding 1st byte and excluding CRC) + const int expectedMessageSize = hexToByte(payload, sourceIndex) + 1; + + if (payloadLength < (2 * expectedMessageSize)) { + // Not an exact check, but close enough to fail early on packets which are seriously too short. + return result; + } + + result.reserve(expectedMessageSize); + + while (targetIndex < expectedMessageSize) { + // end index is start index + block size + 2 byte checksums + int blockSize = (sourceIndex == 1) ? FRAME_FORMAT_A_FIRST_BLOCK_LENGTH : FRAME_FORMAT_A_OTHER_BLOCK_LENGTH; + + if ((targetIndex + blockSize) > expectedMessageSize) { // last block + blockSize = expectedMessageSize - targetIndex; + } + + // FIXME: handle truncated source messages + for (int i = 0; i < blockSize; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + sourceIndex += 4; // Skip 2 bytes CRC => 4 hex chars + targetIndex += blockSize; + } + return result; +} + +/** + * Format: + * [126 bytes message] + [2 bytes CRC] + * [125 bytes message] + [2 bytes CRC] + * (if message length <=126 bytes, only the 1st block exists) + * (last block can be < 125 bytes) + */ +mBusPacket_data mBusPacket_t::removeChecksumsFrameB(const String& payload, uint32_t& checksum) +{ + mBusPacket_data result; + const int payloadLength = payload.length(); + + if (payloadLength < 4) { return result; } + + int sourceIndex = 2; // Starts with "bY" + + // 1st byte contains length of data (excuding 1st byte BUT INCLUDING CRC) + int expectedMessageSize = hexToByte(payload, sourceIndex) + 1; + + if (payloadLength < (2 * expectedMessageSize)) { + return result; + } + + expectedMessageSize -= 2; // CRC of 1st block + + if (expectedMessageSize > 128) { + expectedMessageSize -= 2; // CRC of 2nd block + } + + result.reserve(expectedMessageSize); + + // FIXME: handle truncated source messages + + const int block1Size = expectedMessageSize < 126 ? expectedMessageSize : 126; + + for (int i = 0; i < block1Size; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + sourceIndex += 4; // Skip 2 bytes CRC => 4 hex chars + + if (expectedMessageSize > 126) { + int block2Size = expectedMessageSize - 127; + + if (block2Size > 124) { block2Size = 124; } + + for (int i = 0; i < block2Size; ++i) { + result.push_back(hexToByte(payload, sourceIndex)); + sourceIndex += 2; // 2 hex chars + } + + // [2 bytes CRC] + checksum <<= 8; + checksum ^= hexToUL(payload, sourceIndex, 4); + } + + // remove the checksums and the 1st byte from the actual message length, so that the meaning of this byte is the same as in Frame A + result[0] = static_cast((expectedMessageSize - 1) & 0xff); + + return result; +} diff --git a/src/src/DataStructs/mBusPacket.h b/src/src/DataStructs/mBusPacket.h new file mode 100644 index 000000000..3a6b9ac75 --- /dev/null +++ b/src/src/DataStructs/mBusPacket.h @@ -0,0 +1,120 @@ +#ifndef DATASTRUCTS_MBUSPACKET_H +#define DATASTRUCTS_MBUSPACKET_H + +#include "../../ESPEasy_common.h" + +#include + + +// 0 is sometimes used ("@@@") +// 0xFFFF does not seem to be used ("___") +#define mBus_packet_wildcard_manufacturer 0xFFFF + +// 0 is a valid meter type and 0xFF seems to be reserved +#define mBus_packet_wildcard_metertype 0xFE + +// 0 is a valid serial and 0xFFFFFFFF seems to be reserved +#define mBus_packet_wildcard_serial 0xFFFFFFFE + + +typedef std::vector mBusPacket_data; + +struct mBusPacket_header_t { + mBusPacket_header_t(); + + static String decodeManufacturerID(int id); + static int encodeManufacturerID(const String& id_str); + + String getManufacturerId() const; + + String toString() const; + + uint64_t encode_toUInt64() const; + + void decode_fromUint64(uint64_t encodedValue); + + bool isValid() const; + + bool matchSerial(uint32_t serialNr) const; + + void clear(); + + // Use for stats as key: + union { + uint64_t _encodedValue{}; + struct { + uint64_t _serialNr : 32; + uint64_t _manufacturer : 16; + uint64_t _meterType : 8; + + // Use for filtering + uint64_t _length : 8; + }; + }; +}; + +struct mBusPacket_t { +public: + + bool parse(const String& payload); + + // Get the header of the actual device, not the forwarding device (if present) + const mBusPacket_header_t* getDeviceHeader() const; + + static int16_t decode_LQI_RSSI(uint16_t lqi_rssi, + uint8_t& LQI); + + bool matchSerial(uint32_t serialNr) const; + + uint32_t getDeviceSerial() const; + + String toString() const; + + // 32 bit value used to generate a map key for filtering + // Essentially the XOR of the first 32-bit with the second 32-bit of + // serial, manufacturer, metertype and length. + uint32_t deviceID_to_map_key() const; + + uint32_t deviceID_to_map_key_no_length() const; + +private: + + static uint32_t deviceID_to_map_key(uint64_t id1, uint64_t id2); + + static uint8_t hexToByte(const String& str, + size_t index); + + static mBusPacket_data removeChecksumsFrameA(const String& payload, + uint32_t & checksum); + static mBusPacket_data removeChecksumsFrameB(const String& payload, + uint32_t & checksum); + + bool parseHeaders(const mBusPacket_data& payloadWithoutChecksums); + +public: + + mBusPacket_header_t _deviceId1; + mBusPacket_header_t _deviceId2; + uint16_t _lqi_rssi{}; + + + /* + // Statistics: + // Key: + deviceID1: + - manufacturer + - metertype + - serialnr + + // Value: + - message count + - rssi + - lqi??? + */ + + + // Checksum based on the XOR of all removed checksums from the message + uint32_t _checksum = 0; +}; + +#endif // ifndef DATASTRUCTS_MBUSPACKET_H diff --git a/src/src/DataStructs_templ/SettingsStruct.cpp b/src/src/DataStructs_templ/SettingsStruct.cpp index 5abc49eaa..8c2732e1f 100644 --- a/src/src/DataStructs_templ/SettingsStruct.cpp +++ b/src/src/DataStructs_templ/SettingsStruct.cpp @@ -1,979 +1,1042 @@ -#include "../DataStructs/SettingsStruct.h" - -#include "../../ESPEasy_common.h" - -#ifndef 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" - - -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#endif - - -/* -// VariousBits1 defaults to 0, keep in mind when adding bit lookups. -template -bool SettingsStruct_tmpl::appendUnitToHostname() const { - return !bitRead(VariousBits1, 1); -} - -template -void SettingsStruct_tmpl::appendUnitToHostname(bool value) { - bitWrite(VariousBits1, 1, !value); -} - -template -bool SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused() const { - return bitRead(VariousBits1, 2); -} - -template -void SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused(bool value) { - bitWrite(VariousBits1, 2, value); -} - -template -bool SettingsStruct_tmpl::OldRulesEngine() const { - #ifdef WEBSERVER_NEW_RULES - return !bitRead(VariousBits1, 3); - #else - return true; - #endif -} - -template -void SettingsStruct_tmpl::OldRulesEngine(bool value) { - bitWrite(VariousBits1, 3, !value); -} - -template -bool SettingsStruct_tmpl::ForceWiFi_bg_mode() const { - return bitRead(VariousBits1, 4); -} - -template -void SettingsStruct_tmpl::ForceWiFi_bg_mode(bool value) { - bitWrite(VariousBits1, 4, value); -} - -template -bool SettingsStruct_tmpl::WiFiRestart_connection_lost() const { - return bitRead(VariousBits1, 5); -} - -template -void SettingsStruct_tmpl::WiFiRestart_connection_lost(bool value) { - bitWrite(VariousBits1, 5, value); -} - -template -bool SettingsStruct_tmpl::EcoPowerMode() const { - return bitRead(VariousBits1, 6); -} - -template -void SettingsStruct_tmpl::EcoPowerMode(bool value) { - bitWrite(VariousBits1, 6, value); -} - -template -bool SettingsStruct_tmpl::WifiNoneSleep() const { - return bitRead(VariousBits1, 7); -} - -template -void SettingsStruct_tmpl::WifiNoneSleep(bool value) { - bitWrite(VariousBits1, 7, value); -} - -// Enable send gratuitous ARP by default, so invert the values (default = 0) -template -bool SettingsStruct_tmpl::gratuitousARP() const { - return !bitRead(VariousBits1, 8); -} - -template -void SettingsStruct_tmpl::gratuitousARP(bool value) { - bitWrite(VariousBits1, 8, !value); -} - -template -bool SettingsStruct_tmpl::TolerantLastArgParse() const { - return bitRead(VariousBits1, 9); -} - -template -void SettingsStruct_tmpl::TolerantLastArgParse(bool value) { - bitWrite(VariousBits1, 9, value); -} - -template -bool SettingsStruct_tmpl::SendToHttp_ack() const { - return bitRead(VariousBits1, 10); -} - -template -void SettingsStruct_tmpl::SendToHttp_ack(bool value) { - bitWrite(VariousBits1, 10, value); -} - -template -bool SettingsStruct_tmpl::UseESPEasyNow() const { -#ifdef USES_ESPEASY_NOW - return bitRead(VariousBits1, 11); -#else - return false; -#endif -} - -template -void SettingsStruct_tmpl::UseESPEasyNow(bool value) { -#ifdef USES_ESPEASY_NOW - bitWrite(VariousBits1, 11, value); -#endif -} - -template -bool SettingsStruct_tmpl::IncludeHiddenSSID() const { - return bitRead(VariousBits1, 12); -} - -template -void SettingsStruct_tmpl::IncludeHiddenSSID(bool value) { - bitWrite(VariousBits1, 12, value); -} - -template -bool SettingsStruct_tmpl::UseMaxTXpowerForSending() const { - return bitRead(VariousBits1, 13); -} - -template -void SettingsStruct_tmpl::UseMaxTXpowerForSending(bool value) { - bitWrite(VariousBits1, 13, value); -} - -template -bool SettingsStruct_tmpl::ApDontForceSetup() const { - return bitRead(VariousBits1, 14); -} - -template -void SettingsStruct_tmpl::ApDontForceSetup(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 -bool SettingsStruct_tmpl::JSONBoolWithoutQuotes() const { - return bitRead(VariousBits1, 16); -} - -template -void SettingsStruct_tmpl::JSONBoolWithoutQuotes(bool value) { - bitWrite(VariousBits1, 16, value); -} -*/ - -template -bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(TaskDeviceSendDataFlags[taskIndex], 0); - } - return false; -} - -template -void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value); - } -} -/* -template -bool SettingsStruct_tmpl::DoNotStartAP() const { - return bitRead(VariousBits1, 17); -} - -template -void SettingsStruct_tmpl::DoNotStartAP(bool value) { - bitWrite(VariousBits1, 17, value); -} - - -template -bool SettingsStruct_tmpl::UseAlternativeDeepSleep() const { - return bitRead(VariousBits1, 18); -} - -template -void SettingsStruct_tmpl::UseAlternativeDeepSleep(bool value) { - bitWrite(VariousBits1, 18, value); -} - -template -bool SettingsStruct_tmpl::UseLastWiFiFromRTC() const { - return bitRead(VariousBits1, 19); -} - -template -void SettingsStruct_tmpl::UseLastWiFiFromRTC(bool value) { - bitWrite(VariousBits1, 19, value); -} - -template -bool SettingsStruct_tmpl::EnableTimingStats() const { - return bitRead(VariousBits1, 20); -} - -template -void SettingsStruct_tmpl::EnableTimingStats(bool value) { - bitWrite(VariousBits1, 20, value); -} - -template -bool SettingsStruct_tmpl::AllowTaskValueSetAllPlugins() const { - return bitRead(VariousBits1, 21); -} - -template -void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { - bitWrite(VariousBits1, 21, value); -} - -template -bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { - return bitRead(VariousBits1, 22); -} - -template -void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { - bitWrite(VariousBits1, 22, value); -} - -template -bool SettingsStruct_tmpl::EnableRAMTracking() const { - return bitRead(VariousBits1, 23); -} - -template -void SettingsStruct_tmpl::EnableRAMTracking(bool value) { - bitWrite(VariousBits1, 23, value); -} - -template -bool SettingsStruct_tmpl::EnableRulesCaching() const { - return !bitRead(VariousBits1, 24); -} - -template -void SettingsStruct_tmpl::EnableRulesCaching(bool value) { - bitWrite(VariousBits1, 24, !value); -} - -template -bool SettingsStruct_tmpl::EnableRulesEventReorder() const { - return !bitRead(VariousBits1, 25); -} - -template -void SettingsStruct_tmpl::EnableRulesEventReorder(bool value) { - bitWrite(VariousBits1, 25, !value); -} - -template -bool SettingsStruct_tmpl::AllowOTAUnlimited() const { - return bitRead(VariousBits1, 26); -} - -template -void SettingsStruct_tmpl::AllowOTAUnlimited(bool value) { - bitWrite(VariousBits1, 26, value); -} - -template -bool SettingsStruct_tmpl::SendToHTTP_follow_redirects() const { - return bitRead(VariousBits1, 27); -} - -template -void SettingsStruct_tmpl::SendToHTTP_follow_redirects(bool value) { - bitWrite(VariousBits1, 27, value); -} - -#if FEATURE_AUTO_DARK_MODE -template -uint8_t SettingsStruct_tmpl::getCssMode() const { - return get2BitFromUL(VariousBits1, 28); // Also occupies bit 29! -} - -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 -bool SettingsStruct_tmpl::CheckI2Cdevice() const { // Inverted - return !bitRead(VariousBits1, 30); -} - -template -void SettingsStruct_tmpl::CheckI2Cdevice(bool value) { // Inverted - bitWrite(VariousBits1, 30, !value); -} -#endif // if FEATURE_I2C_DEVICE_CHECK -*/ -/* -template -bool SettingsStruct_tmpl::WaitWiFiConnect() const { - return bitRead(VariousBits2, 0); -} - -template -void SettingsStruct_tmpl::WaitWiFiConnect(bool value) { - bitWrite(VariousBits2, 0, value); -} - - -template -bool SettingsStruct_tmpl::SDK_WiFi_autoreconnect() const { - return bitRead(VariousBits2, 1); -} - -template -void SettingsStruct_tmpl::SDK_WiFi_autoreconnect(bool value) { - bitWrite(VariousBits2, 1, value); -} - - -#if FEATURE_RULES_EASY_COLOR_CODE -template -bool SettingsStruct_tmpl::DisableRulesCodeCompletion() const { - return bitRead(VariousBits2, 2); -} - -template -void SettingsStruct_tmpl::DisableRulesCodeCompletion(bool value) { - bitWrite(VariousBits2, 2, value); -} -#endif // if FEATURE_RULES_EASY_COLOR_CODE -*/ - - -template -bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(VariousTaskBits[taskIndex], 0); - } - return false; -} - -template -void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(VariousTaskBits[taskIndex], 0, value); - } -} - -#if FEATURE_PLUGIN_PRIORITY -template -bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return bitRead(VariousTaskBits[taskIndex], 1); - } - return false; -} - -template -void SettingsStruct_tmpl::setPowerManagerTask(taskIndex_t taskIndex, bool value) { - if (validTaskIndex(taskIndex)) { - bitWrite(VariousTaskBits[taskIndex], 1, value); - } -} - -template -bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return isPowerManagerTask(taskIndex); // Add more? - } - return false; -} -#endif // if FEATURE_PLUGIN_PRIORITY - -template -ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { - return static_cast(ExternalTimeSource >> 1); -} - -template -void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { - uint8_t newValue = static_cast(value) << 1; - if (UseNTP()) { - newValue += 1; - } - ExternalTimeSource = newValue; -} - -template -bool SettingsStruct_tmpl::UseNTP() const { - return bitRead(ExternalTimeSource, 0); -} - -template -void SettingsStruct_tmpl::UseNTP(bool value) { - bitWrite(ExternalTimeSource, 0, value); -} - - -template -void SettingsStruct_tmpl::validate() { - if (UDPPort > 65535) { UDPPort = 0; } - - if ((Latitude < -90.0f) || (Latitude > 90.0f)) { Latitude = 0.0f; } - - if ((Longitude < -180.0f) || (Longitude > 180.0f)) { Longitude = 0.0f; } - - if (VariousBits1 > (1u << 31)) { VariousBits1 = 0; } // FIXME: Check really needed/useful? - ZERO_TERMINATE(Name); - ZERO_TERMINATE(NTPHost); - - if ((I2C_clockSpeed == 0) || (I2C_clockSpeed > 3400000)) { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } - if (WebserverPort == 0) { WebserverPort = 80;} - if (SyslogPort == 0) { SyslogPort = 514; } - - #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - if (console_serial_port == 0 && UseSerial) { - console_serial_port = DEFAULT_CONSOLE_PORT; - // Set default RX/TX pins for Serial0 - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - } -#ifdef ESP8266 - if (console_serial_port == 2) { - // Set default RX/TX pins for Serial0 - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - } else if (console_serial_port == 3) { - // Set default RX/TX pins for Serial0_swapped - console_serial_rxpin = 13; - console_serial_txpin = 15; - } -#endif - #endif -} - -template -bool SettingsStruct_tmpl::networkSettingsEmpty() const { - return IP[0] == 0 && Gateway[0] == 0 && Subnet[0] == 0 && DNS[0] == 0; -} - -template -void SettingsStruct_tmpl::clearNetworkSettings() { - for (uint8_t i = 0; i < 4; ++i) { - IP[i] = 0; - Gateway[i] = 0; - Subnet[i] = 0; - DNS[i] = 0; - ETH_IP[i] = 0; - ETH_Gateway[i] = 0; - ETH_Subnet[i] = 0; - ETH_DNS[i] = 0; - } -} - -template -void SettingsStruct_tmpl::clearTimeSettings() { - ExternalTimeSource = 0; - ZERO_FILL(NTPHost); - TimeZone = 0; - DST = false; - DST_Start = 0; - DST_End = 0; - Latitude = 0.0f; - Longitude = 0.0f; -} - -template -void SettingsStruct_tmpl::clearNotifications() { - for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { - Notification[i] = 0u;// .setInvalid(); - NotificationEnabled[i] = false; - } -} - -template -void SettingsStruct_tmpl::clearControllers() { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - Protocol[i] = 0; - ControllerEnabled[i] = false; - } -} - -template -void SettingsStruct_tmpl::clearTasks() { - for (taskIndex_t task = 0; task < N_TASKS; ++task) { - clearTask(task); - } -} - -template -void SettingsStruct_tmpl::clearLogSettings() { - SyslogLevel = 0; - SerialLogLevel = 0; - WebLogLevel = 0; - SDLogLevel = 0; - SyslogFacility = DEFAULT_SYSLOG_FACILITY; - ZERO_FILL(Syslog_IP); -} - -template -void SettingsStruct_tmpl::clearUnitNameSettings() { - Unit = 0; - ZERO_FILL(Name); - UDPPort = 0; -} - -template -void SettingsStruct_tmpl::clearMisc() { - PID = ESP_PROJECT_PID; - Version = VERSION; - Build = get_build_nr(); - IP_Octet = 0; - Delay = DEFAULT_DELAY; - Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; - Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; - Pin_status_led = DEFAULT_PIN_STATUS_LED; - Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; - Pin_sd_cs = -1; -#ifdef ESP32 - // Ethernet related settings are never used on ESP8266 - ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; - ETH_Pin_mdc = DEFAULT_ETH_PIN_MDC; - ETH_Pin_mdio = DEFAULT_ETH_PIN_MDIO; - ETH_Pin_power = DEFAULT_ETH_PIN_POWER; - ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; - ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; -#endif - NetworkMedium = DEFAULT_NETWORK_MEDIUM; - - I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; - I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; - I2C_Multiplexer_Addr = -1; - memset(I2C_Multiplexer_Channel, -1, sizeof(I2C_Multiplexer_Channel)); - I2C_Multiplexer_ResetPin = -1; - - { - // Here we initialize all data to 0, so this is the ONLY reason why PinBootStates - // can now be directly accessed. - // In all other use cases, use the get and set functions for it. - - ZERO_FILL(PinBootStates); - # ifdef ESP32 - ZERO_FILL(PinBootStates_ESP32); - # endif // ifdef ESP32 - } - BaudRate = DEFAULT_SERIAL_BAUD; - MessageDelay_unused = 0; - deepSleep_wakeTime = 0; - CustomCSS = false; - WDI2CAddress = 0; - UseRules = DEFAULT_USE_RULES; - UseSerial = DEFAULT_USE_SERIAL; - UseSSDP = false; - WireClockStretchLimit = 0; - I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; - WebserverPort = 80; - SyslogPort = 514; - GlobalSync = false; - ConnectionFailuresThreshold = 0; - MQTTRetainFlag_unused = false; - InitSPI = DEFAULT_SPI; - deepSleepOnFail = false; - UseValueLogger = false; - ArduinoOTAEnable = false; - UseRTOSMultitasking = false; - Pin_Reset = -1; - StructSize = sizeof(SettingsStruct_tmpl); - MQTTUseUnitNameAsClientId_unused = 0; - VariousBits1 = 0; - - console_serial_port = DEFAULT_CONSOLE_PORT; - console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - - - OldRulesEngine(DEFAULT_RULES_OLDENGINE); - ForceWiFi_bg_mode(DEFAULT_WIFI_FORCE_BG_MODE); - WiFiRestart_connection_lost(DEFAULT_WIFI_RESTART_WIFI_CONN_LOST); - EcoPowerMode(DEFAULT_ECO_MODE); - WifiNoneSleep(DEFAULT_WIFI_NONE_SLEEP); - gratuitousARP(DEFAULT_GRATUITOUS_ARP); - TolerantLastArgParse(DEFAULT_TOLERANT_LAST_ARG_PARSE); - SendToHttp_ack(DEFAULT_SEND_TO_HTTP_ACK); - #ifdef USES_ESPEASY_NOW - UseESPEasyNow(DEFAULT_USE_ESPEASYNOW); - #else - UseESPEasyNow(false); - #endif - ApDontForceSetup(DEFAULT_AP_DONT_FORCE_SETUP); - DoNotStartAP(DEFAULT_DONT_ALLOW_START_AP); -} - - -template -void SettingsStruct_tmpl::clearTask(taskIndex_t task) { - if (task >= N_TASKS) { return; } - - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { - TaskDeviceID[i][task] = 0u; - TaskDeviceSendData[i][task] = false; - } - TaskDeviceNumber[task] = 0u; //.setInvalid(); - OLD_TaskDeviceID[task] = 0u; // UNUSED: this can be removed - TaskDevicePin1[task] = -1; - TaskDevicePin2[task] = -1; - TaskDevicePin3[task] = -1; - TaskDevicePort[task] = 0u; - TaskDevicePin1PullUp[task] = false; - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGVAR_MAX; ++cv) { - TaskDevicePluginConfig[task][cv] = 0; - } - TaskDevicePin1Inversed[task] = false; - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGFLOATVAR_MAX; ++cv) { - TaskDevicePluginConfigFloat[task][cv] = 0.0f; - } - - for (uint8_t cv = 0; cv < PLUGIN_CONFIGLONGVAR_MAX; ++cv) { - TaskDevicePluginConfigLong[task][cv] = 0; - } - TaskDeviceSendDataFlags[task] = 0u; - VariousTaskBits[task] = 0; - TaskDeviceDataFeed[task] = 0u; - TaskDeviceTimer[task] = 0u; -// TaskDeviceEnabled[task].value = 0u; // Should also clear any temporary flags. - TaskDeviceEnabled[task] = false; - I2C_Multiplexer_Channel[task] = -1; -} - -template -String SettingsStruct_tmpl::getHostname() const { - return this->getHostname(this->appendUnitToHostname()); -} - -template -String SettingsStruct_tmpl::getHostname(bool appendUnit) const { - String hostname = this->getName(); - - if ((this->Unit != 0) && appendUnit) { // only append non-zero unit number - hostname += '_'; - hostname += this->Unit; - } - return hostname; -} - -template -String SettingsStruct_tmpl::getName() const { - String unitname = this->Name; - - return parseTemplate(unitname); -} - -template -bool SettingsStruct_tmpl::getPinBootStateIndex( - int8_t gpio_pin, - int8_t& index_low - # ifdef ESP32 - , int8_t& index_high - # endif // ifdef ESP32 - ) const { - index_low = -1; -# ifdef ESP32 - index_high = -1; - if ((gpio_pin < 0) || !(GPIO_IS_VALID_GPIO(gpio_pin))) { return false; } -# endif // ifdef ESP32 - constexpr int maxStates = NR_ELEMENTS(PinBootStates); - - if (gpio_pin < maxStates) { - index_low = gpio_pin; - return true; - } -# ifdef ESP32 - constexpr int maxStatesesp32 = NR_ELEMENTS(PinBootStates_ESP32); - - index_high = gpio_pin - maxStates; - -# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3)|| defined(ESP32C6) - - // These can all store in the PinBootStates_ESP32 array - return (index_high < maxStatesesp32); - -# elif defined(ESP32S2) - - // First make sure we're not dealing with flash/PSRAM connected pins - if (!((gpio_pin > 21) && (gpio_pin < 33) && (gpio_pin != 26))) { - // Previously used index, to maintain compatibility with previous settings. - - if (index_high >= maxStatesesp32) { - // Now try to fix the bug by inserting the missing ones into unused spots - // This way we don't need to convert existing settings - index_high -= maxStatesesp32; - constexpr int8_t offsetFlashPin = 22 - maxStates; - index_high += offsetFlashPin; - - if (gpio_pin >= 26) { - // Skip index for GPIO 26 - ++index_high; - } - } - return (index_high < maxStatesesp32); - } - -# elif defined(ESP32S3) - - // GPIO 22 ... 32 should never be used. - // Thus: - // - map ... <21> to the beginning of PinBootStates_ESP32 - // - map <33> ... <48> to the end of PinBootStates_ESP32 - if (gpio_pin < 22) { - return true; - } - - if (gpio_pin >= 33) { - index_high = gpio_pin - maxStates + 22 - 33; - return true; - } -# else // if defined(ESP32_CLASSIC) || defined(ESP32C3) - - static_assert(false, "Implement processor architecture"); - -# endif // if defined(ESP32_CLASSIC) || defined(ESP32C3) -# endif // ifdef ESP32 - - return false; -} - -template -PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) const { - if (gpio_pin < 0) return PinBootState::Default_state; -# ifdef ESP8266 - int8_t index_low{}; - - if (getPinBootStateIndex(gpio_pin, index_low)) { - return static_cast(PinBootStates[index_low]); - } - -# endif // ifdef ESP8266 -# ifdef ESP32 - int8_t index_low{}; - int8_t index_high{}; - - if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { - if (index_low >= 0) { - return static_cast(PinBootStates[index_low]); - } - - if (index_high >= 0) { - return static_cast(PinBootStates_ESP32[index_high]); - } - } -# endif // ifdef ESP32 - return PinBootState::Default_state; -} - -template -void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState state) { - if (gpio_pin < 0) return; -# ifdef ESP8266 - int8_t index_low{}; - - if (getPinBootStateIndex(gpio_pin, index_low)) { - PinBootStates[index_low] = static_cast(state); - } -# endif // ifdef ESP8266 - -# ifdef ESP32 - int8_t index_low{}; - int8_t index_high{}; - - if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { - if (index_low >= 0) { - PinBootStates[index_low] = static_cast(state); - } - - if (index_high >= 0) { - PinBootStates_ESP32[index_high] = static_cast(state); - } - } -# endif // ifdef ESP32 -} - -template -bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { - spi_gpios[0] = -1; - spi_gpios[1] = -1; - spi_gpios[2] = -1; - - if (isSPI_valid()) { - # ifdef ESP32 - const SPI_Options_e SPI_selection = static_cast(InitSPI); - - switch (SPI_selection) { - case SPI_Options_e::Vspi_Fspi: - { - spi_gpios[0] = VSPI_FSPI_SCK; - spi_gpios[1] = VSPI_FSPI_MISO; - spi_gpios[2] = VSPI_FSPI_MOSI; - break; - } -#ifdef ESP32_CLASSIC - case SPI_Options_e::Hspi: - { - spi_gpios[0] = HSPI_SCLK; - spi_gpios[1] = HSPI_MISO; - spi_gpios[2] = HSPI_MOSI; - break; - } -#endif - case SPI_Options_e::UserDefined: - { - spi_gpios[0] = SPI_SCLK_pin; - spi_gpios[1] = SPI_MISO_pin; - spi_gpios[2] = SPI_MOSI_pin; - break; - } - case SPI_Options_e::None: - return false; - } - # endif // ifdef ESP32 - # ifdef ESP8266 - spi_gpios[0] = 14; spi_gpios[1] = 12; spi_gpios[2] = 13; - # endif // ifdef ESP8266 - return true; - } - return false; -} - -template -bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { - if (pin < 0) { return false; } - int8_t spi_gpios[3]; - - if (getSPI_pins(spi_gpios)) { - for (uint8_t i = 0; i < 3; ++i) { - if (spi_gpios[i] == pin) { return true; } - } - } - return false; -} - -template -bool SettingsStruct_tmpl::isSPI_valid() const { - if (InitSPI == static_cast(SPI_Options_e::None)) { return false; } - - if (InitSPI == static_cast(SPI_Options_e::UserDefined)) { - return !((SPI_SCLK_pin == -1) || - (SPI_MISO_pin == -1) || - (SPI_MOSI_pin == -1) || - (SPI_SCLK_pin == SPI_MISO_pin) || - (SPI_MISO_pin == SPI_MOSI_pin) || - (SPI_MOSI_pin == SPI_SCLK_pin)); - } - return true; -} - -template -bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { - if (pin < 0) { return false; } - return Pin_i2c_sda == pin || Pin_i2c_scl == pin; -} - -template -bool SettingsStruct_tmpl::isI2CEnabled() const { - return (Pin_i2c_sda != -1) && - (Pin_i2c_scl != -1) && - (I2C_clockSpeed > 0) && - (I2C_clockSpeed_Slow > 0); -} - -template -bool SettingsStruct_tmpl::isEthernetPin(int8_t pin) const { - #if FEATURE_ETHERNET - if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet) { - 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 // if FEATURE_ETHERNET - return false; -} - - -template -bool SettingsStruct_tmpl::isEthernetPinOptional(int8_t pin) const { - #if FEATURE_ETHERNET - if (pin < 0) return false; - if (NetworkMedium == NetworkMedium_t::Ethernet) { - if (isGpioUsedInETHClockMode(ETH_Clock_Mode, pin)) return true; - if (ETH_Pin_mdc == pin) return true; - if (ETH_Pin_mdio == pin) return true; - if (ETH_Pin_power == pin) return true; - } - #endif // if FEATURE_ETHERNET - return false; -} - -template -int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const { - if (validTaskIndex(taskIndex)) { - switch(pinnr) { - case 1: return TaskDevicePin1[taskIndex]; - case 2: return TaskDevicePin2[taskIndex]; - case 3: return TaskDevicePin3[taskIndex]; - } - } - return -1; -} - -template -float SettingsStruct_tmpl::getWiFi_TX_power() const { - return WiFi_TX_power / 4.0f; -} - -template -void SettingsStruct_tmpl::setWiFi_TX_power(float dBm) { - WiFi_TX_power = dBm * 4.0f; -} - -template -pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIndex) const { - if (validTaskIndex(taskIndex)) { - return pluginID_t::toPluginID(TaskDeviceNumber[taskIndex]); - } - return INVALID_PLUGIN_ID; -} - -#endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP +#include "../DataStructs/SettingsStruct.h" + +#include "../../ESPEasy_common.h" + +#ifndef 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" + + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#include "include/esp32x_fixes.h" +#endif + +/* +// VariousBits1 defaults to 0, keep in mind when adding bit lookups. +template +bool SettingsStruct_tmpl::appendUnitToHostname() const { + return !bitRead(VariousBits1, 1); +} + +template +void SettingsStruct_tmpl::appendUnitToHostname(bool value) { + bitWrite(VariousBits1, 1, !value); +} + +template +bool SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused() const { + return bitRead(VariousBits1, 2); +} + +template +void SettingsStruct_tmpl::uniqueMQTTclientIdReconnect_unused(bool value) { + bitWrite(VariousBits1, 2, value); +} + +template +bool SettingsStruct_tmpl::OldRulesEngine() const { + #ifdef WEBSERVER_NEW_RULES + return !bitRead(VariousBits1, 3); + #else + return true; + #endif +} + +template +void SettingsStruct_tmpl::OldRulesEngine(bool value) { + bitWrite(VariousBits1, 3, !value); +} + +template +bool SettingsStruct_tmpl::ForceWiFi_bg_mode() const { + return bitRead(VariousBits1, 4); +} + +template +void SettingsStruct_tmpl::ForceWiFi_bg_mode(bool value) { + bitWrite(VariousBits1, 4, value); +} + +template +bool SettingsStruct_tmpl::WiFiRestart_connection_lost() const { + return bitRead(VariousBits1, 5); +} + +template +void SettingsStruct_tmpl::WiFiRestart_connection_lost(bool value) { + bitWrite(VariousBits1, 5, value); +} + +template +bool SettingsStruct_tmpl::EcoPowerMode() const { + return bitRead(VariousBits1, 6); +} + +template +void SettingsStruct_tmpl::EcoPowerMode(bool value) { + bitWrite(VariousBits1, 6, value); +} + +template +bool SettingsStruct_tmpl::WifiNoneSleep() const { + return bitRead(VariousBits1, 7); +} + +template +void SettingsStruct_tmpl::WifiNoneSleep(bool value) { + bitWrite(VariousBits1, 7, value); +} + +// Enable send gratuitous ARP by default, so invert the values (default = 0) +template +bool SettingsStruct_tmpl::gratuitousARP() const { + return !bitRead(VariousBits1, 8); +} + +template +void SettingsStruct_tmpl::gratuitousARP(bool value) { + bitWrite(VariousBits1, 8, !value); +} + +template +bool SettingsStruct_tmpl::TolerantLastArgParse() const { + return bitRead(VariousBits1, 9); +} + +template +void SettingsStruct_tmpl::TolerantLastArgParse(bool value) { + bitWrite(VariousBits1, 9, value); +} + +template +bool SettingsStruct_tmpl::SendToHttp_ack() const { + return bitRead(VariousBits1, 10); +} + +template +void SettingsStruct_tmpl::SendToHttp_ack(bool value) { + bitWrite(VariousBits1, 10, value); +} + +template +bool SettingsStruct_tmpl::UseESPEasyNow() const { +#ifdef USES_ESPEASY_NOW + return bitRead(VariousBits1, 11); +#else + return false; +#endif +} + +template +void SettingsStruct_tmpl::UseESPEasyNow(bool value) { +#ifdef USES_ESPEASY_NOW + bitWrite(VariousBits1, 11, value); +#endif +} + +template +bool SettingsStruct_tmpl::IncludeHiddenSSID() const { + return bitRead(VariousBits1, 12); +} + +template +void SettingsStruct_tmpl::IncludeHiddenSSID(bool value) { + bitWrite(VariousBits1, 12, value); +} + +template +bool SettingsStruct_tmpl::UseMaxTXpowerForSending() const { + return bitRead(VariousBits1, 13); +} + +template +void SettingsStruct_tmpl::UseMaxTXpowerForSending(bool value) { + bitWrite(VariousBits1, 13, value); +} + +template +bool SettingsStruct_tmpl::ApDontForceSetup() const { + return bitRead(VariousBits1, 14); +} + +template +void SettingsStruct_tmpl::ApDontForceSetup(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 +bool SettingsStruct_tmpl::JSONBoolWithoutQuotes() const { + return bitRead(VariousBits1, 16); +} + +template +void SettingsStruct_tmpl::JSONBoolWithoutQuotes(bool value) { + bitWrite(VariousBits1, 16, value); +} +*/ + +template +bool SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(TaskDeviceSendDataFlags[taskIndex], 0); + } + return false; +} + +template +void SettingsStruct_tmpl::CombineTaskValues_SingleEvent(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(TaskDeviceSendDataFlags[taskIndex], 0, value); + } +} +/* +template +bool SettingsStruct_tmpl::DoNotStartAP() const { + return bitRead(VariousBits1, 17); +} + +template +void SettingsStruct_tmpl::DoNotStartAP(bool value) { + bitWrite(VariousBits1, 17, value); +} + + +template +bool SettingsStruct_tmpl::UseAlternativeDeepSleep() const { + return bitRead(VariousBits1, 18); +} + +template +void SettingsStruct_tmpl::UseAlternativeDeepSleep(bool value) { + bitWrite(VariousBits1, 18, value); +} + +template +bool SettingsStruct_tmpl::UseLastWiFiFromRTC() const { + return bitRead(VariousBits1, 19); +} + +template +void SettingsStruct_tmpl::UseLastWiFiFromRTC(bool value) { + bitWrite(VariousBits1, 19, value); +} + +template +bool SettingsStruct_tmpl::EnableTimingStats() const { + return bitRead(VariousBits1, 20); +} + +template +void SettingsStruct_tmpl::EnableTimingStats(bool value) { + bitWrite(VariousBits1, 20, value); +} + +template +bool SettingsStruct_tmpl::AllowTaskValueSetAllPlugins() const { + return bitRead(VariousBits1, 21); +} + +template +void SettingsStruct_tmpl::AllowTaskValueSetAllPlugins(bool value) { + bitWrite(VariousBits1, 21, value); +} + +template +bool SettingsStruct_tmpl::EnableClearHangingI2Cbus() const { + return bitRead(VariousBits1, 22); +} + +template +void SettingsStruct_tmpl::EnableClearHangingI2Cbus(bool value) { + bitWrite(VariousBits1, 22, value); +} + +template +bool SettingsStruct_tmpl::EnableRAMTracking() const { + return bitRead(VariousBits1, 23); +} + +template +void SettingsStruct_tmpl::EnableRAMTracking(bool value) { + bitWrite(VariousBits1, 23, value); +} + +template +bool SettingsStruct_tmpl::EnableRulesCaching() const { + return !bitRead(VariousBits1, 24); +} + +template +void SettingsStruct_tmpl::EnableRulesCaching(bool value) { + bitWrite(VariousBits1, 24, !value); +} + +template +bool SettingsStruct_tmpl::EnableRulesEventReorder() const { + return !bitRead(VariousBits1, 25); +} + +template +void SettingsStruct_tmpl::EnableRulesEventReorder(bool value) { + bitWrite(VariousBits1, 25, !value); +} + +template +bool SettingsStruct_tmpl::AllowOTAUnlimited() const { + return bitRead(VariousBits1, 26); +} + +template +void SettingsStruct_tmpl::AllowOTAUnlimited(bool value) { + bitWrite(VariousBits1, 26, value); +} + +template +bool SettingsStruct_tmpl::SendToHTTP_follow_redirects() const { + return bitRead(VariousBits1, 27); +} + +template +void SettingsStruct_tmpl::SendToHTTP_follow_redirects(bool value) { + bitWrite(VariousBits1, 27, value); +} + +#if FEATURE_AUTO_DARK_MODE +template +uint8_t SettingsStruct_tmpl::getCssMode() const { + return get2BitFromUL(VariousBits1, 28); // Also occupies bit 29! +} + +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 +bool SettingsStruct_tmpl::CheckI2Cdevice() const { // Inverted + return !bitRead(VariousBits1, 30); +} + +template +void SettingsStruct_tmpl::CheckI2Cdevice(bool value) { // Inverted + bitWrite(VariousBits1, 30, !value); +} +#endif // if FEATURE_I2C_DEVICE_CHECK +*/ +/* +template +bool SettingsStruct_tmpl::WaitWiFiConnect() const { + return bitRead(VariousBits2, 0); +} + +template +void SettingsStruct_tmpl::WaitWiFiConnect(bool value) { + bitWrite(VariousBits2, 0, value); +} + + +template +bool SettingsStruct_tmpl::SDK_WiFi_autoreconnect() const { + return bitRead(VariousBits2, 1); +} + +template +void SettingsStruct_tmpl::SDK_WiFi_autoreconnect(bool value) { + bitWrite(VariousBits2, 1, value); +} + + +#if FEATURE_RULES_EASY_COLOR_CODE +template +bool SettingsStruct_tmpl::DisableRulesCodeCompletion() const { + return bitRead(VariousBits2, 2); +} + +template +void SettingsStruct_tmpl::DisableRulesCodeCompletion(bool value) { + bitWrite(VariousBits2, 2, value); +} +#endif // if FEATURE_RULES_EASY_COLOR_CODE + +#if FEATURE_TARSTREAM_SUPPORT +template +bool SettingsStruct_tmpl::DisableSaveConfigAsTar() const { + return bitRead(VariousBits2, 3); // Using bit 4 now... +} + +template +void SettingsStruct_tmpl::DisableSaveConfigAsTar(bool value) { + bitWrite(VariousBits2, 3, value); // Using bit 4 now... +} +#endif // if FEATURE_TARSTREAM_SUPPORT +*/ + + +template +bool SettingsStruct_tmpl::isTaskEnableReadonly(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(VariousTaskBits[taskIndex], 0); + } + return false; +} + +template +void SettingsStruct_tmpl::setTaskEnableReadonly(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(VariousTaskBits[taskIndex], 0, value); + } +} + +#if FEATURE_PLUGIN_PRIORITY +template +bool SettingsStruct_tmpl::isPowerManagerTask(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return bitRead(VariousTaskBits[taskIndex], 1); + } + return false; +} + +template +void SettingsStruct_tmpl::setPowerManagerTask(taskIndex_t taskIndex, bool value) { + if (validTaskIndex(taskIndex)) { + bitWrite(VariousTaskBits[taskIndex], 1, value); + } +} + +template +bool SettingsStruct_tmpl::isPriorityTask(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + return isPowerManagerTask(taskIndex); // Add more? + } + return false; +} +#endif // if FEATURE_PLUGIN_PRIORITY + +template +ExtTimeSource_e SettingsStruct_tmpl::ExtTimeSource() const { + return static_cast(ExternalTimeSource >> 1); +} + +template +void SettingsStruct_tmpl::ExtTimeSource(ExtTimeSource_e value) { + uint8_t newValue = static_cast(value) << 1; + if (UseNTP()) { + newValue += 1; + } + ExternalTimeSource = newValue; +} + +template +bool SettingsStruct_tmpl::UseNTP() const { + return bitRead(ExternalTimeSource, 0); +} + +template +void SettingsStruct_tmpl::UseNTP(bool value) { + bitWrite(ExternalTimeSource, 0, value); +} + + +template +void SettingsStruct_tmpl::validate() { + if (UDPPort > 65535) { UDPPort = 0; } + + if ((Latitude < -90.0f) || (Latitude > 90.0f)) { Latitude = 0.0f; } + + if ((Longitude < -180.0f) || (Longitude > 180.0f)) { Longitude = 0.0f; } + + if (getVariousBits1() > (1u << 31)) { setVariousBits1(0); } // FIXME: Check really needed/useful? + ZERO_TERMINATE(Name); + ZERO_TERMINATE(NTPHost); + + if ((I2C_clockSpeed == 0) || (I2C_clockSpeed > 3400000)) { I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } + if (WebserverPort == 0) { WebserverPort = 80;} + if (SyslogPort == 0) { SyslogPort = 514; } + + #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + if (console_serial_port == 0 && UseSerial) { + console_serial_port = DEFAULT_CONSOLE_PORT; + // Set default RX/TX pins for Serial0 + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + } +#ifdef ESP8266 + if (console_serial_port == 2) { + // Set default RX/TX pins for Serial0 + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + } else if (console_serial_port == 3) { + // Set default RX/TX pins for Serial0_swapped + console_serial_rxpin = 13; + console_serial_txpin = 15; + } +#endif + #endif +} + +template +bool SettingsStruct_tmpl::networkSettingsEmpty() const { + return IP[0] == 0 && Gateway[0] == 0 && Subnet[0] == 0 && DNS[0] == 0; +} + +template +void SettingsStruct_tmpl::clearNetworkSettings() { + for (uint8_t i = 0; i < 4; ++i) { + IP[i] = 0; + Gateway[i] = 0; + Subnet[i] = 0; + DNS[i] = 0; + ETH_IP[i] = 0; + ETH_Gateway[i] = 0; + ETH_Subnet[i] = 0; + ETH_DNS[i] = 0; + } +} + +template +void SettingsStruct_tmpl::clearTimeSettings() { + ExternalTimeSource = 0; + ZERO_FILL(NTPHost); + TimeZone = 0; + DST = false; + DST_Start = 0; + DST_End = 0; + Latitude = 0.0f; + Longitude = 0.0f; +} + +template +void SettingsStruct_tmpl::clearNotifications() { + for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { + Notification[i] = 0u;// .setInvalid(); + NotificationEnabled[i] = false; + } +} + +template +void SettingsStruct_tmpl::clearControllers() { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + Protocol[i] = 0; + ControllerEnabled[i] = false; + } +} + +template +void SettingsStruct_tmpl::clearTasks() { + for (taskIndex_t task = 0; task < N_TASKS; ++task) { + clearTask(task); + } +} + +template +void SettingsStruct_tmpl::clearLogSettings() { + SyslogLevel = 0; + SerialLogLevel = 0; + WebLogLevel = 0; + SDLogLevel = 0; + SyslogFacility = DEFAULT_SYSLOG_FACILITY; + ZERO_FILL(Syslog_IP); +} + +template +void SettingsStruct_tmpl::clearUnitNameSettings() { + Unit = 0; + ZERO_FILL(Name); + UDPPort = 0; +} + +template +void SettingsStruct_tmpl::clearMisc() { + PID = ESP_PROJECT_PID; + Version = VERSION; + Build = get_build_nr(); + IP_Octet = 0; + Delay = DEFAULT_DELAY; + Pin_i2c_sda = DEFAULT_PIN_I2C_SDA; + Pin_i2c_scl = DEFAULT_PIN_I2C_SCL; + Pin_status_led = DEFAULT_PIN_STATUS_LED; + Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; + Pin_sd_cs = -1; +#ifdef ESP32 + // Ethernet related settings are never used on ESP8266 + ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; + ETH_Pin_mdc_cs = DEFAULT_ETH_PIN_MDC; + ETH_Pin_mdio_irq = DEFAULT_ETH_PIN_MDIO; + ETH_Pin_power_rst = DEFAULT_ETH_PIN_POWER; + ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; + ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; +#endif + NetworkMedium = DEFAULT_NETWORK_MEDIUM; + + I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; + I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; + I2C_Multiplexer_Addr = -1; + memset(I2C_Multiplexer_Channel, -1, sizeof(I2C_Multiplexer_Channel)); + I2C_Multiplexer_ResetPin = -1; + + { + // Here we initialize all data to 0, so this is the ONLY reason why PinBootStates + // can now be directly accessed. + // In all other use cases, use the get and set functions for it. + + ZERO_FILL(PinBootStates); + # ifdef ESP32 + ZERO_FILL(PinBootStates_ESP32); + # endif // ifdef ESP32 + } + BaudRate = DEFAULT_SERIAL_BAUD; + MessageDelay_unused = 0; + deepSleep_wakeTime = 0; + CustomCSS = false; + WDI2CAddress = 0; + UseRules = DEFAULT_USE_RULES; + UseSerial = DEFAULT_USE_SERIAL; + UseSSDP = false; + WireClockStretchLimit = 0; + I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; + WebserverPort = 80; + SyslogPort = 514; + GlobalSync = false; + ConnectionFailuresThreshold = 0; + MQTTRetainFlag_unused = false; + InitSPI = DEFAULT_SPI; + deepSleepOnFail = false; + UseValueLogger = false; + ArduinoOTAEnable = false; + UseRTOSMultitasking = false; + Pin_Reset = -1; + StructSize = sizeof(SettingsStruct_tmpl); + MQTTUseUnitNameAsClientId_unused = 0; + setVariousBits1(0); + setVariousBits2(0); + + console_serial_port = DEFAULT_CONSOLE_PORT; + console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + + OldRulesEngine(DEFAULT_RULES_OLDENGINE); + ForceWiFi_bg_mode(DEFAULT_WIFI_FORCE_BG_MODE); + WiFiRestart_connection_lost(DEFAULT_WIFI_RESTART_WIFI_CONN_LOST); + EcoPowerMode(DEFAULT_ECO_MODE); + WifiNoneSleep(DEFAULT_WIFI_NONE_SLEEP); + gratuitousARP(DEFAULT_GRATUITOUS_ARP); + TolerantLastArgParse(DEFAULT_TOLERANT_LAST_ARG_PARSE); + SendToHttp_ack(DEFAULT_SEND_TO_HTTP_ACK); + #ifdef USES_ESPEASY_NOW + UseESPEasyNow(DEFAULT_USE_ESPEASYNOW); + #else + UseESPEasyNow(false); + #endif + ApDontForceSetup(DEFAULT_AP_DONT_FORCE_SETUP); + DoNotStartAP(DEFAULT_DONT_ALLOW_START_AP); +} + + +template +void SettingsStruct_tmpl::clearTask(taskIndex_t task) { + if (task >= N_TASKS) { return; } + + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { + TaskDeviceID[i][task] = 0u; + TaskDeviceSendData[i][task] = false; + } + TaskDeviceNumber[task] = 0u; //.setInvalid(); + OLD_TaskDeviceID[task] = 0u; // UNUSED: this can be removed + TaskDevicePin1[task] = -1; + TaskDevicePin2[task] = -1; + TaskDevicePin3[task] = -1; + TaskDevicePort[task] = 0u; + TaskDevicePin1PullUp[task] = false; + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGVAR_MAX; ++cv) { + TaskDevicePluginConfig[task][cv] = 0; + } + TaskDevicePin1Inversed[task] = false; + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGFLOATVAR_MAX; ++cv) { + TaskDevicePluginConfigFloat[task][cv] = 0.0f; + } + + for (uint8_t cv = 0; cv < PLUGIN_CONFIGLONGVAR_MAX; ++cv) { + TaskDevicePluginConfigLong[task][cv] = 0; + } + TaskDeviceSendDataFlags[task] = 0u; + VariousTaskBits[task] = 0; + TaskDeviceDataFeed[task] = 0u; + TaskDeviceTimer[task] = 0u; +// TaskDeviceEnabled[task].value = 0u; // Should also clear any temporary flags. + TaskDeviceEnabled[task] = false; + I2C_Multiplexer_Channel[task] = -1; +} + +template +String SettingsStruct_tmpl::getHostname() const { + return this->getHostname(this->appendUnitToHostname()); +} + +template +String SettingsStruct_tmpl::getHostname(bool appendUnit) const { + String hostname = this->getName(); + + if ((this->Unit != 0) && appendUnit) { // only append non-zero unit number + hostname += '_'; + hostname += this->Unit; + } + return hostname; +} + +template +String SettingsStruct_tmpl::getName() const { + String unitname = this->Name; + + return parseTemplate(unitname); +} + +template +bool SettingsStruct_tmpl::getPinBootStateIndex( + int8_t gpio_pin, + int8_t& index_low + # ifdef ESP32 + , int8_t& index_high + # endif // ifdef ESP32 + ) const { + index_low = -1; +# ifdef ESP32 + index_high = -1; + if ((gpio_pin < 0) || !(GPIO_IS_VALID_GPIO(gpio_pin))) { return false; } +# endif // ifdef ESP32 + constexpr int maxStates = NR_ELEMENTS(PinBootStates); + + if (gpio_pin < maxStates) { + index_low = gpio_pin; + return true; + } +# ifdef ESP32 + constexpr int maxStatesesp32 = NR_ELEMENTS(PinBootStates_ESP32); + + index_high = gpio_pin - maxStates; + +# if defined(ESP32_CLASSIC) || defined(ESP32C2) || defined(ESP32C3)|| defined(ESP32C6) + + // These can all store in the PinBootStates_ESP32 array + return (index_high < maxStatesesp32); + +# elif defined(ESP32S2) + + // First make sure we're not dealing with flash/PSRAM connected pins + if (!((gpio_pin > 21) && (gpio_pin < 33) && (gpio_pin != 26))) { + // Previously used index, to maintain compatibility with previous settings. + + if (index_high >= maxStatesesp32) { + // Now try to fix the bug by inserting the missing ones into unused spots + // This way we don't need to convert existing settings + index_high -= maxStatesesp32; + constexpr int8_t offsetFlashPin = 22 - maxStates; + index_high += offsetFlashPin; + + if (gpio_pin >= 26) { + // Skip index for GPIO 26 + ++index_high; + } + } + return (index_high < maxStatesesp32); + } + +# elif defined(ESP32S3) + + // GPIO 22 ... 32 should never be used. + // Thus: + // - map ... <21> to the beginning of PinBootStates_ESP32 + // - map <33> ... <48> to the end of PinBootStates_ESP32 + if (gpio_pin < 22) { + return true; + } + + if (gpio_pin >= 33) { + index_high = gpio_pin - maxStates + 22 - 33; + return true; + } +# else // if defined(ESP32_CLASSIC) || defined(ESP32C3) + + static_assert(false, "Implement processor architecture"); + +# endif // if defined(ESP32_CLASSIC) || defined(ESP32C3) +# endif // ifdef ESP32 + + return false; +} + +template +PinBootState SettingsStruct_tmpl::getPinBootState(int8_t gpio_pin) const { + if (gpio_pin < 0) return PinBootState::Default_state; +# ifdef ESP8266 + int8_t index_low{}; + + if (getPinBootStateIndex(gpio_pin, index_low)) { + return static_cast(PinBootStates[index_low]); + } + +# endif // ifdef ESP8266 +# ifdef ESP32 + int8_t index_low{}; + int8_t index_high{}; + + if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { + if (index_low >= 0) { + return static_cast(PinBootStates[index_low]); + } + + if (index_high >= 0) { + return static_cast(PinBootStates_ESP32[index_high]); + } + } +# endif // ifdef ESP32 + return PinBootState::Default_state; +} + +template +void SettingsStruct_tmpl::setPinBootState(int8_t gpio_pin, PinBootState state) { + if (gpio_pin < 0) return; +# ifdef ESP8266 + int8_t index_low{}; + + if (getPinBootStateIndex(gpio_pin, index_low)) { + PinBootStates[index_low] = static_cast(state); + } +# endif // ifdef ESP8266 + +# ifdef ESP32 + int8_t index_low{}; + int8_t index_high{}; + + if (getPinBootStateIndex(gpio_pin, index_low, index_high)) { + if (index_low >= 0) { + PinBootStates[index_low] = static_cast(state); + } + + if (index_high >= 0) { + PinBootStates_ESP32[index_high] = static_cast(state); + } + } +# endif // ifdef ESP32 +} + +template +bool SettingsStruct_tmpl::getSPI_pins(int8_t spi_gpios[3]) const { + spi_gpios[0] = -1; + spi_gpios[1] = -1; + spi_gpios[2] = -1; + + if (isSPI_valid()) { + # ifdef ESP32 + const SPI_Options_e SPI_selection = static_cast(InitSPI); + + switch (SPI_selection) { + case SPI_Options_e::Vspi_Fspi: + { + spi_gpios[0] = VSPI_FSPI_SCK; + spi_gpios[1] = VSPI_FSPI_MISO; + spi_gpios[2] = VSPI_FSPI_MOSI; + break; + } +#ifdef ESP32_CLASSIC + case SPI_Options_e::Hspi: + { + spi_gpios[0] = HSPI_SCLK; + spi_gpios[1] = HSPI_MISO; + spi_gpios[2] = HSPI_MOSI; + break; + } +#endif + case SPI_Options_e::UserDefined: + { + spi_gpios[0] = SPI_SCLK_pin; + spi_gpios[1] = SPI_MISO_pin; + spi_gpios[2] = SPI_MOSI_pin; + break; + } + case SPI_Options_e::None: + return false; + } + # endif // ifdef ESP32 + # ifdef ESP8266 + spi_gpios[0] = 14; spi_gpios[1] = 12; spi_gpios[2] = 13; + # endif // ifdef ESP8266 + return true; + } + return false; +} + +#ifdef ESP32 +template +spi_host_device_t SettingsStruct_tmpl::getSPI_host() const +{ + if (isSPI_valid()) { + const SPI_Options_e SPI_selection = static_cast(InitSPI); + switch (SPI_selection) { + case SPI_Options_e::Vspi_Fspi: + { + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + } +#ifdef ESP32_CLASSIC + case SPI_Options_e::Hspi: + { + return static_cast(HSPI_HOST); + } +#endif + case SPI_Options_e::UserDefined: + { + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + } + case SPI_Options_e::None: + break; + } + + } + #if ESP_IDF_VERSION_MAJOR < 5 + #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + return static_cast(FSPI_HOST); + #else + return static_cast(VSPI_HOST); + #endif + #else + return spi_host_device_t::SPI_HOST_MAX; + #endif +} +#endif + + +template +bool SettingsStruct_tmpl::isSPI_pin(int8_t pin) const { + if (pin < 0) { return false; } + int8_t spi_gpios[3]; + + if (getSPI_pins(spi_gpios)) { + for (uint8_t i = 0; i < 3; ++i) { + if (spi_gpios[i] == pin) { return true; } + } + } + return false; +} + +template +bool SettingsStruct_tmpl::isSPI_valid() const { + if (InitSPI == static_cast(SPI_Options_e::None)) { return false; } + + if (InitSPI == static_cast(SPI_Options_e::UserDefined)) { + return !((SPI_SCLK_pin == -1) || + (SPI_MISO_pin == -1) || + (SPI_MOSI_pin == -1) || + (SPI_SCLK_pin == SPI_MISO_pin) || + (SPI_MISO_pin == SPI_MOSI_pin) || + (SPI_MOSI_pin == SPI_SCLK_pin)); + } + return true; +} + +template +bool SettingsStruct_tmpl::isI2C_pin(int8_t pin) const { + if (pin < 0) { return false; } + return Pin_i2c_sda == pin || Pin_i2c_scl == pin; +} + +template +bool SettingsStruct_tmpl::isI2CEnabled() const { + return (Pin_i2c_sda != -1) && + (Pin_i2c_scl != -1) && + (I2C_clockSpeed > 0) && + (I2C_clockSpeed_Slow > 0); +} + +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 (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 // if FEATURE_ETHERNET + return false; +} + + +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 (ETH_Pin_mdc_cs == pin) return true; + if (ETH_Pin_mdio_irq == pin) return true; + if (ETH_Pin_power_rst == pin) return true; + } + #endif // if FEATURE_ETHERNET + return false; +} + +template +int8_t SettingsStruct_tmpl::getTaskDevicePin(taskIndex_t taskIndex, uint8_t pinnr) const { + if (validTaskIndex(taskIndex)) { + switch(pinnr) { + case 1: return TaskDevicePin1[taskIndex]; + case 2: return TaskDevicePin2[taskIndex]; + case 3: return TaskDevicePin3[taskIndex]; + } + } + return -1; +} + +template +float SettingsStruct_tmpl::getWiFi_TX_power() const { + return WiFi_TX_power / 4.0f; +} + +template +void SettingsStruct_tmpl::setWiFi_TX_power(float dBm) { + WiFi_TX_power = dBm * 4.0f; +} + +template +pluginID_t SettingsStruct_tmpl::getPluginID_for_task(taskIndex_t taskIndex) const { + if (validTaskIndex(taskIndex)) { + const uint8_t tdn = TaskDeviceNumber[taskIndex]; + if (tdn > 0) { + return pluginID_t::toPluginID(tdn); + } + } + return INVALID_PLUGIN_ID; +} + +#endif // ifndef DATASTRUCTS_SETTINGSSTRUCT_CPP diff --git a/src/src/DataTypes/DeviceModel.h b/src/src/DataTypes/DeviceModel.h index 18c994d55..3af8f1f45 100644 --- a/src/src/DataTypes/DeviceModel.h +++ b/src/src/DataTypes/DeviceModel.h @@ -21,11 +21,13 @@ enum class DeviceModel : uint8_t { DeviceModel_Sonoff_POWr2, DeviceModel_Shelly1, DeviceModel_ShellyPLUG_S, +# if CONFIG_ETH_USE_ESP32_EMAC DeviceModel_Olimex_ESP32_PoE, DeviceModel_Olimex_ESP32_EVB, DeviceModel_Olimex_ESP32_GATEWAY, DeviceModel_wESP32, DeviceModel_WT32_ETH01, +#endif DeviceModel_MAX diff --git a/src/src/DataTypes/ESPEasyFileType.cpp b/src/src/DataTypes/ESPEasyFileType.cpp index afb7268c2..a8b3bf4bc 100644 --- a/src/src/DataTypes/ESPEasyFileType.cpp +++ b/src/src/DataTypes/ESPEasyFileType.cpp @@ -2,8 +2,12 @@ #include "../../ESPEasy_common.h" +#include "../CustomBuild/StorageLayout.h" + #include "../Globals/ResetFactoryDefaultPref.h" +#include "../Helpers/StringConverter.h" + bool matchFileType(const String& filename, FileType::Enum filetype) { if (filename.startsWith(F("/"))) { @@ -14,10 +18,27 @@ bool matchFileType(const String& filename, FileType::Enum filetype) bool isProtectedFileType(const String& filename) { - return matchFileType(filename, FileType::CONFIG_DAT) || - matchFileType(filename, FileType::SECURITY_DAT) || - matchFileType(filename, FileType::NOTIFICATION_DAT) || - matchFileType(filename, FileType::PROVISIONING_DAT); + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + bool isTaskSpecificConfig = false; + const String fname = filename.substring(filename.startsWith(F("/")) ? 1 : 0); + const String mask = F(DAT_TASKS_CUSTOM_EXTENSION_FILEMASK); + const int8_t mPerc = mask.indexOf('%'); + + if ((mPerc > -1) && fname.startsWith(mask.substring(0, mPerc))) { + for (uint8_t n = 0; n < TASKS_MAX && !isTaskSpecificConfig; ++n) { + isTaskSpecificConfig |= (fname.equalsIgnoreCase(strformat(mask, n + 1))); + } + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + + return + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + isTaskSpecificConfig || // Support for extcfgNN.dat + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + matchFileType(filename, FileType::CONFIG_DAT) || + matchFileType(filename, FileType::SECURITY_DAT) || + matchFileType(filename, FileType::NOTIFICATION_DAT) || + matchFileType(filename, FileType::PROVISIONING_DAT); } const __FlashStringHelper* getFileName(FileType::Enum filetype) { diff --git a/src/src/DataTypes/ESPEasyFileType.h b/src/src/DataTypes/ESPEasyFileType.h index c80be67fc..48dbf5567 100644 --- a/src/src/DataTypes/ESPEasyFileType.h +++ b/src/src/DataTypes/ESPEasyFileType.h @@ -1,33 +1,33 @@ -#ifndef DATATYPES_ESPEASYFILETYPE_H -#define DATATYPES_ESPEASYFILETYPE_H - -#include "../../ESPEasy_common.h" - -struct FileType { - enum Enum : short { - CONFIG_DAT, - SECURITY_DAT, - RULES_TXT, - NOTIFICATION_DAT, - PROVISIONING_DAT, - - MAX_FILETYPE - }; -}; - -bool matchFileType(const String& filename, FileType::Enum filetype); - -bool isProtectedFileType(const String& filename); - -const __FlashStringHelper * getFileName(FileType::Enum filetype); -String getFileName(FileType::Enum filetype, - unsigned int filenr); - -// filenr = 0...3 for files rules1.txt ... rules4.txt -String getRulesFileName(unsigned int filenr); - -bool getDownloadFiletypeChecked(FileType::Enum filetype, - unsigned int filenr); - - +#ifndef DATATYPES_ESPEASYFILETYPE_H +#define DATATYPES_ESPEASYFILETYPE_H + +#include "../../ESPEasy_common.h" + +struct FileType { + enum Enum : short { + CONFIG_DAT, + SECURITY_DAT, + RULES_TXT, + NOTIFICATION_DAT, + PROVISIONING_DAT, + + MAX_FILETYPE + }; +}; + +bool matchFileType(const String& filename, FileType::Enum filetype); + +bool isProtectedFileType(const String& filename); + +const __FlashStringHelper * getFileName(FileType::Enum filetype); +String getFileName(FileType::Enum filetype, + unsigned int filenr); + +// filenr = 0...3 for files rules1.txt ... rules4.txt +String getRulesFileName(unsigned int filenr); + +bool getDownloadFiletypeChecked(FileType::Enum filetype, + unsigned int filenr); + + #endif // ifndef DATATYPES_ESPEASYFILETYPE_H \ No newline at end of file diff --git a/src/src/DataTypes/ESPEasyTimeSource.cpp b/src/src/DataTypes/ESPEasyTimeSource.cpp index faa9f4c04..37427c125 100644 --- a/src/src/DataTypes/ESPEasyTimeSource.cpp +++ b/src/src/DataTypes/ESPEasyTimeSource.cpp @@ -1,102 +1,112 @@ -#include "../DataTypes/ESPEasyTimeSource.h" - -#include "../../ESPEasy_common.h" - -const __FlashStringHelper* toString(timeSource_t timeSource) -{ - switch (timeSource) { - case timeSource_t::GPS_PPS_time_source: return F("GPS PPS"); - case timeSource_t::GPS_time_source: return F("GPS"); - case timeSource_t::NTP_time_source: return F("NTP"); - case timeSource_t::Manual_set: return F("Manual"); - case timeSource_t::ESP_now_peer: return F(ESPEASY_NOW_NAME " peer"); - case timeSource_t::ESPEASY_p2p_UDP: return F("ESPEasy p2p"); - case timeSource_t::External_RTC_time_source: return F("Ext. RTC at boot"); - case timeSource_t::Restore_RTC_time_source: return F("RTC at boot"); - case timeSource_t::No_time_source: return F("No time set"); - } - return F("Unknown"); -} - -bool isExternalTimeSource(timeSource_t timeSource) -{ - // timeSource_t::ESP_now_peer or timeSource_t::ESPEASY_p2p_UDP - // should NOT be considered "external" - // It may be an unreliable source if no other source is present in the network. - - switch (timeSource) { - case timeSource_t::GPS_PPS_time_source: - case timeSource_t::GPS_time_source: - case timeSource_t::NTP_time_source: - case timeSource_t::External_RTC_time_source: - case timeSource_t::Manual_set: - return true; - default: - return false; - } -} - -// Typical time wander for ESP nodes is 0.04 ms/sec -// Meaning per 25 sec, the time may wander 1 msec. -#define TIME_WANDER_FACTOR 25000 - -unsigned long computeExpectedWander(timeSource_t timeSource, - unsigned long timePassedSinceLastTimeSync) -{ - unsigned long expectedWander_ms = timePassedSinceLastTimeSync / TIME_WANDER_FACTOR; - - switch (timeSource) { - case timeSource_t::GPS_PPS_time_source: - { - expectedWander_ms += 1; - break; - } - case timeSource_t::GPS_time_source: - { - // Not sure about the wander here, as GPS does not have a drift. - // But the moment a message is received from a second's start may differ. - expectedWander_ms += 10; - break; - } - case timeSource_t::NTP_time_source: - { - // Typical time needed to perform a NTP request to an online NTP server - expectedWander_ms += 30; - break; - } - - case timeSource_t::ESP_now_peer: - case timeSource_t::ESPEASY_p2p_UDP: - { - // expected wander is 144 per hour. - // Using a 'penalty' of 1000 makes it only preferrable over NTP after +/- 7 hour. - expectedWander_ms += 1000; - break; - } - - case timeSource_t::External_RTC_time_source: - { - // Will be off by +/- 500 msec - expectedWander_ms += 500; - break; - } - case timeSource_t::Restore_RTC_time_source: - { - // May be off by the time needed to reboot + some time since the last update of the RTC - // If a reboot was due to a watchdog reset, then it will be an additional 2 - 6 seconds. - expectedWander_ms += 5000; - break; - } - case timeSource_t::Manual_set: - { - expectedWander_ms += 10000; - break; - } - case timeSource_t::No_time_source: - { - // Cannot sync from it. - return 1 << 30; - } - } - return expectedWander_ms; -} +#include "../DataTypes/ESPEasyTimeSource.h" + +#include "../../ESPEasy_common.h" + +const __FlashStringHelper* toString(timeSource_t timeSource) +{ + switch (timeSource) { + case timeSource_t::GPS_PPS_time_source: return F("GPS PPS"); + case timeSource_t::GPS_time_source: return F("GPS"); + case timeSource_t::NTP_time_source: return F("NTP"); + case timeSource_t::Manual_set: return F("Manual"); + case timeSource_t::ESP_now_peer: return F(ESPEASY_NOW_NAME " peer"); + case timeSource_t::ESPEASY_p2p_UDP: return F("ESPEasy p2p"); + case timeSource_t::External_RTC_time_source: return F("Ext. RTC at boot"); + case timeSource_t::Restore_RTC_time_source: return F("RTC at boot"); + case timeSource_t::No_time_source: return F("No time set"); + } + return F("Unknown"); +} + +bool isExternalTimeSource(timeSource_t timeSource) +{ + // timeSource_t::ESP_now_peer or timeSource_t::ESPEASY_p2p_UDP + // should NOT be considered "external" + // It may be an unreliable source if no other source is present in the network. + + switch (timeSource) { + case timeSource_t::GPS_PPS_time_source: + case timeSource_t::GPS_time_source: + case timeSource_t::NTP_time_source: + case timeSource_t::External_RTC_time_source: + case timeSource_t::Manual_set: + return true; + default: + return false; + } +} + +// Typical time wander for ESP nodes is 0.04 ms/sec +// Meaning per 25 sec, the time may wander 1 msec. +#define TIME_WANDER_FACTOR 25000 + +uint32_t updateExpectedWander( + int32_t current_wander, + uint32_t timePassedSinceLastTimeSync) +{ + if (current_wander < 0) { + return current_wander; + } + return current_wander + (timePassedSinceLastTimeSync / TIME_WANDER_FACTOR); +} + +uint32_t computeExpectedWander(timeSource_t timeSource, + uint32_t timePassedSinceLastTimeSync) +{ + uint32_t expectedWander_ms = timePassedSinceLastTimeSync / TIME_WANDER_FACTOR; + + switch (timeSource) { + case timeSource_t::GPS_PPS_time_source: + { + expectedWander_ms += 1; + break; + } + case timeSource_t::GPS_time_source: + { + // Not sure about the wander here, as GPS does not have a drift. + // But the moment a message is received from a second's start may differ. + expectedWander_ms += 10; + break; + } + case timeSource_t::NTP_time_source: + { + // Typical time needed to perform a NTP request to an online NTP server + expectedWander_ms += 30; + break; + } + + case timeSource_t::ESP_now_peer: + case timeSource_t::ESPEASY_p2p_UDP: + { + // expected wander is 144 per hour. + // Using a 'penalty' of 1000 makes it only preferrable over NTP after +/- 7 hour. + expectedWander_ms += 1000; + break; + } + + case timeSource_t::External_RTC_time_source: + { + // Will be off by +/- 500 msec + expectedWander_ms += 500; + break; + } + case timeSource_t::Restore_RTC_time_source: + { + // May be off by the time needed to reboot + some time since the last update of the RTC + // If a reboot was due to a watchdog reset, then it will be an additional 2 - 6 seconds. + expectedWander_ms += 5000; + break; + } + case timeSource_t::Manual_set: + { + expectedWander_ms += 10000; + break; + } + case timeSource_t::No_time_source: + { + // Cannot sync from it. + return 1 << 30; + } + } + return expectedWander_ms; +} diff --git a/src/src/DataTypes/ESPEasyTimeSource.h b/src/src/DataTypes/ESPEasyTimeSource.h index c660f2f26..565753282 100644 --- a/src/src/DataTypes/ESPEasyTimeSource.h +++ b/src/src/DataTypes/ESPEasyTimeSource.h @@ -1,47 +1,51 @@ -#ifndef DATATYPES_ESPEASYTIMESOURCE_H -#define DATATYPES_ESPEASYTIMESOURCE_H - - -#include "../../ESPEasy_common.h" - -#include - - -class String; - -#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 3600000 -#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC 3600 - -// Time Source type, sort by priority. -// Enum values are sent via NodeStruct, so only add new ones and don't change existing values -// typical time wander of an ESP module is 40 ppm, or 0.04 msec/sec, or roughly 3.5 seconds per 24h. -enum class timeSource_t : uint8_t { - // External time source (considered more reliable) - GPS_PPS_time_source = 5, // 1 - 10 msec accuracy - GPS_time_source = 10, // 10 - 100 msec accuracy - NTP_time_source = 15, // 20 - 100 msec accuracy - - // Manual override has higher priority because it is some kind of external sync - Manual_set = 20, // Unknown accuracy - - // Sources which may drift over time due to lack of external synchronization. - ESP_now_peer = 40, // < 5 msec accuracy between nodes, but time on the whole network may drift - ESPEASY_p2p_UDP = 41, - External_RTC_time_source = 45, // Typically +/- 500 msec off. - - Restore_RTC_time_source = 50, // > 1 sec difference per reboot - No_time_source = 255 // No time set -}; - -const __FlashStringHelper* toString(timeSource_t timeSource); -bool isExternalTimeSource(timeSource_t timeSource); - -// Only use peers if there is no external source available. -// A network without external synced source may drift as a whole -// All nodes in the network may be in sync with each other, but get out of sync with the rest of the world. -// Therefore use a strong bias for external synced nodes. -// But also must make sure the same NTP synced node will be held responsible for the entire network. -unsigned long computeExpectedWander(timeSource_t timeSource, - unsigned long timePassedSinceLastTimeSync); - +#ifndef DATATYPES_ESPEASYTIMESOURCE_H +#define DATATYPES_ESPEASYTIMESOURCE_H + + +#include "../../ESPEasy_common.h" + +#include + + +class String; + +#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC 3600000 +#define EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC 3600 + +// Time Source type, sort by priority. +// Enum values are sent via NodeStruct, so only add new ones and don't change existing values +// typical time wander of an ESP module is 40 ppm, or 0.04 msec/sec, or roughly 3.5 seconds per 24h. +enum class timeSource_t : uint8_t { + // External time source (considered more reliable) + GPS_PPS_time_source = 5, // 1 - 10 msec accuracy + GPS_time_source = 10, // 10 - 100 msec accuracy + NTP_time_source = 15, // 20 - 100 msec accuracy + + // Manual override has higher priority because it is some kind of external sync + Manual_set = 20, // Unknown accuracy + + // Sources which may drift over time due to lack of external synchronization. + ESP_now_peer = 40, // < 5 msec accuracy between nodes, but time on the whole network may drift + ESPEASY_p2p_UDP = 41, + External_RTC_time_source = 45, // Typically +/- 500 msec off. + + Restore_RTC_time_source = 50, // > 1 sec difference per reboot + No_time_source = 255 // No time set +}; + +const __FlashStringHelper* toString(timeSource_t timeSource); +bool isExternalTimeSource(timeSource_t timeSource); + +// Only use peers if there is no external source available. +// A network without external synced source may drift as a whole +// All nodes in the network may be in sync with each other, but get out of sync with the rest of the world. +// Therefore use a strong bias for external synced nodes. +// But also must make sure the same NTP synced node will be held responsible for the entire network. +uint32_t computeExpectedWander(timeSource_t timeSource, + uint32_t timePassedSinceLastTimeSync = 0u); + +uint32_t updateExpectedWander( + int32_t current_wander, + uint32_t timePassedSinceLastTimeSync); + #endif /* DATATYPES_ESPEASYTIMESOURCE_H */ \ No newline at end of file diff --git a/src/src/DataTypes/ESPEasy_plugin_functions.h b/src/src/DataTypes/ESPEasy_plugin_functions.h index f59b51c96..267ea867a 100644 --- a/src/src/DataTypes/ESPEasy_plugin_functions.h +++ b/src/src/DataTypes/ESPEasy_plugin_functions.h @@ -1,129 +1,135 @@ -#ifndef DATATYPES_ESPEASY_PLUGIN_DEFS_H -#define DATATYPES_ESPEASY_PLUGIN_DEFS_H - -#include "../../ESPEasy_common.h" - - -// ******************************************************************************** -// 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 -#if FEATURE_PLUGIN_STATS - 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_MAX_FUNCTION // Leave as last one. -}; - - -#define NrBitsPluginFunctions NR_BITS(static_cast(PLUGIN_MAX_FUNCTION)) - -// ******************************************************************************** -// CPlugin (Controller) function calls -// ******************************************************************************** - -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_PROTOCOL_TEMPLATE, - CPLUGIN_PROTOCOL_SEND, - CPLUGIN_PROTOCOL_RECV, - CPLUGIN_GET_DEVICENAME, - CPLUGIN_WEBFORM_SAVE, - CPLUGIN_WEBFORM_LOAD, - CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, - CPLUGIN_TASK_CHANGE_NOTIFICATION, - CPLUGIN_INIT, - CPLUGIN_UDP_IN, - CPLUGIN_FLUSH, // Force offloading data stored in buffers, called before sleep/reboot - 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, - CPLUGIN_WRITE, // Send commands to a controller. - - - // new messages for autodiscover controller plugins (experimental) i.e. C014 - CPLUGIN_GOT_CONNECTED, // call after connected to mqtt server to publich device autodicover features - CPLUGIN_GOT_INVALID, // should be called before major changes i.e. changing the device name to clean up data on the - // controller. !ToDo - CPLUGIN_INTERVAL, // call every interval loop - 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. - }; -}; - -// ******************************************************************************** -// NPlugin (Notification) function calls -// ******************************************************************************** -class NPlugin { -public: - - enum class Function { - NPLUGIN_PROTOCOL_ADD = 1, - NPLUGIN_GET_DEVICENAME, - NPLUGIN_WEBFORM_SAVE, - NPLUGIN_WEBFORM_LOAD, - NPLUGIN_WRITE, - NPLUGIN_NOTIFY - }; -}; - - -#endif // DATATYPES_ESPEASY_PLUGIN_DEFS_H +#ifndef DATATYPES_ESPEASY_PLUGIN_DEFS_H +#define DATATYPES_ESPEASY_PLUGIN_DEFS_H + +#include "../../ESPEasy_common.h" + + +// ******************************************************************************** +// 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 +#if FEATURE_PLUGIN_STATS + 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 +#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. +#endif + PLUGIN_WEBFORM_PRE_SERIAL_PARAMS , // Before serial parameters, convert additional parameters like baudrate or specific serial config + + PLUGIN_MAX_FUNCTION // Leave as last one. +}; + + +#define NrBitsPluginFunctions NR_BITS(static_cast(PLUGIN_MAX_FUNCTION)) + +// ******************************************************************************** +// CPlugin (Controller) function calls +// ******************************************************************************** + +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_PROTOCOL_TEMPLATE, + CPLUGIN_PROTOCOL_SEND, + CPLUGIN_PROTOCOL_RECV, + CPLUGIN_GET_DEVICENAME, + CPLUGIN_WEBFORM_SAVE, + CPLUGIN_WEBFORM_LOAD, + CPLUGIN_GET_PROTOCOL_DISPLAY_NAME, + CPLUGIN_TASK_CHANGE_NOTIFICATION, + CPLUGIN_INIT, + CPLUGIN_UDP_IN, + CPLUGIN_FLUSH, // Force offloading data stored in buffers, called before sleep/reboot + 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, + CPLUGIN_WRITE, // Send commands to a controller. + + + // new messages for autodiscover controller plugins (experimental) i.e. C014 + CPLUGIN_GOT_CONNECTED, // call after connected to mqtt server to publich device autodicover features + CPLUGIN_GOT_INVALID, // should be called before major changes i.e. changing the device name to clean up data on the + // controller. !ToDo + CPLUGIN_INTERVAL, // call every interval loop + 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. + }; +}; + +// ******************************************************************************** +// NPlugin (Notification) function calls +// ******************************************************************************** +class NPlugin { +public: + + enum class Function { + NPLUGIN_PROTOCOL_ADD = 1, + NPLUGIN_GET_DEVICENAME, + NPLUGIN_WEBFORM_SAVE, + NPLUGIN_WEBFORM_LOAD, + NPLUGIN_WRITE, + NPLUGIN_NOTIFY + }; +}; + + +#endif // DATATYPES_ESPEASY_PLUGIN_DEFS_H diff --git a/src/src/DataTypes/EthernetParameters.cpp b/src/src/DataTypes/EthernetParameters.cpp index f6f5313ff..3f5d4b1cf 100644 --- a/src/src/DataTypes/EthernetParameters.cpp +++ b/src/src/DataTypes/EthernetParameters.cpp @@ -14,7 +14,7 @@ bool isValid(EthClockMode_t clockMode) { } bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, - int8_t gpio) { + 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))) { @@ -23,7 +23,7 @@ bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, return false; } -const __FlashStringHelper * toString(EthClockMode_t clockMode) { +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"); @@ -37,32 +37,119 @@ const __FlashStringHelper * toString(EthClockMode_t clockMode) { bool isValid(EthPhyType_t phyType) { switch (phyType) { - case EthPhyType_t::LAN8710: +#if CONFIG_ETH_USE_ESP32_EMAC + case EthPhyType_t::LAN8720: case EthPhyType_t::TLK110: - return true; +# if ESP_IDF_VERSION_MAJOR > 3 case EthPhyType_t::RTL8201: + case EthPhyType_t::JL1101: case EthPhyType_t::DP83848: - case EthPhyType_t::DM9051: - #if ESP_IDF_VERSION_MAJOR > 3 - return true; // FIXME TD-er: Must check if supported per IDF version - #else - return false; - #endif + case EthPhyType_t::KSZ8041: + case EthPhyType_t::KSZ8081: +# endif // if ESP_IDF_VERSION_MAJOR > 3 + return true; +#endif // if CONFIG_ETH_USE_ESP32_EMAC - // Do not use default: as this allows the compiler to detect any missing cases. +#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 + case EthPhyType_t::W5500: return true; +# 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 + case EthPhyType_t::notSet: + break; } return false; } -const __FlashStringHelper * toString(EthPhyType_t phyType) { +#if FEATURE_ETHERNET +bool isSPI_EthernetType(EthPhyType_t phyType) { +# if ESP_IDF_VERSION_MAJOR >= 5 + return +# if CONFIG_ETH_SPI_ETHERNET_DM9051 + phyType == EthPhyType_t::DM9051 || +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 + phyType == EthPhyType_t::W5500 || +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + phyType == EthPhyType_t::KSZ8851 || +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + false; +# else // if ESP_IDF_VERSION_MAJOR >= 5 + return false; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +} + +eth_phy_type_t to_ESP_phy_type(EthPhyType_t phyType) +{ switch (phyType) { - case EthPhyType_t::LAN8710: return F("LAN8710/LAN8720"); - case EthPhyType_t::TLK110: return F("TLK110"); - case EthPhyType_t::RTL8201: return F("RTL8201"); - case EthPhyType_t::DP83848: return F("DP83848"); - case EthPhyType_t::DM9051: return F("DM9051"); +# if CONFIG_ETH_USE_ESP32_EMAC + case EthPhyType_t::LAN8720: return ETH_PHY_LAN8720; + case EthPhyType_t::TLK110: return ETH_PHY_TLK110; +# if ESP_IDF_VERSION_MAJOR > 3 + case EthPhyType_t::RTL8201: return ETH_PHY_RTL8201; + case EthPhyType_t::JL1101: return ETH_PHY_JL1101; + 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 + +# if ESP_IDF_VERSION_MAJOR >= 5 +# if CONFIG_ETH_SPI_ETHERNET_DM9051 + case EthPhyType_t::DM9051: return ETH_PHY_DM9051; +# endif // if CONFIG_ETH_SPI_ETHERNET_DM9051 +# if CONFIG_ETH_SPI_ETHERNET_W5500 + case EthPhyType_t::W5500: return ETH_PHY_W5500; +# endif // if CONFIG_ETH_SPI_ETHERNET_W5500 +# if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL + case EthPhyType_t::KSZ8851: return ETH_PHY_KSZ8851; +# endif // if CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + case EthPhyType_t::notSet: + break; + } + 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 + case EthPhyType_t::RTL8201: return F("RTL8201"); + case EthPhyType_t::JL1101: return F("JL1101"); + 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 + +#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 + case EthPhyType_t::W5500: return F("W5500(SPI)"); +# 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 + case EthPhyType_t::notSet: + break; // Do not use default: as this allows the compiler to detect any missing cases. } - return F("Unknown"); + return F("- None -"); } diff --git a/src/src/DataTypes/EthernetParameters.h b/src/src/DataTypes/EthernetParameters.h index 08441fb87..b139832ab 100644 --- a/src/src/DataTypes/EthernetParameters.h +++ b/src/src/DataTypes/EthernetParameters.h @@ -3,6 +3,7 @@ #include "../../ESPEasy_common.h" + // Is stored in settings enum class EthClockMode_t : uint8_t { Ext_crystal_osc = 0, @@ -20,16 +21,43 @@ bool isGpioUsedInETHClockMode(EthClockMode_t clockMode, // Is stored in settings enum class EthPhyType_t : uint8_t { - LAN8710 = 0, +#if CONFIG_ETH_USE_ESP32_EMAC + LAN8720 = 0, TLK110 = 1, +#if ESP_IDF_VERSION_MAJOR > 3 + RTL8201 = 2, - DP83848 = 3, - DM9051 = 4 - //,KSZ8081 = 5 + JL1101 = 3, + 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); diff --git a/src/src/DataTypes/EventValueSource.h b/src/src/DataTypes/EventValueSource.h index 8b60bd236..12de4c45e 100644 --- a/src/src/DataTypes/EventValueSource.h +++ b/src/src/DataTypes/EventValueSource.h @@ -1,52 +1,52 @@ -#ifndef DATATYPES_EVENT_VALUE_SOURCE_H -#define DATATYPES_EVENT_VALUE_SOURCE_H - -#include "../../ESPEasy_common.h" - -struct EventValueSourceGroup { - enum class Enum : uint8_t { - RESTRICTED, - ALL - }; -}; - - -struct EventValueSource { - // Keep the values as they can be used by other/older builds to communicate with ESPEasy - enum class Enum : uint8_t { - VALUE_SOURCE_NOT_SET = 0, - VALUE_SOURCE_SYSTEM = 1, - VALUE_SOURCE_SERIAL = 2, - VALUE_SOURCE_HTTP = 3, - VALUE_SOURCE_MQTT = 4, - VALUE_SOURCE_UDP = 5, - VALUE_SOURCE_WEB_FRONTEND = 6, - VALUE_SOURCE_RULES = 7, - 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; - } -}; - - -#endif // DATATYPES_EVENT_VALUE_SOURCE_H +#ifndef DATATYPES_EVENT_VALUE_SOURCE_H +#define DATATYPES_EVENT_VALUE_SOURCE_H + +#include "../../ESPEasy_common.h" + +struct EventValueSourceGroup { + enum class Enum : uint8_t { + RESTRICTED, + ALL + }; +}; + + +struct EventValueSource { + // Keep the values as they can be used by other/older builds to communicate with ESPEasy + enum class Enum : uint8_t { + VALUE_SOURCE_NOT_SET = 0, + VALUE_SOURCE_SYSTEM = 1, + VALUE_SOURCE_SERIAL = 2, + VALUE_SOURCE_HTTP = 3, + VALUE_SOURCE_MQTT = 4, + VALUE_SOURCE_UDP = 5, + VALUE_SOURCE_WEB_FRONTEND = 6, + VALUE_SOURCE_RULES = 7, + 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; + } +}; + + +#endif // DATATYPES_EVENT_VALUE_SOURCE_H diff --git a/src/src/DataTypes/NetworkMedium.cpp b/src/src/DataTypes/NetworkMedium.cpp index adcf7e987..b66d210e7 100644 --- a/src/src/DataTypes/NetworkMedium.cpp +++ b/src/src/DataTypes/NetworkMedium.cpp @@ -1,33 +1,33 @@ - -#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"); + +#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/SettingsType.cpp b/src/src/DataTypes/SettingsType.cpp index 3903a59fc..52ddaf8f6 100644 --- a/src/src/DataTypes/SettingsType.cpp +++ b/src/src/DataTypes/SettingsType.cpp @@ -1,260 +1,266 @@ -#include "../DataTypes/SettingsType.h" - -#include "../CustomBuild/StorageLayout.h" -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/ExtraTaskSettingsStruct.h" -#include "../DataStructs/NotificationSettingsStruct.h" -#include "../DataStructs/SecurityStruct.h" -#include "../DataTypes/ESPEasyFileType.h" -#include "../Globals/Settings.h" - -const __FlashStringHelper * SettingsType::getSettingsTypeString(Enum settingsType) { - switch (settingsType) { - case Enum::BasicSettings_Type: return F("Settings"); - case Enum::TaskSettings_Type: return F("TaskSettings"); - case Enum::CustomTaskSettings_Type: return F("CustomTaskSettings"); - case Enum::ControllerSettings_Type: return F("ControllerSettings"); - case Enum::CustomControllerSettings_Type: return F("CustomControllerSettings"); - case Enum::NotificationSettings_Type: - #if FEATURE_NOTIFIER - return F("NotificationSettings"); - #else - break; - #endif - case Enum::SecuritySettings_Type: return F("SecuritySettings"); - case Enum::ExtdControllerCredentials_Type: return F("ExtendedControllerCredentials"); - #if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: return F("CDN_url"); - #endif - - case Enum::SettingsType_MAX: break; - } - return F(""); -} - -/********************************************************************************************\ - Offsets in settings files - \*********************************************************************************************/ -bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_index, int& offset, int& max_size, int& struct_size) { - // The defined offsets should be used with () just in case they are the result of a formula in the defines. - struct_size = 0; - max_index = -1; - offset = -1; - - switch (settingsType) { - case Enum::BasicSettings_Type: - { - max_index = 1; - offset = 0; - max_size = (DAT_BASIC_SETTINGS_SIZE); - struct_size = sizeof(SettingsStruct); - break; - } - case Enum::TaskSettings_Type: - { - max_index = TASKS_MAX; - offset = (DAT_OFFSET_TASKS) + (index * (DAT_TASKS_DISTANCE)); - max_size = DAT_TASKS_SIZE; - struct_size = sizeof(ExtraTaskSettingsStruct); - break; - } - case Enum::CustomTaskSettings_Type: - { - if (!getSettingsParameters(Enum::TaskSettings_Type, index, max_index, offset, max_size, struct_size)) - return false; - offset += (DAT_TASKS_CUSTOM_OFFSET); - max_size = DAT_TASKS_CUSTOM_SIZE; - - // struct_size may differ. - struct_size = 0; - break; - } - case Enum::ControllerSettings_Type: - { - max_index = CONTROLLER_MAX; - offset = (DAT_OFFSET_CONTROLLER) + (index * (DAT_CONTROLLER_SIZE)); - max_size = DAT_CONTROLLER_SIZE; - struct_size = sizeof(ControllerSettingsStruct); - break; - } - case Enum::CustomControllerSettings_Type: - { - max_index = CONTROLLER_MAX; - offset = (DAT_OFFSET_CUSTOM_CONTROLLER) + (index * (DAT_CUSTOM_CONTROLLER_SIZE)); - max_size = DAT_CUSTOM_CONTROLLER_SIZE; - - // struct_size may differ. - struct_size = 0; - break; - } - case Enum::NotificationSettings_Type: - { -#if FEATURE_NOTIFIER - max_index = NOTIFICATION_MAX; - offset = index * (DAT_NOTIFICATION_SIZE); - max_size = DAT_NOTIFICATION_SIZE; - struct_size = sizeof(NotificationSettingsStruct); - break; -#else - return false; -#endif - } - case Enum::SecuritySettings_Type: - { - max_index = 1; - offset = 0; - max_size = DAT_SECURITYSETTINGS_SIZE; - struct_size = sizeof(SecurityStruct); - break; - } - case Enum::ExtdControllerCredentials_Type: - { - max_index = 1; - offset = DAT_EXTDCONTR_CRED_OFFSET; - max_size = DAT_EXTDCONTR_CRED_SIZE; - - // struct_size may differ. - struct_size = 0; - break; - } -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: - { - max_index = 1; - offset = DAT_OFFSET_CDN; - max_size = DAT_CDN_SIZE; - - // struct_size may differ. - struct_size = 0; - } - break; -#endif - - case Enum::SettingsType_MAX: - { - max_index = -1; - offset = -1; - return false; - } - } - return index >= 0 && index < max_index; -} - -bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& offset, int& max_size) { - int max_index = -1; - int struct_size; - - if (!getSettingsParameters(settingsType, index, max_index, offset, max_size, struct_size)) { - return false; - } - - if ((index >= 0) && (index < max_index)) { return true; } - offset = -1; - return false; -} - -int SettingsType::getMaxFilePos(Enum settingsType) { - int max_index, offset, max_size{}; - int struct_size = 0; - - if (getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size) && - getSettingsParameters(settingsType, max_index - 1, offset, max_size)) - return offset + max_size - 1; - return -1; -} - -int SettingsType::getFileSize(Enum settingsType) { - SettingsType::SettingsFileEnum file_type = SettingsType::getSettingsFile(settingsType); - int max_file_pos = 0; - - for (int st = 0; st < static_cast(Enum::SettingsType_MAX); ++st) { - if (SettingsType::getSettingsFile(static_cast(st)) == file_type) { - const int filePos = SettingsType::getMaxFilePos(static_cast(st)); - - if (filePos > max_file_pos) { - max_file_pos = filePos; - } - } - } - return max_file_pos; -} - -#ifndef BUILD_MINIMAL_OTA -unsigned int SettingsType::getSVGcolor(Enum settingsType) { - switch (settingsType) { - case Enum::BasicSettings_Type: - return 0x5F0A87; - case Enum::TaskSettings_Type: - return 0xEE6352; - case Enum::CustomTaskSettings_Type: - return 0x59CD90; - case Enum::ControllerSettings_Type: - return 0x3FA7D6; - case Enum::CustomControllerSettings_Type: - return 0xFAC05E; - case Enum::NotificationSettings_Type: - return 0xF79D84; - - case Enum::SecuritySettings_Type: - return 0xff00a2; - case Enum::ExtdControllerCredentials_Type: - return 0xc300ff; -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: - return 0xff6600; -#endif - case Enum::SettingsType_MAX: - break; - } - return 0; -} - -#endif // ifndef BUILD_MINIMAL_OTA - -SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) -{ - switch (settingsType) { - case Enum::BasicSettings_Type: - case Enum::TaskSettings_Type: - case Enum::CustomTaskSettings_Type: - case Enum::ControllerSettings_Type: - case Enum::CustomControllerSettings_Type: -#if FEATURE_ALTERNATIVE_CDN_URL - case Enum::CdnSettings_Type: -#endif - return SettingsFileEnum::FILE_CONFIG_type; - case Enum::NotificationSettings_Type: - return SettingsFileEnum::FILE_NOTIFICATION_type; - case Enum::SecuritySettings_Type: - case Enum::ExtdControllerCredentials_Type: - return SettingsFileEnum::FILE_SECURITY_type; - - case Enum::SettingsType_MAX: - break; - } - return SettingsFileEnum::FILE_UNKNOWN_type; -} - -String SettingsType::getSettingsFileName(Enum settingsType) { - return getSettingsFileName(getSettingsFile(settingsType)); -} - -const __FlashStringHelper * SettingsType::getSettingsFileName(SettingsType::SettingsFileEnum file_type) { - switch (file_type) { - 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); - case SettingsFileEnum::FILE_UNKNOWN_type: break; - } - return F(""); -} - -size_t SettingsType::getInitFileSize(SettingsType::SettingsFileEnum file_type) { - switch (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; - case SettingsFileEnum::FILE_UNKNOWN_type: break; - } - return 0; +#include "../DataTypes/SettingsType.h" + +#include "../CustomBuild/StorageLayout.h" +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/ExtraTaskSettingsStruct.h" +#include "../DataStructs/NotificationSettingsStruct.h" +#include "../DataStructs/SecurityStruct.h" +#include "../DataTypes/ESPEasyFileType.h" +#include "../Globals/Settings.h" +#include "../Helpers/StringConverter.h" + +const __FlashStringHelper * SettingsType::getSettingsTypeString(Enum settingsType) { + switch (settingsType) { + case Enum::BasicSettings_Type: return F("Settings"); + case Enum::TaskSettings_Type: return F("TaskSettings"); + case Enum::CustomTaskSettings_Type: return F("CustomTaskSettings"); + case Enum::ControllerSettings_Type: return F("ControllerSettings"); + case Enum::CustomControllerSettings_Type: return F("CustomControllerSettings"); + case Enum::NotificationSettings_Type: + #if FEATURE_NOTIFIER + return F("NotificationSettings"); + #else + break; + #endif + case Enum::SecuritySettings_Type: return F("SecuritySettings"); + case Enum::ExtdControllerCredentials_Type: return F("ExtendedControllerCredentials"); + #if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: return F("CDN_url"); + #endif + + case Enum::SettingsType_MAX: break; + } + return F(""); +} + +/********************************************************************************************\ + Offsets in settings files + \*********************************************************************************************/ +bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& max_index, int& offset, int& max_size, int& struct_size) { + // The defined offsets should be used with () just in case they are the result of a formula in the defines. + struct_size = 0; + max_index = -1; + offset = -1; + + switch (settingsType) { + case Enum::BasicSettings_Type: + { + max_index = 1; + offset = 0; + max_size = (DAT_BASIC_SETTINGS_SIZE); + struct_size = sizeof(SettingsStruct); + break; + } + case Enum::TaskSettings_Type: + { + max_index = TASKS_MAX; + offset = (DAT_OFFSET_TASKS) + (index * (DAT_TASKS_DISTANCE)); + max_size = DAT_TASKS_SIZE; + struct_size = sizeof(ExtraTaskSettingsStruct); + break; + } + case Enum::CustomTaskSettings_Type: + { + if (!getSettingsParameters(Enum::TaskSettings_Type, index, max_index, offset, max_size, struct_size)) + return false; + offset += (DAT_TASKS_CUSTOM_OFFSET); + max_size = (DAT_TASKS_CUSTOM_SIZE + DAT_TASKS_CUSTOM_EXTENSION_SIZE); + + // struct_size may differ. + struct_size = 0; + break; + } + case Enum::ControllerSettings_Type: + { + max_index = CONTROLLER_MAX; + offset = (DAT_OFFSET_CONTROLLER) + (index * (DAT_CONTROLLER_SIZE)); + max_size = DAT_CONTROLLER_SIZE; + struct_size = sizeof(ControllerSettingsStruct); + break; + } + case Enum::CustomControllerSettings_Type: + { + max_index = CONTROLLER_MAX; + offset = (DAT_OFFSET_CUSTOM_CONTROLLER) + (index * (DAT_CUSTOM_CONTROLLER_SIZE)); + max_size = DAT_CUSTOM_CONTROLLER_SIZE; + + // struct_size may differ. + struct_size = 0; + break; + } + case Enum::NotificationSettings_Type: + { +#if FEATURE_NOTIFIER + max_index = NOTIFICATION_MAX; + offset = index * (DAT_NOTIFICATION_SIZE); + max_size = DAT_NOTIFICATION_SIZE; + struct_size = sizeof(NotificationSettingsStruct); + break; +#else + return false; +#endif + } + case Enum::SecuritySettings_Type: + { + max_index = 1; + offset = 0; + max_size = DAT_SECURITYSETTINGS_SIZE; + struct_size = sizeof(SecurityStruct); + break; + } + case Enum::ExtdControllerCredentials_Type: + { + max_index = 1; + offset = DAT_EXTDCONTR_CRED_OFFSET; + max_size = DAT_EXTDCONTR_CRED_SIZE; + + // struct_size may differ. + struct_size = 0; + break; + } +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: + { + max_index = 1; + offset = DAT_OFFSET_CDN; + max_size = DAT_CDN_SIZE; + + // struct_size may differ. + struct_size = 0; + } + break; +#endif + + case Enum::SettingsType_MAX: + { + max_index = -1; + offset = -1; + return false; + } + } + return index >= 0 && index < max_index; +} + +bool SettingsType::getSettingsParameters(Enum settingsType, int index, int& offset, int& max_size) { + int max_index = -1; + int struct_size; + + if (!getSettingsParameters(settingsType, index, max_index, offset, max_size, struct_size)) { + return false; + } + + if ((index >= 0) && (index < max_index)) { return true; } + offset = -1; + return false; +} + +int SettingsType::getMaxFilePos(Enum settingsType) { + int max_index, offset, max_size{}; + int struct_size = 0; + + if (getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size) && + getSettingsParameters(settingsType, max_index - 1, offset, max_size)) + return offset + max_size - 1; + return -1; +} + +int SettingsType::getFileSize(Enum settingsType) { + SettingsType::SettingsFileEnum file_type = SettingsType::getSettingsFile(settingsType); + int max_file_pos = 0; + + for (int st = 0; st < static_cast(Enum::SettingsType_MAX); ++st) { + if (SettingsType::getSettingsFile(static_cast(st)) == file_type) { + const int filePos = SettingsType::getMaxFilePos(static_cast(st)); + + if (filePos > max_file_pos) { + max_file_pos = filePos; + } + } + } + return max_file_pos; +} + +#ifndef BUILD_MINIMAL_OTA +unsigned int SettingsType::getSVGcolor(Enum settingsType) { + switch (settingsType) { + case Enum::BasicSettings_Type: + return 0x5F0A87; + case Enum::TaskSettings_Type: + return 0xEE6352; + case Enum::CustomTaskSettings_Type: + return 0x59CD90; + case Enum::ControllerSettings_Type: + return 0x3FA7D6; + case Enum::CustomControllerSettings_Type: + return 0xFAC05E; + case Enum::NotificationSettings_Type: + return 0xF79D84; + + case Enum::SecuritySettings_Type: + return 0xff00a2; + case Enum::ExtdControllerCredentials_Type: + return 0xc300ff; +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: + return 0xff6600; +#endif + case Enum::SettingsType_MAX: + break; + } + return 0; +} + +#endif // ifndef BUILD_MINIMAL_OTA + +SettingsType::SettingsFileEnum SettingsType::getSettingsFile(Enum settingsType) +{ + switch (settingsType) { + case Enum::BasicSettings_Type: + case Enum::TaskSettings_Type: + case Enum::CustomTaskSettings_Type: + case Enum::ControllerSettings_Type: + case Enum::CustomControllerSettings_Type: +#if FEATURE_ALTERNATIVE_CDN_URL + case Enum::CdnSettings_Type: +#endif + return SettingsFileEnum::FILE_CONFIG_type; + case Enum::NotificationSettings_Type: + return SettingsFileEnum::FILE_NOTIFICATION_type; + case Enum::SecuritySettings_Type: + case Enum::ExtdControllerCredentials_Type: + return SettingsFileEnum::FILE_SECURITY_type; + + case Enum::SettingsType_MAX: + break; + } + return SettingsFileEnum::FILE_UNKNOWN_type; +} + +String SettingsType::getSettingsFileName(Enum settingsType, int index) { + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + if ((Enum::CustomTaskSettings_Type == settingsType) && validTaskIndex(index)) { + return strformat(F(DAT_TASKS_CUSTOM_EXTENSION_FILEMASK), index + 1); // Add 0/1 offset to match displayed task ID + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + return getSettingsFileName(getSettingsFile(settingsType)); +} + +const __FlashStringHelper * SettingsType::getSettingsFileName(SettingsType::SettingsFileEnum file_type) { + switch (file_type) { + 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); + case SettingsFileEnum::FILE_UNKNOWN_type: break; + } + return F(""); +} + +size_t SettingsType::getInitFileSize(SettingsType::SettingsFileEnum file_type) { + switch (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; + case SettingsFileEnum::FILE_UNKNOWN_type: break; + } + return 0; } \ No newline at end of file diff --git a/src/src/DataTypes/SettingsType.h b/src/src/DataTypes/SettingsType.h index 11c18ad76..5030e2f7c 100644 --- a/src/src/DataTypes/SettingsType.h +++ b/src/src/DataTypes/SettingsType.h @@ -2,7 +2,7 @@ #define DATATYPES_SETTINGSTYPE_H #include "../../ESPEasy_common.h" - +#include "../DataTypes/TaskIndex.h" class SettingsType { public: @@ -50,7 +50,8 @@ public: #endif // ifndef BUILD_MINIMAL_OTA static SettingsFileEnum getSettingsFile(Enum settingsType); - static String getSettingsFileName(Enum settingsType); + static String getSettingsFileName(Enum settingsType, + int index = INVALID_TASK_INDEX); static const __FlashStringHelper * getSettingsFileName(SettingsType::SettingsFileEnum file_type); static size_t getInitFileSize(SettingsType::SettingsFileEnum file_type); }; diff --git a/src/src/DataTypes/TaskValues_Data.cpp b/src/src/DataTypes/TaskValues_Data.cpp index 9fa84188e..961b630b4 100644 --- a/src/src/DataTypes/TaskValues_Data.cpp +++ b/src/src/DataTypes/TaskValues_Data.cpp @@ -1,253 +1,278 @@ -#include "../DataTypes/TaskValues_Data.h" - -#include "../DataStructs/TimingStats.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter_Numerical.h" - -TaskValues_Data_t::TaskValues_Data_t() { - ZERO_FILL(binary); -} - -TaskValues_Data_t::TaskValues_Data_t(const TaskValues_Data_t& other) -{ - memcpy(binary, other.binary, sizeof(binary)); -} - -TaskValues_Data_t& TaskValues_Data_t::operator=(const TaskValues_Data_t& other) -{ - memcpy(binary, other.binary, sizeof(binary)); - return *this; -} - -void TaskValues_Data_t::clear() { - ZERO_FILL(binary); -} - -void TaskValues_Data_t::copyValue(const TaskValues_Data_t& other, uint8_t varNr, Sensor_VType sensorType) -{ - if (sensorType != Sensor_VType::SENSOR_TYPE_STRING) { - if (is32bitOutputDataType(sensorType)) { - if (varNr < VARS_PER_TASK) { - uint32s[varNr] = other.uint32s[varNr]; - } -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - } else { - if ((varNr < (VARS_PER_TASK / 2))) { - uint64s[varNr] = other.uint64s[varNr]; - } -#endif - } - } -} - -unsigned long TaskValues_Data_t::getSensorTypeLong() const -{ - const uint16_t low = floats[0]; - const uint16_t high = floats[1]; - unsigned long value = high; - - value <<= 16; - value |= low; - return value; -} - -void TaskValues_Data_t::setSensorTypeLong(unsigned long value) -{ - floats[0] = value & 0xFFFF; - floats[1] = (value >> 16) & 0xFFFF; -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - -int32_t TaskValues_Data_t::getInt32(uint8_t varNr) const -{ - if (varNr < VARS_PER_TASK) { - return int32s[varNr]; - } - return 0; -} - -void TaskValues_Data_t::setInt32(uint8_t varNr, int32_t value) -{ - if (varNr < VARS_PER_TASK) { - int32s[varNr] = value; - } -} -#endif - -uint32_t TaskValues_Data_t::getUint32(uint8_t varNr) const -{ - if (varNr < VARS_PER_TASK) { - return uint32s[varNr]; - } - return 0u; -} - -void TaskValues_Data_t::setUint32(uint8_t varNr, uint32_t value) -{ - if (varNr < VARS_PER_TASK) { - uint32s[varNr] = value; - } -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - -int64_t TaskValues_Data_t::getInt64(uint8_t varNr) const -{ - if ((varNr < (VARS_PER_TASK / 2))) { - return int64s[varNr]; - } - return 0; -} - -void TaskValues_Data_t::setInt64(uint8_t varNr, int64_t value) -{ - if ((varNr < (VARS_PER_TASK / 2))) { - int64s[varNr] = value; - } -} - -uint64_t TaskValues_Data_t::getUint64(uint8_t varNr) const -{ - if ((varNr < (VARS_PER_TASK / 2))) { - return uint64s[varNr]; - } - return 0u; -} - -void TaskValues_Data_t::setUint64(uint8_t varNr, uint64_t value) -{ - if ((varNr < (VARS_PER_TASK / 2))) { - uint64s[varNr] = value; - } -} -#endif - -float TaskValues_Data_t::getFloat(uint8_t varNr) const -{ - if (varNr < VARS_PER_TASK) { - return floats[varNr]; - } - return 0.0f; -} - -void TaskValues_Data_t::setFloat(uint8_t varNr, float value) -{ - if (varNr < VARS_PER_TASK) { - floats[varNr] = value; - } -} - -#if FEATURE_EXTENDED_TASK_VALUE_TYPES -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -double TaskValues_Data_t::getDouble(uint8_t varNr) const -{ - if ((varNr < (VARS_PER_TASK / 2))) { - return doubles[varNr]; - } - return 0.0; -} - -void TaskValues_Data_t::setDouble(uint8_t varNr, double value) -{ - if ((varNr < (VARS_PER_TASK / 2))) { - doubles[varNr] = value; - } -} -#endif -#endif - -ESPEASY_RULES_FLOAT_TYPE TaskValues_Data_t::getAsDouble(uint8_t varNr, Sensor_VType sensorType) const -{ - if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { - return getSensorTypeLong(); - } else if (isFloatOutputDataType(sensorType)) { - return getFloat(varNr); -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - } else if (isUInt32OutputDataType(sensorType)) { - return getUint32(varNr); - } else if (isInt32OutputDataType(sensorType)) { - return getInt32(varNr); - } else if (isUInt64OutputDataType(sensorType)) { - return getUint64(varNr); - } else if (isInt64OutputDataType(sensorType)) { - return getInt64(varNr); -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (isDoubleOutputDataType(sensorType)) { - return getDouble(varNr); -#endif -#endif - } - return 0.0; -} - -void TaskValues_Data_t::set(uint8_t varNr, const ESPEASY_RULES_FLOAT_TYPE& value, Sensor_VType sensorType) -{ - if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { - // Legacy formatting the old "SENSOR_TYPE_ULONG" type - setSensorTypeLong(value); - } else if (isFloatOutputDataType(sensorType)) { - setFloat(varNr, value); -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - } else if (isUInt32OutputDataType(sensorType)) { - setUint32(varNr, value); - } else if (isInt32OutputDataType(sensorType)) { - setInt32(varNr, value); - } else if (isUInt64OutputDataType(sensorType)) { - setUint64(varNr, value); - } else if (isInt64OutputDataType(sensorType)) { - setInt64(varNr, value); -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (isDoubleOutputDataType(sensorType)) { - setDouble(varNr, value); -#endif -#endif - } -} - -bool TaskValues_Data_t::isValid(uint8_t varNr, Sensor_VType sensorType) const -{ - if (sensorType == Sensor_VType::SENSOR_TYPE_NONE) { - return false; - } else if (isFloatOutputDataType(sensorType)) { - return isValidFloat(getFloat(varNr)); -#if FEATURE_EXTENDED_TASK_VALUE_TYPES -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (isDoubleOutputDataType(sensorType)) { - return isValidDouble(getDouble(varNr)); -#endif -#endif - } - return true; -} - -String TaskValues_Data_t::getAsString(uint8_t varNr, Sensor_VType sensorType, uint8_t nrDecimals) const -{ - String result; - START_TIMER; - - if (isFloatOutputDataType(sensorType)) { - result = toString(getFloat(varNr), nrDecimals); -#if FEATURE_EXTENDED_TASK_VALUE_TYPES -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - } else if (isDoubleOutputDataType(sensorType)) { - result = doubleToString(getDouble(varNr), nrDecimals); -#endif -#endif - } else if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { - return String(getSensorTypeLong()); -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - } else if (isUInt32OutputDataType(sensorType)) { - return String(getUint32(varNr)); - } else if (isInt32OutputDataType(sensorType)) { - return String(getInt32(varNr)); - } else if (isUInt64OutputDataType(sensorType)) { - return ull2String(getUint64(varNr)); - } else if (isInt64OutputDataType(sensorType)) { - return ll2String(getInt64(varNr)); -#endif - } - result.trim(); - STOP_TIMER(GET_TASKVALUE_AS_STRING); - return result; -} +#include "../DataTypes/TaskValues_Data.h" + +#include "../DataStructs/TimingStats.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter_Numerical.h" + +TaskValues_Data_t::TaskValues_Data_t() { + ZERO_FILL(binary); +} + +TaskValues_Data_t::TaskValues_Data_t(const TaskValues_Data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); +} + +TaskValues_Data_t& TaskValues_Data_t::operator=(const TaskValues_Data_t& other) +{ + memcpy(binary, other.binary, sizeof(binary)); + return *this; +} + +void TaskValues_Data_t::clear() { + ZERO_FILL(binary); +} + +void TaskValues_Data_t::copyValue(const TaskValues_Data_t& other, uint8_t varNr, Sensor_VType sensorType) +{ + if (sensorType != Sensor_VType::SENSOR_TYPE_STRING) { + if (is32bitOutputDataType(sensorType)) { + if (varNr < VARS_PER_TASK) { + constexpr unsigned int size_32bit = sizeof(float); + memcpy(binary + (varNr * size_32bit), other.binary + (varNr * size_32bit), size_32bit); + } + } +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + else { + if ((varNr < (VARS_PER_TASK / 2))) { + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(binary + (varNr * size_64bit), other.binary + (varNr * size_64bit), size_64bit); + } + } +#endif + } +} + +unsigned long TaskValues_Data_t::getSensorTypeLong() const +{ + const uint16_t low = getFloat(0); + const uint16_t high = getFloat(1); + unsigned long value = high; + + value <<= 16; + value |= low; + return value; +} + +void TaskValues_Data_t::setSensorTypeLong(unsigned long value) +{ + setFloat(0, value & 0xFFFF); + setFloat(1, (value >> 16) & 0xFFFF); +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + +int32_t TaskValues_Data_t::getInt32(uint8_t varNr) const +{ + if (varNr < VARS_PER_TASK) { + int32_t res{}; + constexpr unsigned int size_32bit = sizeof(float); + memcpy(&res, binary + (varNr * size_32bit), size_32bit); + return res; + } + return 0; +} + +void TaskValues_Data_t::setInt32(uint8_t varNr, int32_t value) +{ + if (varNr < VARS_PER_TASK) { + constexpr unsigned int size_32bit = sizeof(float); + memcpy(binary + (varNr * size_32bit), &value, size_32bit); + } +} +#endif + +uint32_t TaskValues_Data_t::getUint32(uint8_t varNr) const +{ + if (varNr < VARS_PER_TASK) { + uint32_t res{}; + constexpr unsigned int size_32bit = sizeof(float); + memcpy(&res, binary + (varNr * size_32bit), size_32bit); + return res; + } + return 0u; +} + +void TaskValues_Data_t::setUint32(uint8_t varNr, uint32_t value) +{ + if (varNr < VARS_PER_TASK) { + constexpr unsigned int size_32bit = sizeof(float); + memcpy(binary + (varNr * size_32bit), &value, size_32bit); + } +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + +int64_t TaskValues_Data_t::getInt64(uint8_t varNr) const +{ + if ((varNr < (VARS_PER_TASK / 2))) { + int64_t res{}; + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(&res, binary + (varNr * size_64bit), size_64bit); + return res; + } + return 0; +} + +void TaskValues_Data_t::setInt64(uint8_t varNr, int64_t value) +{ + if ((varNr < (VARS_PER_TASK / 2))) { + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(binary + (varNr * size_64bit), &value, size_64bit); + } +} + +uint64_t TaskValues_Data_t::getUint64(uint8_t varNr) const +{ + if ((varNr < (VARS_PER_TASK / 2))) { + uint64_t res{}; + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(&res, binary + (varNr * size_64bit), size_64bit); + return res; + } + return 0u; +} + +void TaskValues_Data_t::setUint64(uint8_t varNr, uint64_t value) +{ + if ((varNr < (VARS_PER_TASK / 2))) { + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(binary + (varNr * size_64bit), &value, size_64bit); + } +} +#endif + +float TaskValues_Data_t::getFloat(uint8_t varNr) const +{ + if (varNr < VARS_PER_TASK) { + float res{}; + constexpr unsigned int size_32bit = sizeof(float); + memcpy(&res, binary + (varNr * size_32bit), size_32bit); + return res; + } + return 0.0f; +} + +void TaskValues_Data_t::setFloat(uint8_t varNr, float value) +{ + if (varNr < VARS_PER_TASK) { + constexpr unsigned int size_32bit = sizeof(float); + memcpy(binary + (varNr * size_32bit), &value, size_32bit); + } +} + +#if FEATURE_EXTENDED_TASK_VALUE_TYPES +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +double TaskValues_Data_t::getDouble(uint8_t varNr) const +{ + if ((varNr < (VARS_PER_TASK / 2))) { + double res{}; + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(&res, binary + (varNr * size_64bit), size_64bit); + return res; + } + return 0.0; +} + +void TaskValues_Data_t::setDouble(uint8_t varNr, double value) +{ + if ((varNr < (VARS_PER_TASK / 2))) { + constexpr unsigned int size_64bit = sizeof(uint64_t); + memcpy(binary + (varNr * size_64bit), &value, size_64bit); + } +} +#endif +#endif + +ESPEASY_RULES_FLOAT_TYPE TaskValues_Data_t::getAsDouble(uint8_t varNr, Sensor_VType sensorType) const +{ + if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { + return getSensorTypeLong(); + } else if (isFloatOutputDataType(sensorType)) { + return getFloat(varNr); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + } else if (isUInt32OutputDataType(sensorType)) { + return getUint32(varNr); + } else if (isInt32OutputDataType(sensorType)) { + return getInt32(varNr); + } else if (isUInt64OutputDataType(sensorType)) { + return getUint64(varNr); + } else if (isInt64OutputDataType(sensorType)) { + return getInt64(varNr); +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (isDoubleOutputDataType(sensorType)) { + return getDouble(varNr); +#endif +#endif + } + return 0.0; +} + +void TaskValues_Data_t::set(uint8_t varNr, const ESPEASY_RULES_FLOAT_TYPE& value, Sensor_VType sensorType) +{ + if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { + // Legacy formatting the old "SENSOR_TYPE_ULONG" type + setSensorTypeLong(value); + } else if (isFloatOutputDataType(sensorType)) { + setFloat(varNr, value); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + } else if (isUInt32OutputDataType(sensorType)) { + setUint32(varNr, value); + } else if (isInt32OutputDataType(sensorType)) { + setInt32(varNr, value); + } else if (isUInt64OutputDataType(sensorType)) { + setUint64(varNr, value); + } else if (isInt64OutputDataType(sensorType)) { + setInt64(varNr, value); +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (isDoubleOutputDataType(sensorType)) { + setDouble(varNr, value); +#endif +#endif + } +} + +bool TaskValues_Data_t::isValid(uint8_t varNr, Sensor_VType sensorType) const +{ + if (sensorType == Sensor_VType::SENSOR_TYPE_NONE) { + return false; + } else if (isFloatOutputDataType(sensorType)) { + return isValidFloat(getFloat(varNr)); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (isDoubleOutputDataType(sensorType)) { + return isValidDouble(getDouble(varNr)); +#endif +#endif + } + return true; +} + +String TaskValues_Data_t::getAsString(uint8_t varNr, Sensor_VType sensorType, uint8_t nrDecimals) const +{ + String result; + + if (isFloatOutputDataType(sensorType)) { + result = toString(getFloat(varNr), nrDecimals); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + } else if (isDoubleOutputDataType(sensorType)) { + result = doubleToString(getDouble(varNr), nrDecimals); +#endif +#endif + } else if (sensorType == Sensor_VType::SENSOR_TYPE_ULONG) { + return String(getSensorTypeLong()); +#if FEATURE_EXTENDED_TASK_VALUE_TYPES + } else if (isUInt32OutputDataType(sensorType)) { + return String(getUint32(varNr)); + } else if (isInt32OutputDataType(sensorType)) { + return String(getInt32(varNr)); + } else if (isUInt64OutputDataType(sensorType)) { + return ull2String(getUint64(varNr)); + } else if (isInt64OutputDataType(sensorType)) { + return ll2String(getInt64(varNr)); +#endif + } + result.trim(); + return result; +} diff --git a/src/src/DataTypes/TaskValues_Data.h b/src/src/DataTypes/TaskValues_Data.h index dbfe5e443..9d46fac4e 100644 --- a/src/src/DataTypes/TaskValues_Data.h +++ b/src/src/DataTypes/TaskValues_Data.h @@ -65,20 +65,7 @@ struct __attribute__((__packed__)) TaskValues_Data_t { String getAsString(uint8_t varNr, Sensor_VType sensorType, uint8_t nrDecimals = 0) const; - - union { - uint8_t binary[VARS_PER_TASK * sizeof(float)]; - float floats[VARS_PER_TASK]; - uint32_t uint32s[VARS_PER_TASK]; -#if FEATURE_EXTENDED_TASK_VALUE_TYPES - int32_t int32s[VARS_PER_TASK]; - uint64_t uint64s[VARS_PER_TASK / 2]; - int64_t int64s[VARS_PER_TASK / 2]; -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - double doubles[VARS_PER_TASK / 2]; -#endif -#endif - }; + uint8_t binary[VARS_PER_TASK * sizeof(float)]{}; }; #endif // ifndef DATATYPES_TASKVALUES_DATA_H diff --git a/src/src/ESPEasyCore/Controller.cpp b/src/src/ESPEasyCore/Controller.cpp index a7aa3868a..909715926 100644 --- a/src/src/ESPEasyCore/Controller.cpp +++ b/src/src/ESPEasyCore/Controller.cpp @@ -37,7 +37,7 @@ constexpr pluginID_t PLUGIN_ID_MQTT_IMPORT(37); // ******************************************************************************** // Interface for Sending to Controllers // ******************************************************************************** -void sendData(struct EventStruct *event) +void sendData(struct EventStruct *event, bool sendEvents) { START_TIMER; #ifndef BUILD_NO_RAM_TRACKER @@ -45,7 +45,7 @@ void sendData(struct EventStruct *event) #endif // ifndef BUILD_NO_RAM_TRACKER // LoadTaskSettings(event->TaskIndex); - if (Settings.UseRules) { + if (Settings.UseRules && sendEvents) { createRuleEvents(event); } @@ -57,14 +57,13 @@ void sendData(struct EventStruct *event) for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - event->ControllerIndex = x; - event->idx = Settings.TaskDeviceID[x][event->TaskIndex]; - - if (Settings.TaskDeviceSendData[event->ControllerIndex][event->TaskIndex] && - Settings.ControllerEnabled[event->ControllerIndex] && - Settings.Protocol[event->ControllerIndex]) + if (Settings.ControllerEnabled[x] && + Settings.TaskDeviceSendData[x][event->TaskIndex] && + Settings.Protocol[x]) { - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); + event->ControllerIndex = x; + const protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(event->ControllerIndex); + event->idx = Settings.TaskDeviceID[x][event->TaskIndex]; if (validUserVar(event)) { String dummy; @@ -378,7 +377,8 @@ bool MQTTConnect(controllerIndex_t controller_idx) #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS MQTTclient.setClient(mqtt); -#endif + MQTTclient.setKeepAlive(10); + MQTTclient.setSocketTimeout(timeout); if (ControllerSettings->UseDNS) { MQTTclient.setServer(ControllerSettings->getHost().c_str(), ControllerSettings->Port); @@ -401,7 +401,7 @@ bool MQTTConnect(controllerIndex_t controller_idx) addLog(LOG_LEVEL_ERROR, F("MQTT : Intentional reconnect")); } - const unsigned long connect_start_time = millis(); + const uint64_t statisticsTimerStart(getMicros64()); // https://github.com/knolleary/pubsubclient/issues/458#issuecomment-493875150 if (hasControllerCredentialsSet(controller_idx, *ControllerSettings)) { @@ -426,10 +426,12 @@ bool MQTTConnect(controllerIndex_t controller_idx) } delay(0); + count_connection_results( + MQTTresult, + F("MQTT : Broker "), + Settings.Protocol[controller_idx], + statisticsTimerStart); - uint8_t controller_number = Settings.Protocol[controller_idx]; - - count_connection_results(MQTTresult, F("MQTT : Broker "), controller_number, connect_start_time); #if FEATURE_MQTT_TLS if (mqtt_tls != nullptr) { @@ -783,7 +785,7 @@ bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, const if (MQTT_queueFull(controller_idx)) { return false; } - const bool success = MQTTDelayHandler->addToQueue(std::unique_ptr(new MQTT_queue_element(controller_idx, taskIndex, topic, payload, retained, callbackTask))); + const bool success = MQTTDelayHandler->addToQueue(std::unique_ptr(new (std::nothrow) MQTT_queue_element(controller_idx, taskIndex, topic, payload, retained, callbackTask))); scheduleNextMQTTdelayQueue(); return success; @@ -798,7 +800,7 @@ bool MQTTpublish(controllerIndex_t controller_idx, taskIndex_t taskIndex, Strin return false; } - const bool success = MQTTDelayHandler->addToQueue(std::unique_ptr(new MQTT_queue_element(controller_idx, taskIndex, std::move(topic), std::move(payload), retained, callbackTask))); + const bool success = MQTTDelayHandler->addToQueue(std::unique_ptr(new (std::nothrow) MQTT_queue_element(controller_idx, taskIndex, std::move(topic), std::move(payload), retained, callbackTask))); scheduleNextMQTTdelayQueue(); return success; @@ -907,6 +909,9 @@ void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime) void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime, unsigned long lasttimer) { 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 @@ -921,7 +926,7 @@ void SensorSendTask(struct EventStruct *event, unsigned long timestampUnixTime, struct EventStruct TempEvent(event->TaskIndex); TempEvent.Source = event->Source; - TempEvent.timestamp = timestampUnixTime; + TempEvent.timestamp_sec = timestampUnixTime; checkDeviceVTypeForTask(&TempEvent); String dummy; diff --git a/src/src/ESPEasyCore/Controller.h b/src/src/ESPEasyCore/Controller.h index f229d9ac4..410c76df7 100644 --- a/src/src/ESPEasyCore/Controller.h +++ b/src/src/ESPEasyCore/Controller.h @@ -9,7 +9,7 @@ // ******************************************************************************** // Interface for Sending to Controllers // ******************************************************************************** -void sendData(struct EventStruct *event); +void sendData(struct EventStruct *event, bool sendEvents = true); bool validUserVar(struct EventStruct *event); diff --git a/src/src/ESPEasyCore/ESPEasyEth.cpp b/src/src/ESPEasyCore/ESPEasyEth.cpp index 66c257a2a..c0b8a8ac9 100644 --- a/src/src/ESPEasyCore/ESPEasyEth.cpp +++ b/src/src/ESPEasyCore/ESPEasyEth.cpp @@ -1,289 +1,372 @@ -#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) - && isValid(Settings.ETH_Clock_Mode) - && isValid(Settings.NetworkMedium) - && validGpio(Settings.ETH_Pin_mdc) - && validGpio(Settings.ETH_Pin_mdio) - && (validGpio(Settings.ETH_Pin_power) || (Settings.ETH_Pin_power == -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; - log += F(" Eth Clock mode: "); - log += toString(Settings.ETH_Clock_Mode); - log += F(" MDC Pin: "); - log += String(Settings.ETH_Pin_mdc); - log += F(" MIO Pin: "); - log += String(Settings.ETH_Pin_mdio); - log += F(" Power Pin: "); - log += String(Settings.ETH_Pin_power); - 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) { - ethResetGPIOpins(); -#if ESP_IDF_VERSION_MAJOR < 5 - EthEventData.ethInitSuccess = ETH.begin( - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_power, - Settings.ETH_Pin_mdc, - Settings.ETH_Pin_mdio, - (eth_phy_type_t)Settings.ETH_Phy_Type, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); -#else - EthEventData.ethInitSuccess = ETH.begin( - (eth_phy_type_t)Settings.ETH_Phy_Type, - Settings.ETH_Phy_Addr, - Settings.ETH_Pin_mdc, - Settings.ETH_Pin_mdio, - Settings.ETH_Pin_power, - (eth_clock_mode_t)Settings.ETH_Clock_Mode); - -#endif - } - if (EthEventData.ethInitSuccess) { - // FIXME TD-er: Not sure if this is correctly set to false - //EthEventData.ethConnectAttemptNeeded = false; - - if (EthLinkUp()) { - // We might miss the connected event, since we are already connected. - EthEventData.markConnected(); - } - } - return EthEventData.ethInitSuccess; -} - -void ethPower(bool enable) { - if (Settings.ETH_Pin_power != -1) { - if (GPIO_Internal_Read(Settings.ETH_Pin_power) == 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); - - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power, 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() { - // 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); - gpio_reset_pin((gpio_num_t)Settings.ETH_Pin_mdio); - 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 - /* - 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; -} - +#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/ESPEasyEthEvent.cpp b/src/src/ESPEasyCore/ESPEasyEthEvent.cpp index 149f0ce36..8f69a1417 100644 --- a/src/src/ESPEasyCore/ESPEasyEthEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyEthEvent.cpp @@ -9,7 +9,7 @@ // ******************************************************************************** -// Functions called on events. +// Functions called on events. ** WARNING ** // Make sure not to call anything in these functions that result in delay() or yield() // ******************************************************************************** @@ -22,25 +22,25 @@ void EthEvent(WiFiEvent_t event, arduino_event_info_t info) { 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!")); +// 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")); +// 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")); +// addLog(LOG_LEVEL_INFO, F("ETH event: Got IP")); break; case ARDUINO_EVENT_ETH_DISCONNECTED: - addLog(LOG_LEVEL_ERROR, F("ETH event: 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")); +// addLog(LOG_LEVEL_INFO, F("ETH event: Stopped")); break; # if ESP_IDF_VERSION_MAJOR > 3 case ARDUINO_EVENT_ETH_GOT_IP6: @@ -52,10 +52,10 @@ void EthEvent(WiFiEvent_t event, arduino_event_info_t info) { 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()); +// addLog(LOG_LEVEL_INFO, String(F("ETH event: Got IP6 ")) + ip.toString(true)); } #else - addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); +// addLog(LOG_LEVEL_INFO, F("ETH event: Got IP6")); #endif break; default: diff --git a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp b/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp index 6e48a796d..50e35132c 100644 --- a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.cpp @@ -17,6 +17,7 @@ # include "../Globals/NetworkState.h" # include "../Globals/Settings.h" +# include "../Helpers/LongTermTimer.h" # include "../Helpers/Network.h" # include "../Helpers/Networking.h" # include "../Helpers/PeriodicalActions.h" @@ -29,10 +30,7 @@ void handle_unprocessedEthEvents() { // Process disconnect events before connect events. #if FEATURE_USE_IPV6 if (!EthEventData.processedGotIP6) { -#if FEATURE_ESPEASY_P2P - updateUDPport(); -#endif - EthEventData.processedGotIP6 = true; + processEthernetGotIPv6(); } #endif @@ -167,14 +165,19 @@ void processEthernetGotIP() { 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 (!dns0 && !dns1) { - addLog(LOG_LEVEL_ERROR, F("ETH : No DNS server received via DHCP, use cached DNS IP")); - setDNS(0, EthEventData.dns0_cache); - setDNS(1, EthEventData.dns1_cache); - } else { - EthEventData.dns0_cache = dns0; - EthEventData.dns1_cache = dns1; + 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)) @@ -247,8 +250,30 @@ void processEthernetGotIP() { 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 index 5c6708f89..40c10dcf8 100644 --- a/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.h +++ b/src/src/ESPEasyCore/ESPEasyEth_ProcessEvent.h @@ -11,6 +11,9 @@ 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 index 07a8767a3..548c21280 100644 --- a/src/src/ESPEasyCore/ESPEasyNetwork.cpp +++ b/src/src/ESPEasyCore/ESPEasyNetwork.cpp @@ -1,418 +1,426 @@ -#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(); - } - #endif - break; - case NetworkMedium_t::WIFI: - WiFiEventData.timerAPoff.setMillisFromNow(WIFI_AP_OFF_TIMER_DURATION); - WiFiEventData.timerAPstart.clear(); - if (new_medium == NetworkMedium_t::Ethernet) { - WifiDisconnect(); - } - 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 (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().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) { - String hostname(NetworkGetHostNameFromSettings(force_add_unitnr)); - // Create hostname with - instead of spaces - - // See RFC952. - // Allowed chars: - // * letters (a-z, A-Z) - // * numerals (0-9) - // * Hyphen (-) - replaceUnicodeByChar(hostname, '-'); - for (size_t i = 0; i < hostname.length(); ++i) { - const char c = hostname[i]; - if (!isAlphaNumeric(c)) { - hostname[i] = '-'; - } - } - - // May not start or end with a hyphen - const String dash('-'); - 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 = concat(F("ESPEasy-"), hostname); - } - - if (hostname.length() > 24) { - hostname = hostname.substring(0, 24); - } - - 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.timeSource > 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) { - #ifdef ESP_IDF_VERSION_MAJOR - // 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 +#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; + 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 (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) { + String hostname(NetworkGetHostNameFromSettings(force_add_unitnr)); + // Create hostname with - instead of spaces + + // See RFC952. + // Allowed chars: + // * letters (a-z, A-Z) + // * numerals (0-9) + // * Hyphen (-) + replaceUnicodeByChar(hostname, '-'); + for (size_t i = 0; i < hostname.length(); ++i) { + const char c = hostname[i]; + if (!isAlphaNumeric(c)) { + hostname[i] = '-'; + } + } + + // May not start or end with a hyphen + const String dash('-'); + 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 = concat(F("ESPEasy-"), hostname); + } + + if (hostname.length() > 24) { + hostname = hostname.substring(0, 24); + } + + 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 index e132bf3fc..c6e00d7af 100644 --- a/src/src/ESPEasyCore/ESPEasyNetwork.h +++ b/src/src/ESPEasyCore/ESPEasyNetwork.h @@ -1,55 +1,55 @@ -#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); -MAC_address WifiSoftAPmacAddress(); -MAC_address WifiSTAmacAddress(); - -void CheckRunningServices(); - -#if FEATURE_ETHERNET -bool EthFullDuplex(); -bool EthLinkUp(); -uint8_t EthLinkSpeed(); -#endif // if FEATURE_ETHERNET - - +#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); +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 6edc03fa4..481306671 100644 --- a/src/src/ESPEasyCore/ESPEasyRules.cpp +++ b/src/src/ESPEasyCore/ESPEasyRules.cpp @@ -125,13 +125,12 @@ void rulesProcessing(const String& event) { if (fileExists(fileName)) { rulesProcessingFile(fileName, event); } -# ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG else { - addLog(LOG_LEVEL_DEBUG, String(F("EVENT: ")) + event + - F(" is ingnored. File ") + fileName + - F(" not found.")); + addLog(LOG_LEVEL_DEBUG, strformat(F("EVENT: %s is ingnored. File %s not found."), + event.c_str(), fileName.c_str())); } -# endif // ifndef BUILD_NO_DEBUG + # endif // ifndef BUILD_NO_DEBUG #endif // WEBSERVER_NEW_RULES } @@ -497,7 +496,8 @@ void parse_string_commands(String& line) { if (command_i != -1) { const string_commands_e command = static_cast(command_i); - // addLog(LOG_LEVEL_INFO, String(F("parse_string_commands cmd: ")) + cmd_s_lower + " " + arg1 + " " + arg2 + " " + arg3); + // addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s %s %s %s"), + // cmd_s_lower.c_str(), arg1.c_str(), arg2.c_str(), arg3.c_str())); switch (command) { case string_commands_e::substring: @@ -625,7 +625,7 @@ void parse_string_commands(String& line) { /* if (replacement.length() > 0) { - addLog(LOG_LEVEL_INFO, String(F("parse_string_commands cmd: ")) + fullCommand + String(F(" -> ")) + replacement); + addLog(LOG_LEVEL_INFO, strformat(F("parse_string_commands cmd: %s -> %s"), fullCommand.c_str(), replacement.c_str()); } */ } @@ -825,11 +825,10 @@ void parseCompleteNonCommentLine(String& line, const String& event, } if (isCommand && lineStartsWith_pct_event) { - action = String(F("restrict,")) + action; + action = concat(F("restrict,"), action); if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Rules : Prefix command with 'restrict': "); - log += action; - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, + concat(F("Rules : Prefix command with 'restrict': "), action)); } } @@ -978,9 +977,10 @@ void processMatchedRule(String& action, const String& event, } if (executeRestricted) { - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2).c_str()); + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES_RESTRICTED, parseStringToEndKeepCase(action, 2)}); } else { - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str()); + // Use action.c_str() here as we need to preserve the action string. + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_RULES, action.c_str()}); } delay(0); } @@ -1298,7 +1298,7 @@ void createRuleEvents(struct EventStruct *event) { // These also only yield a single value, so no need to check for combining task values. if (event->getSensorType() == Sensor_VType::SENSOR_TYPE_STRING) { size_t expectedSize = 2 + getTaskDeviceName(event->TaskIndex).length(); - expectedSize += getTaskValueName(event->TaskIndex, 0).length(); + expectedSize += Cache.getTaskDeviceValueName(event->TaskIndex, 0).length(); bool appendCompleteStringvalue = false; @@ -1312,7 +1312,7 @@ void createRuleEvents(struct EventStruct *event) { } eventString += getTaskDeviceName(event->TaskIndex); eventString += '#'; - eventString += getTaskValueName(event->TaskIndex, 0); + eventString += Cache.getTaskDeviceValueName(event->TaskIndex, 0); eventString += '='; eventString += '`'; if (appendCompleteStringvalue) { @@ -1337,7 +1337,7 @@ void createRuleEvents(struct EventStruct *event) { eventQueue.add(event->TaskIndex, F("All"), eventvalues); } else { for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, varNr), formatUserVarNoCheck(event, varNr)); + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, varNr), formatUserVarNoCheck(event, varNr)); } } } diff --git a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp b/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp index c344bf39e..ea4d769fd 100644 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWiFiEvent.cpp @@ -1,440 +1,440 @@ -#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 WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { - _useStaticIp = enabled; -} - -#endif // ifdef ESP32 -#ifdef ESP8266 -void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { - _useStaticIp = enabled; -} - -#endif // ifdef ESP8266 - - -void setUseStaticIP(bool enabled) { - WiFi_Access_Static_IP tmp_wifi; - - tmp_wifi.set_use_static_ip(enabled); -} - - -// ******************************************************************************** -// 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 - #if FEATURE_USE_IPV6 - WiFi.enableIpV6(); - #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 - WiFiSTAClass::_setStatus(WL_CONNECTION_LOST); - } - #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); - IPAddress ip(IPv6, (const uint8_t*)event->ip6_info.ip.addr, event->ip6_info.ip.zone); - WiFiEventData.markGotIPv6(ip); - addLog(LOG_LEVEL_INFO, String(F("WIFI : STA got IP6 ")) + ip.toString()); - 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 +#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 index 3625b215b..996692c62 100644 --- a/src/src/ESPEasyCore/ESPEasyWiFiEvent.h +++ b/src/src/ESPEasyCore/ESPEasyWiFiEvent.h @@ -1,74 +1,74 @@ -#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 - - +#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 index 9370a4671..296f660ae 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi.cpp @@ -1,1645 +1,1705 @@ -#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) { -#if FEATURE_ESPEASY_P2P - updateUDPport(); -#endif - WiFiEventData.processedGotIP6 = true; - } -#endif - - - if (lastCheckedTime != 0 && timePassedSince(lastCheckedTime) < 100) { - // 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 (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) { - addLog(LOG_LEVEL_INFO, F("WiFi : WiFiConnected(), start AP")); - WifiScan(false); - 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.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(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 - WiFi.IPv6(true); -#endif - - if ((Settings.HiddenSSID_SlowConnectPerBSSID() || !candidate.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()); - } - if (Settings.WaitWiFiConnect() || candidate.isHidden) { -// WiFi.waitForConnectResult(candidate.isHidden ? 3000 : 1000); // https://github.com/arendst/Tasmota/issues/14985 - WiFi.waitForConnectResult(1000); // https://github.com/arendst/Tasmota/issues/14985 - } - 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 - addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); - - // 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 - 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; - } - #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; - } - // Prevent recursion - static bool processingDisconnect = false; - if (processingDisconnect) return; - processingDisconnect = true; - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("WiFi : WifiDisconnect()")); - #endif - #ifdef ESP32 - WiFi.disconnect(); - delay(1); - removeWiFiEventHandler(); - { - 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(); - processingDisconnect = false; -} - -// ******************************************************************************** -// 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; -#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 = false; - const uint32_t max_ms_per_chan = 300; - 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, F("WIFI : [AP] softAPConfig failed!")); - } - - 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) { - addLog(LOG_LEVEL_INFO, F("WIFI : Cannot set mode!!!!!")); - 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 (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.phy_11g != candidate.phy_11n)) { - if ((WIFI_PHY_MODE_11G == phyMode) && !candidate.phy_11g) { - phyMode = WIFI_PHY_MODE_11N; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); - } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.phy_11n) { - phyMode = WIFI_PHY_MODE_11G; - addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); - } - } 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.phy_11b && !candidate.phy_11g && candidate.phy_11n) { - if (candidate.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.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 -} +#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) { + addLog(LOG_LEVEL_INFO, F("WiFi : WiFiConnected(), start AP")); + 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 + addLog(LOG_LEVEL_INFO, F("Reset WiFi.")); + + // 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 + 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; + } + #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.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) { + addLog(LOG_LEVEL_INFO, F("WIFI : Cannot set mode!!!!!")); + 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 (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; + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11n only")); + } else if ((WIFI_PHY_MODE_11N == phyMode) && !candidate.bits.phy_11n) { + phyMode = WIFI_PHY_MODE_11G; + addLog(LOG_LEVEL_INFO, F("WIFI : AP is set to 802.11g only")); + } + } 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 index 0fbea2570..9b680a4fb 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi.h +++ b/src/src/ESPEasyCore/ESPEasyWifi.h @@ -1,157 +1,157 @@ -#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(); - - +#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 index 4e08ff092..ba4761597 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.cpp @@ -1,595 +1,611 @@ -#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); - setWebserverRunning(true); -/* -#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(); -#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().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(); -} - -// 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 - -// setSTA(false); -// NetworkConnectRelaxed(); -#ifdef USES_ESPEASY_NOW - temp_disable_EspEasy_now_timer = millis() + 20000; -#endif - } - } else if (!WiFiEventData.wifiConnectInProgress && !NetworkConnected()) { - WiFiEventData.timerAPstart.setNow(); - } - -} - - - - +#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 index 42fb965d6..826bda61d 100644 --- a/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h +++ b/src/src/ESPEasyCore/ESPEasyWifi_ProcessEvent.h @@ -1,15 +1,18 @@ -#ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H -#define ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H - -#include "../../ESPEasy_common.h" - -void handle_unprocessedNetworkEvents(); -void processDisconnect(); -void processConnect(); -void processGotIP(); -void processDisconnectAPmode(); -void processConnectAPmode(); -void processDisableAPmode(); -void processScanDone(); - -#endif // ifndef ESPEASYCORE_ESPEASYWIFI_PROCESSEVENT_H +#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 ab23476ad..056bb5261 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Console.cpp @@ -149,10 +149,22 @@ void EspEasy_Console_t::reInit() HeapSelectDram ephemeral; # endif // ifdef USE_SECOND_HEAP + unsigned int buffsize = 128; + + const ESPEasySerialPort mainSerialPort = static_cast(_console_serial_port); + +#if USES_HWCDC + if (mainSerialPort == ESPEasySerialPort::usb_hw_cdc) { + buffsize = 2048; + } +#endif // if USES_HWCDC + _mainSerial._serial = new (std::nothrow) ESPeasySerial( - static_cast(_console_serial_port), + mainSerialPort, _console_serial_rxpin, - _console_serial_txpin); + _console_serial_txpin, + false, + buffsize); somethingChanged = true; } # if USES_ESPEASY_CONSOLE_FALLBACK_PORT diff --git a/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp b/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp index ee34230e0..91f39d59b 100644 --- a/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Console_Port.cpp @@ -24,10 +24,12 @@ EspEasy_Console_Port::~EspEasy_Console_Port() { +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT if (_serial != nullptr) { delete _serial; _serial = nullptr; } +#endif } EspEasy_Console_Port::operator bool() const @@ -142,9 +144,10 @@ bool EspEasy_Console_Port::process_consoleInput(uint8_t SerialInByte) if (SerialInByteCounter != 0) { InputBuffer_Serial[SerialInByteCounter] = 0; // serial data completed addToSerialBuffer('>'); - addToSerialBuffer(String(InputBuffer_Serial)); + String cmd(InputBuffer_Serial); + addToSerialBuffer(cmd); addToSerialBuffer('\n'); - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_SERIAL, InputBuffer_Serial); + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SERIAL, std::move(cmd)}, true); SerialInByteCounter = 0; InputBuffer_Serial[0] = 0; // serial data processed, clear buffer return true; diff --git a/src/src/ESPEasyCore/ESPEasy_Log.cpp b/src/src/ESPEasyCore/ESPEasy_Log.cpp index 0ffffd746..eade2faa6 100644 --- a/src/src/ESPEasyCore/ESPEasy_Log.cpp +++ b/src/src/ESPEasyCore/ESPEasy_Log.cpp @@ -115,6 +115,15 @@ 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) + // is also really not a good idea from an ISR call. + return false; + } + #endif return logLevel <= highest_active_log_level; } @@ -146,6 +155,16 @@ uint8_t getWebLogLevel() { } bool loglevelActiveFor(uint8_t 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) + // is also really not a good idea from an ISR call. + return false; + } + #endif + uint8_t logLevelSettings = 0; switch (destination) { case LOG_TO_SERIAL: { @@ -293,6 +312,16 @@ void addToSDLog(uint8_t logLevel, const String& string) 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) + // is also really not a good idea from an ISR call. + return; + } + #endif + if (string.isEmpty()) return; addToSerialLog(logLevel, string); addToSysLog(logLevel, string); @@ -304,6 +333,16 @@ void addLog(uint8_t logLevel, const String& string) void addToLogMove(uint8_t logLevel, 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) + // is also really not a good idea from an ISR call. + return; + } + #endif + if (string.isEmpty()) return; addToSerialLog(logLevel, string); addToSysLog(logLevel, string); diff --git a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp index 331a6b66d..ca21573cb 100644 --- a/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp +++ b/src/src/ESPEasyCore/ESPEasy_backgroundtasks.cpp @@ -1,135 +1,140 @@ -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" - -#include "../../ESPEasy_common.h" - -#include "../../ESPEasy-Globals.h" -#include "../DataStructs/TimingStats.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/NetworkState.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/Networking.h" - - -#if FEATURE_ARDUINO_OTA -#include "../Helpers/OTA.h" -#endif - - - -/*********************************************************************************************\ -* run background tasks -\*********************************************************************************************/ -bool runningBackgroundTasks = false; -void backgroundtasks() -{ - // checkRAM(F("backgroundtasks")); - // always start with a yield - delay(0); - - /* - // Remove this watchdog feed for now. - // See https://github.com/letscontrolit/ESPEasy/issues/1722#issuecomment-419659193 - - #ifdef ESP32 - // Have to find a similar function to call ESP32's esp_task_wdt_feed(); - #else - ESP.wdtFeed(); - #endif - */ - - // prevent recursion! - if (runningBackgroundTasks) - { - return; - } - - // Rate limit calls to run backgroundtasks - static uint32_t lastRunBackgroundTasks = 0; - if (timePassedSince(lastRunBackgroundTasks) < 10) return; - lastRunBackgroundTasks = millis(); - - START_TIMER - #if FEATURE_MDNS - const bool networkConnected = NetworkConnected(); - #else - NetworkConnected(); - #endif - - runningBackgroundTasks = true; - - /* - // Not needed anymore, see: https://arduino-esp8266.readthedocs.io/en/latest/faq/readme.html#how-to-clear-tcp-pcbs-in-time-wait-state - if (networkConnected) { - #if defined(ESP8266) - tcpCleanup(); - #endif - } - */ - - process_serialWriteBuffer(); - - if (!UseRTOSMultitasking) { - serial(); - -// if (webserverRunning) { - web_server.handleClient(); -// } - #if FEATURE_ESPEASY_P2P - checkUDP(); - #endif - } - - #if FEATURE_DNS_SERVER - - // process DNS, only used if the ESP has no valid WiFi config - if (dnsServerActive) { - dnsServer.processNextRequest(); - } - #endif // if FEATURE_DNS_SERVER - - #if FEATURE_ARDUINO_OTA - - if (Settings.ArduinoOTAEnable) { - ArduinoOTA_handle(); - } - - // once OTA is triggered, only handle that and dont do other stuff. (otherwise it fails) - while (ArduinoOTAtriggered) - { - delay(0); - - ArduinoOTA_handle(); - } - - #endif // if FEATURE_ARDUINO_OTA - - #if FEATURE_MDNS - - // Allow MDNS processing - if (networkConnected) { - # ifdef ESP8266 - - // ESP32 does not have an update() function - MDNS.update(); - # endif // ifdef ESP8266 - } - #endif // if FEATURE_MDNS - - delay(0); - - #if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC - update_rtttl(); - #endif // if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC - - statusLED(false); - - runningBackgroundTasks = false; - STOP_TIMER(BACKGROUND_TASKS); -} +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" + +#include "../../ESPEasy_common.h" + +#include "../../ESPEasy-Globals.h" +#include "../DataStructs/TimingStats.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/NetworkState.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/Networking.h" + + +#if FEATURE_ARDUINO_OTA +#include "../Helpers/OTA.h" +#endif + + + +/*********************************************************************************************\ +* run background tasks +\*********************************************************************************************/ +bool runningBackgroundTasks = false; +void backgroundtasks() +{ + // checkRAM(F("backgroundtasks")); + // always start with a yield + delay(0); + + /* + // Remove this watchdog feed for now. + // See https://github.com/letscontrolit/ESPEasy/issues/1722#issuecomment-419659193 + + #ifdef ESP32 + // Have to find a similar function to call ESP32's esp_task_wdt_feed(); + #else + ESP.wdtFeed(); + #endif + */ + + // prevent recursion! + if (runningBackgroundTasks) + { + return; + } + + // Rate limit calls to run backgroundtasks + static uint32_t lastRunBackgroundTasks = 0; + if (timePassedSince(lastRunBackgroundTasks) < 10) return; + lastRunBackgroundTasks = millis(); + + START_TIMER + #if FEATURE_MDNS || FEATURE_ESPEASY_P2P + const bool networkConnected = NetworkConnected(); + #else + NetworkConnected(); + #endif + + runningBackgroundTasks = true; + + /* + // Not needed anymore, see: https://arduino-esp8266.readthedocs.io/en/latest/faq/readme.html#how-to-clear-tcp-pcbs-in-time-wait-state + if (networkConnected) { + #if defined(ESP8266) + tcpCleanup(); + #endif + } + */ + + process_serialWriteBuffer(); + + if (!UseRTOSMultitasking) { + serial(); + +// if (webserverRunning) { + { + START_TIMER + web_server.handleClient(); + STOP_TIMER(WEBSERVER_HANDLE_CLIENT); + } + #if FEATURE_ESPEASY_P2P + if (networkConnected) { + checkUDP(); + } + #endif + } + + #if FEATURE_DNS_SERVER + + // process DNS, only used if the ESP has no valid WiFi config + if (dnsServerActive) { + dnsServer.processNextRequest(); + } + #endif // if FEATURE_DNS_SERVER + + #if FEATURE_ARDUINO_OTA + + if (Settings.ArduinoOTAEnable) { + ArduinoOTA_handle(); + } + + // once OTA is triggered, only handle that and dont do other stuff. (otherwise it fails) + while (ArduinoOTAtriggered) + { + delay(0); + + ArduinoOTA_handle(); + } + + #endif // if FEATURE_ARDUINO_OTA + + #if FEATURE_MDNS + + // Allow MDNS processing + if (networkConnected) { + # ifdef ESP8266 + + // ESP32 does not have an update() function + MDNS.update(); + # endif // ifdef ESP8266 + } + #endif // if FEATURE_MDNS + + delay(0); + + #if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC + update_rtttl(); + #endif // if FEATURE_RTTTL && FEATURE_ANYRTTTL_LIB && FEATURE_ANYRTTTL_ASYNC + + statusLED(false); + + runningBackgroundTasks = false; + STOP_TIMER(BACKGROUND_TASKS); +} diff --git a/src/src/ESPEasyCore/ESPEasy_loop.cpp b/src/src/ESPEasyCore/ESPEasy_loop.cpp index ab9dd9bd9..b18aedb86 100644 --- a/src/src/ESPEasyCore/ESPEasy_loop.cpp +++ b/src/src/ESPEasyCore/ESPEasy_loop.cpp @@ -2,6 +2,7 @@ #include "../../ESPEasy-Globals.h" +#include "../Commands/ExecuteCommand.h" #include "../DataStructs/TimingStats.h" #include "../ESPEasyCore/ESPEasyNetwork.h" #include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" @@ -162,11 +163,13 @@ void ESPEasy_loop() else { if (!UseRTOSMultitasking) { - // On ESP32 the schedule is executed on the 2nd core. + // On ESP32, when using RTOS multitasking, the schedule is executed in a separate RTOS task Scheduler.handle_schedule(); } } + // Calls above may have received/generated commands for the command queue, thus need to process them. + processExecuteCommandQueue(); backgroundtasks(); if (readyForSleep()) { diff --git a/src/src/ESPEasyCore/ESPEasy_setup.cpp b/src/src/ESPEasyCore/ESPEasy_setup.cpp index 4edc06fc0..c682d8798 100644 --- a/src/src/ESPEasyCore/ESPEasy_setup.cpp +++ b/src/src/ESPEasyCore/ESPEasy_setup.cpp @@ -1,666 +1,668 @@ -#include "../ESPEasyCore/ESPEasy_setup.h" - -#include "../../ESPEasy_fdwdecl.h" // Needed for PluginInit() and CPluginInit() - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" -#include "../Commands/InternalCommands_decoder.h" -#include "../CustomBuild/CompiletimeDefines.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/Cache.h" -#include "../Globals/ESPEasy_Console.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/NetworkState.h" -#include "../Globals/RTC.h" -#include "../Globals/Statistics.h" -#include "../Globals/WiFi_AP_Candidates.h" -#include "../Helpers/_CPlugin_init.h" -#include "../Helpers/_NPlugin_init.h" -#include "../Helpers/_Plugin_init.h" -#include "../Helpers/DeepSleep.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_checks.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringGenerator_System.h" -#include "../WebServer/ESPEasy_WebServer.h" - - -#ifdef USE_RTOS_MULTITASKING -# include "../Helpers/Networking.h" -# include "../Helpers/PeriodicalActions.h" -#endif // ifdef USE_RTOS_MULTITASKING - -#if FEATURE_ARDUINO_OTA -# include "../Helpers/OTA.h" -#endif // if FEATURE_ARDUINO_OTA - -#ifdef ESP32 - -#if ESP_IDF_VERSION_MAJOR < 5 -#include -#include -#include -#include -#else -#include -#include -#include -#endif - -#if CONFIG_IDF_TARGET_ESP32 -#if ESP_IDF_VERSION_MAJOR < 5 -# include "hal/efuse_ll.h" -# include "hal/efuse_hal.h" -#else -#include -#endif -#endif - -#endif - - -#ifdef USE_RTOS_MULTITASKING -void RTOS_TaskServers(void *parameter) -{ - while (true) { - delay(100); - web_server.handleClient(); - #if FEATURE_ESPEASY_P2P - checkUDP(); - #endif - } -} - -void RTOS_TaskSerial(void *parameter) -{ - while (true) { - delay(100); - serial(); - } -} - -void RTOS_Task10ps(void *parameter) -{ - while (true) { - delay(100); - run10TimesPerSecond(); - } -} - -void RTOS_HandleSchedule(void *parameter) -{ - while (true) { - Scheduler.handle_schedule(); - } -} - -#endif // ifdef USE_RTOS_MULTITASKING - - -/*********************************************************************************************\ -* ISR call back function for handling the watchdog. -\*********************************************************************************************/ -void sw_watchdog_callback(void *arg) -{ - yield(); // feed the WD - ++sw_watchdog_callback_count; -} - -/*********************************************************************************************\ -* SETUP -\*********************************************************************************************/ -void ESPEasy_setup() -{ -#if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) -// disable_extra4k_at_link_time(); -#endif -#ifdef PHASE_LOCKED_WAVEFORM - enablePhaseLockedWaveform(); -#endif -#ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; -#endif -#ifdef ESP32 -#ifdef DISABLE_ESP32_BROWNOUT - DisableBrownout(); // Workaround possible weak LDO resulting in brownout detection during Wifi connection -#endif // DISABLE_ESP32_BROWNOUT - -#ifdef BOARD_HAS_PSRAM - psramInit(); -#endif - -#if CONFIG_IDF_TARGET_ESP32 - // restore GPIO16/17 if no PSRAM is found - if (!FoundPSRAM()) { - // test if the CPU is not pico - #if ESP_IDF_VERSION_MAJOR < 5 - uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG); - #else - uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_PACKAGE); - #endif - uint32_t pkg_version = chip_ver & 0x7; - if (pkg_version <= 3) { // D0WD, S0WD, D2WD - gpio_reset_pin(GPIO_NUM_16); - gpio_reset_pin(GPIO_NUM_17); - } - } -#endif // if CONFIG_IDF_TARGET_ESP32 - initADC(); -#endif // ESP32 -#ifndef BUILD_NO_RAM_TRACKER - lowestFreeStack = getFreeStackWatermark(); - lowestRAM = FreeMem(); -#endif // ifndef BUILD_NO_RAM_TRACKER - -#ifdef ESP32 - ResetFactoryDefaultPreference.init(); -#endif - -#ifndef BUILD_NO_DEBUG -// checkAll_internalCommands(); -#endif - - PluginSetup(); - CPluginSetup(); - - initWiFi(); - WiFiEventData.clearAll(); - -#ifndef BUILD_MINIMAL_OTA - run_compiletime_checks(); -#endif -#ifdef ESP8266 - - // ets_isr_attach(8, sw_watchdog_callback, nullptr); // Set a callback for feeding the watchdog. -#endif // ifdef ESP8266 - - - // Read ADC at boot, before WiFi tries to connect. - // see https://github.com/letscontrolit/ESPEasy/issues/2646 -#if FEATURE_ADC_VCC - vcc = ESP.getVcc() / 1000.0f; -#endif // if FEATURE_ADC_VCC -#ifdef ESP8266 - espeasy_analogRead(A0); -#endif // ifdef ESP8266 - - initAnalogWrite(); - - resetPluginTaskData(); - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setup")); - #endif // ifndef BUILD_NO_RAM_TRACKER - ESPEasy_Console.begin(115200); - - // serialPrint("\n\n\nBOOOTTT\n\n\n"); - - initLog(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("initLog()")); - #endif - #ifdef BOARD_HAS_PSRAM - if (FoundPSRAM()) { - if (UsePSRAM()) { - addLog(LOG_LEVEL_INFO, F("Using PSRAM")); - } else { - addLog(LOG_LEVEL_ERROR, F("PSRAM found, unable to use")); - } - } - #endif - - if (SpiffsSectors() < 32) - { - serialPrintln(F("\nNo (or too small) FS area..\nSystem Halted\nPlease reflash with 128k FS minimum!")); - - while (true) { - delay(1); - } - } - - emergencyReset(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("\n\n\rINIT : Booting version: "); - log += getValue(LabelType::BINARY_FILENAME); - log += F(", ("); - log += get_build_origin(); - log += F(") "); - log += getValue(LabelType::GIT_BUILD); - log += F(" ("); - log += getSystemLibraryString(); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - log = F("INIT : Free RAM:"); - log += FreeMem(); - addLogMove(LOG_LEVEL_INFO, log); - } - - readBootCause(); - - { - String log = F("INIT : "); - log += getLastBootCauseString(); - - if (readFromRTC()) - { - RTC.bootFailedCount++; - RTC.bootCounter++; - lastMixedSchedulerId_beforereboot.mixed_id = RTC.lastMixedSchedulerId; - readUserVarFromRTC(); - - log += F(" #"); - log += RTC.bootCounter; - - #ifndef BUILD_NO_DEBUG - log += F(" Last Action before Reboot: "); - log += ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); - log += F(" Last systime: "); - log += RTC.lastSysTime; - #endif // ifndef BUILD_NO_DEBUG - } - - // cold boot (RTC memory empty) - else - { - initRTC(); - - // cold boot situation - if (lastBootCause == BOOT_CAUSE_MANUAL_REBOOT) { // only set this if not set earlier during boot stage. - lastBootCause = BOOT_CAUSE_COLD_BOOT; - } - log = F("INIT : Cold Boot"); - } - - log += F(" - Restart Reason: "); - log += getResetReasonString(); - - RTC.deepSleepState = 0; - saveToRTC(); - - addLogMove(LOG_LEVEL_INFO, log); - } - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("RTC init")); - #endif - - fileSystemCheck(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("fileSystemCheck()")); - #endif - - // progMemMD5check(); - LoadSettings(); -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - ESPEasy_Console.reInit(); -#endif - - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("LoadSettings()")); - #endif - -#ifdef ESP32 - if (Settings.EcoPowerMode()) { - // Configure dynamic frequency scaling: - // maximum and minimum frequencies are set in sdkconfig, - // automatic light sleep is enabled if tickless idle support is enabled. -#if ESP_IDF_VERSION_MAJOR < 5 -#if CONFIG_IDF_TARGET_ESP32 - esp_pm_config_esp32_t pm_config = -#elif CONFIG_IDF_TARGET_ESP32S3 - esp_pm_config_esp32s3_t pm_config = -#elif CONFIG_IDF_TARGET_ESP32S2 - esp_pm_config_esp32s2_t pm_config = -#elif CONFIG_IDF_TARGET_ESP32C6 - esp_pm_config_esp32c3_t pm_config = -#elif CONFIG_IDF_TARGET_ESP32C3 - esp_pm_config_esp32c3_t pm_config = -#elif CONFIG_IDF_TARGET_ESP32C2 - esp_pm_config_esp32c2_t pm_config = -#endif - { - .max_freq_mhz = getCPU_MaxFreqMHz(), - - .min_freq_mhz = getCPU_MinFreqMHz(), -#if CONFIG_FREERTOS_USE_TICKLESS_IDLE - .light_sleep_enable = true -#endif - }; -#else - esp_pm_config_t pm_config = { - .max_freq_mhz = getCPU_MaxFreqMHz(), - .min_freq_mhz = 80, -#if CONFIG_FREERTOS_USE_TICKLESS_IDLE - .light_sleep_enable = true -#else - .light_sleep_enable = false -#endif - }; -#endif - esp_pm_configure(&pm_config); -#if CONFIG_IDF_TARGET_ESP32 - } else { - // Set the max/min frequency based on what's being reported by the efuses. - // Only ESP32 seems to have this function. - esp_pm_config_esp32_t pm_config = { - .max_freq_mhz = getCPU_MaxFreqMHz(), - .min_freq_mhz = getCPU_MinFreqMHz(), -#if CONFIG_FREERTOS_USE_TICKLESS_IDLE - .light_sleep_enable = false -#endif - }; - esp_pm_configure(&pm_config); -#endif - } -#endif - - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("hardwareInit")); - #endif // ifndef BUILD_NO_RAM_TRACKER - hardwareInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("hardwareInit()")); - #endif - - node_time.restoreFromRTC(); - - Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. - - if ((RTC.bootFailedCount > 10) && (RTC.bootCounter > 10)) { - uint8_t toDisable = RTC.bootFailedCount - 10; - toDisable = disablePlugin(toDisable); - - if (toDisable != 0) { - toDisable = disableController(toDisable); - } - #if FEATURE_NOTIFIER - if (toDisable != 0) { - toDisable = disableNotification(toDisable); - } - #endif - - if (toDisable != 0) { - toDisable = disableRules(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllPlugins(toDisable); - } - - if (toDisable != 0) { - toDisable = disableAllControllers(toDisable); - } -#if FEATURE_NOTIFIER - if (toDisable != 0) { - toDisable = disableAllNotifications(toDisable); - } -#endif - } - #if FEATURE_ETHERNET - - // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. - // This only works after LoadSettings(); - // Do not call setNetworkMedium here as that may try to clean up settings. - active_network_medium = Settings.NetworkMedium; - #else - if (Settings.NetworkMedium == NetworkMedium_t::Ethernet) { - Settings.NetworkMedium = 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) { - 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()) { - addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); - WifiScan(false); - } -// setWifiMode(WIFI_OFF); - } - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("WifiScan()")); - #endif - - - // setWifiMode(WIFI_STA); - checkRuleSets(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("checkRuleSets()")); - #endif - - - // if different version, eeprom settings structure has changed. Full Reset needed - // on a fresh ESP module eeprom values are set to 255. Version results into -1 (signed int) - if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) - { - // Direct Serial is allowed here, since this is only an emergency task. - serialPrint(F("\nPID:")); - serialPrintln(String(Settings.PID)); - serialPrint(F("Version:")); - serialPrintln(String(Settings.Version)); - serialPrintln(F("INIT : Incorrect PID or version!")); - delay(1000); - ResetFactory(); - } - - initSerial(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("initSerial()")); - #endif - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("INIT : Free RAM:"); - log += FreeMem(); - addLogMove(LOG_LEVEL_INFO, log); - } - -# ifndef BUILD_NO_DEBUG - if (Settings.UseSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { - ESPEasy_Console.setDebugOutput(true); - } -#endif - - timermqtt_interval = 250; // Interval for checking MQTT - timerAwakeFromDeepSleep = millis(); - CPluginInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("CPluginInit()")); - #endif - #if FEATURE_NOTIFIER - NPluginInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("NPluginInit()")); - #endif - #endif // if FEATURE_NOTIFIER - - PluginInit(); - - initSerial(); // Plugins may have altered serial, so re-init serial - - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("PluginInit()")); - #endif - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(80); - log += concat(F("INFO : Plugins: "), getDeviceCount() + 1); - log += ' '; - log += getPluginDescriptionString(); - log += F(" ("); - log += getSystemLibraryString(); - log += ')'; - addLogMove(LOG_LEVEL_INFO, log); - } - -/* - if ((getDeviceCount() + 1) >= PLUGIN_MAX) { - addLog(LOG_LEVEL_ERROR, concat(F("Programming error! - Increase PLUGIN_MAX ("), getDeviceCount()) + ')'); - } -*/ - - clearAllCaches(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("clearAllCaches()")); - #endif - - if (Settings.UseRules && isDeepSleepEnabled()) - { - String event = F("System#NoSleep="); - event += Settings.deepSleep_wakeTime; - rulesProcessing(event); // TD-er: Process events in the setup() now. - } - - if (Settings.UseRules) - { - String event = F("System#Wake"); - rulesProcessing(event); // TD-er: Process events in the setup() now. - } - #ifdef ESP32 - if (Settings.UseRules) - { - const uint32_t gpio_strap = GPIO_REG_READ(GPIO_STRAP_REG); -// BOOT_MODE_GET(); - - // Event values: - // ESP32 : GPIO-5, GPIO-15, GPIO-4, GPIO-2, GPIO-0, GPIO-12 - // ESP32-C3: bit 0: GPIO2, bit 2: GPIO8, bit 3: GPIO9 - // ESP32-S2: Unclear what bits represent which strapping state. - // ESP32-S3: bit5 ~ bit2 correspond to strapping pins GPIO3, GPIO45, GPIO0, and GPIO46 respectively. - String event = F("System#BootMode="); - event += bitRead(gpio_strap, 0); - event += ','; - event += bitRead(gpio_strap, 1); - event += ','; - event += bitRead(gpio_strap, 2); - event += ','; - event += bitRead(gpio_strap, 3); - event += ','; - event += bitRead(gpio_strap, 4); - event += ','; - event += bitRead(gpio_strap, 5); - rulesProcessing(event); - } - #endif - - #if FEATURE_ETHERNET - if (Settings.ETH_Pin_power != -1) { - GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power, 1); - } - - #endif - - NetworkConnectRelaxed(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("NetworkConnectRelaxed()")); - #endif - - setWebserverRunning(true); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("setWebserverRunning()")); - #endif - - - #if FEATURE_REPORTING - ReportStatus(); - #endif // if FEATURE_REPORTING - - #if FEATURE_ARDUINO_OTA - ArduinoOTAInit(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("ArduinoOTAInit()")); - #endif - #endif // if FEATURE_ARDUINO_OTA - - if (node_time.systemTimePresent()) { - node_time.initTime(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("node_time.initTime()")); - #endif - } - - if (Settings.UseRules) - { - String event = F("System#Boot"); - rulesProcessing(event); // TD-er: Process events in the setup() now. - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("rulesProcessing(System#Boot)")); - #endif - } - - writeDefaultCSS(); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("writeDefaultCSS()")); - #endif - - - UseRTOSMultitasking = Settings.UseRTOSMultitasking; - #ifdef USE_RTOS_MULTITASKING - - if (UseRTOSMultitasking) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, F("RTOS : Launching tasks")); - } - xTaskCreatePinnedToCore(RTOS_TaskServers, "RTOS_TaskServers", 16384, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore(RTOS_TaskSerial, "RTOS_TaskSerial", 8192, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore(RTOS_Task10ps, "RTOS_Task10ps", 8192, nullptr, 1, nullptr, 1); - xTaskCreatePinnedToCore( - RTOS_HandleSchedule, /* Function to implement the task */ - "RTOS_HandleSchedule", /* Name of the task */ - 16384, /* Stack size in words */ - nullptr, /* Task input parameter */ - 1, /* Priority of the task */ - nullptr, /* Task handle. */ - 1); /* Core where the task should run */ - } - #endif // ifdef USE_RTOS_MULTITASKING - - // Start the interval timers at N msec from now. - // Make sure to start them at some time after eachother, - // since they will keep running at the same interval. - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_20MSEC, 5); // timer for periodic actions 50 x per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_100MSEC, 66); // timer for periodic actions 10 x per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_1SEC, 777); // timer for periodic actions once per/sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_30SEC, 1333); // timer for watchdog once per 30 sec - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 88); // timer for interaction with MQTT - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_STATISTICS, 2222); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("Scheduler.setIntervalTimerOverride")); - #endif - -} +#include "../ESPEasyCore/ESPEasy_setup.h" + +#include "../../ESPEasy_fdwdecl.h" // Needed for PluginInit() and CPluginInit() + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" +#include "../Commands/InternalCommands_decoder.h" +#include "../CustomBuild/CompiletimeDefines.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyRules.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/ESPEasyWifi_ProcessEvent.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/Cache.h" +#include "../Globals/ESPEasy_Console.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/NetworkState.h" +#include "../Globals/RTC.h" +#include "../Globals/Statistics.h" +#include "../Globals/WiFi_AP_Candidates.h" +#include "../Helpers/_CPlugin_init.h" +#include "../Helpers/_NPlugin_init.h" +#include "../Helpers/_Plugin_init.h" +#include "../Helpers/DeepSleep.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_checks.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringGenerator_System.h" +#include "../WebServer/ESPEasy_WebServer.h" + + +#ifdef USE_RTOS_MULTITASKING +# include "../Helpers/Networking.h" +# include "../Helpers/PeriodicalActions.h" +#endif // ifdef USE_RTOS_MULTITASKING + +#if FEATURE_ARDUINO_OTA +# include "../Helpers/OTA.h" +#endif // if FEATURE_ARDUINO_OTA + +#ifdef ESP32 + +#if ESP_IDF_VERSION_MAJOR < 5 +#include +#include +#include +#include +#else +#include +#include +#include +#endif + +#if CONFIG_IDF_TARGET_ESP32 +#if ESP_IDF_VERSION_MAJOR < 5 +# include "hal/efuse_ll.h" +# include "hal/efuse_hal.h" +#else +#include +#endif +#endif + +#endif + + +#ifdef USE_RTOS_MULTITASKING +void RTOS_TaskServers(void *parameter) +{ + while (true) { + delay(100); + web_server.handleClient(); + #if FEATURE_ESPEASY_P2P + checkUDP(); + #endif + } +} + +void RTOS_TaskSerial(void *parameter) +{ + while (true) { + delay(100); + serial(); + } +} + +void RTOS_Task10ps(void *parameter) +{ + while (true) { + delay(100); + run10TimesPerSecond(); + } +} + +void RTOS_HandleSchedule(void *parameter) +{ + while (true) { + Scheduler.handle_schedule(); + } +} + +#endif // ifdef USE_RTOS_MULTITASKING + + +/*********************************************************************************************\ +* ISR call back function for handling the watchdog. +\*********************************************************************************************/ +void sw_watchdog_callback(void *arg) +{ + yield(); // feed the WD + ++sw_watchdog_callback_count; +} + +/*********************************************************************************************\ +* SETUP +\*********************************************************************************************/ +void ESPEasy_setup() +{ +#if defined(ESP8266_DISABLE_EXTRA4K) || defined(USE_SECOND_HEAP) +// disable_extra4k_at_link_time(); +#endif +#ifdef PHASE_LOCKED_WAVEFORM + enablePhaseLockedWaveform(); +#endif +#ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; +#endif +#ifdef ESP32 +#ifdef DISABLE_ESP32_BROWNOUT + DisableBrownout(); // Workaround possible weak LDO resulting in brownout detection during Wifi connection +#endif // DISABLE_ESP32_BROWNOUT + +#ifdef BOARD_HAS_PSRAM + psramInit(); +#endif + +#if CONFIG_IDF_TARGET_ESP32 + // restore GPIO16/17 if no PSRAM is found + if (!FoundPSRAM()) { + // test if the CPU is not pico + #if ESP_IDF_VERSION_MAJOR < 5 + uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG); + #else + uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_PACKAGE); + #endif + uint32_t pkg_version = chip_ver & 0x7; + if (pkg_version <= 3) { // D0WD, S0WD, D2WD + gpio_reset_pin(GPIO_NUM_16); + gpio_reset_pin(GPIO_NUM_17); + } + } +#endif // if CONFIG_IDF_TARGET_ESP32 + initADC(); +#endif // ESP32 +#ifndef BUILD_NO_RAM_TRACKER + lowestFreeStack = getFreeStackWatermark(); + lowestRAM = FreeMem(); +#endif // ifndef BUILD_NO_RAM_TRACKER + +/* +#ifdef ESP32 +{ + ESPEasy_NVS_Helper preferences; + ResetFactoryDefaultPreference.init(preferences); +} +#endif +*/ +#ifndef BUILD_NO_DEBUG +// checkAll_internalCommands(); +#endif + + PluginSetup(); + CPluginSetup(); + + initWiFi(); + WiFiEventData.clearAll(); + +#ifndef BUILD_MINIMAL_OTA + run_compiletime_checks(); +#endif +#ifdef ESP8266 + + // ets_isr_attach(8, sw_watchdog_callback, nullptr); // Set a callback for feeding the watchdog. +#endif // ifdef ESP8266 + + + // Read ADC at boot, before WiFi tries to connect. + // see https://github.com/letscontrolit/ESPEasy/issues/2646 +#if FEATURE_ADC_VCC + vcc = ESP.getVcc() / 1000.0f; +#endif // if FEATURE_ADC_VCC +#ifdef ESP8266 + espeasy_analogRead(A0); +#endif // ifdef ESP8266 + + initAnalogWrite(); + + resetPluginTaskData(); + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setup")); + #endif // ifndef BUILD_NO_RAM_TRACKER + ESPEasy_Console.begin(115200); + + // serialPrint("\n\n\nBOOOTTT\n\n\n"); + + initLog(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("initLog()")); + #endif + #ifdef BOARD_HAS_PSRAM + if (FoundPSRAM()) { + if (UsePSRAM()) { + addLog(LOG_LEVEL_INFO, F("Using PSRAM")); + } else { + addLog(LOG_LEVEL_ERROR, F("PSRAM found, unable to use")); + } + } + #endif + + if (SpiffsSectors() < 32) + { + serialPrintln(F("\nNo (or too small) FS area..\nSystem Halted\nPlease reflash with 128k FS minimum!")); + + while (true) { + delay(1); + } + } + + emergencyReset(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("\n\n\rINIT : Booting version: "); + log += getValue(LabelType::BINARY_FILENAME); + log += F(", ("); + log += get_build_origin(); + log += F(") "); + log += getValue(LabelType::GIT_BUILD); + log += F(" ("); + log += getSystemLibraryString(); + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + log = F("INIT : Free RAM:"); + log += FreeMem(); + addLogMove(LOG_LEVEL_INFO, log); + } + + readBootCause(); + + { + String log = F("INIT : "); + log += getLastBootCauseString(); + + if (readFromRTC()) + { + RTC.bootFailedCount++; + RTC.bootCounter++; + lastMixedSchedulerId_beforereboot.mixed_id = RTC.lastMixedSchedulerId; + readUserVarFromRTC(); + + log += F(" #"); + log += RTC.bootCounter; + + #ifndef BUILD_NO_DEBUG + log += F(" Last Action before Reboot: "); + log += ESPEasy_Scheduler::decodeSchedulerId(lastMixedSchedulerId_beforereboot); + log += F(" Last systime: "); + log += RTC.lastSysTime; + #endif // ifndef BUILD_NO_DEBUG + } + + // cold boot (RTC memory empty) + else + { + initRTC(); + + // cold boot situation + if (lastBootCause == BOOT_CAUSE_MANUAL_REBOOT) { // only set this if not set earlier during boot stage. + lastBootCause = BOOT_CAUSE_COLD_BOOT; + } + log = F("INIT : Cold Boot"); + } + + log += F(" - Restart Reason: "); + log += getResetReasonString(); + + RTC.deepSleepState = 0; + saveToRTC(); + + addLogMove(LOG_LEVEL_INFO, log); + } + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("RTC init")); + #endif + + fileSystemCheck(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("fileSystemCheck()")); + #endif + + // progMemMD5check(); + LoadSettings(); +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + ESPEasy_Console.reInit(); +#endif + + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("LoadSettings()")); + #endif + +#ifdef ESP32 + if (Settings.EcoPowerMode()) { + // Configure dynamic frequency scaling: + // maximum and minimum frequencies are set in sdkconfig, + // automatic light sleep is enabled if tickless idle support is enabled. +#if ESP_IDF_VERSION_MAJOR < 5 +#if CONFIG_IDF_TARGET_ESP32 + esp_pm_config_esp32_t pm_config = +#elif CONFIG_IDF_TARGET_ESP32S3 + esp_pm_config_esp32s3_t pm_config = +#elif CONFIG_IDF_TARGET_ESP32S2 + esp_pm_config_esp32s2_t pm_config = +#elif CONFIG_IDF_TARGET_ESP32C6 + esp_pm_config_esp32c3_t pm_config = +#elif CONFIG_IDF_TARGET_ESP32C3 + esp_pm_config_esp32c3_t pm_config = +#elif CONFIG_IDF_TARGET_ESP32C2 + esp_pm_config_esp32c2_t pm_config = +#endif + { + .max_freq_mhz = getCPU_MaxFreqMHz(), + + .min_freq_mhz = getCPU_MinFreqMHz(), +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = true +#endif + }; +#else + esp_pm_config_t pm_config = { + .max_freq_mhz = getCPU_MaxFreqMHz(), + .min_freq_mhz = 80, +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = true +#else + .light_sleep_enable = false +#endif + }; +#endif + esp_pm_configure(&pm_config); +#if CONFIG_IDF_TARGET_ESP32 + } else { + // Set the max/min frequency based on what's being reported by the efuses. + // Only ESP32 seems to have this function. + esp_pm_config_esp32_t pm_config = { + .max_freq_mhz = getCPU_MaxFreqMHz(), + .min_freq_mhz = getCPU_MinFreqMHz(), +#if CONFIG_FREERTOS_USE_TICKLESS_IDLE + .light_sleep_enable = false +#endif + }; + esp_pm_configure(&pm_config); +#endif + } +#endif + + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("hardwareInit")); + #endif // ifndef BUILD_NO_RAM_TRACKER + hardwareInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("hardwareInit()")); + #endif + + node_time.restoreFromRTC(); + + Settings.UseRTOSMultitasking = false; // For now, disable it, we experience heap corruption. + + if ((RTC.bootFailedCount > 10) && (RTC.bootCounter > 10)) { + uint8_t toDisable = RTC.bootFailedCount - 10; + toDisable = disablePlugin(toDisable); + + if (toDisable != 0) { + toDisable = disableController(toDisable); + } + #if FEATURE_NOTIFIER + if (toDisable != 0) { + toDisable = disableNotification(toDisable); + } + #endif + + if (toDisable != 0) { + toDisable = disableRules(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllPlugins(toDisable); + } + + if (toDisable != 0) { + toDisable = disableAllControllers(toDisable); + } +#if FEATURE_NOTIFIER + if (toDisable != 0) { + toDisable = disableAllNotifications(toDisable); + } +#endif + } + #if FEATURE_ETHERNET + + // This ensures, that changing WIFI OR ETHERNET MODE happens properly only after reboot. Changing without reboot would not be a good idea. + // This only works after LoadSettings(); + // Do not call setNetworkMedium here as that may try to clean up settings. + active_network_medium = Settings.NetworkMedium; + #else + if (Settings.NetworkMedium == NetworkMedium_t::Ethernet) { + Settings.NetworkMedium = 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) { + 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()) { + addLog(LOG_LEVEL_INFO, F("Setup: Scan all channels")); + WifiScan(false); + } +// setWifiMode(WIFI_OFF); + } + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("WifiScan()")); + #endif + + + // setWifiMode(WIFI_STA); + checkRuleSets(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("checkRuleSets()")); + #endif + + + // if different version, eeprom settings structure has changed. Full Reset needed + // on a fresh ESP module eeprom values are set to 255. Version results into -1 (signed int) + if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) + { + // Direct Serial is allowed here, since this is only an emergency task. + serialPrint(F("\nPID:")); + serialPrintln(String(Settings.PID)); + serialPrint(F("Version:")); + serialPrintln(String(Settings.Version)); + serialPrintln(F("INIT : Incorrect PID or version!")); + delay(1000); + ResetFactory(); + } + + initSerial(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("initSerial()")); + #endif + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("INIT : Free RAM:"), FreeMem())); + } + +# ifndef BUILD_NO_DEBUG + if (Settings.UseSerial && (Settings.SerialLogLevel >= LOG_LEVEL_DEBUG_MORE)) { + ESPEasy_Console.setDebugOutput(true); + } +#endif + + timermqtt_interval = 250; // Interval for checking MQTT + timerAwakeFromDeepSleep = millis(); + CPluginInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("CPluginInit()")); + #endif + #if FEATURE_NOTIFIER + NPluginInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("NPluginInit()")); + #endif + #endif // if FEATURE_NOTIFIER + + PluginInit(); + + initSerial(); // Plugins may have altered serial, so re-init serial + + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("PluginInit()")); + #endif + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(80); + log += concat(F("INFO : Plugins: "), getDeviceCount() + 1); + log += ' '; + log += getPluginDescriptionString(); + log += F(" ("); + log += getSystemLibraryString(); + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + } + +/* + if ((getDeviceCount() + 1) >= PLUGIN_MAX) { + addLog(LOG_LEVEL_ERROR, concat(F("Programming error! - Increase PLUGIN_MAX ("), getDeviceCount()) + ')'); + } +*/ + + clearAllCaches(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("clearAllCaches()")); + #endif + + if (Settings.UseRules && isDeepSleepEnabled()) + { + String event = F("System#NoSleep="); + event += Settings.deepSleep_wakeTime; + rulesProcessing(event); // TD-er: Process events in the setup() now. + } + + if (Settings.UseRules) + { + String event = F("System#Wake"); + rulesProcessing(event); // TD-er: Process events in the setup() now. + } + #ifdef ESP32 + if (Settings.UseRules) + { + const uint32_t gpio_strap = GPIO_REG_READ(GPIO_STRAP_REG); +// BOOT_MODE_GET(); + + // Event values: + // ESP32 : GPIO-5, GPIO-15, GPIO-4, GPIO-2, GPIO-0, GPIO-12 + // ESP32-C3: bit 0: GPIO2, bit 2: GPIO8, bit 3: GPIO9 + // ESP32-S2: Unclear what bits represent which strapping state. + // ESP32-S3: bit5 ~ bit2 correspond to strapping pins GPIO3, GPIO45, GPIO0, and GPIO46 respectively. + String event = F("System#BootMode="); + event += bitRead(gpio_strap, 0); + event += ','; + event += bitRead(gpio_strap, 1); + event += ','; + event += bitRead(gpio_strap, 2); + event += ','; + event += bitRead(gpio_strap, 3); + event += ','; + event += bitRead(gpio_strap, 4); + event += ','; + event += bitRead(gpio_strap, 5); + rulesProcessing(event); + } + #endif + + #if FEATURE_ETHERNET + if (Settings.ETH_Pin_power_rst != -1) { + GPIO_Write(PLUGIN_GPIO, Settings.ETH_Pin_power_rst, 1); + } + + #endif + + NetworkConnectRelaxed(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("NetworkConnectRelaxed()")); + #endif + + setWebserverRunning(true); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("setWebserverRunning()")); + #endif + + + #if FEATURE_REPORTING + ReportStatus(); + #endif // if FEATURE_REPORTING + + #if FEATURE_ARDUINO_OTA + ArduinoOTAInit(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("ArduinoOTAInit()")); + #endif + #endif // if FEATURE_ARDUINO_OTA + + if (node_time.systemTimePresent()) { + node_time.initTime(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("node_time.initTime()")); + #endif + } + + if (Settings.UseRules) + { + String event = F("System#Boot"); + rulesProcessing(event); // TD-er: Process events in the setup() now. + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("rulesProcessing(System#Boot)")); + #endif + } + + writeDefaultCSS(); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("writeDefaultCSS()")); + #endif + + + UseRTOSMultitasking = Settings.UseRTOSMultitasking; + #ifdef USE_RTOS_MULTITASKING + + if (UseRTOSMultitasking) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("RTOS : Launching tasks")); + } + xTaskCreatePinnedToCore(RTOS_TaskServers, "RTOS_TaskServers", 16384, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore(RTOS_TaskSerial, "RTOS_TaskSerial", 8192, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore(RTOS_Task10ps, "RTOS_Task10ps", 8192, nullptr, 1, nullptr, 1); + xTaskCreatePinnedToCore( + RTOS_HandleSchedule, /* Function to implement the task */ + "RTOS_HandleSchedule", /* Name of the task */ + 16384, /* Stack size in words */ + nullptr, /* Task input parameter */ + 1, /* Priority of the task */ + nullptr, /* Task handle. */ + 1); /* Core where the task should run */ + } + #endif // ifdef USE_RTOS_MULTITASKING + + // Start the interval timers at N msec from now. + // Make sure to start them at some time after eachother, + // since they will keep running at the same interval. + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_20MSEC, 5); // timer for periodic actions 50 x per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_100MSEC, 66); // timer for periodic actions 10 x per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_1SEC, 777); // timer for periodic actions once per/sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_30SEC, 1333); // timer for watchdog once per 30 sec + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 88); // timer for interaction with MQTT + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_STATISTICS, 2222); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("Scheduler.setIntervalTimerOverride")); + #endif + +} diff --git a/src/src/Globals/CPlugins.cpp b/src/src/Globals/CPlugins.cpp index 474a9d3e2..083044c84 100644 --- a/src/src/Globals/CPlugins.cpp +++ b/src/src/Globals/CPlugins.cpp @@ -1,221 +1,224 @@ -#include "../Globals/CPlugins.h" - -#include "../../_Plugin_Helper.h" -#include "../DataStructs/ESPEasy_EventStruct.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Globals/Settings.h" -#include "../Helpers/_CPlugin_init.h" - - -/********************************************************************************************\ - Call CPlugin functions - \*********************************************************************************************/ -bool CPluginCall(CPlugin::Function Function, struct EventStruct *event) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - String dummy; - - return CPluginCall(Function, event, dummy); -} - -bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& str) -{ - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif // ifdef USE_SECOND_HEAP - - struct EventStruct TempEvent; - - if (event == 0) { - event = &TempEvent; - } - - switch (Function) - { - case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: - // only called from CPluginSetup() directly using protocolIndex - break; - - // calls to all active controllers - case CPlugin::Function::CPLUGIN_INIT_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 - case CPlugin::Function::CPLUGIN_GOT_INVALID: // calls to mark unit as invalid - case CPlugin::Function::CPLUGIN_FLUSH: - case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: - case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: - case CPlugin::Function::CPLUGIN_WRITE: - { - const bool success = Function != CPlugin::Function::CPLUGIN_WRITE; - - if (Function == CPlugin::Function::CPLUGIN_INIT_ALL) { - Function = CPlugin::Function::CPLUGIN_INIT; - } - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if ((Settings.Protocol[x] != 0) && Settings.ControllerEnabled[x]) { - event->ControllerIndex = x; - String command; - - if (Function == CPlugin::Function::CPLUGIN_WRITE) { - command = str; - } - - if (CPluginCall( - getProtocolIndex_from_ControllerIndex(x), - Function, - event, - command)) { - if (Function == CPlugin::Function::CPLUGIN_WRITE) { - // Need to stop when write call was handled - return true; - } - } - } - } - return success; - } - - // calls to specific controller - 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_GET_DEVICENAME: - 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 (Function == CPlugin::Function::CPLUGIN_PROTOCOL_SEND) { - checkDeviceVTypeForTask(event); - } - success = CPluginCall( - getProtocolIndex_from_ControllerIndex(controllerindex), - Function, - event, - str); - - } - #ifdef ESP32 - - if (Function == CPlugin::Function::CPLUGIN_EXIT) { - Cache.clearControllerSettings(controllerindex); - } - #endif // ifdef ESP32 - } - return success; - } - - case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: // calls to send acknowledge back to controller - - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { - CPluginCall( - getProtocolIndex_from_ControllerIndex(x), - Function, - event, - str); - } - } - return true; - } - - return false; -} - -// Check if there is any controller enabled. -bool anyControllerEnabled() { - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { - if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { - return true; - } - } - return false; -} - -// Find first enabled controller index with this protocol -controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid) { - if (supportedCPluginID(cpluginid)) { - for (controllerIndex_t i = 0; i < CONTROLLER_MAX; i++) { - if ((Settings.Protocol[i] == cpluginid) && Settings.ControllerEnabled[i]) { - return i; - } - } - } - return INVALID_CONTROLLER_INDEX; -} - -bool validProtocolIndex(protocolIndex_t index) -{ - return validProtocolIndex_init(index); -} - -/* -bool validControllerIndex(controllerIndex_t index) -{ - return index < CONTROLLER_MAX; -} -*/ - -bool validCPluginID(cpluginID_t cpluginID) -{ - return getProtocolIndex_from_CPluginID_(cpluginID) != INVALID_PROTOCOL_INDEX; -} - -bool supportedCPluginID(cpluginID_t cpluginID) -{ - return validProtocolIndex(getProtocolIndex_from_CPluginID_(cpluginID)); -} - -protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index) { - if (validControllerIndex(index)) { - return getProtocolIndex_from_CPluginID_(Settings.Protocol[index]); - } - return INVALID_PROTOCOL_INDEX; -} - -protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { - return getProtocolIndex_from_CPluginID_(cpluginID); -} - -cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index) { - return getCPluginID_from_ProtocolIndex_(index); -} - -cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index) { - const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(index); - - return getCPluginID_from_ProtocolIndex(protocolIndex); -} - -String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex) { - String controllerName; - - if (validProtocolIndex(ProtocolIndex)) { - CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); - } - return controllerName; -} - -String getCPluginNameFromCPluginID(cpluginID_t cpluginID) { - protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID_(cpluginID); - - if (!validProtocolIndex(protocolIndex)) { - return strformat(F("CPlugin %d not included in build"), cpluginID); - } - return getCPluginNameFromProtocolIndex(protocolIndex); -} +#include "../Globals/CPlugins.h" + +#include "../../_Plugin_Helper.h" +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Globals/Settings.h" +#include "../Helpers/_CPlugin_init.h" + + +/********************************************************************************************\ + Call CPlugin functions + \*********************************************************************************************/ +bool CPluginCall(CPlugin::Function Function, struct EventStruct *event) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + String dummy; + + return CPluginCall(Function, event, dummy); +} + +bool CPluginCall(CPlugin::Function Function, struct EventStruct *event, String& str) +{ + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + struct EventStruct TempEvent; + + if (event == 0) { + event = &TempEvent; + } + + switch (Function) + { + case CPlugin::Function::CPLUGIN_PROTOCOL_ADD: + // only called from CPluginSetup() directly using protocolIndex + break; + + // calls to all active controllers + case CPlugin::Function::CPLUGIN_INIT_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 + case CPlugin::Function::CPLUGIN_GOT_INVALID: // calls to mark unit as invalid + case CPlugin::Function::CPLUGIN_FLUSH: + case CPlugin::Function::CPLUGIN_TEN_PER_SECOND: + case CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND: + case CPlugin::Function::CPLUGIN_WRITE: + { + const bool success = Function != CPlugin::Function::CPLUGIN_WRITE; + + if (Function == CPlugin::Function::CPLUGIN_INIT_ALL) { + Function = CPlugin::Function::CPLUGIN_INIT; + } + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if ((Settings.Protocol[x] != 0) && Settings.ControllerEnabled[x]) { + event->ControllerIndex = x; + String command; + + if (Function == CPlugin::Function::CPLUGIN_WRITE) { + command = str; + } + + if (CPluginCall( + getProtocolIndex_from_ControllerIndex(x), + Function, + event, + command)) { + if (Function == CPlugin::Function::CPLUGIN_WRITE) { + // Need to stop when write call was handled + return true; + } + } + } + } + return success; + } + + // calls to specific controller + 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_GET_DEVICENAME: + 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 (Function == CPlugin::Function::CPLUGIN_PROTOCOL_SEND) { + checkDeviceVTypeForTask(event); + } + success = CPluginCall( + getProtocolIndex_from_ControllerIndex(controllerindex), + Function, + event, + str); + + } + #ifdef ESP32 + + if (Function == CPlugin::Function::CPLUGIN_EXIT) { + Cache.clearControllerSettings(controllerindex); + } + #endif // ifdef ESP32 + } + return success; + } + + case CPlugin::Function::CPLUGIN_ACKNOWLEDGE: // calls to send acknowledge back to controller + + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { + CPluginCall( + getProtocolIndex_from_ControllerIndex(x), + Function, + event, + str); + } + } + return true; + case CPlugin::Function::CPLUGIN_CONNECT_SUCCESS: + case CPlugin::Function::CPLUGIN_CONNECT_FAIL: + break; + } + + return false; +} + +// Check if there is any controller enabled. +bool anyControllerEnabled() { + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) { + if (Settings.ControllerEnabled[x] && supportedCPluginID(Settings.Protocol[x])) { + return true; + } + } + return false; +} + +// Find first enabled controller index with this protocol +controllerIndex_t findFirstEnabledControllerWithId(cpluginID_t cpluginid) { + if (supportedCPluginID(cpluginid)) { + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; i++) { + if ((Settings.Protocol[i] == cpluginid) && Settings.ControllerEnabled[i]) { + return i; + } + } + } + return INVALID_CONTROLLER_INDEX; +} + +bool validProtocolIndex(protocolIndex_t index) +{ + return validProtocolIndex_init(index); +} + +/* +bool validControllerIndex(controllerIndex_t index) +{ + return index < CONTROLLER_MAX; +} +*/ + +bool validCPluginID(cpluginID_t cpluginID) +{ + return getProtocolIndex_from_CPluginID_(cpluginID) != INVALID_PROTOCOL_INDEX; +} + +bool supportedCPluginID(cpluginID_t cpluginID) +{ + return validProtocolIndex(getProtocolIndex_from_CPluginID_(cpluginID)); +} + +protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index) { + if (validControllerIndex(index)) { + return getProtocolIndex_from_CPluginID_(Settings.Protocol[index]); + } + return INVALID_PROTOCOL_INDEX; +} + +protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID) { + return getProtocolIndex_from_CPluginID_(cpluginID); +} + +cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index) { + return getCPluginID_from_ProtocolIndex_(index); +} + +cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index) { + const protocolIndex_t protocolIndex = getProtocolIndex_from_ControllerIndex(index); + + return getCPluginID_from_ProtocolIndex(protocolIndex); +} + +String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex) { + String controllerName; + + if (validProtocolIndex(ProtocolIndex)) { + CPluginCall(ProtocolIndex, CPlugin::Function::CPLUGIN_GET_DEVICENAME, nullptr, controllerName); + } + return controllerName; +} + +String getCPluginNameFromCPluginID(cpluginID_t cpluginID) { + protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID_(cpluginID); + + if (!validProtocolIndex(protocolIndex)) { + return strformat(F("CPlugin %d not included in build"), cpluginID); + } + return getCPluginNameFromProtocolIndex(protocolIndex); +} diff --git a/src/src/Globals/CPlugins.h b/src/src/Globals/CPlugins.h index 3b4c082ed..31e9ab3ae 100644 --- a/src/src/Globals/CPlugins.h +++ b/src/src/Globals/CPlugins.h @@ -1,69 +1,69 @@ -#ifndef GLOBALS_CPLUGIN_H -#define GLOBALS_CPLUGIN_H - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../DataTypes/CPluginID.h" -#include "../DataTypes/ControllerIndex.h" -#include "../DataTypes/ProtocolIndex.h" - - -/********************************************************************************************\ - Structures to address the Cplugins (controllers) and their configurations. - - A build of ESPeasy may not have all Cplugins included. - So there has to be some administration to keep track of what Cplugin is present - and how to address it. - - We have: - - CPlugin, like _C001.ino. - - Controller -> A selected instance of a CPlugin (analog to "task" for plugins, shown in the Controllers tab in the web interface) - - Protocol -> A CPlugin included in the build. - - We have the following one-to-one relations: - - CPlugin_id_to_ProtocolIndex - Map from CPlugin ID to Protocol Index. - - ProtocolIndex_to_CPlugin_id - Vector from ProtocolIndex to CPlugin ID. - - CPlugin_ptr - Array of function pointers to call Cplugins. - - Protocol - Vector of ProtocolStruct containing Cplugin specific information. - \*********************************************************************************************/ - - -bool CPluginCall(CPlugin::Function Function, - struct EventStruct *event); -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); - -bool validProtocolIndex(protocolIndex_t index); - - -// bool validControllerIndex(controllerIndex_t index); -#define validControllerIndex(C_X) ((C_X) < CONTROLLER_MAX) - -// Check whether CPlugin is included in build. -bool validCPluginID(cpluginID_t cpluginID); - -// Check if cplugin is included in build. -// N.B. Invalid cplugin is also not considered supported. -// This is essentially (validCPluginID && validProtocolIndex) -bool supportedCPluginID(cpluginID_t cpluginID); -protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index); -protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID); -cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index); -cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index); - -String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex); -String getCPluginNameFromCPluginID(cpluginID_t cpluginID); - - -#endif // GLOBALS_CPLUGIN_H +#ifndef GLOBALS_CPLUGIN_H +#define GLOBALS_CPLUGIN_H + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/CPluginID.h" +#include "../DataTypes/ControllerIndex.h" +#include "../DataTypes/ProtocolIndex.h" + + +/********************************************************************************************\ + Structures to address the Cplugins (controllers) and their configurations. + + A build of ESPeasy may not have all Cplugins included. + So there has to be some administration to keep track of what Cplugin is present + and how to address it. + + We have: + - CPlugin, like _C001.ino. + - Controller -> A selected instance of a CPlugin (analog to "task" for plugins, shown in the Controllers tab in the web interface) + - Protocol -> A CPlugin included in the build. + + We have the following one-to-one relations: + - CPlugin_id_to_ProtocolIndex - Map from CPlugin ID to Protocol Index. + - ProtocolIndex_to_CPlugin_id - Vector from ProtocolIndex to CPlugin ID. + - CPlugin_ptr - Array of function pointers to call Cplugins. + - Protocol - Vector of ProtocolStruct containing Cplugin specific information. + \*********************************************************************************************/ + + +bool CPluginCall(CPlugin::Function Function, + struct EventStruct *event); +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); + +bool validProtocolIndex(protocolIndex_t index); + + +// bool validControllerIndex(controllerIndex_t index); +#define validControllerIndex(C_X) ((C_X) < CONTROLLER_MAX) + +// Check whether CPlugin is included in build. +bool validCPluginID(cpluginID_t cpluginID); + +// Check if cplugin is included in build. +// N.B. Invalid cplugin is also not considered supported. +// This is essentially (validCPluginID && validProtocolIndex) +bool supportedCPluginID(cpluginID_t cpluginID); +protocolIndex_t getProtocolIndex_from_ControllerIndex(controllerIndex_t index); +protocolIndex_t getProtocolIndex_from_CPluginID(cpluginID_t cpluginID); +cpluginID_t getCPluginID_from_ProtocolIndex(protocolIndex_t index); +cpluginID_t getCPluginID_from_ControllerIndex(controllerIndex_t index); + +String getCPluginNameFromProtocolIndex(protocolIndex_t ProtocolIndex); +String getCPluginNameFromCPluginID(cpluginID_t cpluginID); + + +#endif // GLOBALS_CPLUGIN_H diff --git a/src/src/Globals/ESPEasyWiFiEvent.cpp b/src/src/Globals/ESPEasyWiFiEvent.cpp index d601d8d7c..9ec0c0a46 100644 --- a/src/src/Globals/ESPEasyWiFiEvent.cpp +++ b/src/src/Globals/ESPEasyWiFiEvent.cpp @@ -1,18 +1,18 @@ -#include "../Globals/ESPEasyWiFiEvent.h" - -#include "../../ESPEasy_common.h" - - -#ifdef ESP8266 -WiFiEventHandler stationConnectedHandler; -WiFiEventHandler stationDisconnectedHandler; -WiFiEventHandler stationGotIpHandler; -WiFiEventHandler stationModeDHCPTimeoutHandler; -WiFiEventHandler stationModeAuthModeChangeHandler; -WiFiEventHandler APModeStationConnectedHandler; -WiFiEventHandler APModeStationDisconnectedHandler; -#endif // ifdef ESP8266 - -WiFiEventData_t WiFiEventData; - - +#include "../Globals/ESPEasyWiFiEvent.h" + +#include "../../ESPEasy_common.h" + + +#ifdef ESP8266 +WiFiEventHandler stationConnectedHandler; +WiFiEventHandler stationDisconnectedHandler; +WiFiEventHandler stationGotIpHandler; +WiFiEventHandler stationModeDHCPTimeoutHandler; +WiFiEventHandler stationModeAuthModeChangeHandler; +WiFiEventHandler APModeStationConnectedHandler; +WiFiEventHandler APModeStationDisconnectedHandler; +#endif // ifdef ESP8266 + +WiFiEventData_t WiFiEventData; + + diff --git a/src/src/Globals/ESPEasyWiFiEvent.h b/src/src/Globals/ESPEasyWiFiEvent.h index 8d83519b8..068e3f773 100644 --- a/src/src/Globals/ESPEasyWiFiEvent.h +++ b/src/src/Globals/ESPEasyWiFiEvent.h @@ -1,39 +1,39 @@ -#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 +#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/NetworkState.cpp b/src/src/Globals/NetworkState.cpp index 123b1b5cc..e5c286c85 100644 --- a/src/src/Globals/NetworkState.cpp +++ b/src/src/Globals/NetworkState.cpp @@ -1,28 +1,28 @@ -#include "../Globals/NetworkState.h" - -#include "../../ESPEasy_common.h" - - -// Ethernet Connection status -NetworkMedium_t active_network_medium = NetworkMedium_t::NotSet; - -bool webserverRunning(false); -bool webserver_init(false); - -#if FEATURE_MDNS -bool mDNS_init(false); -#endif - - -// NTP status -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); - - - -// udp protocol stuff (syslog, global sync, node info list, ntp time) -WiFiUDP portUDP; +#include "../Globals/NetworkState.h" + +#include "../../ESPEasy_common.h" + + +// Ethernet Connection status +NetworkMedium_t active_network_medium = NetworkMedium_t::NotSet; + +bool webserverRunning(false); +bool webserver_init(false); + +#if FEATURE_MDNS +bool mDNS_init(false); +#endif + + +// NTP status +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); + + + +// udp protocol stuff (syslog, global sync, node info list, ntp time) +WiFiUDP portUDP; diff --git a/src/src/Globals/NetworkState.h b/src/src/Globals/NetworkState.h index 2951de87d..b7664cb84 100644 --- a/src/src/Globals/NetworkState.h +++ b/src/src/Globals/NetworkState.h @@ -1,34 +1,34 @@ -#ifndef GLOBALS_NETWORKSTATE_H -#define GLOBALS_NETWORKSTATE_H - -#include "../../ESPEasy_common.h" - -#include -#include - -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../DataTypes/NetworkMedium.h" - -// Ethernet Connectiopn status -extern NetworkMedium_t active_network_medium; - -extern bool webserverRunning; -extern bool webserver_init; -#if FEATURE_MDNS -extern bool mDNS_init; -#endif - - -// NTP status -extern bool statusNTPInitialized; - - -// Setup DNS, only used if the ESP has no valid WiFi config -extern const uint8_t DNS_PORT; -extern IPAddress apIP; - -// udp protocol stuff (syslog, global sync, node info list, ntp time) -extern WiFiUDP portUDP; - - -#endif // GLOBALS_NETWORKSTATE_H +#ifndef GLOBALS_NETWORKSTATE_H +#define GLOBALS_NETWORKSTATE_H + +#include "../../ESPEasy_common.h" + +#include +#include + +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../DataTypes/NetworkMedium.h" + +// Ethernet Connectiopn status +extern NetworkMedium_t active_network_medium; + +extern bool webserverRunning; +extern bool webserver_init; +#if FEATURE_MDNS +extern bool mDNS_init; +#endif + + +// NTP status +extern bool statusNTPInitialized; + + +// Setup DNS, only used if the ESP has no valid WiFi config +extern const uint8_t DNS_PORT; +extern IPAddress apIP; + +// udp protocol stuff (syslog, global sync, node info list, ntp time) +extern WiFiUDP portUDP; + + +#endif // GLOBALS_NETWORKSTATE_H diff --git a/src/src/Globals/Nodes.cpp b/src/src/Globals/Nodes.cpp index 003286fd4..26e983f06 100644 --- a/src/src/Globals/Nodes.cpp +++ b/src/src/Globals/Nodes.cpp @@ -1,7 +1,7 @@ -#include "../Globals/Nodes.h" - -#if FEATURE_ESPEASY_P2P - -NodesHandler Nodes; - +#include "../Globals/Nodes.h" + +#if FEATURE_ESPEASY_P2P + +NodesHandler Nodes; + #endif \ No newline at end of file diff --git a/src/src/Globals/Plugins.cpp b/src/src/Globals/Plugins.cpp index fff0e97a4..956b38d56 100644 --- a/src/src/Globals/Plugins.cpp +++ b/src/src/Globals/Plugins.cpp @@ -1,967 +1,1094 @@ -#include "../Globals/Plugins.h" - -#include "../CustomBuild/ESPEasyLimits.h" - -#include "../../_Plugin_Helper.h" - -#include "../DataStructs/ESPEasy_EventStruct.h" -#include "../DataStructs/TimingStats.h" - -#include "../DataTypes/ESPEasy_plugin_functions.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/Serial.h" - -#include "../Globals/Cache.h" -#include "../Globals/Device.h" -#include "../Globals/ESPEasy_Scheduler.h" -#include "../Globals/ExtraTaskSettings.h" -#include "../Globals/EventQueue.h" -#include "../Globals/GlobalMapPortStatus.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" - -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT -#include "../Helpers/_Plugin_Helper_serial.h" -#endif - -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Hardware_I2C.h" -#include "../Helpers/Misc.h" -#include "../Helpers/_Plugin_init.h" -#include "../Helpers/PortStatus.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - -#include - - - - -bool validDeviceIndex(deviceIndex_t index) { - return validDeviceIndex_init(index); -} -/* -bool validTaskIndex(taskIndex_t index) { - return index < TASKS_MAX; -} - -bool validPluginID(pluginID_t pluginID) { - return (pluginID != INVALID_PLUGIN_ID); -} -*/ -bool validPluginID_fullcheck(pluginID_t pluginID) { - return getDeviceIndex_from_PluginID(pluginID) != INVALID_DEVICE_INDEX; -} -/* -bool validUserVarIndex(userVarIndex_t index) { - return index < USERVAR_MAX_INDEX; -} - -bool validTaskVarIndex(taskVarIndex_t index) { - return index < VARS_PER_TASK; -} -*/ - -bool supportedPluginID(pluginID_t pluginID) { - return validDeviceIndex(getDeviceIndex(pluginID)); -} - -deviceIndex_t getDeviceIndex_from_TaskIndex(taskIndex_t taskIndex) { - if (validTaskIndex(taskIndex)) { - return getDeviceIndex(Settings.getPluginID_for_task(taskIndex)); - } - return INVALID_DEVICE_INDEX; -} - -/********************************************************************************************* - * get the taskPluginID with required checks, INVALID_PLUGIN_ID when invalid - ********************************************************************************************/ -pluginID_t getPluginID_from_TaskIndex(taskIndex_t taskIndex) { - if (validTaskIndex(taskIndex)) { - const pluginID_t pluginID = Settings.getPluginID_for_task(taskIndex); - if (supportedPluginID(pluginID)) - return pluginID; - } - return INVALID_PLUGIN_ID; -} - -#if FEATURE_PLUGIN_PRIORITY -bool isPluginI2CPowerManager_from_TaskIndex(taskIndex_t taskIndex) { - if (validTaskIndex(taskIndex)) { - deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - if (validDeviceIndex(deviceIndex)) { - return (Device[deviceIndex].Type == DEVICE_TYPE_I2C) && - Device[deviceIndex].PowerManager && - Settings.isPowerManagerTask(taskIndex); - } - } - return false; -} -#endif // if FEATURE_PLUGIN_PRIORITY - -deviceIndex_t getDeviceIndex(pluginID_t pluginID) -{ - return getDeviceIndex_from_PluginID(pluginID); -} - -/********************************************************************************************\ - Find name of plugin given the plugin device index.. - \*********************************************************************************************/ -String getPluginNameFromDeviceIndex(deviceIndex_t deviceIndex) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - String deviceName; - - if (validDeviceIndex(deviceIndex)) { - PluginCall(deviceIndex, PLUGIN_GET_DEVICENAME, nullptr, deviceName); - } - return deviceName; -} - -String getPluginNameFromPluginID(pluginID_t pluginID) { - deviceIndex_t deviceIndex = getDeviceIndex(pluginID); - - if (!validDeviceIndex(deviceIndex)) { - return strformat(F("Plugin %d not included in build"), pluginID.value); - } - return getPluginNameFromDeviceIndex(deviceIndex); -} - -#if FEATURE_I2C_DEVICE_SCAN -bool checkPluginI2CAddressFromDeviceIndex(deviceIndex_t deviceIndex, uint8_t i2cAddress) { - bool hasI2CAddress = false; - - if (validDeviceIndex(deviceIndex)) { - String dummy; - struct EventStruct TempEvent; - TempEvent.Par1 = i2cAddress; - hasI2CAddress = PluginCall(deviceIndex, PLUGIN_I2C_HAS_ADDRESS, &TempEvent, dummy); - } - return hasI2CAddress; -} -#endif // if FEATURE_I2C_DEVICE_SCAN - -#if FEATURE_I2C_GET_ADDRESS -uint8_t getTaskI2CAddress(taskIndex_t taskIndex) { - uint8_t getI2CAddress = 0; - const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - if (validTaskIndex(taskIndex) && validDeviceIndex(deviceIndex)) { - String dummy; - struct EventStruct TempEvent; - TempEvent.setTaskIndex(taskIndex); - TempEvent.Par1 = 0; - if (PluginCall(deviceIndex, PLUGIN_I2C_GET_ADDRESS, &TempEvent, dummy)) { - getI2CAddress = TempEvent.Par1; - } - } - return getI2CAddress; -} -#endif // if FEATURE_I2C_GET_ADDRESS - - -// ******************************************************************************** -// Functions to assist changing I2C multiplexer port or clock speed -// when addressing a task -// ******************************************************************************** - -bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { - if (!validTaskIndex(taskIndex) || !validDeviceIndex(DeviceIndex)) { - return false; - } - if (Device[DeviceIndex].Type != DEVICE_TYPE_I2C) { - return true; // No I2C task, so consider all-OK - } - if (I2C_state != I2C_bus_state::OK) { - return false; // Bus state is not OK, so do not consider task runnable - } - #if FEATURE_I2CMULTIPLEXER - I2CMultiplexerSelectByTaskIndex(taskIndex); - // Output is selected after this write, so now we must make sure the - // frequency is set before anything else is sent. - #endif // if FEATURE_I2CMULTIPLEXER - - if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { - I2CSelectLowClockSpeed(); // Set to slow - } - return true; -} - - -void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { - if (!validTaskIndex(taskIndex) || !validDeviceIndex(DeviceIndex)) { - return; - } - if (Device[DeviceIndex].Type != DEVICE_TYPE_I2C) { - return; - } - #if FEATURE_I2CMULTIPLEXER - I2CMultiplexerOff(); - #endif // if FEATURE_I2CMULTIPLEXER - - I2CSelectHighClockSpeed(); // Reset -} - -// Add an event to the event queue. -// event value 1 = taskIndex (first task = 1) -// event value 2 = return value of the plugin function -// Example: TaskInit#bme=1,0 (taskindex = 0, return value = 0) -void queueTaskEvent(const String& eventName, taskIndex_t taskIndex, const String& value_str) { - if (Settings.UseRules) { - String event = strformat( - F("%s#%s=%d"), - eventName.c_str(), - getTaskDeviceName(taskIndex).c_str(), - taskIndex + 1); - if (value_str.length() > 0) { - event += ','; - event += wrapWithQuotesIfContainsParameterSeparatorChar(value_str); - } - eventQueue.addMove(std::move(event)); - } -} - -void queueTaskEvent(const String& eventName, taskIndex_t taskIndex, const int& value1) { - queueTaskEvent(eventName, taskIndex, String(value1)); -} - -void queueTaskEvent(const __FlashStringHelper * eventName, taskIndex_t taskIndex, const String& value1) { - queueTaskEvent(String(eventName), taskIndex, value1); -} - -void queueTaskEvent(const __FlashStringHelper * eventName, taskIndex_t taskIndex, const int& value1) { - queueTaskEvent(String(eventName), taskIndex, String(value1)); -} - -void loadDefaultTaskValueNames_ifEmpty(taskIndex_t TaskIndex) { - String oldNames[VARS_PER_TASK]; - uint8_t oldNrDec[VARS_PER_TASK]; - LoadTaskSettings(TaskIndex); - - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - oldNames[i] = ExtraTaskSettings.TaskDeviceValueNames[i]; - oldNrDec[i] = ExtraTaskSettings.TaskDeviceValueDecimals[i]; - oldNames[i].trim(); - } - - struct EventStruct TempEvent(TaskIndex); - String dummy; - // the plugin call should populate ExtraTaskSettings with its default values. - PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); - - // Restore the settings that were already set by the user - for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { - const bool isDefault = oldNames[i].isEmpty(); - ExtraTaskSettings.isDefaultTaskVarName(i, isDefault); - if (!isDefault) { - ExtraTaskSettings.setTaskDeviceValueName(i, oldNames[i]); - ExtraTaskSettings.TaskDeviceValueDecimals[i] = oldNrDec[i]; - } - } -} - -/** - * Call the plugin of 1 task for 1 function, with standard EventStruct and optional command string - */ -bool PluginCallForTask(taskIndex_t taskIndex, uint8_t Function, EventStruct *TempEvent, String& command, EventStruct *event = nullptr) { - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - bool retval = false; - const bool considerTaskEnabled = Settings.TaskDeviceEnabled[taskIndex]; - //|| (Settings.TaskDeviceEnabled[taskIndex].enabled && Function == PLUGIN_INIT); - - if (considerTaskEnabled && validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex))) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - if (validDeviceIndex(DeviceIndex)) { - if (Settings.TaskDeviceDataFeed[taskIndex] == 0) // these calls only to tasks with local feed - { - if (Function == PLUGIN_INIT) { - LoadTaskSettings(taskIndex); - } - TempEvent->setTaskIndex(taskIndex); - TempEvent->sensorType = Device[DeviceIndex].VType; - if (event != nullptr) { - TempEvent->OriginTaskIndex = event->TaskIndex; - } - - if (!prepare_I2C_by_taskIndex(taskIndex, DeviceIndex)) { - return false; - } - #ifndef BUILD_NO_RAM_TRACKER - switch (Function) { - case PLUGIN_WRITE: // First set - case PLUGIN_REQUEST: - case PLUGIN_ONCE_A_SECOND: // Second set - case PLUGIN_TEN_PER_SECOND: - case PLUGIN_FIFTY_PER_SECOND: - case PLUGIN_INIT: // Second set, instead of PLUGIN_INIT_ALL - case PLUGIN_CLOCK_IN: - case PLUGIN_TIME_CHANGE: - #if FEATURE_PLUGIN_PRIORITY - case PLUGIN_PRIORITY_INIT: - #endif // if FEATURE_PLUGIN_PRIORITY - { - checkRAM(F("PluginCall_s"), taskIndex); - break; - } - } - #endif - #if FEATURE_I2C_DEVICE_CHECK - bool i2cStatusOk = true; - if ((Function == PLUGIN_INIT) && (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) && !Device[DeviceIndex].I2CNoDeviceCheck) { - const uint8_t i2cAddr = getTaskI2CAddress(taskIndex); - if (i2cAddr > 0) { - START_TIMER; - i2cStatusOk = I2C_deviceCheck(i2cAddr); - STOP_TIMER_TASK(DeviceIndex, PLUGIN_I2C_GET_ADDRESS); - } - } - if (i2cStatusOk) { - #endif // if FEATURE_I2C_DEVICE_CHECK - #ifndef BUILD_NO_RAM_TRACKER - switch (Function) { - case PLUGIN_WRITE: // First set - case PLUGIN_REQUEST: - case PLUGIN_ONCE_A_SECOND: // Second set - case PLUGIN_TEN_PER_SECOND: - case PLUGIN_FIFTY_PER_SECOND: - case PLUGIN_INIT: // Second set, instead of PLUGIN_INIT_ALL - case PLUGIN_CLOCK_IN: - case PLUGIN_TIME_CHANGE: - { - checkRAM(F("PluginCall_s"), taskIndex); - break; - } - } - #endif - if (Function == PLUGIN_INIT) { - // 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_at_init(TempEvent->TaskIndex); - } - - START_TIMER; - retval = (PluginCall(DeviceIndex, Function, TempEvent, command)); - - STOP_TIMER_TASK(DeviceIndex, Function); - - if (Function == PLUGIN_INIT) { - #if FEATURE_PLUGIN_STATS - if (Device[DeviceIndex].PluginStats) { - PluginTaskData_base *taskData = getPluginTaskData(taskIndex); - if (taskData == nullptr) { - // Plugin apparently does not have PluginTaskData. - // Create Plugin Task data if it has "Stats" checked. - LoadTaskSettings(taskIndex); - if (ExtraTaskSettings.anyEnabledPluginStats()) { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - initPluginTaskData(taskIndex, new (std::nothrow) _StatsOnly_data_struct()); - } - } - } - #endif // if FEATURE_PLUGIN_STATS - queueTaskEvent(F("TaskInit"), taskIndex, retval); - } - #if FEATURE_I2C_DEVICE_CHECK - } - #endif // if FEATURE_I2C_DEVICE_CHECK - - post_I2C_by_taskIndex(taskIndex, DeviceIndex); - delay(0); // SMY: call delay(0) unconditionally - } else { - #if FEATURE_PLUGIN_STATS - if (Function == PLUGIN_INIT && Device[DeviceIndex].PluginStats) { - PluginTaskData_base *taskData = getPluginTaskData(taskIndex); - if (taskData == nullptr) { - // Plugin apparently does not have PluginTaskData. - // Create Plugin Task data if it has "Stats" checked. - LoadTaskSettings(taskIndex); - if (ExtraTaskSettings.anyEnabledPluginStats()) { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - initPluginTaskData(taskIndex, new (std::nothrow) _StatsOnly_data_struct()); - } - } - } - #endif // if FEATURE_PLUGIN_STATS - } - } - } - return retval; -} - -/*********************************************************************************************\ -* Function call to all or specific plugins -\*********************************************************************************************/ -bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) -{ - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - struct EventStruct TempEvent; - - if (event == nullptr) { - event = &TempEvent; - } - else { - TempEvent.deep_copy(*event); - } - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("PluginCall"), Function); - #endif - - switch (Function) - { - // Unconditional calls to all plugins - // FIXME TD-er: PLUGIN_UNCONDITIONAL_POLL is not being used at the moment - /* - case PLUGIN_UNCONDITIONAL_POLL: - { - - const unsigned maxDeviceIndex = getNrBuiltInDeviceIndex(); - - for (deviceIndex_t x; x < maxDeviceIndex; ++x) { - START_TIMER; - PluginCall(x, Function, event, str); - STOP_TIMER_TASK(x, Function); - delay(0); // SMY: call delay(0) unconditionally - } - return true; - } - */ - - case PLUGIN_MONITOR: - - for (auto it = globalMapPortStatus.begin(); it != globalMapPortStatus.end(); ++it) { - // only call monitor function if there the need to - if (it->second.monitor || it->second.command || it->second.init) { - TempEvent.Par1 = getPortFromKey(it->first); - - // initialize the "x" variable to synch with the pluginNumber if second.x == -1 - if (!validDeviceIndex(it->second.x)) { it->second.x = getDeviceIndex(getPluginFromKey(it->first)); } - - const deviceIndex_t DeviceIndex = it->second.x; - if (validDeviceIndex(DeviceIndex)) { - START_TIMER; - PluginCall(DeviceIndex, Function, &TempEvent, str); - STOP_TIMER_TASK(DeviceIndex, Function); - } - } - } - return true; - - - // Call to all plugins. Return at first match - case PLUGIN_WRITE: -// case PLUGIN_REQUEST: @giig1967g: replaced by new function getGPIOPluginValues() - { - taskIndex_t firstTask = 0; - taskIndex_t lastTask = TASKS_MAX; - String command = String(str); // Local copy to avoid warning in ExecuteCommand - int dotPos = command.indexOf('.'); // Find first period - if (Function == PLUGIN_WRITE // Only applicable on PLUGIN_WRITE function - && dotPos > -1) { // First precondition is just a quick check for a period (fail-fast strategy) - const String arg0 = parseString(command, 1); // Get first argument - dotPos = arg0.indexOf('.'); - if (dotPos > -1) { - String thisTaskName = parseString(arg0, 1, '.'); // Extract taskname prefix - removeChar(thisTaskName, '['); // Remove the optional square brackets - removeChar(thisTaskName, ']'); - if (thisTaskName.length() > 0) { // Second precondition - taskIndex_t thisTask = findTaskIndexByName(thisTaskName); - if (!validTaskIndex(thisTask)) { // Taskname not found or invalid, check for a task number? - thisTask = static_cast(atoi(thisTaskName.c_str())); - if (thisTask == 0 || thisTask > TASKS_MAX) { - thisTask = INVALID_TASK_INDEX; - } else { - thisTask--; // 0-based - } - } - if (validTaskIndex(thisTask)) { // Known taskindex? -#ifdef USES_P022 // Exclude P022 as it has rather explicit differences in commands when used with the []. prefix - const pluginID_t pluginID = Settings.getPluginID_for_task(thisTask); - if (Settings.TaskDeviceEnabled[thisTask] // and internally needs to know wether it was called with the taskname prefixed - && validPluginID_fullcheck(pluginID) - && Settings.TaskDeviceDataFeed[thisTask] == 0) { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(thisTask); - constexpr pluginID_t P022_PCA9685_PLUGIN_ID(22); - if (validDeviceIndex(DeviceIndex) && - pluginID == P022_PCA9685_PLUGIN_ID /* PLUGIN_ID_022 define no longer available, 'assume' 22 for now */) { - thisTask = INVALID_TASK_INDEX; - } - } - if (validTaskIndex(thisTask)) { -#endif - firstTask = thisTask; - lastTask = thisTask + 1; // Add 1 to satisfy the for condition - command = command.substring(dotPos + 1); // Remove []. prefix -#ifdef USES_P022 - } -#endif - } - } - } - } - // String info = F("PLUGIN_WRITE first: "); // To remove - // info += firstTask; - // info += F(" last: "); - // info += lastTask; - // addLog(LOG_LEVEL_INFO, info); - - for (taskIndex_t task = firstTask; task < lastTask; task++) - { - bool retval = PluginCallForTask(task, Function, &TempEvent, command); - - if (!retval) { - if (1 == (lastTask - firstTask)) { - // These plugin task data commands are generic, so only apply them on a specific task. - // Don't try to match them on the first task that may have such data. - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(task); - if (nullptr != taskData) { - if (taskData->plugin_write_base(event, command)) { - retval = true; - } - } - } - } - - if (retval) { - EventStruct CPlugin_ack_event; - CPlugin_ack_event.deep_copy(TempEvent); - CPlugin_ack_event.setTaskIndex(task); - CPluginCall(CPlugin::Function::CPLUGIN_ACKNOWLEDGE, &CPlugin_ack_event, command); - return true; - } - } - -/* - 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)) { - delay(0); // SMY: call delay(0) unconditionally - CPluginCall(CPlugin::Function::CPLUGIN_ACKNOWLEDGE, event, str); - return true; - } - } - } -*/ - break; - } - - // Call to all plugins used in a task. Return at first match - case PLUGIN_SERIAL_IN: - case PLUGIN_UDP_IN: - { - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) - { - if (Settings.TaskDeviceEnabled[taskIndex]) { - if (PluginCallForTask(taskIndex, Function, &TempEvent, str)) { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("PluginCallUDP"), taskIndex); - #endif - return true; - } - } - } - return false; - } - - // Call to all plugins that are used in a task - case PLUGIN_ONCE_A_SECOND: - case PLUGIN_TEN_PER_SECOND: - case PLUGIN_FIFTY_PER_SECOND: - case PLUGIN_INIT_ALL: - case PLUGIN_CLOCK_IN: - case PLUGIN_TIME_CHANGE: - { - if (Function == PLUGIN_INIT_ALL) { - Function = PLUGIN_INIT; - } - bool result = true; - - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) - { - #ifndef BUILD_NO_DEBUG - const int freemem_begin = ESP.getFreeHeap(); - #endif - - bool retval = PluginCallForTask(taskIndex, Function, &TempEvent, str, event); - - if (Function == PLUGIN_INIT) { - if (!retval && Settings.TaskDeviceDataFeed[taskIndex] == 0) { - // Disable temporarily as PLUGIN_INIT failed - // FIXME TD-er: Should reschedule call to PLUGIN_INIT???? - // What interval? (see: PR #4793) - //Settings.TaskDeviceEnabled[taskIndex].setRetryInit(); - //Scheduler.setPluginTaskTimer(10000, taskIndex, PLUGIN_INIT); - Settings.TaskDeviceEnabled[taskIndex] = false; - result = false; - } - #ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - // See also logMemUsageAfter() - const int freemem_end = ESP.getFreeHeap(); - String log; - if (log.reserve(128)) { - log = F("After PLUGIN_INIT "); - log += F(" task: "); - if (taskIndex < 9) log += ' '; - log += taskIndex + 1; - while (log.length() < 30) log += ' '; - log += F("Free mem after: "); - log += freemem_end; - while (log.length() < 53) log += ' '; - log += F("plugin: "); - log += freemem_begin - freemem_end; - while (log.length() < 67) log += ' '; - - log += Settings.TaskDeviceEnabled[taskIndex] ? F("[ena]") : F("[dis]"); - while (log.length() < 73) log += ' '; - log += getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(taskIndex)); - - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - #endif - } - } - - return result; - } - - #if FEATURE_PLUGIN_PRIORITY - case PLUGIN_PRIORITY_INIT_ALL: - { - if (Function == PLUGIN_PRIORITY_INIT_ALL) { - addLogMove(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; - } - - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) { - bool isPriority = PluginCallForTask(taskIndex, Function, &TempEvent, str, event); - - if ((Function == PLUGIN_PRIORITY_INIT) && isPriority) { // If this is a priority task, then initialize it, next PLUGIN_INIT call must be self-ignored by plugin! - clearPluginTaskData(taskIndex); // Make sure any task data is actually cleared. - if (PluginCallForTask(taskIndex, PLUGIN_INIT, &TempEvent, str, event) && - loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("INIT : Started Priority task %d, [%s] %s"), - taskIndex + 1, - getTaskDeviceName(taskIndex).c_str(), - getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(taskIndex)).c_str())); - } - } - } - - return true; - } - #endif // if FEATURE_PLUGIN_PRIORITY - - // Call to specific task which may interact with the hardware - case PLUGIN_INIT: - case PLUGIN_EXIT: - case PLUGIN_WEBFORM_LOAD: - case PLUGIN_WEBFORM_LOAD_ALWAYS: - case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: - case PLUGIN_READ: - case PLUGIN_GET_PACKED_RAW_DATA: - case PLUGIN_TASKTIMER_IN: - case PLUGIN_PROCESS_CONTROLLER_DATA: - { - // FIXME TD-er: Code duplication with PluginCallForTask - if (!validTaskIndex(event->TaskIndex)) { - return false; - } - if (Function == PLUGIN_READ || Function == PLUGIN_INIT || Function == PLUGIN_PROCESS_CONTROLLER_DATA) { - if (!Settings.TaskDeviceEnabled[event->TaskIndex]) { - return false; - } - } - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); - - if (validDeviceIndex(DeviceIndex)) { - if (ExtraTaskSettings.TaskIndex != event->TaskIndex) { - if (Function == PLUGIN_READ && Device[DeviceIndex].ErrorStateValues) { - // PLUGIN_READ should not need to access ExtraTaskSettings except for what's already being cached. - // Only exception is when ErrorStateValues is needed. - // Therefore only need to call LoadTaskSettings for those tasks with ErrorStateValues - LoadTaskSettings(event->TaskIndex); - } else if (Function == PLUGIN_INIT || Function == PLUGIN_WEBFORM_LOAD || Function == PLUGIN_WEBFORM_LOAD_ALWAYS) { - // LoadTaskSettings may call PLUGIN_GET_DEVICEVALUENAMES. - LoadTaskSettings(event->TaskIndex); - } - } - event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM_PluginCall_task(event->TaskIndex, Function); - #endif - - if (!prepare_I2C_by_taskIndex(event->TaskIndex, DeviceIndex)) { - return false; - } - bool retval = false; - const bool performPluginCall = - (Function != PLUGIN_READ) || - (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0); - #if FEATURE_I2C_DEVICE_CHECK - bool i2cStatusOk = true; - if (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0) { - // Only for locally connected sensors, not virtual ones via p2p. - if (((Function == PLUGIN_INIT) || (Function == PLUGIN_READ)) - && (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) && !Device[DeviceIndex].I2CNoDeviceCheck) { - const uint8_t i2cAddr = getTaskI2CAddress(event->TaskIndex); - if (i2cAddr > 0) { - START_TIMER; - // Disable task when device is unreachable for 10 PLUGIN_READs or 1 PLUGIN_INIT - i2cStatusOk = I2C_deviceCheck(i2cAddr, event->TaskIndex, Function == PLUGIN_INIT ? 1 : 10); - STOP_TIMER_TASK(DeviceIndex, PLUGIN_I2C_GET_ADDRESS); - } - } - } - if (i2cStatusOk) { - #endif // if FEATURE_I2C_DEVICE_CHECK - START_TIMER; - - if (((Function == PLUGIN_INIT) || - (Function == PLUGIN_WEBFORM_LOAD)) && - Device[DeviceIndex].ErrorStateValues) { // Only when we support ErrorStateValues - // FIXME TD-er: Not sure if this should be called here. - // 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) - } - - if ((Function == PLUGIN_INIT) - #if FEATURE_PLUGIN_PRIORITY - && !Settings.isPriorityTask(event->TaskIndex) // Don't clear already initialized PriorityTask data - #endif // if FEATURE_PLUGIN_PRIORITY - ) { - // Make sure any task data is actually cleared. - clearPluginTaskData(event->TaskIndex); - /* - #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - if (Device[DeviceIndex].isSerial()) { - checkSerialConflict( - serialHelper_getSerialType(event), - serialHelper_getRxPin(event), - serialHelper_getTxPin(event)); - } - #endif - */ - } - - if (performPluginCall) { - retval = PluginCall(DeviceIndex, Function, event, str); - } else { - retval = event->Source == EventValueSource::Enum::VALUE_SOURCE_UDP; - } - - if (Function == PLUGIN_READ) { - if (!retval) { - String errorStr; - if (PluginCall(DeviceIndex, PLUGIN_READ_ERROR_OCCURED, event, errorStr)) - { - // Apparently the last read call resulted in an error - // Send event indicating the error. - queueTaskEvent(F("TaskError"), event->TaskIndex, errorStr); - } - } else { - // Must be done as soon as there are new values, so we can keep a copy of the previous value - // This previous value may be needed in formulas using %pvalue% - UserVar.markPluginRead(event->TaskIndex); - #if FEATURE_PLUGIN_STATS - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(event->TaskIndex); - if (taskData != nullptr) { - taskData->pushPluginStatsValues(event, !Device[DeviceIndex].TaskLogsOwnPeaks); - } - #endif // if FEATURE_PLUGIN_STATS - saveUserVarToRTC(); - } - } - if (Function == PLUGIN_INIT) { - if (!retval && Settings.TaskDeviceDataFeed[event->TaskIndex] == 0) { - // Disable temporarily as PLUGIN_INIT failed - // FIXME TD-er: Should reschedule call to PLUGIN_INIT???? - Settings.TaskDeviceEnabled[event->TaskIndex] = false; - } else { - #if FEATURE_PLUGIN_STATS - if (Device[DeviceIndex].PluginStats) { - PluginTaskData_base *taskData = getPluginTaskData(event->TaskIndex); - if (taskData == nullptr) { - // Plugin apparently does not have PluginTaskData. - // Create Plugin Task data if it has "Stats" checked. - LoadTaskSettings(event->TaskIndex); - if (ExtraTaskSettings.anyEnabledPluginStats()) { - initPluginTaskData(event->TaskIndex, new (std::nothrow) _StatsOnly_data_struct()); - } - } - } - #endif // if FEATURE_PLUGIN_STATS - // Schedule the plugin to be read. - Scheduler.schedule_task_device_timer_at_init(TempEvent.TaskIndex); - queueTaskEvent(F("TaskInit"), event->TaskIndex, retval); - } - } - if (Function == PLUGIN_EXIT) { - clearPluginTaskData(event->TaskIndex); -// initSerial(); - queueTaskEvent(F("TaskExit"), event->TaskIndex, retval); - updateActiveTaskUseSerial0(); - } - STOP_TIMER_TASK(DeviceIndex, Function); - #if FEATURE_I2C_DEVICE_CHECK - } - #endif // if FEATURE_I2C_DEVICE_CHECK - post_I2C_by_taskIndex(event->TaskIndex, DeviceIndex); - delay(0); // SMY: call delay(0) unconditionally - - return retval; - } - return false; - } - - // Call to specific task not interacting with hardware - case PLUGIN_GET_CONFIG_VALUE: - case PLUGIN_GET_DEVICEVALUENAMES: - case PLUGIN_GET_DEVICEGPIONAMES: - case PLUGIN_WEBFORM_SAVE: - case PLUGIN_WEBFORM_SHOW_VALUES: - case PLUGIN_WEBFORM_SHOW_CONFIG: - case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: - case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: - case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: - #if FEATURE_PLUGIN_STATS - case PLUGIN_WEBFORM_LOAD_SHOW_STATS: - #endif // if FEATURE_PLUGIN_STATS - case PLUGIN_SET_CONFIG: - case PLUGIN_SET_DEFAULTS: - case PLUGIN_I2C_HAS_ADDRESS: - case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: - case PLUGIN_INIT_VALUE_RANGES: - - // PLUGIN_MQTT_xxx functions are directly called from the scheduler. - //case PLUGIN_MQTT_CONNECTION_STATE: - //case PLUGIN_MQTT_IMPORT: - { - START_TIMER; - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); - - if (validDeviceIndex(DeviceIndex)) { - if (Function == PLUGIN_GET_DEVICEVALUENAMES || - Function == PLUGIN_WEBFORM_SAVE || - Function == PLUGIN_WEBFORM_LOAD || - Function == PLUGIN_WEBFORM_LOAD_ALWAYS || - Function == PLUGIN_SET_DEFAULTS || - Function == PLUGIN_INIT_VALUE_RANGES || - Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS - ) { - LoadTaskSettings(event->TaskIndex); - } - event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; - - #ifndef BUILD_NO_RAM_TRACKER - checkRAM_PluginCall_task(event->TaskIndex, Function); - #endif - - if (Function == PLUGIN_SET_DEFAULTS) { - for (int i = 0; i < VARS_PER_TASK; ++i) { - UserVar.setFloat(event->TaskIndex, i, 0.0f); - } - } - - bool retval = PluginCall(DeviceIndex, Function, event, str); - - // Calls may have updated ExtraTaskSettings, so validate them. - ExtraTaskSettings.validate(); - - if (Function == PLUGIN_GET_DEVICEVALUENAMES || - Function == PLUGIN_WEBFORM_SAVE || - Function == PLUGIN_SET_DEFAULTS || - Function == PLUGIN_INIT_VALUE_RANGES || - (Function == PLUGIN_SET_CONFIG && retval)) { - // Each of these may update ExtraTaskSettings, but it may not have been saved yet. - // Thus update the cache just in case something from it is requested from the cache. - Cache.updateExtraTaskSettingsCache(); - UserVar.clear_computed(event->TaskIndex); - } - if (Function == PLUGIN_SET_DEFAULTS) { - saveUserVarToRTC(); - } - if (Function == PLUGIN_GET_CONFIG_VALUE && !retval) { - // Try to match a statistical property of a task value. - // e.g.: [taskname#valuename.avg] - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(event->TaskIndex); - if (nullptr != taskData) { - if (taskData->plugin_get_config_value_base(event, str)) { - retval = true; - } - } - } - - - STOP_TIMER_TASK(DeviceIndex, Function); - delay(0); // SMY: call delay(0) unconditionally - return retval; - } - return false; - } - - // Frequently made call to specific task not interacting with hardware - case PLUGIN_GET_DEVICEVALUECOUNT: - case PLUGIN_GET_DEVICEVTYPE: - case PLUGIN_FORMAT_USERVAR: - { - START_TIMER; - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); - - if (validDeviceIndex(DeviceIndex)) { - event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; - - #ifndef BUILD_NO_RAM_TRACKER -// checkRAM_PluginCall_task(event->TaskIndex, Function); - #endif - - if (Function == PLUGIN_GET_DEVICEVALUECOUNT) { - event->Par1 = Device[DeviceIndex].ValueCount; - } - if (Function == PLUGIN_GET_DEVICEVTYPE) { - event->sensorType = Device[DeviceIndex].VType; - } - bool retval = PluginCall(DeviceIndex, Function, event, str); - if (Function == PLUGIN_GET_DEVICEVALUECOUNT) { - // Check if we have a valid value count. - if (Output_Data_type_t::Simple == Device[DeviceIndex].OutputDataType) { - if (event->Par1 < 1 || event->Par1 > VARS_PER_TASK) { - // Output_Data_type_t::Simple only allows for 1 .. 4 output types. - // Apparently the value is not correct, so use the default. - event->Par1 = Device[DeviceIndex].ValueCount; - } - } - } - STOP_TIMER_TASK(DeviceIndex, Function); - delay(0); // SMY: call delay(0) unconditionally - return retval; - } - return false; - } - - - } // case - return false; -} - +#include "../Globals/Plugins.h" + +#include "../CustomBuild/ESPEasyLimits.h" + +#include "../../_Plugin_Helper.h" + +#include "../DataStructs/ESPEasy_EventStruct.h" +#include "../DataStructs/TimingStats.h" + +#include "../DataTypes/ESPEasy_plugin_functions.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/Serial.h" + +#include "../Globals/Cache.h" +#include "../Globals/Device.h" +#include "../Globals/ESPEasy_Scheduler.h" +#include "../Globals/ExtraTaskSettings.h" +#include "../Globals/EventQueue.h" +#include "../Globals/GlobalMapPortStatus.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" + +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT +# include "../Helpers/_Plugin_Helper_serial.h" +#endif // if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/Misc.h" +#include "../Helpers/_Plugin_init.h" +#include "../Helpers/PortStatus.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + +#include + + +bool validDeviceIndex(deviceIndex_t index) { + return validDeviceIndex_init(index); +} + +/* + bool validTaskIndex(taskIndex_t index) { + return index < TASKS_MAX; + } + + bool validPluginID(pluginID_t pluginID) { + return (pluginID != INVALID_PLUGIN_ID); + } + */ +bool validPluginID_fullcheck(pluginID_t pluginID) { + return getDeviceIndex_from_PluginID(pluginID) != INVALID_DEVICE_INDEX; +} + +/* + bool validUserVarIndex(userVarIndex_t index) { + return index < USERVAR_MAX_INDEX; + } + + bool validTaskVarIndex(taskVarIndex_t index) { + return index < VARS_PER_TASK; + } + */ +bool supportedPluginID(pluginID_t pluginID) { + return validDeviceIndex(getDeviceIndex(pluginID)); +} + +deviceIndex_t getDeviceIndex_from_TaskIndex(taskIndex_t taskIndex) { + if (validTaskIndex(taskIndex)) { + return getDeviceIndex(Settings.getPluginID_for_task(taskIndex)); + } + return INVALID_DEVICE_INDEX; +} + +/********************************************************************************************* + * get the taskPluginID with required checks, INVALID_PLUGIN_ID when invalid + ********************************************************************************************/ +pluginID_t getPluginID_from_TaskIndex(taskIndex_t taskIndex) { + if (validTaskIndex(taskIndex)) { + const pluginID_t pluginID = Settings.getPluginID_for_task(taskIndex); + + if (supportedPluginID(pluginID)) { + return pluginID; + } + } + return INVALID_PLUGIN_ID; +} + +#if FEATURE_PLUGIN_PRIORITY +bool isPluginI2CPowerManager_from_TaskIndex(taskIndex_t taskIndex) { + if (validTaskIndex(taskIndex)) { + deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (validDeviceIndex(deviceIndex)) { + return (Device[deviceIndex].Type == DEVICE_TYPE_I2C) && + Device[deviceIndex].PowerManager && + Settings.isPowerManagerTask(taskIndex); + } + } + return false; +} + +#endif // if FEATURE_PLUGIN_PRIORITY + +deviceIndex_t getDeviceIndex(pluginID_t pluginID) +{ + return getDeviceIndex_from_PluginID(pluginID); +} + +/********************************************************************************************\ + Find name of plugin given the plugin device index.. + \*********************************************************************************************/ +String getPluginNameFromDeviceIndex(deviceIndex_t deviceIndex) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + String deviceName; + + if (validDeviceIndex(deviceIndex)) { + PluginCall(deviceIndex, PLUGIN_GET_DEVICENAME, nullptr, deviceName); + } + return deviceName; +} + +String getPluginNameFromPluginID(pluginID_t pluginID) { + deviceIndex_t deviceIndex = getDeviceIndex(pluginID); + + if (!validDeviceIndex(deviceIndex)) { + return strformat(F("Plugin %d not included in build"), pluginID.value); + } + return getPluginNameFromDeviceIndex(deviceIndex); +} + +#if FEATURE_I2C_DEVICE_SCAN +bool checkPluginI2CAddressFromDeviceIndex(deviceIndex_t deviceIndex, uint8_t i2cAddress) { + bool hasI2CAddress = false; + + if (validDeviceIndex(deviceIndex)) { + String dummy; + struct EventStruct TempEvent; + TempEvent.Par1 = i2cAddress; + hasI2CAddress = PluginCall(deviceIndex, PLUGIN_I2C_HAS_ADDRESS, &TempEvent, dummy); + } + return hasI2CAddress; +} + +#endif // if FEATURE_I2C_DEVICE_SCAN + +bool getPluginDisplayParametersFromTaskIndex(taskIndex_t taskIndex, uint16_t& x, uint16_t& y, uint16_t& r, uint16_t& colorDepth) { + if (!validTaskIndex(taskIndex)) { return false; } + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (validDeviceIndex(deviceIndex)) { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); + + if (validPluginID(pluginID)) { + String dummy; + struct EventStruct TempEvent; + TempEvent.setTaskIndex(taskIndex); + + if (PluginCall(deviceIndex, PLUGIN_GET_DISPLAY_PARAMETERS, &TempEvent, dummy)) { + x = TempEvent.Par1; + y = TempEvent.Par2; + r = TempEvent.Par3; + colorDepth = TempEvent.Par4; + return true; + } + } + } + return false; +} + +#if FEATURE_I2C_GET_ADDRESS +uint8_t getTaskI2CAddress(taskIndex_t taskIndex) { + uint8_t getI2CAddress = 0; + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (validTaskIndex(taskIndex) && validDeviceIndex(deviceIndex)) { + String dummy; + struct EventStruct TempEvent; + TempEvent.setTaskIndex(taskIndex); + TempEvent.Par1 = 0; + + if (PluginCall(deviceIndex, PLUGIN_I2C_GET_ADDRESS, &TempEvent, dummy)) { + getI2CAddress = TempEvent.Par1; + } + } + return getI2CAddress; +} + +#endif // if FEATURE_I2C_GET_ADDRESS + + +// ******************************************************************************** +// Functions to assist changing I2C multiplexer port or clock speed +// when addressing a task +// ******************************************************************************** + +bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { + if (!validTaskIndex(taskIndex) || !validDeviceIndex(DeviceIndex)) { + return false; + } + + if (Device[DeviceIndex].Type != DEVICE_TYPE_I2C) { + return true; // No I2C task, so consider all-OK + } + + if (I2C_state != I2C_bus_state::OK) { + return false; // Bus state is not OK, so do not consider task runnable + } + #if FEATURE_I2CMULTIPLEXER + I2CMultiplexerSelectByTaskIndex(taskIndex); + + // Output is selected after this write, so now we must make sure the + // frequency is set before anything else is sent. + #endif // if FEATURE_I2CMULTIPLEXER + + if (bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)) { + I2CSelectLowClockSpeed(); // Set to slow + } + return true; +} + +void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) { + if (!validTaskIndex(taskIndex) || !validDeviceIndex(DeviceIndex)) { + return; + } + + if (Device[DeviceIndex].Type != DEVICE_TYPE_I2C) { + return; + } + #if FEATURE_I2CMULTIPLEXER + I2CMultiplexerOff(); + #endif // if FEATURE_I2CMULTIPLEXER + + I2CSelectHighClockSpeed(); // Reset +} + +// Add an event to the event queue. +// event value 1 = taskIndex (first task = 1) +// event value 2 = return value of the plugin function +// Example: TaskInit#bme=1,0 (taskindex = 0, return value = 0) +void queueTaskEvent(const String& eventName, taskIndex_t taskIndex, const String& value_str) { + if (Settings.UseRules) { + String event = strformat( + F("%s#%s=%d"), + eventName.c_str(), + getTaskDeviceName(taskIndex).c_str(), + taskIndex + 1); + + if (value_str.length() > 0) { + event += ','; + event += wrapWithQuotesIfContainsParameterSeparatorChar(value_str); + } + eventQueue.addMove(std::move(event)); + } +} + +void queueTaskEvent(const String& eventName, taskIndex_t taskIndex, const int& value1) { + queueTaskEvent(eventName, taskIndex, String(value1)); +} + +void queueTaskEvent(const __FlashStringHelper *eventName, taskIndex_t taskIndex, const String& value1) { + queueTaskEvent(String(eventName), taskIndex, value1); +} + +void queueTaskEvent(const __FlashStringHelper *eventName, taskIndex_t taskIndex, const int& value1) { + queueTaskEvent(String(eventName), taskIndex, String(value1)); +} + +void loadDefaultTaskValueNames_ifEmpty(taskIndex_t TaskIndex) { + String oldNames[VARS_PER_TASK]; + uint8_t oldNrDec[VARS_PER_TASK]; + + LoadTaskSettings(TaskIndex); + + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + oldNames[i] = ExtraTaskSettings.TaskDeviceValueNames[i]; + oldNrDec[i] = ExtraTaskSettings.TaskDeviceValueDecimals[i]; + oldNames[i].trim(); + } + + struct EventStruct TempEvent(TaskIndex); + String dummy; + + // the plugin call should populate ExtraTaskSettings with its default values. + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); + + // Restore the settings that were already set by the user + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { + const bool isDefault = oldNames[i].isEmpty(); + ExtraTaskSettings.isDefaultTaskVarName(i, isDefault); + + if (!isDefault) { + ExtraTaskSettings.setTaskDeviceValueName(i, oldNames[i]); + ExtraTaskSettings.TaskDeviceValueDecimals[i] = oldNrDec[i]; + } + } +} + +/** + * Call the plugin of 1 task for 1 function, with standard EventStruct and optional command string + */ +bool PluginCallForTask(taskIndex_t taskIndex, uint8_t Function, EventStruct *TempEvent, String& command, EventStruct *event = nullptr) { + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + bool retval = false; + const bool considerTaskEnabled = Settings.TaskDeviceEnabled[taskIndex]; + + // || (Settings.TaskDeviceEnabled[taskIndex].enabled && Function == PLUGIN_INIT); + + if (considerTaskEnabled) { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex))) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (validDeviceIndex(DeviceIndex)) { + if (Function == PLUGIN_INIT) { + UserVar.clear_computed(taskIndex); + LoadTaskSettings(taskIndex); + } + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) // these calls only to tasks with local feed + { + TempEvent->setTaskIndex(taskIndex); + // Need to 'clear' the sensorType first, before calling getSensorType() + TempEvent->sensorType = Sensor_VType::SENSOR_TYPE_NOT_SET; + TempEvent->getSensorType(); + + if (event != nullptr) { + TempEvent->OriginTaskIndex = event->TaskIndex; + } + + if (!prepare_I2C_by_taskIndex(taskIndex, DeviceIndex)) { + return false; + } + #ifndef BUILD_NO_RAM_TRACKER + + if (Settings.EnableRAMTracking()) { + switch (Function) { + case PLUGIN_WRITE: // First set + case PLUGIN_REQUEST: + case PLUGIN_ONCE_A_SECOND: // Second set + case PLUGIN_TEN_PER_SECOND: + case PLUGIN_FIFTY_PER_SECOND: + case PLUGIN_INIT: // Second set, instead of PLUGIN_INIT_ALL + case PLUGIN_CLOCK_IN: + case PLUGIN_TIME_CHANGE: + # if FEATURE_PLUGIN_PRIORITY + case PLUGIN_PRIORITY_INIT: + # endif // if FEATURE_PLUGIN_PRIORITY + { + checkRAM(F("PluginCall_s"), taskIndex); + break; + } + } + } + #endif // ifndef BUILD_NO_RAM_TRACKER + #if FEATURE_I2C_DEVICE_CHECK + bool i2cStatusOk = true; + + if ((Function == PLUGIN_INIT) && (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) && !Device[DeviceIndex].I2CNoDeviceCheck) { + const uint8_t i2cAddr = getTaskI2CAddress(taskIndex); + + if (i2cAddr > 0) { + START_TIMER; + i2cStatusOk = I2C_deviceCheck(i2cAddr); + STOP_TIMER_TASK(DeviceIndex, PLUGIN_I2C_GET_ADDRESS); + } + } + + if (i2cStatusOk) { + #endif // if FEATURE_I2C_DEVICE_CHECK + #ifndef BUILD_NO_RAM_TRACKER + + if (Settings.EnableRAMTracking()) { + switch (Function) { + case PLUGIN_WRITE: // First set + case PLUGIN_REQUEST: + case PLUGIN_ONCE_A_SECOND: // Second set + case PLUGIN_TEN_PER_SECOND: + case PLUGIN_FIFTY_PER_SECOND: + case PLUGIN_INIT: // Second set, instead of PLUGIN_INIT_ALL + case PLUGIN_CLOCK_IN: + case PLUGIN_TIME_CHANGE: + { + checkRAM(F("PluginCall_s"), taskIndex); + break; + } + } + } + #endif // ifndef BUILD_NO_RAM_TRACKER + + if (Function == PLUGIN_INIT) { + // 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_at_init(TempEvent->TaskIndex); + } + + START_TIMER; + retval = (PluginCall(DeviceIndex, Function, TempEvent, command)); + + STOP_TIMER_TASK(DeviceIndex, Function); + + if (Function == PLUGIN_INIT) { + #if FEATURE_PLUGIN_STATS + + if (Device[DeviceIndex].PluginStats) { + PluginTaskData_base *taskData = getPluginTaskData(taskIndex); + + if (taskData == nullptr) { + // Plugin apparently does not have PluginTaskData. + // Create Plugin Task data if it has "Stats" checked. + LoadTaskSettings(taskIndex); + + if (ExtraTaskSettings.anyEnabledPluginStats()) { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + initPluginTaskData(taskIndex, new (std::nothrow) _StatsOnly_data_struct()); + } + } + } + #endif // if FEATURE_PLUGIN_STATS + queueTaskEvent(F("TaskInit"), taskIndex, retval); + } + #if FEATURE_I2C_DEVICE_CHECK + } + #endif // if FEATURE_I2C_DEVICE_CHECK + + post_I2C_by_taskIndex(taskIndex, DeviceIndex); + delay(0); // SMY: call delay(0) unconditionally + } else { + #if FEATURE_PLUGIN_STATS + + if ((Function == PLUGIN_INIT) && Device[DeviceIndex].PluginStats) { + PluginTaskData_base *taskData = getPluginTaskData(taskIndex); + + if (taskData == nullptr) { + // Plugin apparently does not have PluginTaskData. + // Create Plugin Task data if it has "Stats" checked. + LoadTaskSettings(taskIndex); + + if (ExtraTaskSettings.anyEnabledPluginStats()) { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + initPluginTaskData(taskIndex, new (std::nothrow) _StatsOnly_data_struct()); + } + } + } + #endif // if FEATURE_PLUGIN_STATS + } + } + } + } + return retval; +} + +/*********************************************************************************************\ +* Function call to all or specific plugins +\*********************************************************************************************/ +bool PluginCall(uint8_t Function, struct EventStruct *event, String& str) +{ + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif // ifdef USE_SECOND_HEAP + + struct EventStruct TempEvent; + + if (event == nullptr) { + event = &TempEvent; + } + else { + TempEvent.deep_copy(*event); + } + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("PluginCall"), Function); + #endif // ifndef BUILD_NO_RAM_TRACKER + + switch (Function) + { + // Unconditional calls to all plugins + // FIXME TD-er: PLUGIN_UNCONDITIONAL_POLL is not being used at the moment + + /* + case PLUGIN_UNCONDITIONAL_POLL: + { + + const unsigned maxDeviceIndex = getNrBuiltInDeviceIndex(); + + for (deviceIndex_t x; x < maxDeviceIndex; ++x) { + START_TIMER; + PluginCall(x, Function, event, str); + STOP_TIMER_TASK(x, Function); + delay(0); // SMY: call delay(0) unconditionally + } + return true; + } + */ + + case PLUGIN_MONITOR: + + for (auto it = globalMapPortStatus.begin(); it != globalMapPortStatus.end(); ++it) { + // only call monitor function if there the need to + if (it->second.monitor || it->second.command || it->second.init) { + TempEvent.Par1 = getPortFromKey(it->first); + + // initialize the "x" variable to synch with the pluginNumber if second.x == -1 + if (!validDeviceIndex(it->second.x)) { it->second.x = getDeviceIndex(getPluginFromKey(it->first)); } + + const deviceIndex_t DeviceIndex = it->second.x; + + if (validDeviceIndex(DeviceIndex)) { + START_TIMER; + PluginCall(DeviceIndex, Function, &TempEvent, str); + STOP_TIMER_TASK(DeviceIndex, Function); + } + } + } + return true; + + + // Call to all plugins. Return at first match + case PLUGIN_WRITE: + // case PLUGIN_REQUEST: @giig1967g: replaced by new function getGPIOPluginValues() + { + taskIndex_t firstTask = 0; + taskIndex_t lastTask = TASKS_MAX; + String command = String(str); // Local copy to avoid warning in ExecuteCommand + int dotPos = command.indexOf('.'); // Find first period + + if ((Function == PLUGIN_WRITE) // Only applicable on PLUGIN_WRITE function + && (dotPos > -1)) { // First precondition is just a quick check for a period (fail-fast strategy) + const String arg0 = parseString(command, 1); // Get first argument + dotPos = arg0.indexOf('.'); + + if (dotPos > -1) { + String thisTaskName = parseString(arg0, 1, '.'); // Extract taskname prefix + removeChar(thisTaskName, '['); // Remove the optional square brackets + removeChar(thisTaskName, ']'); + + if (thisTaskName.length() > 0) { // Second precondition + taskIndex_t thisTask = findTaskIndexByName(thisTaskName); + + if (!validTaskIndex(thisTask)) { // Taskname not found or invalid, check for a task number? + thisTask = static_cast(atoi(thisTaskName.c_str())); + + if ((thisTask == 0) || (thisTask > TASKS_MAX)) { + thisTask = INVALID_TASK_INDEX; + } else { + thisTask--; // 0-based + } + } + + if (validTaskIndex(thisTask)) { // Known taskindex? +#ifdef USES_P022 // Exclude P022 as it has rather explicit differences in commands when used with the + // []. prefix + const pluginID_t pluginID = Settings.getPluginID_for_task(thisTask); + + if (Settings.TaskDeviceEnabled[thisTask] // and internally needs to know wether it was called with the taskname prefixed + && validPluginID_fullcheck(pluginID) + && (Settings.TaskDeviceDataFeed[thisTask] == 0)) { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(thisTask); + constexpr pluginID_t P022_PCA9685_PLUGIN_ID(22); + + if (validDeviceIndex(DeviceIndex) && + (pluginID == P022_PCA9685_PLUGIN_ID) /* PLUGIN_ID_022 define no longer available, 'assume' 22 for now */) { + thisTask = INVALID_TASK_INDEX; + } + } + + if (validTaskIndex(thisTask)) { +#endif // ifdef USES_P022 + firstTask = thisTask; + lastTask = thisTask + 1; // Add 1 to satisfy the for condition + command = command.substring(dotPos + 1); // Remove []. prefix +#ifdef USES_P022 + } +#endif // ifdef USES_P022 + } + } + } + } + + // String info = F("PLUGIN_WRITE first: "); // To remove + // info += firstTask; + // info += F(" last: "); + // info += lastTask; + // addLog(LOG_LEVEL_INFO, info); + + for (taskIndex_t task = firstTask; task < lastTask; task++) + { + bool retval = PluginCallForTask(task, Function, &TempEvent, command); + + if (!retval) { + if (1 == (lastTask - firstTask)) { + // These plugin task data commands are generic, so only apply them on a specific task. + // Don't try to match them on the first task that may have such data. + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(task); + + if (nullptr != taskData) { + if (taskData->plugin_write_base(event, command)) { + retval = true; + } + } + } + } + + if (retval) { + EventStruct CPlugin_ack_event; + CPlugin_ack_event.deep_copy(TempEvent); + CPlugin_ack_event.setTaskIndex(task); + CPluginCall(CPlugin::Function::CPLUGIN_ACKNOWLEDGE, &CPlugin_ack_event, command); + return true; + } + } + + /* + 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)) { + delay(0); // SMY: call delay(0) unconditionally + CPluginCall(CPlugin::Function::CPLUGIN_ACKNOWLEDGE, event, str); + return true; + } + } + } + */ + break; + } + + // Call to all plugins used in a task. Return at first match + case PLUGIN_SERIAL_IN: + case PLUGIN_UDP_IN: + { + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + if (Settings.TaskDeviceEnabled[taskIndex]) { + if (PluginCallForTask(taskIndex, Function, &TempEvent, str)) { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("PluginCallUDP"), taskIndex); + #endif // ifndef BUILD_NO_RAM_TRACKER + return true; + } + } + } + return false; + } + + // Call to all plugins that are used in a task + case PLUGIN_ONCE_A_SECOND: + case PLUGIN_TEN_PER_SECOND: + case PLUGIN_FIFTY_PER_SECOND: + case PLUGIN_INIT_ALL: + case PLUGIN_CLOCK_IN: + case PLUGIN_TIME_CHANGE: + { + if (Function == PLUGIN_INIT_ALL) { + Function = PLUGIN_INIT; + } + bool result = true; + + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + #ifndef BUILD_NO_DEBUG + int freemem_begin{}; + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + freemem_begin = ESP.getFreeHeap(); + } + #endif // ifndef BUILD_NO_DEBUG + + bool retval = PluginCallForTask(taskIndex, Function, &TempEvent, str, event); + + if (Function == PLUGIN_INIT) { + UserVar.clear_computed(taskIndex); + + if (!retval && (Settings.TaskDeviceDataFeed[taskIndex] == 0)) { + // Disable temporarily as PLUGIN_INIT failed + // FIXME TD-er: Should reschedule call to PLUGIN_INIT???? + // What interval? (see: PR #4793) + // Settings.TaskDeviceEnabled[taskIndex].setRetryInit(); + // Scheduler.setPluginTaskTimer(10000, taskIndex, PLUGIN_INIT); + Settings.TaskDeviceEnabled[taskIndex] = false; + result = false; + } + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + // See also logMemUsageAfter() + const int freemem_end = ESP.getFreeHeap(); + String log; + + if (log.reserve(128)) { + log = F("After PLUGIN_INIT "); + log += F(" task: "); + + if (taskIndex < 9) { log += ' '; } + log += taskIndex + 1; + + while (log.length() < 30) { log += ' '; } + log += F("Free mem after: "); + log += freemem_end; + + while (log.length() < 53) { log += ' '; } + log += F("plugin: "); + log += freemem_begin - freemem_end; + + while (log.length() < 67) { log += ' '; } + + log += Settings.TaskDeviceEnabled[taskIndex] ? F("[ena]") : F("[dis]"); + + while (log.length() < 73) { log += ' '; } + log += getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(taskIndex)); + + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + #endif // ifndef BUILD_NO_DEBUG + } + } + + return result; + } + + #if FEATURE_PLUGIN_PRIORITY + case PLUGIN_PRIORITY_INIT_ALL: + { + if (Function == PLUGIN_PRIORITY_INIT_ALL) { + addLogMove(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; + } + + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) { + bool isPriority = PluginCallForTask(taskIndex, Function, &TempEvent, str, event); + + if ((Function == PLUGIN_PRIORITY_INIT) && isPriority) { // If this is a priority task, then initialize it, next PLUGIN_INIT call + // must be self-ignored by plugin! + clearPluginTaskData(taskIndex); // Make sure any task data is actually cleared. + + if (PluginCallForTask(taskIndex, PLUGIN_INIT, &TempEvent, str, event) && + loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("INIT : Started Priority task %d, [%s] %s"), + taskIndex + 1, + getTaskDeviceName(taskIndex).c_str(), + getPluginNameFromDeviceIndex(getDeviceIndex_from_TaskIndex(taskIndex)).c_str())); + } + } + } + + return true; + } + #endif // if FEATURE_PLUGIN_PRIORITY + + // Call to specific task which may interact with the hardware + case PLUGIN_INIT: + case PLUGIN_EXIT: + case PLUGIN_WEBFORM_LOAD: + case PLUGIN_WEBFORM_LOAD_ALWAYS: + case PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR: + case PLUGIN_READ: + case PLUGIN_GET_PACKED_RAW_DATA: + case PLUGIN_TASKTIMER_IN: + case PLUGIN_PROCESS_CONTROLLER_DATA: + { + // FIXME TD-er: Code duplication with PluginCallForTask + if (!validTaskIndex(event->TaskIndex)) { + return false; + } + + if ((Function == PLUGIN_READ) || (Function == PLUGIN_INIT) || (Function == PLUGIN_PROCESS_CONTROLLER_DATA)) { + if (!Settings.TaskDeviceEnabled[event->TaskIndex]) { + return false; + } + + if (Function == PLUGIN_INIT) { + UserVar.clear_computed(event->TaskIndex); + } + } + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); + + if (validDeviceIndex(DeviceIndex)) { + if (ExtraTaskSettings.TaskIndex != event->TaskIndex) { + if ((Function == PLUGIN_READ) && Device[DeviceIndex].ErrorStateValues) { + // PLUGIN_READ should not need to access ExtraTaskSettings except for what's already being cached. + // Only exception is when ErrorStateValues is needed. + // Therefore only need to call LoadTaskSettings for those tasks with ErrorStateValues + LoadTaskSettings(event->TaskIndex); + } else if ((Function == PLUGIN_INIT) || (Function == PLUGIN_WEBFORM_LOAD) || (Function == PLUGIN_WEBFORM_LOAD_ALWAYS)) { + // LoadTaskSettings may call PLUGIN_GET_DEVICEVALUENAMES. + LoadTaskSettings(event->TaskIndex); + } + } + event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM_PluginCall_task(event->TaskIndex, Function); + #endif // ifndef BUILD_NO_RAM_TRACKER + + if (!prepare_I2C_by_taskIndex(event->TaskIndex, DeviceIndex)) { + return false; + } + bool retval = false; + const bool performPluginCall = + (Function != PLUGIN_READ && Function != PLUGIN_INIT) || + (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0); + #if FEATURE_I2C_DEVICE_CHECK + bool i2cStatusOk = true; + + if (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0) { + // Only for locally connected sensors, not virtual ones via p2p. + if (((Function == PLUGIN_INIT) || (Function == PLUGIN_READ)) + && (Device[DeviceIndex].Type == DEVICE_TYPE_I2C) && !Device[DeviceIndex].I2CNoDeviceCheck) { + const uint8_t i2cAddr = getTaskI2CAddress(event->TaskIndex); + + if (i2cAddr > 0) { + START_TIMER; + + // Disable task when device is unreachable for 10 PLUGIN_READs or 1 PLUGIN_INIT + i2cStatusOk = I2C_deviceCheck(i2cAddr, event->TaskIndex, Function == PLUGIN_INIT ? 1 : 10); + STOP_TIMER_TASK(DeviceIndex, PLUGIN_I2C_GET_ADDRESS); + } + } + } + + if (i2cStatusOk) { + #endif // if FEATURE_I2C_DEVICE_CHECK + START_TIMER; + + if (((Function == PLUGIN_INIT) || + (Function == PLUGIN_WEBFORM_LOAD)) && + Device[DeviceIndex].ErrorStateValues) { // Only when we support ErrorStateValues + // FIXME TD-er: Not sure if this should be called here. + // 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) + } + + if ((Function == PLUGIN_INIT) + #if FEATURE_PLUGIN_PRIORITY + && !Settings.isPriorityTask(event->TaskIndex) // Don't clear already initialized PriorityTask data + #endif // if FEATURE_PLUGIN_PRIORITY + ) { + // Make sure any task data is actually cleared. + clearPluginTaskData(event->TaskIndex); + + /* + #if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + if (Device[DeviceIndex].isSerial()) { + checkSerialConflict( + serialHelper_getSerialType(event), + serialHelper_getRxPin(event), + serialHelper_getTxPin(event)); + } + #endif + */ + } + + if (performPluginCall) { + retval = PluginCall(DeviceIndex, Function, event, str); + } else { + retval = event->Source == EventValueSource::Enum::VALUE_SOURCE_UDP; + } + + if (Function == PLUGIN_READ) { + if (!retval) { + String errorStr; + + if (PluginCall(DeviceIndex, PLUGIN_READ_ERROR_OCCURED, event, errorStr)) + { + // Apparently the last read call resulted in an error + // Send event indicating the error. + queueTaskEvent(F("TaskError"), event->TaskIndex, errorStr); + } + } else { + // Must be done as soon as there are new values, so we can keep a copy of the previous value + // This previous value may be needed in formulas using %pvalue% + UserVar.markPluginRead(event->TaskIndex); + #if FEATURE_PLUGIN_STATS + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(event->TaskIndex); + + if (taskData != nullptr) { + // FIXME TD-er: Must make this flag configurable + const bool onlyUpdateTimestampWhenSame = true; + const bool trackpeaks = + Settings.TaskDeviceDataFeed[event->TaskIndex] != 0 || // Receive data from remote node + !Device[DeviceIndex].TaskLogsOwnPeaks; + + taskData->pushPluginStatsValues( + event, + trackpeaks, + onlyUpdateTimestampWhenSame); + } + #endif // if FEATURE_PLUGIN_STATS + saveUserVarToRTC(); + } + } + + if (Function == PLUGIN_INIT) { + if (!retval && (Settings.TaskDeviceDataFeed[event->TaskIndex] == 0)) { + // Disable temporarily as PLUGIN_INIT failed + // FIXME TD-er: Should reschedule call to PLUGIN_INIT???? + Settings.TaskDeviceEnabled[event->TaskIndex] = false; + } else { + #if FEATURE_PLUGIN_STATS + + if (Device[DeviceIndex].PluginStats) { + PluginTaskData_base *taskData = getPluginTaskData(event->TaskIndex); + + if (taskData == nullptr) { + // Plugin apparently does not have PluginTaskData. + // Create Plugin Task data if it has "Stats" checked. + LoadTaskSettings(event->TaskIndex); + + if (ExtraTaskSettings.anyEnabledPluginStats()) { + initPluginTaskData(event->TaskIndex, new (std::nothrow) _StatsOnly_data_struct()); + } + } + } + #endif // if FEATURE_PLUGIN_STATS + // Schedule the plugin to be read. + Scheduler.schedule_task_device_timer_at_init(TempEvent.TaskIndex); + queueTaskEvent(F("TaskInit"), event->TaskIndex, retval); + } + } + + if (Function == PLUGIN_EXIT) { + UserVar.clear_computed(event->TaskIndex); + clearPluginTaskData(event->TaskIndex); + + // initSerial(); + queueTaskEvent(F("TaskExit"), event->TaskIndex, retval); + updateActiveTaskUseSerial0(); + } + STOP_TIMER_TASK(DeviceIndex, Function); + #if FEATURE_I2C_DEVICE_CHECK + } + #endif // if FEATURE_I2C_DEVICE_CHECK + post_I2C_by_taskIndex(event->TaskIndex, DeviceIndex); + delay(0); // SMY: call delay(0) unconditionally + + return retval; + } + return false; + } + + // Call to specific task not interacting with hardware + case PLUGIN_GET_CONFIG_VALUE: + case PLUGIN_GET_DEVICEVALUENAMES: + case PLUGIN_GET_DEVICEGPIONAMES: + case PLUGIN_WEBFORM_SAVE: + case PLUGIN_WEBFORM_SHOW_VALUES: + case PLUGIN_WEBFORM_SHOW_CONFIG: + case PLUGIN_WEBFORM_SHOW_I2C_PARAMS: + case PLUGIN_WEBFORM_PRE_SERIAL_PARAMS: + case PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS: + case PLUGIN_WEBFORM_SHOW_GPIO_DESCR: + #if FEATURE_PLUGIN_STATS + case PLUGIN_WEBFORM_LOAD_SHOW_STATS: + #endif // if FEATURE_PLUGIN_STATS + case PLUGIN_SET_CONFIG: + case PLUGIN_SET_DEFAULTS: + case PLUGIN_I2C_HAS_ADDRESS: + case PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT: + case PLUGIN_INIT_VALUE_RANGES: + #ifdef USES_ESPEASY_NOW + case PLUGIN_FILTEROUT_CONTROLLER_DATA: + #endif // ifdef USES_ESPEASY_NOW + + // PLUGIN_MQTT_xxx functions are directly called from the scheduler. + // case PLUGIN_MQTT_CONNECTION_STATE: + // case PLUGIN_MQTT_IMPORT: + { + START_TIMER; + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); + + if (validDeviceIndex(DeviceIndex)) { + if ((Function == PLUGIN_GET_DEVICEVALUENAMES) || + (Function == PLUGIN_WEBFORM_SAVE) || + (Function == PLUGIN_WEBFORM_LOAD) || + (Function == PLUGIN_WEBFORM_LOAD_ALWAYS) || + (Function == PLUGIN_SET_DEFAULTS) || + (Function == PLUGIN_INIT_VALUE_RANGES) || + (Function == PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS) || + (Function == PLUGIN_WEBFORM_PRE_SERIAL_PARAMS) + ) { + LoadTaskSettings(event->TaskIndex); + } + event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; + + #ifndef BUILD_NO_RAM_TRACKER + checkRAM_PluginCall_task(event->TaskIndex, Function); + #endif // ifndef BUILD_NO_RAM_TRACKER + + if (Function == PLUGIN_SET_DEFAULTS) { + for (int i = 0; i < VARS_PER_TASK; ++i) { + UserVar.setFloat(event->TaskIndex, i, 0.0f); + } + } + + bool retval = PluginCall(DeviceIndex, Function, event, str); + + // Calls may have updated ExtraTaskSettings, so validate them. + ExtraTaskSettings.validate(); + + if ((Function == PLUGIN_GET_DEVICEVALUENAMES) || + (Function == PLUGIN_WEBFORM_SAVE) || + (Function == PLUGIN_SET_DEFAULTS) || + (Function == PLUGIN_INIT_VALUE_RANGES) || + ((Function == PLUGIN_SET_CONFIG) && retval)) { + // Each of these may update ExtraTaskSettings, but it may not have been saved yet. + // Thus update the cache just in case something from it is requested from the cache. + Cache.updateExtraTaskSettingsCache(); + } + + if (Function == PLUGIN_SET_DEFAULTS) { + saveUserVarToRTC(); + } + + if ((Function == PLUGIN_GET_CONFIG_VALUE) && !retval) { + // Try to match a statistical property of a task value. + // e.g.: [taskname#valuename.avg] + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(event->TaskIndex); + + if (nullptr != taskData) { + if (taskData->plugin_get_config_value_base(event, str)) { + retval = true; + } + } + } + + + STOP_TIMER_TASK(DeviceIndex, Function); + delay(0); // SMY: call delay(0) unconditionally + return retval; + } + return false; + } + + // Frequently made call to specific task not interacting with hardware + case PLUGIN_GET_DEVICEVALUECOUNT: + case PLUGIN_GET_DEVICEVTYPE: + case PLUGIN_FORMAT_USERVAR: + { + START_TIMER; + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); + + if (validDeviceIndex(DeviceIndex)) { + event->BaseVarIndex = event->TaskIndex * VARS_PER_TASK; + + #ifndef BUILD_NO_RAM_TRACKER + + // checkRAM_PluginCall_task(event->TaskIndex, Function); + #endif // ifndef BUILD_NO_RAM_TRACKER + + if (Function == PLUGIN_GET_DEVICEVALUECOUNT) { + event->Par1 = Device[DeviceIndex].ValueCount; + } + + if (Function == PLUGIN_GET_DEVICEVTYPE) { + event->sensorType = Device[DeviceIndex].VType; + } + bool retval = PluginCall(DeviceIndex, Function, event, str); + + if (Function == PLUGIN_GET_DEVICEVALUECOUNT) { + // Check if we have a valid value count. + if (Output_Data_type_t::Simple == Device[DeviceIndex].OutputDataType) { + if ((event->Par1 < 1) || (event->Par1 > VARS_PER_TASK)) { + // Output_Data_type_t::Simple only allows for 1 .. 4 output types. + // Apparently the value is not correct, so use the default. + event->Par1 = Device[DeviceIndex].ValueCount; + } + } + } + STOP_TIMER_TASK(DeviceIndex, Function); + delay(0); // SMY: call delay(0) unconditionally + return retval; + } + return false; + } + } // case + return false; +} diff --git a/src/src/Globals/Plugins.h b/src/src/Globals/Plugins.h index f6fed0f6c..67c775d24 100644 --- a/src/src/Globals/Plugins.h +++ b/src/src/Globals/Plugins.h @@ -1,109 +1,114 @@ -#ifndef GLOBALS_PLUGIN_H -#define GLOBALS_PLUGIN_H - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/ESPEasyLimits.h" - -#include "../DataTypes/PluginID.h" -#include "../DataTypes/DeviceIndex.h" -#include "../DataTypes/TaskIndex.h" - -#include - -/********************************************************************************************\ - Structures to address the plugins and device configurations. - - A build of ESPeasy may not have all plugins included. - So there has to be some administration to keep track of what plugin is present - and how to address this plugin. - The data structures containing the available plugins are addressed via a DeviceIndex. - - We have: - - Plugin, like _P001_Switch.ino. - - Task -> A selected instance of a Plugin (Tasks are shown in the web interface) - - Device -> A Plugin included in the build. - - - We have the following one-to-one relations: - - Plugin_id_to_DeviceIndex - Map from Plugin ID to Device Index. - - DeviceIndex_to_Plugin_id - Vector from DeviceIndex to Plugin ID. - - Plugin_ptr - Array of function pointers to call plugins. - - Device - Vector of DeviceStruct containing plugin specific information. - - - UserVar has the output values for a task. - - BaseVarIndex = taskIndex * VARS_PER_TASK - - taskVarIndex = 0 ... (VARS_PER_TASK - 1) - - userVarIndex = BaseVarIndex + taskVarIndex => 0 ... USERVAR_MAX_INDEX - - USERVAR_MAX_INDEX = (TASKS_MAX * VARS_PER_TASK) - \*********************************************************************************************/ - -struct EventStruct; - - - -bool validDeviceIndex(deviceIndex_t index); - -// TD-er: Converted simple functions to defines to reduce bin size - -// bool validTaskIndex(taskIndex_t index); -#define validTaskIndex(X) ((X) < (TASKS_MAX)) - -// bool validPluginID(pluginID_t pluginID); -#define validPluginID(P_ID) ((P_ID) != (INVALID_PLUGIN_ID)) - -bool validPluginID_fullcheck(pluginID_t pluginID); - -// bool validUserVarIndex(userVarIndex_t index); -#define validUserVarIndex(U_VAR_X) ((U_VAR_X) < (USERVAR_MAX_INDEX)) - -// bool validTaskVarIndex(taskVarIndex_t index); -#define validTaskVarIndex(T_VAR_X) ((T_VAR_X) < (VARS_PER_TASK)) - -// Check if plugin is included in build. -// N.B. Invalid plugin is also not considered supported. -// This is essentially (validPluginID && validDeviceIndex) -bool supportedPluginID(pluginID_t pluginID); - -deviceIndex_t getDeviceIndex_from_TaskIndex(taskIndex_t taskIndex); -/********************************************************************************************* - * get the taskPluginID with required checks, INVALID_PLUGIN_ID when invalid - ********************************************************************************************/ -pluginID_t getPluginID_from_TaskIndex(taskIndex_t taskIndex); - -#if FEATURE_PLUGIN_PRIORITY -bool isPluginI2CPowerManager_from_TaskIndex(taskIndex_t taskIndex); -#endif // if FEATURE_PLUGIN_PRIORITY - -/********************************************************************************************\ - Find Device Index given a plugin ID - \*********************************************************************************************/ -deviceIndex_t getDeviceIndex(pluginID_t Number); - -String getPluginNameFromDeviceIndex(deviceIndex_t deviceIndex); -#if FEATURE_I2C_DEVICE_SCAN -bool checkPluginI2CAddressFromDeviceIndex(deviceIndex_t deviceIndex, uint8_t i2cAddress); -#endif // if FEATURE_I2C_DEVICE_SCAN -#if FEATURE_I2C_GET_ADDRESS -uint8_t getTaskI2CAddress(taskIndex_t taskIndex); -#endif // if FEATURE_I2C_GET_ADDRESS - -String getPluginNameFromPluginID(pluginID_t pluginID); - - -// Prepare I2C bus for next call to task -// Return false if task is I2C, but I2C bus is not ready -bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); -void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); - -void loadDefaultTaskValueNames_ifEmpty(taskIndex_t TaskIndex); - -/*********************************************************************************************\ -* Function call to all or specific plugins -\*********************************************************************************************/ -bool PluginCall(uint8_t Function, struct EventStruct *event, String& str); - - - +#ifndef GLOBALS_PLUGIN_H +#define GLOBALS_PLUGIN_H + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/ESPEasyLimits.h" + +#include "../DataTypes/PluginID.h" +#include "../DataTypes/DeviceIndex.h" +#include "../DataTypes/TaskIndex.h" + +#include + +/********************************************************************************************\ + Structures to address the plugins and device configurations. + + A build of ESPeasy may not have all plugins included. + So there has to be some administration to keep track of what plugin is present + and how to address this plugin. + The data structures containing the available plugins are addressed via a DeviceIndex. + + We have: + - Plugin, like _P001_Switch.ino. + - Task -> A selected instance of a Plugin (Tasks are shown in the web interface) + - Device -> A Plugin included in the build. + + + We have the following one-to-one relations: + - Plugin_id_to_DeviceIndex - Map from Plugin ID to Device Index. + - DeviceIndex_to_Plugin_id - Vector from DeviceIndex to Plugin ID. + - Plugin_ptr - Array of function pointers to call plugins. + - Device - Vector of DeviceStruct containing plugin specific information. + + + UserVar has the output values for a task. + - BaseVarIndex = taskIndex * VARS_PER_TASK + - taskVarIndex = 0 ... (VARS_PER_TASK - 1) + - userVarIndex = BaseVarIndex + taskVarIndex => 0 ... USERVAR_MAX_INDEX + - USERVAR_MAX_INDEX = (TASKS_MAX * VARS_PER_TASK) + \*********************************************************************************************/ + +struct EventStruct; + + + +bool validDeviceIndex(deviceIndex_t index); + +// TD-er: Converted simple functions to defines to reduce bin size + +// bool validTaskIndex(taskIndex_t index); +#define validTaskIndex(X) ((X) < (TASKS_MAX)) + +// bool validPluginID(pluginID_t pluginID); +#define validPluginID(P_ID) ((P_ID) != (INVALID_PLUGIN_ID)) + +bool validPluginID_fullcheck(pluginID_t pluginID); + +// bool validUserVarIndex(userVarIndex_t index); +#define validUserVarIndex(U_VAR_X) ((U_VAR_X) < (USERVAR_MAX_INDEX)) + +// bool validTaskVarIndex(taskVarIndex_t index); +#define validTaskVarIndex(T_VAR_X) ((T_VAR_X) < (VARS_PER_TASK)) + +// Check if plugin is included in build. +// N.B. Invalid plugin is also not considered supported. +// This is essentially (validPluginID && validDeviceIndex) +bool supportedPluginID(pluginID_t pluginID); + +deviceIndex_t getDeviceIndex_from_TaskIndex(taskIndex_t taskIndex); +/********************************************************************************************* + * get the taskPluginID with required checks, INVALID_PLUGIN_ID when invalid + ********************************************************************************************/ +pluginID_t getPluginID_from_TaskIndex(taskIndex_t taskIndex); + +#if FEATURE_PLUGIN_PRIORITY +bool isPluginI2CPowerManager_from_TaskIndex(taskIndex_t taskIndex); +#endif // if FEATURE_PLUGIN_PRIORITY + +/********************************************************************************************\ + Find Device Index given a plugin ID + \*********************************************************************************************/ +deviceIndex_t getDeviceIndex(pluginID_t Number); + +String getPluginNameFromDeviceIndex(deviceIndex_t deviceIndex); +#if FEATURE_I2C_DEVICE_SCAN +bool checkPluginI2CAddressFromDeviceIndex(deviceIndex_t deviceIndex, uint8_t i2cAddress); +#endif // if FEATURE_I2C_DEVICE_SCAN +bool getPluginDisplayParametersFromTaskIndex(taskIndex_t taskIndex, + uint16_t & x, + uint16_t & y, + uint16_t & r, + uint16_t & colorDepth); +#if FEATURE_I2C_GET_ADDRESS +uint8_t getTaskI2CAddress(taskIndex_t taskIndex); +#endif // if FEATURE_I2C_GET_ADDRESS + +String getPluginNameFromPluginID(pluginID_t pluginID); + + +// Prepare I2C bus for next call to task +// Return false if task is I2C, but I2C bus is not ready +bool prepare_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); +void post_I2C_by_taskIndex(taskIndex_t taskIndex, deviceIndex_t DeviceIndex); + +void loadDefaultTaskValueNames_ifEmpty(taskIndex_t TaskIndex); + +/*********************************************************************************************\ +* Function call to all or specific plugins +\*********************************************************************************************/ +bool PluginCall(uint8_t Function, struct EventStruct *event, String& str); + + + #endif // GLOBALS_PLUGIN_H \ No newline at end of file diff --git a/src/src/Globals/RamTracker.cpp b/src/src/Globals/RamTracker.cpp index 2299e2060..2e8ce55af 100644 --- a/src/src/Globals/RamTracker.cpp +++ b/src/src/Globals/RamTracker.cpp @@ -1,220 +1,233 @@ -#include "../Globals/RamTracker.h" - -#ifndef BUILD_NO_RAM_TRACKER - -#if FEATURE_TIMING_STATS -#include "../DataStructs/TimingStats.h" -#endif - -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" - -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/StringConverter.h" - -RamTracker myRamTracker; - -/********************************************************************************************\ - Global convenience functions calling RamTracker - \*********************************************************************************************/ - -void checkRAMtoLog(void){ - myRamTracker.getTraceBuffer(); -} - -void checkRAM(const String &flashString, int a ) { - checkRAM(flashString, String(a)); -} - -void checkRAM(const __FlashStringHelper * flashString, int a) { - checkRAM(String(flashString), String(a)); -} - - -void checkRAM(const __FlashStringHelper * flashString, const String& a) { - checkRAM(String(flashString), a); -} - -void checkRAM(const __FlashStringHelper * flashString, const __FlashStringHelper * a) { - checkRAM_values values; - if (!values.mustContinue()) return; - String s = flashString; - s += F(" ("); - s += a; - s += ')'; - checkRAM(values, s); -} - -void checkRAM(const String& flashString, const String &a ) { - checkRAM_values values; - if (!values.mustContinue()) return; - String s = flashString; - s += F(" ("); - s += a; - s += ')'; - checkRAM(values, s); -} - -void checkRAM(const __FlashStringHelper * descr ) { - checkRAM_values values; - if (values.mustContinue()) - checkRAM(values, String(descr)); -} - -void checkRAM_PluginCall_task(uint8_t taskIndex, uint8_t Function) { - checkRAM_values values; - if (!values.mustContinue()) return; - String s = concat(F("PluginCall_task_"), taskIndex + 1); - - s += F(" ("); - #if FEATURE_TIMING_STATS - s += getPluginFunctionName(Function); - #else // if FEATURE_TIMING_STATS - s += String(Function); - #endif // if FEATURE_TIMING_STATS - s += ')'; - checkRAM(values, s); -} - -void checkRAM(const String& descr ) { - checkRAM_values values; - checkRAM(values, descr); -} - -checkRAM_values::checkRAM_values() { - freeStack = getFreeStackWatermark(); -#ifdef ESP32 - freeRAM = ESP.getMinFreeHeap(); -#else - freeRAM = FreeMem(); -#endif -} - -bool checkRAM_values::mustContinue() const { - return Settings.EnableRAMTracking() || - freeStack <= lowestFreeStack || - freeRAM <= lowestRAM; -} - -void checkRAM(const checkRAM_values & values, const String& descr) -{ - if (Settings.EnableRAMTracking()) - myRamTracker.registerRamState(descr); - - if (values.freeStack <= lowestFreeStack) { - lowestFreeStack = values.freeStack; - lowestFreeStackfunction = descr; - } - - if (values.freeRAM <= lowestRAM) - { - lowestRAM = values.freeRAM; - lowestRAMfunction = std::move(descr); - } -} - -/********************************************************************************************\ - RamTracker class - \*********************************************************************************************/ - - - -// find highest the trace with the largest minimum memory (gets replaced by worse one) -unsigned int RamTracker::bestCaseTrace(void) { - unsigned int lowestMemoryInTrace = 0; - unsigned int lowestMemoryInTraceIndex = 0; - - for (int i = 0; i < TRACES; i++) { - if (tracesMemory[i] > lowestMemoryInTrace) { - lowestMemoryInTrace = tracesMemory[i]; - lowestMemoryInTraceIndex = i; - } - } - - // serialPrintln(lowestMemoryInTraceIndex); - return lowestMemoryInTraceIndex; -} - -RamTracker::RamTracker(void) { - readPtr = 0; - writePtr = 0; - - for (int i = 0; i < TRACES; i++) { - traces[i] = String(); - tracesMemory[i] = 0xffffffff; // init with best case memory values, so they get replaced if memory goes lower - } - - for (int i = 0; i < TRACEENTRIES; i++) { - nextAction[i] = "startup"; - nextActionStartMemory[i] = ESP.getFreeHeap(); // init with best case memory values, so they get replaced if memory goes lower - } -} - -void RamTracker::registerRamState(const String& s) { // store function - nextAction[writePtr] = s; // name and mem - nextActionStartMemory[writePtr] = ESP.getFreeHeap(); // in cyclic buffer. - int bestCase = bestCaseTrace(); // find best case memory trace - - if (ESP.getFreeHeap() < tracesMemory[bestCase]) { // compare to current memory value - traces[bestCase] = String(); - readPtr = writePtr + 1; // read out buffer, oldest value first - - if (readPtr >= TRACEENTRIES) { - readPtr = 0; // read pointer wrap around - } - tracesMemory[bestCase] = ESP.getFreeHeap(); // store new lowest value of that trace - - for (int i = 0; i < TRACEENTRIES; i++) { // tranfer cyclic buffer strings and mem values to this trace - traces[bestCase] += nextAction[readPtr]; - traces[bestCase] += F("-> "); - traces[bestCase] += String(nextActionStartMemory[readPtr]); - traces[bestCase] += ' '; - readPtr++; - - if (readPtr >= TRACEENTRIES) { readPtr = 0; // wrap around read pointer - } - } - } - writePtr++; - - if (writePtr >= TRACEENTRIES) { writePtr = 0; // inc write pointer and wrap around too. - } -} - - // return giant strings, one line per trace. Add stremToWeb method to avoid large strings. -void RamTracker::getTraceBuffer() { -#ifndef BUILD_NO_DEBUG - if (Settings.EnableRAMTracking() && loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - String retval = F("Memtrace\n"); - - for (int i = 0; i < TRACES; i++) { - retval += String(i); - retval += F(": lowest: "); - retval += String(tracesMemory[i]); - retval += ' '; - retval += traces[i]; - addLogMove(LOG_LEVEL_DEBUG_DEV, retval); - retval = String(); - } - } -#endif // ifndef BUILD_NO_DEBUG -} - -#else // BUILD_NO_RAM_TRACKER -/* - -void checkRAMtoLog(void) {} - -void checkRAM(const String& flashString, - int a) {} - -void checkRAM(const String& flashString, - const String& a) {} - -void checkRAM(const String& descr) {} -*/ - +#include "../Globals/RamTracker.h" + +#ifndef BUILD_NO_RAM_TRACKER + +#if FEATURE_TIMING_STATS +#include "../DataStructs/TimingStats.h" +#endif + +#include "../ESPEasyCore/ESPEasy_Log.h" + +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" + +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/StringConverter.h" + +RamTracker myRamTracker; + +/********************************************************************************************\ + Global convenience functions calling RamTracker + \*********************************************************************************************/ + +void checkRAMtoLog(void){ + myRamTracker.getTraceBuffer(); +} + +void checkRAM(const String &flashString, int a ) { + checkRAM(flashString, String(a)); +} + +void checkRAM(const __FlashStringHelper * flashString, int a) { + checkRAM(String(flashString), String(a)); +} + + +void checkRAM(const __FlashStringHelper * flashString, const String& a) { + checkRAM(String(flashString), a); +} + +void checkRAM(const __FlashStringHelper * flashString, const __FlashStringHelper * a) { + checkRAM_values values; + if (!values.mustContinue()) return; + String s = flashString; + s += F(" ("); + s += a; + s += ')'; + checkRAM(values, s); +} + +void checkRAM(const String& flashString, const String &a ) { + checkRAM_values values; + if (!values.mustContinue()) return; + String s = flashString; + s += F(" ("); + s += a; + s += ')'; + checkRAM(values, s); +} + +void checkRAM(const __FlashStringHelper * descr ) { + checkRAM_values values; + if (values.mustContinue()) + checkRAM(values, String(descr)); +} + +void checkRAM_PluginCall_task(uint8_t taskIndex, uint8_t Function) { + checkRAM_values values; + if (!values.mustContinue()) return; + String s = concat(F("PluginCall_task_"), taskIndex + 1); + + s += F(" ("); + #if FEATURE_TIMING_STATS + s += getPluginFunctionName(Function); + #else // if FEATURE_TIMING_STATS + s += String(Function); + #endif // if FEATURE_TIMING_STATS + s += ')'; + checkRAM(values, s); +} + +void checkRAM(const String& descr ) { + checkRAM_values values; + checkRAM(values, descr); +} + +checkRAM_values::checkRAM_values() { + freeStack = getFreeStackWatermark(); +#ifdef ESP32 + freeRAM = ESP.getMinFreeHeap(); +#else + freeRAM = FreeMem(); +#endif +} + +bool checkRAM_values::mustContinue() const { + // Here we simply check to see if it is desired to continue creating a description string. + // When no description string is created, it would still be nice to get some idea of the lowest stack/ram while we're here. + if (Settings.EnableRAMTracking()) { + return freeStack <= lowestFreeStack || + freeRAM <= lowestRAM; + } + + if (freeStack <= lowestFreeStack) { + lowestFreeStack = freeStack; + } + + if (freeRAM <= lowestRAM) + { + lowestRAM = freeRAM; + } + return false; +} + +void checkRAM(const checkRAM_values & values, const String& descr) +{ + if (Settings.EnableRAMTracking()) + myRamTracker.registerRamState(descr); + + if (values.freeStack <= lowestFreeStack) { + lowestFreeStack = values.freeStack; + lowestFreeStackfunction = descr; + } + + if (values.freeRAM <= lowestRAM) + { + lowestRAM = values.freeRAM; + lowestRAMfunction = std::move(descr); + } +} + +/********************************************************************************************\ + RamTracker class + \*********************************************************************************************/ + + + +// find highest the trace with the largest minimum memory (gets replaced by worse one) +unsigned int RamTracker::bestCaseTrace(void) { + unsigned int lowestMemoryInTrace = 0; + unsigned int lowestMemoryInTraceIndex = 0; + + for (int i = 0; i < TRACES; i++) { + if (tracesMemory[i] > lowestMemoryInTrace) { + lowestMemoryInTrace = tracesMemory[i]; + lowestMemoryInTraceIndex = i; + } + } + + // serialPrintln(lowestMemoryInTraceIndex); + return lowestMemoryInTraceIndex; +} + +RamTracker::RamTracker(void) { + readPtr = 0; + writePtr = 0; + + for (int i = 0; i < TRACES; i++) { + traces[i] = String(); + tracesMemory[i] = 0xffffffff; // init with best case memory values, so they get replaced if memory goes lower + } + + for (int i = 0; i < TRACEENTRIES; i++) { + nextAction[i] = "startup"; + nextActionStartMemory[i] = ESP.getFreeHeap(); // init with best case memory values, so they get replaced if memory goes lower + } +} + +void RamTracker::registerRamState(const String& s) { // store function + nextAction[writePtr] = s; // name and mem + nextActionStartMemory[writePtr] = ESP.getFreeHeap(); // in cyclic buffer. + int bestCase = bestCaseTrace(); // find best case memory trace + + if (ESP.getFreeHeap() < tracesMemory[bestCase]) { // compare to current memory value + traces[bestCase] = String(); + readPtr = writePtr + 1; // read out buffer, oldest value first + + if (readPtr >= TRACEENTRIES) { + readPtr = 0; // read pointer wrap around + } + tracesMemory[bestCase] = ESP.getFreeHeap(); // store new lowest value of that trace + + for (int i = 0; i < TRACEENTRIES; i++) { // tranfer cyclic buffer strings and mem values to this trace + traces[bestCase] += nextAction[readPtr]; + traces[bestCase] += F("-> "); + traces[bestCase] += String(nextActionStartMemory[readPtr]); + traces[bestCase] += ' '; + readPtr++; + + if (readPtr >= TRACEENTRIES) { readPtr = 0; // wrap around read pointer + } + } + } + writePtr++; + + if (writePtr >= TRACEENTRIES) { writePtr = 0; // inc write pointer and wrap around too. + } +} + + // return giant strings, one line per trace. Add stremToWeb method to avoid large strings. +void RamTracker::getTraceBuffer() { +#ifndef BUILD_NO_DEBUG + if (Settings.EnableRAMTracking() && loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + String retval = F("Memtrace\n"); + + for (int i = 0; i < TRACES; i++) { + retval += String(i); + retval += F(": lowest: "); + retval += String(tracesMemory[i]); + retval += ' '; + retval += traces[i]; + addLogMove(LOG_LEVEL_DEBUG_DEV, retval); + retval = String(); + } + } +#endif // ifndef BUILD_NO_DEBUG +} + +#else // BUILD_NO_RAM_TRACKER +/* + +void checkRAMtoLog(void) {} + +void checkRAM(const String& flashString, + int a) {} + +void checkRAM(const String& flashString, + const String& a) {} + +void checkRAM(const String& descr) {} +*/ + #endif // BUILD_NO_RAM_TRACKER \ No newline at end of file diff --git a/src/src/Globals/RulesCalculate.cpp b/src/src/Globals/RulesCalculate.cpp index 54f5cf6d6..6a339417a 100644 --- a/src/src/Globals/RulesCalculate.cpp +++ b/src/src/Globals/RulesCalculate.cpp @@ -2,6 +2,7 @@ #include "../DataStructs/TimingStats.h" #include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" #include "../Helpers/StringConverter_Numerical.h" RulesCalculate_t RulesCalculate{}; @@ -32,11 +33,8 @@ int CalculateParam(const String& TmpStr, int errorValue) { #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("CALCULATE PARAM: "); - log += TmpStr; - log += F(" = "); - log += roundf(param); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("CALCULATE PARAM: %s = %.6g"), TmpStr.c_str(), roundf(param))); } #endif // ifndef BUILD_NO_DEBUG } else { @@ -85,7 +83,7 @@ CalculateReturnCode Calculate(const String& input, log += F("Unknown token"); break; case CalculateReturnCode::ERROR_TOKEN_LENGTH_EXCEEDED: - log += String(F("Exceeded token length (")) + TOKEN_LENGTH + ')'; + log += strformat(F("Exceeded token length (%d)"), TOKEN_LENGTH); break; case CalculateReturnCode::OK: // Already handled, but need to have all cases here so the compiler can warn if we're missing one. diff --git a/src/src/Helpers/AdafruitGFX_helper.cpp b/src/src/Helpers/AdafruitGFX_helper.cpp index 6883e9481..4bab47c63 100644 --- a/src/src/Helpers/AdafruitGFX_helper.cpp +++ b/src/src/Helpers/AdafruitGFX_helper.cpp @@ -1,3390 +1,3631 @@ -#include "../Helpers/AdafruitGFX_helper.h" -#include "../../_Plugin_Helper.h" - -#ifdef PLUGIN_USES_ADAFRUITGFX - -# include "../Helpers/StringConverter.h" -# include "../WebServer/Markup_Forms.h" - -# if ADAGFX_FONTS_INCLUDED -# include "../Static/Fonts/Seven_Segment24pt7b.h" -# include "../Static/Fonts/Seven_Segment18pt7b.h" -# include "../Static/Fonts/FreeSans9pt7b.h" -# ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED -# include "../Static/Fonts/angelina8pt7b.h" -# include "../Static/Fonts/NovaMono8pt7b.h" -# include "../Static/Fonts/unispace8pt7b.h" -# include "../Static/Fonts/unispace_italic8pt7b.h" -# include "../Static/Fonts/whitrabt8pt7b.h" -# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO -# include "../Static/Fonts/Roboto_Regular8pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO -# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED -# include "../Static/Fonts/RobotoCondensed_Regular8pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED -# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO -# include "../Static/Fonts/RobotoMono_Regular8pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO -# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED -# ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED -# include "../Static/Fonts/angelina12pt7b.h" -# include "../Static/Fonts/NovaMono12pt7b.h" -# include "../Static/Fonts/RepetitionScrolling12pt7b.h" -# include "../Static/Fonts/unispace12pt7b.h" -# include "../Static/Fonts/unispace_italic12pt7b.h" -# include "../Static/Fonts/whitrabt12pt7b.h" -# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO -# include "../Static/Fonts/Roboto_Regular12pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO -# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED -# include "../Static/Fonts/RobotoCondensed_Regular12pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED -# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO -# include "../Static/Fonts/RobotoMono_Regular12pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO -# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED -# ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED -# include "../Static/Fonts/AmerikaSans16pt7b.h" -# include "../Static/Fonts/whitrabt16pt7b.h" -# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO -# include "../Static/Fonts/Roboto_Regular16pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO -# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED -# include "../Static/Fonts/RobotoCondensed_Regular16pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED -# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO -# include "../Static/Fonts/RobotoMono_Regular16pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO -# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED -# ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED -# include "../Static/Fonts/whitrabt18pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED -# ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED -# include "../Static/Fonts/whitrabt20pt7b.h" -# endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED -# endif // if ADAGFX_FONTS_INCLUDED - -# if FEATURE_SD && defined(ADAGFX_ENABLE_BMP_DISPLAY) -# include -# endif // if FEATURE_SD && defined(ADAGFX_ENABLE_BMP_DISPLAY) - - -/****************************************************************************************** - * get the display text for a 'text print mode' enum value - *****************************************************************************************/ -const __FlashStringHelper* toString(const AdaGFXTextPrintMode& mode) { - switch (mode) { - case AdaGFXTextPrintMode::ContinueToNextLine: return F("Continue to next line"); - case AdaGFXTextPrintMode::TruncateExceedingMessage: return F("Truncate exceeding message"); - case AdaGFXTextPrintMode::ClearThenTruncate: return F("Clear then truncate exceeding message"); - case AdaGFXTextPrintMode::TruncateExceedingCentered: return F("Truncate, centered if maxWidth set"); - case AdaGFXTextPrintMode::MAX: break; - } - return F("None"); -} - -/****************************************************************************************** - * get the display text for a color depth enum value - *****************************************************************************************/ -const __FlashStringHelper* toString(const AdaGFXColorDepth& colorDepth) { - switch (colorDepth) { - case AdaGFXColorDepth::Monochrome: return F("Monochrome"); - case AdaGFXColorDepth::BlackWhiteRed: return F("Monochrome + 1 color"); - case AdaGFXColorDepth::BlackWhite2Greyscales: return F("Monochrome + 2 grey levels"); - # if ADAGFX_SUPPORT_7COLOR - case AdaGFXColorDepth::SevenColor: return F("eInk - 7 colors"); - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::EightColor: return F("TFT - 8 colors"); - case AdaGFXColorDepth::SixteenColor: return F("TFT - 16 colors"); - # endif // if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::FullColor: return F("Full color - 65535 colors"); - } - return F("None"); -} - -# if ADAGFX_ENABLE_BUTTON_DRAW - -/****************************************************************************************** - * get the display text for a button type enum value - *****************************************************************************************/ -const __FlashStringHelper* toString(const Button_type_e button) { - switch (button) { - case Button_type_e::None: return F("None"); - case Button_type_e::Square: return F("Square"); - case Button_type_e::Rounded: return F("Rounded"); - case Button_type_e::Circle: return F("Circle"); - case Button_type_e::ArrowLeft: return F("Arrow, left"); - case Button_type_e::ArrowUp: return F("Arrow, up"); - case Button_type_e::ArrowRight: return F("Arrow, right"); - case Button_type_e::ArrowDown: return F("Arrow, down"); - case Button_type_e::Button_MAX: break; - } - return F("Unsupported!"); -} - -/****************************************************************************************** - * get the display text for a button layout enum value - *****************************************************************************************/ -const __FlashStringHelper* toString(const Button_layout_e layout) { - switch (layout) { - case Button_layout_e::CenterAligned: return F("Centered"); - case Button_layout_e::LeftAligned: return F("Left-aligned"); - case Button_layout_e::TopAligned: return F("Top-aligned"); - case Button_layout_e::RightAligned: return F("Right-aligned"); - case Button_layout_e::BottomAligned: return F("Bottom-aligned"); - case Button_layout_e::LeftTopAligned: return F("Left-Top-aligned"); - case Button_layout_e::RightTopAligned: return F("Right-Top-aligned"); - case Button_layout_e::RightBottomAligned: return F("Right-Bottom-aligned"); - case Button_layout_e::LeftBottomAligned: return F("Left-Bottom-aligned"); - case Button_layout_e::NoCaption: return F("No Caption"); - case Button_layout_e::Bitmap: return F("Bitmap image"); - case Button_layout_e::Alignment_MAX: break; - } - return F("Unsupported!"); -} - -# endif // if ADAGFX_ENABLE_BUTTON_DRAW - -/***************************************************************************************** - * Show a selector for all available 'Text print mode' options, for use in PLUGIN_WEBFORM_LOAD - ****************************************************************************************/ -void AdaGFXFormTextPrintMode(const __FlashStringHelper *id, - uint8_t selectedIndex) { - const int textModeCount = static_cast(AdaGFXTextPrintMode::MAX); - const __FlashStringHelper *textModes[textModeCount] = { // Be sure to use all available modes from enum! - toString(AdaGFXTextPrintMode::ContinueToNextLine), - toString(AdaGFXTextPrintMode::TruncateExceedingMessage), - toString(AdaGFXTextPrintMode::ClearThenTruncate), - toString(AdaGFXTextPrintMode::TruncateExceedingCentered), - }; - const int textModeOptions[textModeCount] = { - static_cast(AdaGFXTextPrintMode::ContinueToNextLine), - static_cast(AdaGFXTextPrintMode::TruncateExceedingMessage), - static_cast(AdaGFXTextPrintMode::ClearThenTruncate), - static_cast(AdaGFXTextPrintMode::TruncateExceedingCentered), - }; - - addFormSelector(F("Text print Mode"), id, textModeCount, textModes, textModeOptions, selectedIndex); -} - -void AdaGFXFormColorDepth(const __FlashStringHelper *id, - uint16_t selectedIndex, - bool enabled) { - # if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - const int colorDepthCount = 7 + 1; - # else // if ADAGFX_SUPPORT_8and16COLOR - const int colorDepthCount = 5 + 1; - # endif // if ADAGFX_SUPPORT_8and16COLOR - # else // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - const int colorDepthCount = 6 + 1; - # else // if ADAGFX_SUPPORT_8and16COLOR - const int colorDepthCount = 4 + 1; - # endif // if ADAGFX_SUPPORT_8and16COLOR - # endif // if ADAGFX_SUPPORT_7COLOR - const __FlashStringHelper *colorDepths[colorDepthCount] = { // Be sure to use all available modes from enum! - toString(static_cast(0)), // include None - toString(AdaGFXColorDepth::Monochrome), - toString(AdaGFXColorDepth::BlackWhiteRed), - toString(AdaGFXColorDepth::BlackWhite2Greyscales), - # if ADAGFX_SUPPORT_7COLOR - toString(AdaGFXColorDepth::SevenColor), - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - toString(AdaGFXColorDepth::EightColor), - toString(AdaGFXColorDepth::SixteenColor), - # endif // if ADAGFX_SUPPORT_8and16COLOR - toString(AdaGFXColorDepth::FullColor) - }; - const int colorDepthOptions[colorDepthCount] = { - 0, - static_cast(AdaGFXColorDepth::Monochrome), - static_cast(AdaGFXColorDepth::BlackWhiteRed), - static_cast(AdaGFXColorDepth::BlackWhite2Greyscales), - # if ADAGFX_SUPPORT_7COLOR - static_cast(AdaGFXColorDepth::SevenColor), - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - static_cast(AdaGFXColorDepth::EightColor), - static_cast(AdaGFXColorDepth::SixteenColor), - # endif // if ADAGFX_SUPPORT_8and16COLOR - static_cast(AdaGFXColorDepth::FullColor) - }; - - addRowLabel_tr_id(F("Display Color-depth"), id); - addSelector(id, colorDepthCount, colorDepths, colorDepthOptions, NULL, selectedIndex, false, enabled); -} - -/***************************************************************************************** - * Show a selector for Rotation options, supported by Adafruit_GFX - ****************************************************************************************/ -void AdaGFXFormRotation(const __FlashStringHelper *id, - uint8_t selectedIndex) { - const __FlashStringHelper *rotationOptions[] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; - const int rotationOptionValues[] = { 0, 1, 2, 3 }; - - addFormSelector(F("Rotation"), id, 4, rotationOptions, rotationOptionValues, selectedIndex); -} - -/***************************************************************************************** - * Show a checkbox & note to disable background-fill for text - ****************************************************************************************/ -void AdaGFXFormTextBackgroundFill(const __FlashStringHelper *id, - uint8_t selectedIndex) { - addFormCheckBox(F("Background-fill for text"), id, selectedIndex); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Fill entire line-height with background color.")); - # endif // ifndef LIMIT_BUILD_SIZE -} - -/***************************************************************************************** - * Show a checkbox & note to enable col/row mode for txp, txz and txtfull subcommands - ****************************************************************************************/ -void AdaGFXFormTextColRowMode(const __FlashStringHelper *id, - bool selectedState) { - addFormCheckBox(F("Text Coordinates in col/row"), id, selectedState); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Unchecked: Coordinates in pixels. Applies only to 'txp', 'txz' and 'txtfull' subcommands.")); - # endif // ifndef LIMIT_BUILD_SIZE -} - -/***************************************************************************************** - * Show a checkbox & note to enable -1 px compatibility mode for txp and txtfull subcommands - ****************************************************************************************/ -void AdaGFXFormOnePixelCompatibilityOption(const __FlashStringHelper *id, - uint8_t selectedIndex) { - addFormCheckBox(F("Use -1px offset for txp & txtfull"), id, selectedIndex); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("This is for compatibility with the original plugin implementation.")); - # endif // ifndef LIMIT_BUILD_SIZE -} - -/***************************************************************************************** - * Show 2 input fields for Foreground and Background color, translated to known color names or hex with # prefix - ****************************************************************************************/ -void AdaGFXFormForeAndBackColors(const __FlashStringHelper *foregroundId, - uint16_t foregroundColor, - const __FlashStringHelper *backgroundId, - uint16_t backgroundColor, - AdaGFXColorDepth colorDepth) { - String color = AdaGFXcolorToString(foregroundColor, colorDepth); - - addFormTextBox(F("Foreground color"), foregroundId, color, 11); - color = AdaGFXcolorToString(backgroundColor, colorDepth); - addFormTextBox(F("Background color"), backgroundId, color, 11); - # ifndef LIMIT_BUILD_SIZE - addFormNote(F("Use Color name, '#RGB565' (# + 1..4 hex nibbles) or '#RRGGBB' (# + 6 hex nibbles RGB color).")); - addFormNote(F("NB: Colors stored as RGB565 value!")); - # else // ifndef LIMIT_BUILD_SIZE - addFormNote(F("Use Color name, # + 1..4 hex RGB565 or # + 6 hex nibbles RGB color.")); - # endif // ifndef LIMIT_BUILD_SIZE -} - -/***************************************************************************************** - * Show a pin selector and percentage 1..100 for Backlight settings - ****************************************************************************************/ -void AdaGFXFormBacklight(const __FlashStringHelper *backlightPinId, - int8_t backlightPin, - const __FlashStringHelper *backlightPercentageId, - uint16_t backlightPercentage) { - addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Backlight ")), backlightPinId, backlightPin); - - addFormNumericBox(F("Backlight percentage"), backlightPercentageId, backlightPercentage, 0, 100); - addUnit(F("0-100%")); -} - -/***************************************************************************************** - * Show pin selector, inverse option and timeout inputs for Displaybutton settings - ****************************************************************************************/ -void AdaGFXFormDisplayButton(const __FlashStringHelper *buttonPinId, - int8_t buttonPin, - const __FlashStringHelper *buttonInverseId, - bool buttonInverse, - const __FlashStringHelper *displayTimeoutId, - int displayTimeout) { - addFormPinSelect(PinSelectPurpose::Generic_input, F("Display button"), buttonPinId, buttonPin); - - addFormCheckBox(F("Inversed Logic"), buttonInverseId, buttonInverse); - - addFormNumericBox(F("Display Timeout"), displayTimeoutId, displayTimeout, 0); - addUnit(F("0 = off")); -} - -/***************************************************************************************** - * Show a numeric input 1..10 for Font scaling setting - ****************************************************************************************/ -void AdaGFXFormFontScaling(const __FlashStringHelper *fontScalingId, - uint8_t fontScaling, - uint8_t maxScale) { - addFormNumericBox(F("Font scaling"), fontScalingId, fontScaling, 1, maxScale); - String unit = F("1x.."); - - unit += maxScale; - unit += 'x'; - addUnit(unit); -} - -/***************************************************************************************** - * Show a selector for line-spacing setting, supported by Adafruit_GFX - ****************************************************************************************/ -void AdaGFXFormLineSpacing(const __FlashStringHelper *id, - uint8_t selectedIndex) { - String lineSpacings[16]; - int lineSpacingOptions[16]; - - for (uint8_t i = 0; i < 16; i++) { - if (15 == i) { - # ifndef LIMIT_BUILD_SIZE - lineSpacings[i] = F("Auto, using font height * scaling"); - # else // ifndef LIMIT_BUILD_SIZE - lineSpacings[i] = F("Auto"); - # endif // ifndef LIMIT_BUILD_SIZE - } else { - lineSpacings[i] = i; - } - lineSpacingOptions[i] = i; - } - addFormSelector(F("Linespacing"), id, 16, lineSpacings, lineSpacingOptions, selectedIndex); - addUnit(F("px")); -} - -/**************************************************************************** - * AdaGFXparseTemplate: Replace variables and adjust unicode special characters to Adafruit font - ***************************************************************************/ -String AdaGFXparseTemplate(const String & tmpString, - const uint8_t lineSize, - AdafruitGFX_helper *gfxHelper) { - # if ADAGFX_PARSE_SUBCOMMAND - - String result = tmpString; - - if (nullptr != gfxHelper) { - String trigger = gfxHelper->getTrigger(); - - if (!trigger.isEmpty()) { - int16_t prefixTrigger = result.indexOf(ADAGFX_PARSE_PREFIX); - int16_t postfixTrigger = result.indexOf(ADAGFX_PARSE_POSTFIX, prefixTrigger + 1); - - while ((prefixTrigger > -1) && (postfixTrigger > -1) && (postfixTrigger > prefixTrigger)) { // Might be valid - String subcommand = result.substring(prefixTrigger + ADAGFX_PARSE_POSTFIX_LEN, postfixTrigger); - - if (!subcommand.isEmpty()) { - String command; - command.reserve(trigger.length() + 1 + subcommand.length()); - command += trigger; - command += ','; - command += subcommand; - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - String log; - log.reserve(command.length() + 20); - log += F("AdaGFX: inline cmd: "); - log += command; - addLogMove(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG - - if (gfxHelper->processCommand(command)) { // Execute command and remove from result incl. pre/postfix - result.remove(prefixTrigger, (postfixTrigger - prefixTrigger) + ADAGFX_PARSE_POSTFIX_LEN); - prefixTrigger = result.indexOf(ADAGFX_PARSE_PREFIX); - postfixTrigger = result.indexOf(ADAGFX_PARSE_POSTFIX, prefixTrigger + 1); - } else { // If the command fails, exit further processing - prefixTrigger = -1; - postfixTrigger = -1; - # ifndef BUILD_NO_DEBUG - addLog(ADAGFX_LOG_LEVEL, F("AdaGFX: inline cmd: unknown")); - # endif // ifndef BUILD_NO_DEBUG - } - } else { - prefixTrigger = -1; - postfixTrigger = -1; - } - } - } - } - result = parseTemplate_padded(result, lineSize); - # else // if ADAGFX_PARSE_SUBCOMMAND - String result = parseTemplate_padded(tmpString, lineSize); - # endif // if ADAGFX_PARSE_SUBCOMMAND - - const char euro[4] = { 0xe2, 0x82, 0xac, 0 }; // Unicode euro symbol - const char euro_ascii[2] = { 0xED, 0 }; // Euro symbol - result.replace(euro, euro_ascii); - - char unicodePrefix = 0xc2; - - if (result.indexOf(unicodePrefix) != -1) { - const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol - const char degree_ascii[2] = { 0xf7, 0 }; // degree symbol - result.replace(degree, degree_ascii); - - const char pound[3] = { 0xc2, 0xa3, 0 }; // Unicode pound symbol - const char pound_ascii[2] = { 0x9C, 0 }; // pound symbol - result.replace(pound, pound_ascii); - - const char yen[3] = { 0xc2, 0xa5, 0 }; // Unicode yen symbol - const char yen_ascii[2] = { 0x9D, 0 }; // yen symbol - result.replace(yen, yen_ascii); - - const char cent[3] = { 0xc2, 0xa2, 0 }; // Unicode cent symbol - const char cent_ascii[2] = { 0x9B, 0 }; // cent symbol - result.replace(cent, cent_ascii); - - const char mu[3] = { 0xc2, 0xb5, 0 }; // Unicode mu/micro (µ) symbol - const char mu_ascii[2] = { 0xe5, 0 }; // mu/micro symbol - result.replace(mu, mu_ascii); - - const char plusmin[3] = { 0xc2, 0xb1, 0 }; // Unicode plusminus symbol - const char plusmin_ascii[2] = { 0xf0, 0 }; // plusminus symbol - result.replace(plusmin, plusmin_ascii); - - const char laquo[3] = { 0xc2, 0xab, 0 }; // Unicode left aquo symbol - const char laquo_ascii[2] = { 0xae, 0 }; // left aquo symbol - result.replace(laquo, laquo_ascii); - - const char raquo[3] = { 0xc2, 0xbb, 0 }; // Unicode right aquote symbol - const char raquo_ascii[2] = { 0xaf, 0 }; // right aquote symbol - result.replace(raquo, raquo_ascii); - - const char half[3] = { 0xc2, 0xbd, 0 }; // Unicode half 1/2 symbol - const char half_ascii[2] = { 0xab, 0 }; // half 1/2 symbol - result.replace(half, half_ascii); - - const char quart[3] = { 0xc2, 0xbc, 0 }; // Unicode quart 1/4 symbol - const char quart_ascii[2] = { 0xac, 0 }; // quart 1/4 symbol - result.replace(quart, quart_ascii); - - const char sup2[3] = { 0xc2, 0xb2, 0 }; // Unicode superscript 2 symbol - const char sup2_ascii[2] = { 0xfc, 0 }; // superscript 2 symbol - result.replace(sup2, sup2_ascii); - - // Unsupported characters, replace by something useful - const char sup1[3] = { 0xc2, 0xb9, 0 }; // Unicode superscript 1 symbol - const char sup1_ascii[2] = { 0x31, 0 }; // regular 1 (missing from font) - result.replace(sup1, sup1_ascii); - - const char sup3[3] = { 0xc2, 0xb3, 0 }; // Unicode superscript 3 symbol - const char sup3_ascii[2] = { 0x33, 0 }; // regular 3 (missing from font) - result.replace(sup3, sup3_ascii); - - const char frac34[3] = { 0xc2, 0xbe, 0 }; // Unicode fraction 3/4 symbol - const char frac34_ascii[2] = { 0x5c, 0 }; // regular \ (missing from font) - result.replace(frac34, frac34_ascii); - delay(0); - } - - unicodePrefix = 0xc3; - - if (result.indexOf(unicodePrefix) != -1) { - // See: https://github.com/letscontrolit/ESPEasy/issues/2081 - - const char umlautAE_uni[3] = { 0xc3, 0x84, 0 }; // Unicode Umlaute AE - const char umlautAE_ascii[2] = { 0x8e, 0 }; // Umlaute A - result.replace(umlautAE_uni, umlautAE_ascii); - - const char umlaut_ae_uni[3] = { 0xc3, 0xa4, 0 }; // Unicode Umlaute ae - const char umlautae_ascii[2] = { 0x84, 0 }; // Umlaute a - result.replace(umlaut_ae_uni, umlautae_ascii); - - const char umlautOE_uni[3] = { 0xc3, 0x96, 0 }; // Unicode Umlaute OE - const char umlautOE_ascii[2] = { 0x99, 0 }; // Umlaute O - result.replace(umlautOE_uni, umlautOE_ascii); - - const char umlaut_oe_uni[3] = { 0xc3, 0xb6, 0 }; // Unicode Umlaute oe - const char umlautoe_ascii[2] = { 0x94, 0 }; // Umlaute o - result.replace(umlaut_oe_uni, umlautoe_ascii); - - const char umlautUE_uni[3] = { 0xc3, 0x9c, 0 }; // Unicode Umlaute UE - const char umlautUE_ascii[2] = { 0x9a, 0 }; // Umlaute U - result.replace(umlautUE_uni, umlautUE_ascii); - - const char umlaut_ue_uni[3] = { 0xc3, 0xbc, 0 }; // Unicode Umlaute ue - const char umlautue_ascii[2] = { 0x81, 0 }; // Umlaute u - result.replace(umlaut_ue_uni, umlautue_ascii); - - const char divide_uni[3] = { 0xc3, 0xb7, 0 }; // Unicode divide symbol - const char divide_ascii[2] = { 0xf5, 0 }; // Divide symbol - result.replace(divide_uni, divide_ascii); - - const char umlaut_sz_uni[3] = { 0xc3, 0x9f, 0 }; // Unicode Umlaute sz - const char umlaut_sz_ascii[2] = { 0xe0, 0 }; // Umlaute B - result.replace(umlaut_sz_uni, umlaut_sz_ascii); - - // Unsupported characters, replace by something useful - const char times[3] = { 0xc3, 0x97, 0 }; // Unicode multiplication symbol - const char times_ascii[2] = { 0x78, 0 }; // regular x (missing from font) - result.replace(times, times_ascii); - delay(0); - } - - // Handle '{0xNN...}' hex values in template, where NN can be any hex value from 01..FF (practically 20..FF). - int16_t hexPrefix = 0; - int16_t hexPostfix; - const String hexSeparators = F(" ,.:;-"); - - while (((hexPrefix = result.indexOf(F("{0x"), hexPrefix)) > -1) && - ((hexPostfix = result.indexOf('}', hexPrefix)) > -1)) { - String replace; - - for (int16_t ci = hexPrefix + 3; ci < hexPostfix - 1; ci += 2) { // Multiple of 2 only - uint32_t hexValue = hexToUL(result.substring(ci, ci + 2)); - - if (hexValue > 0) { - replace += static_cast(hexValue); - } - - while (hexSeparators.indexOf(result.substring(ci + 2, ci + 3)) > -1 && ci < hexPostfix) { - ci++; - } - } - - if (!replace.isEmpty()) { - result.replace(result.substring(hexPrefix, hexPostfix + 1), replace); - } - } - - for (uint16_t l = result.length(); l > 0 && isSpace(result[l - 1]); l--) { // Right-trim - result.remove(l - 1); - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - String log; - log.reserve(result.length() + 24); - log += F("AdaGFX: parse result: '"); - log += result; - log += '\''; - addLogMove(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG - return result; -} - -// AdafruitGFX_helper class methods - -/**************************************************************************** - * parameterized constructors - ***************************************************************************/ -AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_GFX *display, - const String & trigger, - const uint16_t res_x, - const uint16_t res_y, - const AdaGFXColorDepth & colorDepth, - const AdaGFXTextPrintMode& textPrintMode, - const uint8_t fontscaling, - const uint16_t fgcolor, - const uint16_t bgcolor, - const bool useValidation, - const bool textBackFill) - : _display(display), _trigger(trigger), _res_x(res_x), _res_y(res_y), _colorDepth(colorDepth), - _textPrintMode(textPrintMode), _fontscaling(fontscaling), _fgcolor(fgcolor), _bgcolor(bgcolor), - _useValidation(useValidation), _textBackFill(textBackFill) -{ - addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: GFX Init.")); -} - -# if ADAGFX_ENABLE_BMP_DISPLAY -AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_SPITFT *display, - const String & trigger, - const uint16_t res_x, - const uint16_t res_y, - const AdaGFXColorDepth & colorDepth, - const AdaGFXTextPrintMode& textPrintMode, - const uint8_t fontscaling, - const uint16_t fgcolor, - const uint16_t bgcolor, - const bool useValidation, - const bool textBackFill) - : _tft(display), _trigger(trigger), _res_x(res_x), _res_y(res_y), _colorDepth(colorDepth), - _textPrintMode(textPrintMode), _fontscaling(fontscaling), _fgcolor(fgcolor), _bgcolor(bgcolor), - _useValidation(useValidation), _textBackFill(textBackFill) -{ - _display = _tft; - addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: TFT Init.")); -} - -# endif // if ADAGFX_ENABLE_BMP_DISPLAY - -/**************************************************************************** - * common initialization, called from constructors - ***************************************************************************/ -void AdafruitGFX_helper::initialize() { - _trigger.toLowerCase(); // store trigger in lowercase - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - String log; - log.reserve(65); - log += F("AdaGFX: Init, x: "); - log += _res_x; - log += F(", y: "); - log += _res_y; - log += F(", colors: "); - log += static_cast(_colorDepth); - log += F(", trigger: "); - log += _trigger; - log += F(", "); - log += getFeatures(); - addLogMove(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG - - _display_x = _res_x; // Store initial resolution - _display_y = _res_y; - - # if ADAGFX_ENABLE_FRAMED_WINDOW - defineWindow(0, 0, _res_x, _res_y, 0, 0); // Add window 0 at rotation 0 - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - if (_fontscaling < 1) { _fontscaling = 1; } - - if (nullptr != _display) { - _display->setTextSize(_fontscaling); - _display->setTextColor(_fgcolor, _bgcolor); // initialize text colors - _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); - } -} - -/**************************************************************************** - * Show enabled features of the helper - ***************************************************************************/ -String AdafruitGFX_helper::getFeatures() { - String log = F("Features:"); - - # if (defined(ADAGFX_USE_ASCIITABLE) && ADAGFX_USE_ASCIITABLE) - log += F(" asciitable,"); - # endif // if (defined(ADAGFX_USE_ASCIITABLE) && ADAGFX_USE_ASCIITABLE) - # if (defined(ADAGFX_ENABLE_EXTRA_CMDS) && ADAGFX_ENABLE_EXTRA_CMDS) - log += F(" lm/lmr,"); - # endif // if (defined(ADAGFX_ENABLE_EXTRA_CMDS) && ADAGFX_ENABLE_EXTRA_CMDS) - # if (defined(ADAGFX_ENABLE_BMP_DISPLAY) && ADAGFX_ENABLE_BMP_DISPLAY) - log += F(" bmp,"); - # endif // if (defined(ADAGFX_ENABLE_BMP_DISPLAY) && ADAGFX_ENABLE_BMP_DISPLAY) - # if (defined(ADAGFX_ENABLE_BUTTON_DRAW) && ADAGFX_ENABLE_BUTTON_DRAW) - log += F(" btn,"); - # endif // if (defined(ADAGFX_ENABLE_BUTTON_DRAW) && ADAGFX_ENABLE_BUTTON_DRAW)` - # if (defined(ADAGFX_ENABLE_FRAMED_WINDOW) && ADAGFX_ENABLE_FRAMED_WINDOW) - log += F(" win,"); - # endif // if (defined(ADAGFX_ENABLE_FRAMED_WINDOW) && ADAGFX_ENABLE_FRAMED_WINDOW) - # if (defined(ADAGFX_ENABLE_GET_CONFIG_VALUE) && ADAGFX_ENABLE_GET_CONFIG_VALUE) - log += F(" getconf,"); - # endif // if (defined(ADAGFX_ENABLE_GET_CONFIG_VALUE) && ADAGFX_ENABLE_GET_CONFIG_VALUE) - - if (log.endsWith(F(","))) { - log.remove(log.length() - 1); - } - return log; -} - -/**************************************************************************** - * getCursorXY: get the current (text) cursor coordinates, either in pixels or cols/rows, depending on related setting - ***************************************************************************/ -void AdafruitGFX_helper::getCursorXY(int16_t& currentX, - int16_t& currentY) { - _lastX = _display->getCursorX(); - _lastY = _display->getCursorY(); - - if (_columnRowMode && (_lastX != 0)) { _lastX /= _fontwidth; } - - if (_columnRowMode && (_lastY != 0)) { _lastY /= _fontheight; } - currentX = _lastX; - currentY = _lastY; -} - -/**************************************************************************** - * setTxtfullCompensation: x and/or y values defined here are subtracted from x and y position - * - set to 1 for x-1/y-1 pixel for P095 and P096 - * - set to 2 for y+1 pixel - * - set to 3 for x+1 pixel - ***************************************************************************/ -void AdafruitGFX_helper::setTxtfullCompensation(uint8_t compensation) { - switch (compensation) { - case 1: // P095 - { - _x_compensation = 1; - _y_compensation = 1; - break; - } - case 2: - { - _x_compensation = 0; - _y_compensation = -1; - break; - } - case 3: - { - _x_compensation = -1; - _y_compensation = 0; - break; - } - default: - { - _x_compensation = 0; - _y_compensation = 0; - break; - } - } -} - -/**************************************************************************** - * invertDisplay(): Store display-inverted state and proxy to _display - ***************************************************************************/ -void AdafruitGFX_helper::invertDisplay(bool i) { - _displayInverted = i; - _display->invertDisplay(_displayInverted); -} - -/**************************************************************************** - * processCommand: Parse string to ,[,...] and execute that command - ***************************************************************************/ -const char adagfx_commands[] PROGMEM = "txt|txp|txz|txl|txc|txs|txtfull|clear|rot|tpm|" // 0..9 - "asciitable|font|l|lh|lv|lm|lmr|r|rf|c|" // 10..19 - "cf|t|tf|rr|rrf|px|pxh|pxv|bmp|btn|" // 20..29 - "win|defwin|delwin"; // 30.. -enum class adagfx_commands_e : int8_t { - invalid = -1, - txt = 0, // 0 - txp, - txz, - txl, - txc, - txs, - txtfull, - clear, - rot, - tpm, // 9 - asciitable, // 10 - font, - l, - lh, - lv, - lm, - lmr, - r, - rf, - c, // 19 - cf, // 20 - t, - tf, - rr, - rrf, - px, - pxh, - pxv, - bmp, - btn, // 29 - win, // 30 - defwin, - delwin, -}; -const char adagfx_fonts[] PROGMEM = "default|sevenseg24|sevenseg18|freesans|" - # ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - "angelina8prop|novamono8pt|unispace8pt|unispaceitalic8pt|whiterabbit8pt|roboto8pt|robotocond8pt|robotomono8pt|" - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - "angelina12prop|novamono12pt|repetitionscrolling12pt|unispace12pt|unispaceitalic12pt|whiterabbit12pt|roboto12pt|robotocond12pt|robotomono12pt|" - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - "amerikasans16pt|whiterabbit16pt|roboto16pt|robotocond16pt|robotomono16pt|" - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED - "whiterabbit18pt|" - # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED - "whiterabbit20pt" - # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED - ""; -enum class adagfx_fonts_e : int8_t { - invalid = -1, - default_font = 0, - sevenseg24, - sevenseg18, - freesans, - # ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - angelina8prop, - novamono8pt, // 8pt - unispace8pt, - unispaceitalic8pt, - whiterabbit8pt, - roboto8pt, - robotocond8pt, - robotomono8pt, // 8pt - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - angelina12prop, // 12pt - novamono12pt, - repetitionscrolling12pt, - unispace12pt, - unispaceitalic12pt, - whiterabbit12pt, - roboto12pt, - robotocond12pt, - robotomono12pt, // 12pt - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - amerikasans16pt, // 16pt - whiterabbit16pt, - roboto16pt, - robotocond16pt, - robotomono16pt, // 16pt - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED - whiterabbit18pt, // 18pt - # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED - whiterabbit20pt, // 20pt - # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED -}; - -bool AdafruitGFX_helper::processCommand(const String& string) { - bool success = false; - - if ((nullptr == _display) || _trigger.isEmpty()) { return success; } - - String cmd = parseString(string, 1); // lower case - String subcommand = parseString(string, 2); - uint16_t res_x = _res_x; - uint16_t res_y = _res_y; - uint16_t _xo = 0; - uint16_t _yo = 0; - - # if ADAGFX_ENABLE_FRAMED_WINDOW - getWindowLimits(res_x, res_y); - getWindowOffsets(_xo, _yo); - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - if (!(cmd.equals(_trigger) || - isAdaGFXTrigger(cmd)) || - subcommand.isEmpty()) { return success; } // Only support own trigger, and at least a non=empty subcommand - - String log; - std::vector sParams; - std::vector nParams; - uint8_t emptyCount = 0; - int argCount = 0; - bool loop = true; - - while (loop) { // Process all provided arguments - // 0-offset + 1st and 2nd argument used by trigger/subcommand, don't trim off spaces - sParams.push_back(parseStringKeepCaseNoTrim(string, argCount + 3)); - nParams.push_back(0); - validIntFromString(sParams[argCount], nParams[argCount]); - - if (sParams[argCount].isEmpty()) { - emptyCount++; - } else { - emptyCount = 0; // Reset empty counter - } - loop = emptyCount < 3 || argCount <= ADAGFX_PARSE_MAX_ARGS; // Keep picking up arguments until we have the last 3 empty - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - log = ':'; - log += argCount; - log += ' '; - log += sParams[argCount]; - addLog(LOG_LEVEL_DEBUG_DEV, log); - } - # endif // ifndef BUILD_NO_DEBUG - - argCount++; - } - argCount -= emptyCount; // Not counting the empty arguments - success = true; // If we get this far, we'll flip the flag if something wrong is found - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - log.reserve(90); - log.clear(); - log += F("AdaGFX: command: "); - log += _trigger; - log += F(" argCount: "); - log += argCount; - log += ':'; - log += string; - addLog(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG - - const int subcommand_i = GetCommandCode(subcommand.c_str(), adagfx_commands); - const adagfx_commands_e subcmd = static_cast(subcommand_i); - - if (adagfx_commands_e::txt == subcmd) // txt: Print text at last cursor position, ends at next line! - { - _display->println(parseStringToEndKeepCaseNoTrim(string, 3)); // Print entire rest of provided line - } - else if ((adagfx_commands_e::txp == subcmd) && (argCount == 2)) // txp: Text position - { - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1], _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - if (_columnRowMode) { - _display->setCursor(nParams[0] * _fontwidth + _xo, nParams[1] * _fontheight + _yo); - } else { - _display->setCursor(nParams[0] + _xo - _x_compensation, nParams[1] + _yo - _y_compensation); - } - } - } - else if ((adagfx_commands_e::txz == subcmd) && (argCount >= 3)) // txz: Text at position - { - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1], _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - if (_columnRowMode) { - _display->setCursor(nParams[0] * _fontwidth + _xo, nParams[1] * _fontheight + _yo); - } else { - _display->setCursor(nParams[0] + _xo, nParams[1] + _yo); - } - _display->println(parseStringToEndKeepCaseNoTrim(string, 5)); // Print entire rest of provided line - } - } - else if ((adagfx_commands_e::txl == subcmd) && (argCount >= 2)) // txl: Text at line(s) - { - uint8_t _line = 0; - uint8_t _column = 0; - uint8_t idx = 0; - bool currentColRowState = _columnRowMode; - setColumnRowMode(true); // this command is by default set to Column/Row mode - - while (idx < argCount && !sParams[idx + 1].isEmpty()) { - if (nParams[idx] > 0) { - _line = nParams[idx]; - } else { - _line++; - } - printText(sParams[idx + 1].c_str(), _column, _line - 1, _fontscaling, _fgcolor, _bgcolor); - idx += 2; - } - setColumnRowMode(currentColRowState); - } - else if ((adagfx_commands_e::txc == subcmd) && ((argCount == 1) || (argCount == 2))) // txc: Textcolor, fg and opt. bg colors - { - _fgcolor = AdaGFXparseColor(sParams[0], _colorDepth); - - if (argCount == 1) { - _bgcolor = _fgcolor; // Transparent background - _display->setTextColor(_fgcolor); - } else { // argCount=2 - _bgcolor = AdaGFXparseColor(sParams[1], _colorDepth); - _display->setTextColor(_fgcolor, _bgcolor); - } - } - else if ((adagfx_commands_e::txs == subcmd) && (argCount == 1)) // txs: Text size = font scaling, 1..10 - { - if ((nParams[0] >= 0) && (nParams[0] <= 10)) { - _fontscaling = nParams[0]; - _display->setTextSize(_fontscaling); - calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); - } else { - success = false; - } - } - else if ((adagfx_commands_e::txtfull == subcmd) && (argCount >= 3) && (argCount <= 8)) { // txtfull: Text at position, with size and color - switch (argCount) { - case 3: // single text - - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0] - _x_compensation, nParams[1] - _y_compensation, _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - printText(sParams[2].c_str(), - nParams[0] - _x_compensation, - nParams[1] - _y_compensation, - _fontscaling, - _fgcolor, - _fgcolor); // transparent bg - } - break; - case 4: // text + size - - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0] - _x_compensation, nParams[1] - _y_compensation, _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - printText(sParams[3].c_str(), - nParams[0] - _x_compensation, - nParams[1] - _y_compensation, - nParams[2], - _fgcolor, - _fgcolor); // transparent bg - } - break; - case 5: // text + size + color - - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0] - _x_compensation, nParams[1] - _y_compensation, _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - uint16_t color = AdaGFXparseColor(sParams[3], _colorDepth); - printText(sParams[4].c_str(), - nParams[0] - _x_compensation, - nParams[1] - _y_compensation, - nParams[2], - color, - color); // transparent bg - } - break; - case 6: // text + size + color + bkcolor - - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0] - _x_compensation, nParams[1] - _y_compensation, _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - printText(sParams[5].c_str(), - nParams[0] - _x_compensation, - nParams[1] - _y_compensation, - nParams[2], - AdaGFXparseColor(sParams[3], _colorDepth), - AdaGFXparseColor(sParams[4], _colorDepth)); - } - break; - case 7: // 7: text + size + color + bkcolor + printmode - case 8: // as 7 but: + maxwidth - - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0] - _x_compensation, nParams[1] - _y_compensation, _columnRowMode)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - AdaGFXTextPrintMode tmpPrintMode = _textPrintMode; - - if ((nParams[5] >= 0) && (nParams[5] < static_cast(AdaGFXTextPrintMode::MAX))) { - _textPrintMode = static_cast(nParams[5]); - _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); - } - printText(sParams[argCount - 1].c_str(), - nParams[0] - _x_compensation, - nParams[1] - _y_compensation, - nParams[2], - AdaGFXparseColor(sParams[3], _colorDepth), - AdaGFXparseColor(sParams[4], _colorDepth), - argCount == 8 ? nParams[argCount - 2] : 0); - - if (_textPrintMode != tmpPrintMode) { - _textPrintMode = tmpPrintMode; - _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); - } - } - break; - default: - success = false; - break; - } - } - else if (adagfx_commands_e::clear == subcmd) // clear: Clear display - { - # if ADAGFX_ENABLE_FRAMED_WINDOW - - if (_window == 0) - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - { - _display->fillScreen(argCount == 0 ? _bgcolor : AdaGFXparseColor(sParams[0], _colorDepth)); - } - # if ADAGFX_ENABLE_FRAMED_WINDOW - else { - // logWindows(F("clear ")); // Use for debugging only - uint16_t _w = 0, _h = 0; - getWindowLimits(_w, _h); - _display->fillRect(_xo, _yo, _w, _h, - argCount == 0 ? _bgcolor : AdaGFXparseColor(sParams[0], _colorDepth)); - } - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - } - else if ((adagfx_commands_e::rot == subcmd) && (argCount == 1)) // rot: Rotation - { - if ((nParams[0] < 0) || (nParams[0] > 3)) { - success = false; - } else { - setRotation(nParams[0]); - } - } - else if ((adagfx_commands_e::tpm == subcmd) && (argCount == 1)) // tpm: Text Print Mode - { - if ((nParams[0] < 0) || (nParams[0] >= static_cast(AdaGFXTextPrintMode::MAX))) { - success = false; - } else { - _textPrintMode = static_cast(nParams[0]); - _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); - } - } - # if ADAGFX_USE_ASCIITABLE - else if (adagfx_commands_e::asciitable == subcmd) // Show ASCII table - { - String line; - const int16_t start = 0x80 + (argCount >= 1 && nParams[0] >= -4 && nParams[0] < 4 ? nParams[0] * 0x20 : 0); - const uint8_t scale = (argCount == 2 && nParams[1] > 0 && nParams[1] <= 10 ? nParams[1] : 2); - const uint8_t currentScale = _fontscaling; - - if (_fontscaling != scale) { // Set fontscaling - _fontscaling = scale; - _display->setTextSize(_fontscaling); - calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); - } - line.reserve(_textcols); - _display->setCursor(0, 0); - int16_t row = 0; - const bool colMode = _columnRowMode; - _columnRowMode = true; - - for (int16_t i = start; i <= 0xFF && row < _textrows; i++) { - if ((i % 4 == 0) && (line.length() > (_textcols - 8u))) { // 8 = 4x space + char - printText(line.c_str(), 0, row, _fontscaling, _fgcolor, _bgcolor); - line.clear(); - row++; - } - - if (line.isEmpty()) { - line += F("0x"); - - if (i < 0x10) { line += '0'; } - line += String(i, HEX); - } - line += ' '; - line += static_cast(((i == 0x0A) || (i == 0x0D) ? 0x20 : i)); // Show a space instead of CR/LF - } - - if (row < _textrows) { - printText(line.c_str(), 0, row, _fontscaling, _fgcolor, _bgcolor); - } - - _columnRowMode = colMode; // Restore - - if (_fontscaling != currentScale) { // Restore if needed - _fontscaling = currentScale; - _display->setTextSize(_fontscaling); - calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); - } - } - # endif // if ADAGFX_USE_ASCIITABLE - else if ((adagfx_commands_e::font == subcmd) && (argCount == 1)) { // font: Change font - # if ADAGFX_FONTS_INCLUDED - sParams[0].toLowerCase(); - - char ftmp[24]{}; - const int font_i = GetCommandCode(ftmp, sizeof(ftmp), sParams[0].c_str(), adagfx_fonts); - const adagfx_fonts_e font = static_cast(font_i); - - if (adagfx_fonts_e::sevenseg24 == font) { - _display->setFont(&Seven_Segment24pt7b); - calculateTextMetrics(21, 42, 35, true); - } else if (adagfx_fonts_e::sevenseg18 == font) { - _display->setFont(&Seven_Segment18pt7b); - calculateTextMetrics(16, 33, 26, true); - } else if (adagfx_fonts_e::freesans == font) { - _display->setFont(&FreeSans9pt7b); - calculateTextMetrics(10, 16, 12); - - // Extra 8pt fonts: - # ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA - } else if (adagfx_fonts_e::angelina8prop == font) { // Proportional font! - _display->setFont(&angelina8pt7b); - calculateTextMetrics(6, 16, 12, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA - # ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO - } else if (adagfx_fonts_e::novamono8pt == font) { - _display->setFont(&NovaMono8pt7b); - calculateTextMetrics(9, 16, 12); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO - # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE - } else if (adagfx_fonts_e::unispace8pt == font) { - _display->setFont(&unispace8pt7b); - calculateTextMetrics(13, 24, 20); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE - # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC - } else if (adagfx_fonts_e::unispaceitalic8pt == font) { - _display->setFont(&unispace_italic8pt7b); - calculateTextMetrics(13, 24, 20); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC - # ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT - } else if (adagfx_fonts_e::whiterabbit8pt == font) { - _display->setFont(&whitrabt8pt7b); - calculateTextMetrics(10, 16, 12); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT - # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO - } else if (adagfx_fonts_e::roboto8pt == font) { // Proportional font! - _display->setFont(&Roboto_Regular8pt7b); - calculateTextMetrics(10, 16, 12, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO - # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED - } else if (adagfx_fonts_e::robotocond8pt == font) { // Proportional font! - _display->setFont(&RobotoCondensed_Regular8pt7b); - calculateTextMetrics(9, 16, 12, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED - # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO - } else if (adagfx_fonts_e::robotomono8pt == font) { - _display->setFont(&RobotoMono_Regular8pt7b); - calculateTextMetrics(10, 16, 12); - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO - # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED - // Extra 12pt fonts: - # ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA - } else if (adagfx_fonts_e::angelina12prop == font) { // Proportional font! - _display->setFont(&angelina12pt7b); - calculateTextMetrics(8, 22, 18, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA - # ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO - } else if (adagfx_fonts_e::novamono12pt == font) { - _display->setFont(&NovaMono12pt7b); - calculateTextMetrics(13, 26, 22); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO - # ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG - } else if (adagfx_fonts_e::repetitionscrolling12pt == font) { - _display->setFont(&RepetitionScrolling12pt7b); - calculateTextMetrics(13, 22, 18); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG - # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE - } else if (adagfx_fonts_e::unispace12pt == font) { - _display->setFont(&unispace12pt7b); - calculateTextMetrics(18, 30, 26); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE - # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC - } else if (adagfx_fonts_e::unispaceitalic12pt == font) { - _display->setFont(&unispace_italic12pt7b); - calculateTextMetrics(18, 30, 26); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC - # ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT - } else if (adagfx_fonts_e::whiterabbit12pt == font) { - _display->setFont(&whitrabt12pt7b); - calculateTextMetrics(13, 20, 16); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT - # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO - } else if (adagfx_fonts_e::roboto12pt == font) { // Proportional font! - _display->setFont(&Roboto_Regular12pt7b); - calculateTextMetrics(13, 20, 16, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO - # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED - } else if (adagfx_fonts_e::robotocond12pt == font) { // Proportional font! - _display->setFont(&RobotoCondensed_Regular12pt7b); - calculateTextMetrics(13, 20, 16, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED - # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO - } else if (adagfx_fonts_e::robotomono12pt == font) { - _display->setFont(&RobotoMono_Regular12pt7b); - calculateTextMetrics(13, 20, 16); - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO - # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS - } else if (adagfx_fonts_e::amerikasans16pt == font) { // Proportional font! - _display->setFont(&AmerikaSans16pt7b); - calculateTextMetrics(17, 30, 26, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS - # ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT - } else if (adagfx_fonts_e::whiterabbit16pt == font) { - _display->setFont(&whitrabt16pt7b); - calculateTextMetrics(18, 26, 22); - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT - # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO - } else if (adagfx_fonts_e::roboto16pt == font) { // Proportional font! - _display->setFont(&Roboto_Regular16pt7b); - calculateTextMetrics(18, 27, 23, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO - # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED - } else if (adagfx_fonts_e::robotocond16pt == font) { // Proportional font! - _display->setFont(&RobotoCondensed_Regular16pt7b); - calculateTextMetrics(18, 27, 23, true); - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED - # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO - } else if (adagfx_fonts_e::robotomono16pt == font) { - _display->setFont(&RobotoMono_Regular16pt7b); - calculateTextMetrics(18, 27, 23); - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO - # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT - } else if (adagfx_fonts_e::whiterabbit18pt == font) { - _display->setFont(&whitrabt18pt7b); - calculateTextMetrics(21, 30, 26); - # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT - # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT - # ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED - # ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT - } else if (adagfx_fonts_e::whiterabbit20pt == font) { - _display->setFont(&whitrabt20pt7b); - calculateTextMetrics(24, 32, 28); - # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT - # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED - } else if (adagfx_fonts_e::default_font == font) { // font,default is always available! - _display->setFont(); - calculateTextMetrics(6, 9); - } else { - success = false; - } - # else // if ADAGFX_FONTS_INCLUDED - success = false; - # endif // if ADAGFX_FONTS_INCLUDED - } - else if ((adagfx_commands_e::l == subcmd) && (argCount == 5)) { // l: Line - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], nParams[3])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawLine(nParams[0] + _xo, nParams[1] + _yo, nParams[2] + _xo, nParams[3] + _yo, AdaGFXparseColor(sParams[4], _colorDepth)); - } - } - else if ((adagfx_commands_e::lh == subcmd) && (argCount == 3)) { // lh: Horizontal line - # if ADAGFX_ARGUMENT_VALIDATION - - if ((nParams[0] < 0) || (nParams[0] > res_x)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawFastHLine(_xo, nParams[0] + _yo, nParams[1], AdaGFXparseColor(sParams[2], _colorDepth)); - } - } - else if ((adagfx_commands_e::lv == subcmd) && (argCount == 3)) { // lv: Vertical line - # if ADAGFX_ARGUMENT_VALIDATION - - if ((nParams[0] < 0) || (nParams[0] > res_y)) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawFastVLine(nParams[0] + _xo, _yo, nParams[1], AdaGFXparseColor(sParams[2], _colorDepth)); - } - } - # if ADAGFX_ENABLE_EXTRA_CMDS - else if (((adagfx_commands_e::lm == subcmd) || (adagfx_commands_e::lmr == subcmd)) && (argCount >= 5)) { // lm/lmr: Multi-line, multiple - // coordinates - uint16_t mcolor = AdaGFXparseColor(sParams[0], _colorDepth); - bool mloop = true; - uint8_t parCount = 0; - uint8_t optCount = 0; - int cx = -1; - int cy = -1; - bool closeLine = false; - bool relativeMode = (adagfx_commands_e::lmr == subcmd); // Use Relative mode - # ifndef BUILD_NO_DEBUG - String log; - log.reserve(40); - # endif // ifndef BUILD_NO_DEBUG - - while (mloop) { - sParams[optCount] = parseString(string, parCount + 4); // 0-offset + 1st and 2nd cmd-argument and 1 for color argument - - if (!validIntFromString(sParams[optCount], nParams[optCount]) && !sParams[optCount].isEmpty()) { - mcolor = AdaGFXparseColor(sParams[optCount], _colorDepth); // Interpret as a color - - if (optCount > 0) { optCount--; } - } - mloop = !sParams[optCount].isEmpty(); - closeLine = equals(sParams[optCount], 'c'); - - if (mloop) { parCount++; optCount++; } // Next argument - - if ((optCount == 4) || closeLine) { // 0..3 = 4th argument or close the line - if (relativeMode) { - nParams[2] += nParams[0]; - nParams[3] += nParams[1]; - } - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], nParams[3])) { - success = false; - mloop = false; // break out - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - if (closeLine) { - nParams[2] = cx; - nParams[3] = cy; - mloop = false; // Exit after closing the line - } - # ifndef BUILD_NO_DEBUG - log.clear(); - log += F("AdaGFX: cmd: lm x/y/x1/y1:"); - log += nParams[0]; - log += '/'; - log += nParams[1]; - log += '/'; - log += nParams[2]; - log += '/'; - log += nParams[3]; - log += F(" loop:"); - log += mloop ? 'T' : 'f'; - log += F(" color:"); - log += AdaGFXcolorToString(mcolor, _colorDepth); - addLog(LOG_LEVEL_INFO, log); - # endif // ifndef BUILD_NO_DEBUG - _display->drawLine(nParams[0] + _xo, nParams[1] + _yo, nParams[2] + _xo, nParams[3] + _yo, mcolor); - - if ((cx == -1) && (cy == -1)) { - cx = nParams[0]; - cy = nParams[1]; - } - nParams[0] = nParams[2]; // Move second set to first set - nParams[1] = nParams[3]; - optCount = 2; // Get second set of arguments only - } - } - } - } - # endif // if ADAGFX_ENABLE_EXTRA_CMDS - else if ((adagfx_commands_e::r == subcmd) && (argCount == 5)) { // r: Rectangle - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[0] + nParams[2], nParams[1] + nParams[3])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawRect(nParams[0] + _xo, nParams[1] + _yo, nParams[2], nParams[3], AdaGFXparseColor(sParams[4], _colorDepth)); - } - } - else if ((adagfx_commands_e::rf == subcmd) && (argCount == 6)) { // rf: Rectangled, filled - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[0] + nParams[2], nParams[1] + nParams[3])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->fillRect(nParams[0] + _xo, nParams[1] + _yo, nParams[2], nParams[3], AdaGFXparseColor(sParams[5], _colorDepth)); - _display->drawRect(nParams[0] + _xo, nParams[1] + _yo, nParams[2], nParams[3], AdaGFXparseColor(sParams[4], _colorDepth)); - } - } - else if ((adagfx_commands_e::c == subcmd) && (argCount == 4)) { // c: Circle - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], 0)) { // Also check radius - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawCircle(nParams[0] + _xo, nParams[1] + _yo, nParams[2], AdaGFXparseColor(sParams[3], _colorDepth)); - } - } - else if ((adagfx_commands_e::cf == subcmd) && (argCount == 5)) { // cf: Circle, filled - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], 0)) { // Also check radius - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->fillCircle(nParams[0] + _xo, nParams[1] + _yo, nParams[2], AdaGFXparseColor(sParams[4], _colorDepth)); - _display->drawCircle(nParams[0] + _xo, nParams[1] + _yo, nParams[2], AdaGFXparseColor(sParams[3], _colorDepth)); - } - } - else if ((adagfx_commands_e::t == subcmd) && (argCount == 7)) { // t: Triangle - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], nParams[3]) || - invalidCoordinates(nParams[4], nParams[5])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawTriangle(nParams[0] + _xo, nParams[1] + _yo, nParams[2] + _xo, nParams[3] + _yo, nParams[4] + _xo, nParams[5] + _yo, - AdaGFXparseColor(sParams[6], _colorDepth)); - } - } - else if ((adagfx_commands_e::tf == subcmd) && (argCount == 8)) { // tf: Triangle, filled - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[2], nParams[3]) || - invalidCoordinates(nParams[4], nParams[5])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->fillTriangle(nParams[0] + _xo, - nParams[1] + _yo, - nParams[2] + _xo, - nParams[3] + _yo, - nParams[4] + _xo, - nParams[5] + _yo, - AdaGFXparseColor(sParams[7], _colorDepth)); - _display->drawTriangle(nParams[0] + _xo, - nParams[1] + _yo, - nParams[2] + _xo, - nParams[3] + _yo, - nParams[4] + _xo, - nParams[5] + _yo, - AdaGFXparseColor(sParams[6], _colorDepth)); - } - } - else if ((adagfx_commands_e::rr == subcmd) && (argCount == 6)) { // rr: Rounded rectangle - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[0] + nParams[2], nParams[1] + nParams[3]) || - invalidCoordinates(nParams[4], 0)) { // Also check radius - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawRoundRect(nParams[0] + _xo, - nParams[1] + _yo, - nParams[2], - nParams[3], - nParams[4], - AdaGFXparseColor(sParams[5], _colorDepth)); - } - } - else if ((adagfx_commands_e::rrf == subcmd) && (argCount == 7)) { // rrf: Rounded rectangle, filled - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[0] + nParams[2], nParams[1] + nParams[3]) || - invalidCoordinates(nParams[4], 0)) { // Also check radius - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->fillRoundRect(nParams[0] + _xo, - nParams[1] + _yo, - nParams[2], - nParams[3], - nParams[4], - AdaGFXparseColor(sParams[6], _colorDepth)); - _display->drawRoundRect(nParams[0] + _xo, - nParams[1] + _yo, - nParams[2], - nParams[3], - nParams[4], - AdaGFXparseColor(sParams[5], _colorDepth)); - } - } - else if ((adagfx_commands_e::px == subcmd) && (argCount == 3)) { // px: Pixel - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[0], nParams[1])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->drawPixel(nParams[0] + _xo, nParams[1] + _yo, AdaGFXparseColor(sParams[2], _colorDepth)); - } - } - else if (((adagfx_commands_e::pxh == subcmd) || (adagfx_commands_e::pxv == subcmd)) && (argCount > 2)) { // pxh/pxv: Pixels, hor./vert. - // incremented merged loop is - # if ADAGFX_ARGUMENT_VALIDATION // smaller than 2 separate loops - - if (invalidCoordinates(nParams[0], nParams[1])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - _display->startWrite(); - _display->writePixel(nParams[0] + _xo, nParams[1] + _yo, AdaGFXparseColor(sParams[2], _colorDepth)); - loop = true; - uint8_t h = 0; - uint8_t v = 0; - bool isPxh = (adagfx_commands_e::pxh == subcmd); - - if (isPxh) { - h++; - } else { - v++; - } - - while (loop) { - String color = parseString(string, h + v + 5); // 5 = 2 + 3 already parsed merged loop is smaller than 2 separate loops - - if (color.isEmpty() - # if ADAGFX_ARGUMENT_VALIDATION - || invalidCoordinates(nParams[0] + h + _xo, nParams[1] + v + _yo) - # endif // if ADAGFX_ARGUMENT_VALIDATION - ) { - loop = false; - } else { - _display->writePixel(nParams[0] + h + _xo, nParams[1] + v + _yo, AdaGFXparseColor(color, _colorDepth)); - - if (isPxh) { - h++; - } else { - v++; - } - } - delay(0); - } - _display->endWrite(); - } - } - # if ADAGFX_ENABLE_BMP_DISPLAY - else if ((adagfx_commands_e::bmp == subcmd) && (argCount == 3)) { // bmp,x,y,filename.bmp : show bmp from file - if (!sParams[2].isEmpty()) { - success = showBmp(sParams[2], nParams[0] + _xo, nParams[1] + _yo); - } else { - success = false; - } - } - # endif // if ADAGFX_ENABLE_BMP_DISPLAY - # if ADAGFX_ENABLE_BUTTON_DRAW - else if ((adagfx_commands_e::btn == subcmd) && (argCount >= 8) && (nParams[7] != 0)) - { // btn,state,m,x,y,w,h,id,type[,ONclr,OFFclr,Captionclr,fontscale,ONcaption,OFFcapt,Borderclr,DisabClr,DisabCaptclr],TaskIndex,Group,SelGrp,objectname - // ev: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17,18,19,20,21 - // nP: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16,17,18,19,20 - // : Draw a button - // m=mode: -2 = disabled, -1 = initial, 0 = default - // state: 0 = off, 1 = on, -2 = off + disabled, -1 = on + disabled - // id: < 0 = clear area - // type & 0x0F: 0 = none, 1 = rectangle, 2 = rounded rect., 3 = circle, - // type & 0xF0 = CenterAligned, LeftAligned, TopAligned, RightAligned, BottomAligned, LeftTopAligned, RightTopAligned, - // RightBottomAligned, LeftBottomAligned, NoCaption - // (*clr = color, TaskIndex, Group and SelGrp are ignored) - # if ADAGFX_ARGUMENT_VALIDATION - - if (invalidCoordinates(nParams[2], nParams[3]) || - invalidCoordinates(nParams[2] + nParams[4], nParams[3] + nParams[5])) { - success = false; - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - // All checked out OK - // Default values - uint16_t onColor = ADAGFX_BLUE; - uint16_t offColor = ADAGFX_RED; - uint16_t captionColor = ADAGFX_WHITE; - uint8_t fontScale = 0; - uint16_t borderColor = ADAGFX_WHITE; - uint16_t disabledColor = 0x9410; // Medium grey - uint16_t disabledCaptionColor = 0x5A69; // Dark grey - - if (!sParams[8].isEmpty()) { onColor = AdaGFXparseColor(sParams[8], _colorDepth); } - - if (!sParams[9].isEmpty()) { offColor = AdaGFXparseColor(sParams[9], _colorDepth); } - - if (!sParams[10].isEmpty()) { captionColor = AdaGFXparseColor(sParams[10], _colorDepth); } - - if ((nParams[11] > 0) && (nParams[11] <= 10)) { fontScale = nParams[11]; } - - if (!sParams[14].isEmpty()) { borderColor = AdaGFXparseColor(sParams[14], _colorDepth); } - - if (!sParams[15].isEmpty()) { disabledColor = AdaGFXparseColor(sParams[15], _colorDepth); } - - if (!sParams[16].isEmpty()) { disabledCaptionColor = AdaGFXparseColor(sParams[16], _colorDepth); } - - uint16_t fillColor = onColor; - uint16_t textColor = captionColor; - bool clearArea = nParams[7] < 0; - nParams[7] = std::abs(nParams[7]); - - Button_type_e buttonType = static_cast(nParams[7] & 0x0F); - Button_layout_e buttonLayout = static_cast(nParams[7] & 0xF0); - - // Check mode & state: -2, -1, 0, 1 to select used colors - if (nParams[0] == 0) { - fillColor = offColor; - } - - if ((nParams[1] == -2) || (nParams[0] < 0)) { - fillColor = disabledColor; - textColor = disabledCaptionColor; - } else if (clearArea) { - fillColor = _bgcolor; // - borderColor = _bgcolor; - } - - // Clear the area? - if ((buttonType != Button_type_e::None) || - clearArea) { - drawButtonShape(buttonType, - nParams[2] + _xo, nParams[3] + _yo, nParams[4], nParams[5], - _bgcolor, _bgcolor); - } - - // Check button-type bits (mask: 0x0F) to draw correct shape - if (!clearArea) { - drawButtonShape(buttonType, - nParams[2] + _xo, nParams[3] + _yo, nParams[4], nParams[5], - fillColor, borderColor); - } - - // Display caption? (or bitmap) - if (!clearArea && - (buttonLayout != Button_layout_e::NoCaption)) { - int16_t x1, y1; - uint16_t w1, h1, w2, h2; - String newString; - - // Determine alignment parameters - if ((nParams[0] == 1) || (nParams[0] == -1)) { // 1 = on+enabled, -1 = on+disabled - newString = sParams[12].isEmpty() ? sParams[6] : sParams[12]; - } else { - newString = sParams[13].isEmpty() ? sParams[6] : sParams[13]; - } - newString = AdaGFXparseTemplate(newString, 20); - - _display->setTextSize(fontScale); // set scaling - _display->getTextBounds(newString, 0, 0, &x1, &y1, &w1, &h1); // get caption length and height in pixels - _display->getTextBounds(F(" "), 0, 0, &x1, &y1, &w2, &h2); // measure space width for little margins - - // Check button-alignment bits (mask 0xF0) for caption placement, modifies the x/y arguments passed! - // Little margin is: from left/right: half of the width of a space, from top/bottom: half of height of the font used - - switch (buttonLayout) { - case Button_layout_e::CenterAligned: - nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically - nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically - break; - case Button_layout_e::LeftAligned: - nParams[2] += w2 / 2; // A little margin from left - nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically - break; - case Button_layout_e::TopAligned: - nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically - nParams[3] += h1 / 2; // A little margin from top - break; - case Button_layout_e::RightAligned: - nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin - nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically - break; - case Button_layout_e::BottomAligned: - nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically - nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin - break; - case Button_layout_e::LeftTopAligned: - nParams[2] += w2 / 2; // A little margin from left - nParams[3] += h1 / 2; // A little margin from top - break; - case Button_layout_e::RightTopAligned: - nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin - nParams[3] += h1 / 2; // A little margin from top - break; - case Button_layout_e::RightBottomAligned: - nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin - nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin - break; - case Button_layout_e::LeftBottomAligned: - nParams[2] += w2 / 2; // A little margin from left - nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin - break; - case Button_layout_e::Bitmap: - { // Use ON/OFF caption to specify (full) bitmap filename - # if ADAGFX_ENABLE_BMP_DISPLAY - - if (!newString.isEmpty()) { - int32_t offX = 0; // Allow optional arguments for x and y offset values, usage: - int32_t offY = 0; // [x,[y,]]filename.bmp - - if (newString.indexOf(',') > -1) { - String tmp = parseString(newString, 1); - validIntFromString(tmp, offX); - newString = parseStringToEndKeepCase(newString, 2); - - if (newString.indexOf(',') > -1) { - tmp = parseString(newString, 1); - validIntFromString(tmp, offY); - newString = parseStringToEndKeepCase(newString, 2); - } - } - success = showBmp(newString, nParams[2] + _xo + offX, nParams[3] + _yo + offY); - } else - # endif // if ADAGFX_ENABLE_BMP_DISPLAY - { - success = false; - } - break; - } - case Button_layout_e::NoCaption: - case Button_layout_e::Alignment_MAX: - break; - } - - if ((buttonLayout != Button_layout_e::NoCaption) && - (buttonLayout != Button_layout_e::Bitmap)) { - // Set position and colors, then print - _display->setCursor(nParams[2] + _xo, nParams[3] + _yo); - _display->setTextColor(textColor, textColor); // transparent bg results in button color - _display->print(newString); - - // restore colors - _display->setTextColor(_fgcolor, _bgcolor); - } - - // restore font scaling - _display->setTextSize(_fontscaling); - } - } - } - # endif // if ADAGFX_ENABLE_BUTTON_DRAW - # if ADAGFX_ENABLE_FRAMED_WINDOW - else if ((adagfx_commands_e::win == subcmd) && (argCount >= 1) && (argCount <= 2)) { // win: select window by id - success = selectWindow(nParams[0], nParams[1]); - } - else if ((adagfx_commands_e::defwin == subcmd) && (argCount >= 5) && (argCount <= 6)) { // defwin: define window - const int8_t rot = _rotation; - # if ADAGFX_ARGUMENT_VALIDATION - const int16_t curWin = getWindow(); - - if (curWin != 0) { selectWindow(0); } // Validate against raw window coordinates - - if (argCount == 6) { setRotation(nParams[5]); } // Use requested rotation - - if (invalidCoordinates(nParams[0], nParams[1]) || - invalidCoordinates(nParams[0] + nParams[2], nParams[1] + nParams[3])) { - success = false; - - if (curWin != 0) { selectWindow(curWin); } // restore current window - - if (rot != _rotation) { setRotation(rot); } // Restore rotation - } else - # endif // if ADAGFX_ARGUMENT_VALIDATION - { - # if ADAGFX_ARGUMENT_VALIDATION - - if (curWin != 0) { selectWindow(curWin); } // restore current window - # endif // if ADAGFX_ARGUMENT_VALIDATION - - if (nParams[4] > 0) { // Window 0 is the raw window, having the full size, created at initialization of this - // helper instance - # ifndef BUILD_NO_DEBUG - int16_t win = // avoid compiler warning - # endif // ifndef BUILD_NO_DEBUG - defineWindow(nParams[0], - nParams[1], - nParams[2], - nParams[3], - nParams[4], - argCount == 6 ? nParams[5] : _rotation); - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("AdaGFX defined window id: "), static_cast(win))); - } - # endif // ifndef BUILD_NO_DEBUG - - if (rot != _rotation) { setRotation(rot); } // Restore rotation, also update new window - } else { - success = false; - } - - // logWindows(F(" deFwin ")); // Use for debugging only? - } - } - else if ((adagfx_commands_e::delwin == subcmd) && (argCount == 1)) { // delwin: delete window - // logWindows(F(" deLwin ")); // use for debugging only - - if (nParams[0] > 0) { // don't delete window 0 - success = deleteWindow(nParams[0]); - } - } - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - else { - success = false; - } - - return success; -} - -/**************************************************************************** - * Get a config value from the plugin - ***************************************************************************/ -# if ADAGFX_ENABLE_GET_CONFIG_VALUE -const char adagfx_getcommands[] PROGMEM = "win|iswin|width|height|length|textheight|rot|txs|tpm"; -enum class adagfx_getcommands_e : int8_t { - invalid = -1, - win = 0, - iswin, - width, - height, - length, - textheight, - rot, - txs, - tpm, -}; - -bool AdafruitGFX_helper::pluginGetConfigValue(String& string) { - bool success = false; - String command = parseString(string, 1); - - const int command_i = GetCommandCode(command.c_str(), adagfx_getcommands); - const adagfx_getcommands_e cmd = static_cast(command_i); - - switch (cmd) { - case adagfx_getcommands_e::win: - { // win: get current window id - # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled - string = getWindow(); - success = true; - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - break; - } - case adagfx_getcommands_e::iswin: - { // iswin: check if windows exists - # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled - command = parseString(string, 2); - int32_t win = 0; - - if (validIntFromString(command, win)) { - string = validWindow(static_cast(win)); - } else { - string = '0'; - } - success = true; // Always correct, just return 'false' if wrong - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - break; - } - case adagfx_getcommands_e::width: - case adagfx_getcommands_e::height: - // width/height: get window width or height - { - # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled - uint16_t w = 0, h = 0; - getWindowLimits(w, h); - - if (adagfx_getcommands_e::width == cmd) { - string = w; - } else { - string = h; - } - success = true; - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - break; - } - case adagfx_getcommands_e::length: - case adagfx_getcommands_e::textheight: - // length/textheight: get text length or height - { - int16_t x1, y1; - uint16_t w1, h1; - String newString = AdaGFXparseTemplate(parseStringToEndKeepCaseNoTrim(string, 2), 0); - _display->getTextBounds(newString, 0, 0, &x1, &y1, &w1, &h1); // Count length and height - - if (adagfx_getcommands_e::length == cmd) { - string = w1; - } else { - string = h1; - } - success = true; - break; - } - case adagfx_getcommands_e::rot: - { // rot: get current rotation setting - string = _rotation; - success = true; - break; - } - case adagfx_getcommands_e::txs: - { // txs: get current text scaling setting - string = _fontscaling; - success = true; - break; - } - case adagfx_getcommands_e::tpm: - { // tpm: get current text print mode setting - string = static_cast(_textPrintMode); - success = true; - break; - } - case adagfx_getcommands_e::invalid: - break; - } - - return success; -} - -# endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE - -/**************************************************************************** - * draw a button shape with provided color, can also clear a previously drawn button - ***************************************************************************/ -# if ADAGFX_ENABLE_BUTTON_DRAW -void AdafruitGFX_helper::drawButtonShape(const Button_type_e& buttonType, - const int & x, - const int & y, - const int & w, - const int & h, - const uint16_t & fillColor, - const uint16_t & borderColor) { - switch (buttonType) { - case Button_type_e::Square: // Rectangle - { - _display->fillRect(x, y, w, h, fillColor); - _display->drawRect(x, y, w, h, borderColor); - break; - } - case Button_type_e::Rounded: // Rounded Rectangle - { - int16_t radius = (w + h) / 20; // average 10 % corner radius w/h - _display->fillRoundRect(x, y, w, h, radius, fillColor); - _display->drawRoundRect(x, y, w, h, radius, borderColor); - break; - } - case Button_type_e::Circle: // Circle - { - int16_t radius = (w + h) / 4; // average radius - _display->fillCircle(x + (w / 2), y + (h / 2), radius, fillColor); - _display->drawCircle(x + (w / 2), y + (h / 2), radius, borderColor); - break; - } - case Button_type_e::ArrowLeft: - { // draw: left-center, right-top, right-bottom - _display->fillTriangle(x, y + h / 2, x + w, y, - x + w, y + h, fillColor); - _display->drawTriangle(x, y + h / 2, x + w, y, - x + w, y + h, borderColor); - break; - } - case Button_type_e::ArrowUp: - { // draw: top-center, right-bottom, left-bottom - _display->fillTriangle(x + w / 2, y, x + w, y + h, - x, y + h, fillColor); - _display->drawTriangle(x + w / 2, y, x + w, y + h, - x, y + h, borderColor); - break; - } - case Button_type_e::ArrowRight: - { // draw: left-top, right-center, left-bottom - _display->fillTriangle(x, y, x + w, y + h / 2, - x, y + h, fillColor); - _display->drawTriangle(x, y, x + w, y + h / 2, - x, y + h, borderColor); - break; - } - case Button_type_e::ArrowDown: - { // draw: left-top, right-top, bottom-center - _display->fillTriangle(x, y, x + w, y, - x + w / 2, y + h, fillColor); - _display->drawTriangle(x, y, x + w, y, - x + w / 2, y + h, borderColor); - break; - } - case Button_type_e::None: - case Button_type_e::Button_MAX: - break; - } -} - -# endif // if ADAGFX_ENABLE_BUTTON_DRAW - -/**************************************************************************** - * printText: Print text on display at a specific pixel or column/row location - ***************************************************************************/ -void AdafruitGFX_helper::printText(const char *string, - const int16_t & X, - const int16_t & Y, - const uint8_t & textSize, - const uint16_t& color, - uint16_t bkcolor, - const uint16_t& maxWidth) { - int16_t _x = X; - int16_t _y = Y + (_heightOffset * textSize); - uint16_t _w = 0; - int16_t xText = 0; - int16_t yText = 0; - uint16_t wText = 0; - uint16_t wChar = 0; - uint16_t hText = 0; - int16_t oTop = 0; - int16_t oBottom = 0; - int16_t oLeft = 0; - uint16_t res_x = _res_x; - uint16_t res_y = _res_y; - uint16_t xOffset = 0; - uint16_t yOffset = 0; - uint16_t hChar1 = 0; - uint16_t wChar1 = 0; - String newString = string; - - # if ADAGFX_ENABLE_FRAMED_WINDOW - getWindowLimits(res_x, res_y); - getWindowOffsets(xOffset, yOffset); - _x += xOffset; - _y += yOffset; - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - _display->setTextSize(textSize); - _display->getTextBounds(String('A'), 0, 0, &xText, &yText, &wChar1, &hChar1); // Calculate ~1 char height - - if (_columnRowMode) { - _x = X * (_fontwidth * textSize); // We need this multiple times - - if (15 == _lineSpacing) { - _y = (Y * (_fontheight * textSize)) + (_heightOffset * textSize); - } else { - _y = (Y * (hChar1 + _lineSpacing)) + _heightOffset; // Apply explicit line spacing - } - } - - _display->setCursor(_x, _y); - _display->setTextColor(color, bkcolor); - - if (_textPrintMode != AdaGFXTextPrintMode::ContinueToNextLine) { - # if ADAGFX_ENABLE_FRAMED_WINDOW - - if (0 == getWindow()) // Only on Window 0 - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - { - wChar = wChar1; - } - _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Calculate length - - while ((newString.length() > 0) && (((_x - xOffset) + wText) > res_x + wChar)) { - newString.remove(newString.length() - 1); // Cut last character off - _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Re-calculate length - } - } - - _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Calculate length - - if ((maxWidth > 0) && ((_x - xOffset) + maxWidth <= res_x)) { - res_x = (_x - xOffset) + maxWidth; - _w = maxWidth; - - if ((_textPrintMode == AdaGFXTextPrintMode::TruncateExceedingCentered) && - (maxWidth > wText)) { - oLeft = (_w - (wText + 2 * (xText - _x))) / 2; - } - } else { - _w = wText + 2 * (xText - _x); - } - - if (_textBackFill && (color != bkcolor)) { // Fill extra space above and below text - oTop -= textSize; - oBottom += textSize; - _y += textSize; - } - - if ((_textPrintMode == AdaGFXTextPrintMode::ClearThenTruncate) || - (color != bkcolor)) { // Clear before print - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("printText: clear: _x:"); - log += _x; - log += F(", oTop:"); - log += oTop; - log += F(", _y:"); - log += _y; - log += F(", xTx:"); - log += xText; - log += F(", yTx:"); - log += yText; - log += F(", wTx:"); - log += wText; - log += F(", hTx:"); - log += hText; - log += F(", oBot:"); - log += oBottom; - log += F(", _res_x/max:"); - log += _res_x; - log += '/'; - log += res_x; - log += F(", str:"); - log += newString; - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG - - if (bkcolor == color) { bkcolor = _bgcolor; } // To get at least the text readable - - if (_textPrintMode == AdaGFXTextPrintMode::ClearThenTruncate) { // oTop is negative so subtract to add... - _display->fillRect(_x + oTop, yText, res_x - (_x - xOffset), hText + oBottom - oTop, bkcolor); // Clear text area to right edge of - // screen - } else { - _display->fillRect(_x + oTop, yText, _w, hText + oBottom - oTop, bkcolor); // Clear text area - } - - delay(0); - } - - _display->setCursor(_x + oLeft, _y); // add left offset to center, _y may be updated - _display->print(newString); -} - -/**************************************************************************** - * getTextSize length and height in pixels - ***************************************************************************/ -uint16_t AdafruitGFX_helper::getTextSize(const String& text, - uint16_t & h) { - int16_t x; - int16_t y; - uint16_t w; - - _display->getTextBounds(text.c_str(), 0, 0, &x, &y, &w, &h); // Count length and height in pixels - return w; -} - -/**************************************************************************** - * color565: convert r, g, b colors to rgb565 (by bit-shifting) - ***************************************************************************/ -uint16_t color565(const uint8_t& red, - const uint8_t& green, - const uint8_t& blue) { - return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3); -} - -/**************************************************************************** - * AdaGFXparseColor: translate color name, rgb565 hex #rGgb or rgb hex #RRGGBB to an RGB565 value, - * also applies color reduction to mono(2), duo(3), quadro(4), septo(7), octo(8), quinto(16)-chrome colors - ***************************************************************************/ - -// Parse color string to RGB565 color -// param [in] s : The color string (white, red, ...) -// Param [in] colorDepth: The requiresed color depth, default: FullColor -// param [in] defaultWhite: Return White color if empty, default: true -// return : color (default ADAGFX_WHITE) -const char adagfx_colornames[] PROGMEM = "black|white|inverse|red|yellow|dark|light|green|blue|orange|navy|darkcyan|" - "darkgreen|maroon|purple|olive|lightgrey|darkgrey|cyan|magenta|greenyellow|pink"; -enum class adagfx_colornames_e : int8_t { - invalid = -1, - black = 0, - white, - inverse, - red, - yellow, - dark, - light, - green, - blue, - orange, - navy, - darkcyan, - darkgreen, - maroon, - purple, - olive, - lightgrey, - darkgrey, - cyan, - magenta, - greenyellow, - pink, -}; - -uint16_t AdaGFXparseColor(String & s, - const AdaGFXColorDepth& colorDepth, - const bool emptyIsBlack) { - s.toLowerCase(); - int32_t result = -1; // No result yet - const int color_i = GetCommandCode(s.c_str(), adagfx_colornames); - - const adagfx_colornames_e color = static_cast(color_i); - - if ((colorDepth == AdaGFXColorDepth::Monochrome) || - (colorDepth == AdaGFXColorDepth::BlackWhiteRed) || - (colorDepth == AdaGFXColorDepth::BlackWhite2Greyscales)) { // Only a limited set of colors is supported - switch (color) { - case adagfx_colornames_e::black: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK); - case adagfx_colornames_e::inverse: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE); - case adagfx_colornames_e::yellow: // Synonym for red - case adagfx_colornames_e::red: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED); - case adagfx_colornames_e::dark: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK); - case adagfx_colornames_e::light: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT); - - // case adagfx_colornames_e::white: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE); - // If we get this far, return the default - default: - return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE); - } - # if ADAGFX_SUPPORT_7COLOR - } else if (colorDepth == AdaGFXColorDepth::SevenColor) { - switch (color) { - case adagfx_colornames_e::black: result = static_cast(AdaGFX7Colors::ADAGFX7C_BLACK); break; - case adagfx_colornames_e::white: result = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); break; - case adagfx_colornames_e::green: result = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); break; - case adagfx_colornames_e::blue: result = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); break; - case adagfx_colornames_e::red: result = static_cast(AdaGFX7Colors::ADAGFX7C_RED); break; - case adagfx_colornames_e::yellow: result = static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW); break; - case adagfx_colornames_e::orange: result = static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE); break; - default: - break; - } - # endif // if ADAGFX_SUPPORT_7COLOR - } else { // Some predefined colors - switch (color) { - case adagfx_colornames_e::black: result = ADAGFX_BLACK; break; - case adagfx_colornames_e::navy: result = ADAGFX_NAVY; break; - case adagfx_colornames_e::darkgreen: result = ADAGFX_DARKGREEN; break; - case adagfx_colornames_e::darkcyan: result = ADAGFX_DARKCYAN; break; - case adagfx_colornames_e::maroon: result = ADAGFX_MAROON; break; - case adagfx_colornames_e::purple: result = ADAGFX_PURPLE; break; - case adagfx_colornames_e::olive: result = ADAGFX_OLIVE; break; - case adagfx_colornames_e::lightgrey: result = ADAGFX_LIGHTGREY; break; - case adagfx_colornames_e::darkgrey: result = ADAGFX_DARKGREY; break; - case adagfx_colornames_e::blue: result = ADAGFX_BLUE; break; - case adagfx_colornames_e::green: result = ADAGFX_GREEN; break; - case adagfx_colornames_e::cyan: result = ADAGFX_CYAN; break; - case adagfx_colornames_e::red: result = ADAGFX_RED; break; - case adagfx_colornames_e::magenta: result = ADAGFX_MAGENTA; break; - case adagfx_colornames_e::yellow: result = ADAGFX_YELLOW; break; - case adagfx_colornames_e::white: result = ADAGFX_WHITE; break; - case adagfx_colornames_e::orange: result = ADAGFX_ORANGE; break; - case adagfx_colornames_e::greenyellow: result = ADAGFX_GREENYELLOW; break; - case adagfx_colornames_e::pink: result = ADAGFX_PINK; break; - default: - break; - } - } - - // Parse default hex #rgb565 (hex) string (1-4 hex nibbles accepted!) - if ((result == -1) && (s.length() >= 2) && (s.length() <= 5) && (s[0] == '#')) { - result = hexToUL(&s[1]); - } - - // Parse default hex #RRGGBB string (must be 6 hex nibbles!) - if ((result == -1) && (s.length() == 7) && (s[0] == '#')) { - // convrt to long value in base16, then split up into r, g, b values - const uint32_t number = hexToUL(&s[1]); - - // uint32_t r = number >> 16 & 0xFF; - // uint32_t g = number >> 8 & 0xFF; - // uint32_t b = number & 0xFF; - // convert to color565 (as used by adafruit lib) - result = color565(number >> 16 & 0xFF, number >> 8 & 0xFF, number & 0xFF); - } - - if ((result == -1) || (result == ADAGFX_WHITE)) { // Default & don't convert white - # if ADAGFX_SUPPORT_8and16COLOR - - if ( - # if ADAGFX_SUPPORT_7COLOR - (colorDepth >= AdaGFXColorDepth::SevenColor) && - # endif // if ADAGFX_SUPPORT_7COLOR - (colorDepth <= AdaGFXColorDepth::SixteenColor)) { - result = static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK); // Monochrome fallback, compatible 7-color - } else - # endif // if ADAGFX_SUPPORT_8and16COLOR - { - if (emptyIsBlack) { - result = ADAGFX_BLACK; - } else { - result = ADAGFX_WHITE; // Color fallback value - } - } - } else { - // Reduce colors? - switch (colorDepth) { - case AdaGFXColorDepth::Monochrome: - case AdaGFXColorDepth::BlackWhiteRed: - case AdaGFXColorDepth::BlackWhite2Greyscales: - // Unsupported at this point, but compiler needs the cases because of the enum class - break; - # if ADAGFX_SUPPORT_7COLOR - case AdaGFXColorDepth::SevenColor: - result = AdaGFXrgb565ToColor7(result); // Convert - break; - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::EightColor: - result = color565((result >> 11 & 0x1F) / 4, (result >> 5 & 0x3F) / 4, (result & 0x1F) / 4); // reduce colors factor 4 - break; - case AdaGFXColorDepth::SixteenColor: - result = color565((result >> 11 & 0x1F) / 2, (result >> 5 & 0x3F) / 2, (result & 0x1F) / 2); // reduce colors factor 2 - break; - # endif // if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::FullColor: - // No color reduction - break; - } - } - return static_cast(result); -} - -const __FlashStringHelper* AdaGFXcolorToString_internal(const uint16_t & color, - const AdaGFXColorDepth& colorDepth, - bool blackIsEmpty); - -// Add a single optionvalue of a color to a datalist (internal/private) -void AdaGFXaddHtmlDataListColorOptionValue(uint16_t color, - AdaGFXColorDepth colorDepth) { - const __FlashStringHelper *clr = AdaGFXcolorToString_internal(color, colorDepth, false); - - if (!equals(clr, '*')) { - addHtml(F("")); - } -} - -/***************************************************************************************** - * Generate a html 'datalist' of the colors available for selected colorDepth, with id provided - ****************************************************************************************/ -void AdaGFXHtmlColorDepthDataList(const __FlashStringHelper *id, - const AdaGFXColorDepth & colorDepth) { - addHtml(F("")); - - switch (colorDepth) { - case AdaGFXColorDepth::BlackWhiteRed: - case AdaGFXColorDepth::BlackWhite2Greyscales: - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT), colorDepth); - - // Fall through - case AdaGFXColorDepth::Monochrome: - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE), colorDepth); - break; - # if ADAGFX_SUPPORT_7COLOR - case AdaGFXColorDepth::SevenColor: - { - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_BLACK), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_WHITE), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_GREEN), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_BLUE), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_RED), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW), colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE), colorDepth); - break; - } - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::EightColor: // TODO: Sort out the actual 8/16 color options - case AdaGFXColorDepth::SixteenColor: - # endif // if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::FullColor: - { - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_BLACK, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_NAVY, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKGREEN, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKCYAN, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_MAROON, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_PURPLE, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_OLIVE, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_LIGHTGREY, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKGREY, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_BLUE, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_GREEN, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_CYAN, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_RED, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_MAGENTA, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_YELLOW, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_WHITE, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_ORANGE, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_GREENYELLOW, colorDepth); - AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_PINK, colorDepth); - break; - } - } - addHtml(F("")); -} - -/***************************************************************************************** - * Convert an RGB565 color (number) to it's name or the #rgb565 hex string, based on depth - ****************************************************************************************/ -String AdaGFXcolorToString(const uint16_t & color, - const AdaGFXColorDepth& colorDepth, - bool blackIsEmpty) { - String result = AdaGFXcolorToString_internal(color, colorDepth, blackIsEmpty); - - if (equals(result, '*')) { - result = '#'; - result += String(color, HEX); - result.toUpperCase(); - } - return result; -} - -const __FlashStringHelper* AdaGFXcolorToString_internal(const uint16_t & color, - const AdaGFXColorDepth& colorDepth, - bool blackIsEmpty) { - switch (colorDepth) { - case AdaGFXColorDepth::Monochrome: - case AdaGFXColorDepth::BlackWhiteRed: - case AdaGFXColorDepth::BlackWhite2Greyscales: - { - switch (color) { - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK): return blackIsEmpty ? F("") : F("black"); - case ADAGFX_WHITE: // Fall through - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE): return F("white"); - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE): return F("inverse"); - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED): return F("red"); - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK): return F("dark"); - case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT): return F("light"); - default: - break; - } - break; - } - # if ADAGFX_SUPPORT_7COLOR - case AdaGFXColorDepth::SevenColor: - { - switch (color) { - case static_cast(AdaGFX7Colors::ADAGFX7C_BLACK): return blackIsEmpty ? F("") : F("black"); - case ADAGFX_WHITE: // Fall through - case static_cast(AdaGFX7Colors::ADAGFX7C_WHITE): return F("white"); - case static_cast(AdaGFX7Colors::ADAGFX7C_GREEN): return F("green"); - case static_cast(AdaGFX7Colors::ADAGFX7C_BLUE): return F("blue"); - case static_cast(AdaGFX7Colors::ADAGFX7C_RED): return F("red"); - case static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW): return F("yellow"); - case static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE): return F("orange"); - default: - break; - } - break; - } - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::EightColor: - case AdaGFXColorDepth::SixteenColor: - # endif // if ADAGFX_SUPPORT_8and16COLOR - case AdaGFXColorDepth::FullColor: - { - switch (color) { - case ADAGFX_BLACK: return blackIsEmpty ? F("") : F("black"); - case ADAGFX_NAVY: return F("navy"); - case ADAGFX_DARKGREEN: return F("darkgreen"); - case ADAGFX_DARKCYAN: return F("darkcyan"); - case ADAGFX_MAROON: return F("maroon"); - case ADAGFX_PURPLE: return F("purple"); - case ADAGFX_OLIVE: return F("olive"); - case ADAGFX_LIGHTGREY: return F("lightgrey"); - case ADAGFX_DARKGREY: return F("darkgrey"); - case ADAGFX_BLUE: return F("blue"); - case ADAGFX_GREEN: return F("green"); - case ADAGFX_CYAN: return F("cyan"); - case ADAGFX_RED: return F("red"); - case ADAGFX_MAGENTA: return F("magenta"); - case ADAGFX_YELLOW: return F("yellow"); - case ADAGFX_WHITE: return F("white"); - case ADAGFX_ORANGE: return F("orange"); - case ADAGFX_GREENYELLOW: return F("greenyellow"); - case ADAGFX_PINK: return F("pink"); - default: - break; - } - break; - } - } - return F("*"); -} - -# if ADAGFX_SUPPORT_7COLOR - -/**************************************************************************** - * AdaGFXrgb565ToColor7: Convert a rgb565 color to the 7 colors supported by 7-color eInk displays - * Borrowed from https://github.com/ZinggJM/GxEPD2 color7() routine - ***************************************************************************/ -uint16_t AdaGFXrgb565ToColor7(const uint16_t& color) { - const uint16_t red = (color & 0xF800); - const uint16_t green = (color & 0x07E0) << 5; - const uint16_t blue = (color & 0x001F) << 11; - uint16_t cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); // Default = white - - if ((red < 0x8000) && (green < 0x8000) && (blue < 0x8000)) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLACK); // black - } - else if ((red >= 0x8000) && (green >= 0x8000) && (blue >= 0x8000)) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); // white - } - else if ((red >= 0x8000) && (blue >= 0x8000)) { - if (red > blue) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_RED); - } else { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // red, blue - } - } - else if ((green >= 0x8000) && (blue >= 0x8000)) { - if (green > blue) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); - } else { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // green, blue - } - } - else if ((red >= 0x8000) && (green >= 0x8000)) { - static const uint16_t y2o_lim = ((ADAGFX_YELLOW - ADAGFX_ORANGE) / 2 + (ADAGFX_ORANGE & 0x07E0)) << 5; - - if (green > y2o_lim) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW); - } else { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE); // yellow, orange - } - } - else if (red >= 0x8000) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_RED); // red - } - else if (green >= 0x8000) { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); // green - } - else { - cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // blue - } - return cv7; -} - -# endif // if ADAGFX_SUPPORT_7COLOR - -/**************************************************************************** - * getTextMetrics: Returns the metrics related to current font - ***************************************************************************/ -void AdafruitGFX_helper::getTextMetrics(uint16_t& textcols, - uint16_t& textrows, - uint8_t & fontwidth, - uint8_t & fontheight, - uint8_t & fontscaling, - uint8_t & heightOffset, - uint16_t& xpix, - uint16_t& ypix) { - textcols = _textcols; - textrows = _textrows; - fontwidth = _fontwidth; - fontheight = _fontheight; - fontscaling = _fontscaling; - heightOffset = _heightOffset; - # if ADAGFX_ENABLE_FRAMED_WINDOW - getWindowLimits(xpix, ypix); - # else // if ADAGFX_ENABLE_FRAMED_WINDOW - xpix = _res_x; - ypix = _res_y; - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW -} - -/**************************************************************************** - * getColors: Returns the current text colors - ***************************************************************************/ -void AdafruitGFX_helper::getColors(uint16_t& fgcolor, - uint16_t& bgcolor) { - fgcolor = _fgcolor; - bgcolor = _bgcolor; -} - -/**************************************************************************** - * calculateTextMetrics: Recalculate the text mertics based on supplied font parameters - ***************************************************************************/ -void AdafruitGFX_helper::calculateTextMetrics(const uint8_t fontwidth, - const uint8_t fontheight, - const int8_t heightOffset, - const bool isProportional) { - uint16_t res_x = _res_x; - uint16_t res_y = _res_y; - - # if ADAGFX_ENABLE_FRAMED_WINDOW - getWindowLimits(res_x, res_y); - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - _fontwidth = fontwidth; - _fontheight = fontheight; - _heightOffset = heightOffset; - _isProportional = isProportional; - _textcols = res_x / (_fontwidth * _fontscaling); - _textrows = res_y / ((_fontheight + _heightOffset) * _fontscaling); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - String log; - log.reserve(60); - log += F("AdaGFX:"); - - if (!_trigger.isEmpty()) { - log += F(" tr: "); - log += _trigger; - } - log += F(" x: "); - log += res_x; - log += F(", y: "); - log += res_y; - log += F(", text columns: "); - log += _textcols; - log += F(" rows: "); - log += _textrows; - addLogMove(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG -} - -# if ADAGFX_ARGUMENT_VALIDATION - -/**************************************************************************** - * invalidCoordinates: Check if X/Y coordinates stay within the limits of the display, - * default pixel-mode, colRowMode true = character mode. - * If Y == 0 then X is allowed the max. value of the display size. - * *** Returns TRUE when invalid !! *** - ***************************************************************************/ -bool AdafruitGFX_helper::invalidCoordinates(const int X, - const int Y, - const bool colRowMode) { - uint16_t res_x = _res_x; - uint16_t res_y = _res_y; - - # if ADAGFX_ENABLE_FRAMED_WINDOW - getWindowLimits(res_x, res_y); - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { - String log; - - log.reserve(49); - log += F("invalidCoordinates: X:"); - log += X; - log += '/'; - log += (colRowMode ? _textcols : res_x); - log += F(" Y:"); - log += Y; - log += '/'; - log += (colRowMode ? _textrows : res_y); - addLogMove(ADAGFX_LOG_LEVEL, log); - } - # endif // ifndef BUILD_NO_DEBUG - - if (!_useValidation) { return false; } - - if (colRowMode) { - return !((X >= 0) && (X <= _textcols) && - (Y >= 0) && (Y <= _textrows)); - } else { - if (Y == 0) { // Y == 0: Accept largest x/y size value for x - return !((X >= 0) && (X <= std::max(res_x, res_y))); - } else { - return !((X >= 0) && (X <= res_x) && - (Y >= 0) && (Y <= res_y)); - } - } -} - -# endif // if ADAGFX_ARGUMENT_VALIDATION - -void AdafruitGFX_helper::setValidation(const bool& state) { - _useValidation = state; -} - -/**************************************************************************** - * rotate the display (and all windows) - ***************************************************************************/ -void AdafruitGFX_helper::setRotation(uint8_t m) { - const uint8_t rotation = m & 3; - - _display->setRotation(m); // Set rotation 0/1/2/3 - _rotation = rotation; - - switch (rotation) { - case 0: - case 2: - _res_x = _display_x; - _res_y = _display_y; - break; - case 1: - case 3: - _res_x = _display_y; - _res_y = _display_x; - break; - } - # if ADAGFX_ENABLE_FRAMED_WINDOW - - for (uint8_t i = 0; i < _windows.size(); i++) { // Swap x/y for all matching windows - switch (rotation) { - case 0: // 0 degrees - _windows[i].top_left.x = _windows[i].org_top_left.x; // All original - _windows[i].top_left.y = _windows[i].org_top_left.y; - _windows[i].width_height.x = _windows[i].org_width_height.x; - _windows[i].width_height.y = _windows[i].org_width_height.y; - break; - case 1: // +90 degrees - _windows[i].top_left.x = _windows[i].org_top_left.y; - _windows[i].top_left.y = _display_x - (_windows[i].org_top_left.x + _windows[i].org_width_height.x); - _windows[i].width_height.x = _windows[i].org_width_height.y; // swapped width/height - _windows[i].width_height.y = _windows[i].org_width_height.x; - break; - case 2: // +180 degrees - _windows[i].top_left.x = _display_x - (_windows[i].org_top_left.x + _windows[i].org_width_height.x); - _windows[i].top_left.y = _display_y - (_windows[i].org_top_left.y + _windows[i].org_width_height.y); - _windows[i].width_height.x = _windows[i].org_width_height.x; - _windows[i].width_height.y = _windows[i].org_width_height.y; - break; - case 3: // +270 degrees - _windows[i].top_left.x = _display_y - (_windows[i].org_top_left.y + _windows[i].org_width_height.y); - _windows[i].top_left.y = _windows[i].org_top_left.x; - _windows[i].width_height.x = _windows[i].org_width_height.y; // swapped width/height - _windows[i].width_height.y = _windows[i].org_width_height.x; - break; - } - _windows[i].rotation = rotation; - } - - // logWindows(F("rot ")); // For debugging only - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); -} - -# if ADAGFX_ENABLE_BMP_DISPLAY - -/**************************************************************************** - * CPA (Copy/paste/adapt) from Adafruit_ImageReader::coreBMP() - * Changes: - * - No 'load to memory' feature - * - No special handling of SD Filesystem/FAT, but File only - * - Adds support for non-SPI displays (like NeoPixel Matrix, and possibly I2C displays, once supported) - ***************************************************************************/ -bool AdafruitGFX_helper::showBmp(const String& filename, - int16_t x, - int16_t y) { - uint32_t offset; // Start of image data in file - uint32_t headerSize; // Indicates BMP version - uint32_t compression = 0; // BMP compression mode - uint32_t colors = 0; // Number of colors in palette - uint32_t rowSize; // >bmpWidth if scanline padding - uint8_t sdbuf[3 * BUFPIXELS]; // BMP read buf (R+G+B/pixel) - - uint32_t destidx = 0; - uint32_t bmpPos = 0; // Next pixel position in file - int bmpWidth; // BMP width & height in pixels - int bmpHeight; - int loadWidth; - int loadHeight; // Region being loaded (clipped) - int loadX; - int loadY; // " - int row; // Current pixel pos. - int col; - uint16_t *quantized = NULL; // 16-bit 5/6/5 color palette - uint16_t tftbuf[BUFPIXELS]; - uint16_t *dest = tftbuf; // TFT working buffer, or NULL if to canvas - int16_t drow = 0; - int16_t dcol = 0; - uint8_t planes; // BMP planes - uint8_t depth; // BMP bit depth - uint8_t r; // Current pixel colors - uint8_t g; - uint8_t b; - uint8_t bitIn = 0; // Bit number for 1-bit data in - - # if ((3 * BUFPIXELS) <= 255) - uint8_t srcidx = sizeof sdbuf; // Current position in sdbuf - # else // if ((3 * BUFPIXELS) <= 255) - uint16_t srcidx = sizeof sdbuf; - # endif // if ((3 * BUFPIXELS) <= 255) - bool flip = true; // BMP is stored bottom-to-top - bool transact = true; // Enable transaction support to work proper with SD czrd, when enabled - bool status = false; // IMAGE_SUCCESS on valid file - - bool canTransact = (nullptr != _tft); - - // If BMP is being drawn off the right or bottom edge of the screen, - // nothing to do here. NOT an error, just a trivial clip operation. - if (_tft && ((x >= _tft->width()) || (y >= _tft->height()))) { - addLog(LOG_LEVEL_INFO, F("showBmp: coordinates off display")); - return false; - } - - // Open requested file on storage - // Search flash file system first, then SD if present - file = tryOpenFile(filename, "r"); - - if (!file) { - addLog(LOG_LEVEL_ERROR, F("showBmp: file not found")); - return false; - } - - // Parse BMP header. 0x4D42 (ASCII 'BM') is the Windows BMP signature. - // There are other values possible in a .BMP file but these are super - // esoteric (e.g. OS/2 struct bitmap array) and NOT supported here! - if (readLE16() == 0x4D42) { // BMP signature - (void)readLE32(); // Read & ignore file size - (void)readLE32(); // Read & ignore creator bytes - offset = readLE32(); // Start of image data - // Read DIB header - headerSize = readLE32(); - bmpWidth = readLE32(); - bmpHeight = readLE32(); - - // If bmpHeight is negative, image is in top-down order. - // This is not canon but has been observed in the wild. - if (bmpHeight < 0) { - bmpHeight = -bmpHeight; - flip = false; - } - planes = readLE16(); - depth = readLE16(); // Bits per pixel - - // Compression mode is present in later BMP versions (default = none) - if (headerSize > 12) { - compression = readLE32(); - (void)readLE32(); // Raw bitmap data size; ignore - (void)readLE32(); // Horizontal resolution, ignore - (void)readLE32(); // Vertical resolution, ignore - colors = readLE32(); // Number of colors in palette, or 0 for 2^depth - (void)readLE32(); // Number of colors used (ignore) - // File position should now be at start of palette (if present) - } - - if (!colors) { - colors = 1 << depth; - } - # ifndef BUILD_NO_DEBUG - String log; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log.reserve(80); - log += F("showBmp: bitmap w:"); - log += bmpWidth; - log += F(", h:"); - log += bmpHeight; - log += F(", dpt:"); - log += depth; - log += F(", colors:"); - log += colors; - log += F(", cmp:"); - log += compression; - log += F(", pl:"); - log += planes; - log += F(", x:"); - log += x; - log += F(", y:"); - log += y; - addLog(LOG_LEVEL_INFO, log); - } - # endif // ifndef BUILD_NO_DEBUG - - loadWidth = bmpWidth; - loadHeight = bmpHeight; - loadX = 0; - loadY = 0; - - if (_display) { - // Crop area to be loaded (if destination is TFT) - if (x < 0) { - loadX = -x; - loadWidth += x; - x = 0; - } - - if (y < 0) { - loadY = -y; - loadHeight += y; - y = 0; - } - - if ((x + loadWidth) > _display->width()) { - loadWidth = _display->width() - x; - } - - if ((y + loadHeight) > _display->height()) { - loadHeight = _display->height() - y; - } - } - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log.clear(); - log += F("showBmp: x:"); - log += x; - log += F(", y:"); - log += y; - log += F(", dw:"); - log += _display->width(); - log += F(", dh:"); - log += _display->height(); - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // ifndef BUILD_NO_DEBUG - - if ((planes == 1) && (compression == 0)) { // Only uncompressed is handled - // BMP rows are padded (if needed) to 4-byte boundary - rowSize = ((depth * bmpWidth + 31) / 32) * 4; - - if ((depth == 24) || (depth == 1)) { // BGR or 1-bit bitmap format - // if (dest) { // Supported format, alloc OK, etc. - status = true; - - if ((loadWidth > 0) && (loadHeight > 0)) { // Clip top/left - _display->startWrite(); // Start SPI (regardless of transact) - - if (canTransact) { - _tft->setAddrWindow(x, y, loadWidth, loadHeight); - } - - if ((depth >= 16) || - (quantized = (uint16_t *)malloc(colors * sizeof(uint16_t)))) { - if (depth < 16) { - // Load and quantize color table - for (uint16_t c = 0; c < colors; c++) { - b = file.read(); - g = file.read(); - r = file.read(); - (void)file.read(); // Ignore 4th byte - quantized[c] = // -V522 - ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); - } - } - - for (row = 0; row < loadHeight; row++) { // For each scanline... - delay(0); // Keep ESP8266 happy - - // Seek to start of scan line. It might seem labor-intensive - // to be doing this on every line, but this method covers a - // lot of gritty details like cropping, flip and scanline - // padding. Also, the seek only takes place if the file - // position actually needs to change (avoids a lot of cluster - // math in SD library). - if (flip) { // Bitmap is stored bottom-to-top order (normal BMP) - bmpPos = offset + (bmpHeight - 1 - (row + loadY)) * rowSize; - } else { // Bitmap is stored top-to-bottom - bmpPos = offset + (row + loadY) * rowSize; - } - - if (depth == 24) { - bmpPos += loadX * 3; - } else { - bmpPos += loadX / 8; - bitIn = 7 - (loadX & 7); - } - - if (file.position() != bmpPos) { // Need seek? - if (transact && canTransact) { - _tft->dmaWait(); - _tft->endWrite(); // End TFT SPI transaction - } - file.seek(bmpPos); // Seek = SD transaction - srcidx = sizeof sdbuf; // Force buffer reload - } - - for (col = 0; col < loadWidth; col++) { // For each pixel... - if (srcidx >= sizeof sdbuf) { // Time to load more? - if (transact && canTransact) { - _tft->dmaWait(); - _tft->endWrite(); // End TFT SPI transact - } - file.read(sdbuf, sizeof sdbuf); // Load from SD - - if (transact && canTransact) { - _display->startWrite(); // Start TFT SPI transact - } - - if (destidx) { // If buffered TFT data - // Non-blocking writes (DMA) have been temporarily - // disabled until this can be rewritten with two - // alternating 'dest' buffers (else the nonblocking - // data out is overwritten in the dest[] write below). - // tft->writePixels(dest, destidx, false); // Write it - delay(0); - - if (canTransact) { - _tft->writePixels(dest, destidx, true); // Write it - } else { - // loop over buffer - - for (uint16_t p = 0; p < destidx; p++) { - _display->drawPixel(x + p, y + drow, dest[p]); - } - } - - if (col % 33 == 0) { delay(0); } - destidx = 0; // and reset dest index - } - - srcidx = 0; // Reset bmp buf index - } - - if (depth == 24) { - // Convert each pixel from BMP to 565 format, save in dest - b = sdbuf[srcidx++]; - g = sdbuf[srcidx++]; // -V557 - r = sdbuf[srcidx++]; // -V557 - dest[destidx++] = - ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); - } else { - // Extract 1-bit color index - uint8_t n = (sdbuf[srcidx] >> bitIn) & 1; - - if (!bitIn) { - srcidx++; - bitIn = 7; - } else { - bitIn--; - } - - // Look up in palette, store in tft dest buf - dest[destidx++] = quantized[n]; - } - dcol++; - } // end pixel loop - - if (_tft) { // Drawing to TFT? - delay(0); - - if (destidx) { // Any remainders? - // See notes above re: DMA - _tft->writePixels(dest, destidx, true); // Write it - destidx = 0; // and reset dest index - } - _tft->dmaWait(); - _tft->endWrite(); // update display - } else { - // loop over buffer - if (destidx) { - for (uint16_t p = 0; p < destidx; p++) { - _display->drawPixel(x + p, y + drow, dest[p]); - - if (p % 100 == 0) { delay(0); } - } - destidx = 0; // and reset dest index - } - } - - drow++; - dcol = 0; - } // end scanline loop - - if (quantized) { - free(quantized); // Palette no longer needed - } - delay(0); - } // end depth>24 or quantized malloc OK - } // end top/left clip - // } // end malloc check - } // end depth check - } // end planes/compression check - - if (status) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("showBmp: Done.")); - # endif // ifndef BUILD_NO_DEBUG - } else { - addLog(LOG_LEVEL_ERROR, F("showBmp: Only uncompressed and 24 or 1 bit color-depth supported.")); - } - } else { // end signature - addLog(LOG_LEVEL_ERROR, F("showBmp: File signature error.")); - } - - file.close(); - return status; // -V680 - - // } -} - -/*! - @brief Reads a little-endian 16-bit unsigned value from currently- - open File, converting if necessary to the microcontroller's - native endianism. (BMP files use little-endian values.) - @return Unsigned 16-bit value, native endianism. - */ -uint16_t AdafruitGFX_helper::readLE16(void) { - // Big-endian or unknown. Byte-by-byte read will perform reversal if needed. - return file.read() | ((uint16_t)file.read() << 8); -} - -/*! - @brief Reads a little-endian 32-bit unsigned value from currently- - open File, converting if necessary to the microcontroller's - native endianism. (BMP files use little-endian values.) - @return Unsigned 32-bit value, native endianism. - */ -uint32_t AdafruitGFX_helper::readLE32(void) { - // Big-endian or unknown. Byte-by-byte read will perform reversal if needed. - return file.read() | ((uint32_t)file.read() << 8) | - ((uint32_t)file.read() << 16) | ((uint32_t)file.read() << 24); -} - -# endif // if ADAGFX_ENABLE_BMP_DISPLAY - -# if ADAGFX_ENABLE_FRAMED_WINDOW - -/**************************************************************************** - * Check if the requested id is a valid window id - ***************************************************************************/ -bool AdafruitGFX_helper::validWindow(const uint8_t& windowId) { - return getWindowIndex(windowId) != -1; -} - -/**************************************************************************** - * Select this window id as the default - ***************************************************************************/ -bool AdafruitGFX_helper::selectWindow(const uint8_t& windowId, - const int8_t & rotation) { - const int16_t result = getWindowIndex(windowId); - - if (result != -1) { - _windowIndex = result; - _window = windowId; - } - return result != -1; -} - -/**************************************************************************** - * Return the index of the windowId in _windows, -1 if not found - ***************************************************************************/ -int16_t AdafruitGFX_helper::getWindowIndex(const int16_t& windowId) { - size_t result = 0; - - for (auto win = _windows.begin(); win != _windows.end(); win++, result++) { - if ((*win).id == windowId) { - break; - } - } - return result == _windows.size() ? -1 : result; -} - -/**************************************************************************** - * Get the offset for the currently active window - ***************************************************************************/ -void AdafruitGFX_helper::getWindowOffsets(uint16_t& xOffset, - uint16_t& yOffset) { - xOffset = _windows[_windowIndex].top_left.x; - yOffset = _windows[_windowIndex].top_left.y; -} - -/**************************************************************************** - * Get the limits for the currently active window - ***************************************************************************/ -void AdafruitGFX_helper::getWindowLimits(uint16_t& xLimit, - uint16_t& yLimit) { - xLimit = _windows[_windowIndex].width_height.x; - yLimit = _windows[_windowIndex].width_height.y; -} - -/**************************************************************************** - * Define a window and return the ID - ***************************************************************************/ -uint8_t AdafruitGFX_helper::defineWindow(const int16_t& x, - const int16_t& y, - const int16_t& w, - const int16_t& h, - int16_t windowId, - const int8_t & rotation) { - int16_t result = getWindowIndex(windowId); - - if (result < 0) { - result = static_cast(_windows.size()); // previous size - _windows.push_back(tWindowObject()); // add new - - if (windowId < 0) { - windowId = 0; - - for (auto it = _windows.begin(); it != _windows.end(); it++) { - if ((*it).id == windowId) { windowId++; } // Generate a new window id - } - } - _windows[result].id = windowId; - } - _windows[result].top_left.x = x; - _windows[result].top_left.y = y; - _windows[result].width_height.x = w; - _windows[result].width_height.y = h; - - if (rotation >= 0) { - _windows[result].rotation = rotation & 3; - } else { - _windows[result].rotation = _rotation; - } - - // Adjust original coordinate/sizes based on rotation - switch (_windows[result].rotation) { - case 0: // 0 degrees - _windows[result].org_top_left.x = x; // All original - _windows[result].org_top_left.y = y; - _windows[result].org_width_height.x = w; - _windows[result].org_width_height.y = h; - break; - case 1: // +90 degrees - _windows[result].org_top_left.x = _display_x - (y + h); // swapped x/y - _windows[result].org_top_left.y = x; - _windows[result].org_width_height.x = h; // swapped width/height - _windows[result].org_width_height.y = w; - break; - case 2: // +180 degrees - _windows[result].org_top_left.x = _display_x - (x + w); - _windows[result].org_top_left.y = _display_y - (y + h); - _windows[result].org_width_height.x = w; // unchanged - _windows[result].org_width_height.y = h; - break; - case 3: // +270 degrees - _windows[result].org_top_left.x = y; - _windows[result].org_top_left.y = _display_x - (x + w); - _windows[result].org_width_height.x = h; // swapped width/height - _windows[result].org_width_height.y = w; - break; - } - - return _windows[result].id; -} - -/**************************************************************************** - * Remove a window definition - ***************************************************************************/ -bool AdafruitGFX_helper::deleteWindow(const uint8_t& windowId) { - const int16_t result = getWindowIndex(windowId); - - if (result > -1) { - _windows.erase(_windows.begin() + result); - return true; - } - return false; -} - -/**************************************************************************** - * log all current known window definitions - ***************************************************************************/ -void AdafruitGFX_helper::logWindows(const String& prefix) { - # ifndef BUILD_NO_DEBUG - String log; - - log.reserve(50); - - for (auto it = _windows.begin(); it != _windows.end(); it++) { - log.clear(); - log += F("AdaGFX window "); - log += prefix; - log += F(": "); - log += (*it).id; - log += F(", x:"); - log += (*it).top_left.x; - log += F(", y:"); - log += (*it).top_left.y; - log += F(", w:"); - log += (*it).width_height.x; - log += F(", h:"); - log += (*it).width_height.y; - log += F(", rot:"); - log += (*it).rotation; - log += F(", current: "); - log += getWindow(); - log += F(", org x:"); - log += (*it).org_top_left.x; - log += F(", y:"); - log += (*it).org_top_left.y; - log += F(", w:"); - log += (*it).org_width_height.x; - log += F(", h:"); - log += (*it).org_width_height.y; - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // ifndef BUILD_NO_DEBUG -} - -# endif // if ADAGFX_ENABLE_FRAMED_WINDOW - -#endif // ifdef PLUGIN_USES_ADAFRUITGFX +#include "../Helpers/AdafruitGFX_helper.h" +#include "../../_Plugin_Helper.h" + +#ifdef PLUGIN_USES_ADAFRUITGFX + +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringGenerator_Web.h" +# include "../WebServer/Markup_Forms.h" + +# if ADAGFX_FONTS_INCLUDED +# include "../Static/Fonts/Seven_Segment24pt7b.h" +# include "../Static/Fonts/Seven_Segment18pt7b.h" +# include "../Static/Fonts/FreeSans9pt7b.h" +# ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB +# include // Available in Adafruit_GFX library +# endif // ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB +# endif // ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA +# include "../Static/Fonts/angelina8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA +# ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO +# include "../Static/Fonts/NovaMono8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO +# ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE +# include "../Static/Fonts/unispace8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE +# ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC +# include "../Static/Fonts/unispace_italic8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC +# ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT +# include "../Static/Fonts/whitrabt8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT +# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO +# include "../Static/Fonts/Roboto_Regular8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO +# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED +# include "../Static/Fonts/RobotoCondensed_Regular8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED +# ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO +# include "../Static/Fonts/RobotoMono_Regular8pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO +# endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA +# include "../Static/Fonts/angelina12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA +# ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO +# include "../Static/Fonts/NovaMono12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO +# ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG +# include "../Static/Fonts/RepetitionScrolling12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG +# ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE +# include "../Static/Fonts/unispace12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE +# ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC +# include "../Static/Fonts/unispace_italic12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC +# ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT +# include "../Static/Fonts/whitrabt12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT +# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO +# include "../Static/Fonts/Roboto_Regular12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO +# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED +# include "../Static/Fonts/RobotoCondensed_Regular12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED +# ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO +# include "../Static/Fonts/RobotoMono_Regular12pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO +# endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS +# include "../Static/Fonts/AmerikaSans16pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS +# ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT +# include "../Static/Fonts/whitrabt16pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT +# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO +# include "../Static/Fonts/Roboto_Regular16pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO +# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED +# include "../Static/Fonts/RobotoCondensed_Regular16pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED +# ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO +# include "../Static/Fonts/RobotoMono_Regular16pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO +# endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT +# include "../Static/Fonts/whitrabt18pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT +# ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B +# include "../Static/Fonts/7segment18pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B +# ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND +# include "../Static/Fonts/LCD14cond18pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND +# endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT +# include "../Static/Fonts/whitrabt20pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT +# endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED +# ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B +# include "../Static/Fonts/7segment24pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B +# ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND +# include "../Static/Fonts/LCD14cond24pt7b.h" +# endif // ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND +# endif // ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED +# endif // if ADAGFX_FONTS_INCLUDED + +# if FEATURE_SD && defined(ADAGFX_ENABLE_BMP_DISPLAY) +# include +# endif // if FEATURE_SD && defined(ADAGFX_ENABLE_BMP_DISPLAY) + + +/****************************************************************************************** + * get the display text for a 'text print mode' enum value + *****************************************************************************************/ +const __FlashStringHelper* toString(const AdaGFXTextPrintMode& mode) { + switch (mode) { + case AdaGFXTextPrintMode::ContinueToNextLine: return F("Continue to next line"); + case AdaGFXTextPrintMode::TruncateExceedingMessage: return F("Truncate exceeding message"); + case AdaGFXTextPrintMode::ClearThenTruncate: return F("Clear then truncate exceeding message"); + case AdaGFXTextPrintMode::TruncateExceedingCentered: return F("Truncate, centered if maxWidth set"); + case AdaGFXTextPrintMode::MAX: break; + } + return F("None"); +} + +/****************************************************************************************** + * get the display text for a color depth enum value + *****************************************************************************************/ +const __FlashStringHelper* toString(const AdaGFXColorDepth& colorDepth) { + switch (colorDepth) { + case AdaGFXColorDepth::Monochrome: return F("Monochrome"); + case AdaGFXColorDepth::BlackWhiteRed: return F("Monochrome + 1 color"); + case AdaGFXColorDepth::BlackWhite2Greyscales: return F("Monochrome + 2 grey levels"); + # if ADAGFX_SUPPORT_7COLOR + case AdaGFXColorDepth::SevenColor: return F("eInk - 7 colors"); + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::EightColor: return F("TFT - 8 colors"); + case AdaGFXColorDepth::SixteenColor: return F("TFT - 16 colors"); + # endif // if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::FullColor: return F("Full color - 65535 colors"); + } + return F("None"); +} + +# if ADAGFX_ENABLE_BUTTON_DRAW + +/****************************************************************************************** + * get the display text for a button type enum value + *****************************************************************************************/ +const __FlashStringHelper* toString(const Button_type_e button) { + switch (button) { + case Button_type_e::None: return F("None"); + case Button_type_e::Square: return F("Square"); + case Button_type_e::Rounded: return F("Rounded"); + case Button_type_e::Circle: return F("Circle"); + case Button_type_e::ArrowLeft: return F("Arrow, left"); + case Button_type_e::ArrowUp: return F("Arrow, up"); + case Button_type_e::ArrowRight: return F("Arrow, right"); + case Button_type_e::ArrowDown: return F("Arrow, down"); + } + return F("Unsupported!"); +} + +/****************************************************************************************** + * get the display text for a button layout enum value + *****************************************************************************************/ +const __FlashStringHelper* toString(const Button_layout_e layout) { + switch (layout) { + case Button_layout_e::CenterAligned: return F("Centered"); + case Button_layout_e::LeftAligned: return F("Left-aligned"); + case Button_layout_e::TopAligned: return F("Top-aligned"); + case Button_layout_e::RightAligned: return F("Right-aligned"); + case Button_layout_e::BottomAligned: return F("Bottom-aligned"); + case Button_layout_e::LeftTopAligned: return F("Left-Top-aligned"); + case Button_layout_e::RightTopAligned: return F("Right-Top-aligned"); + case Button_layout_e::RightBottomAligned: return F("Right-Bottom-aligned"); + case Button_layout_e::LeftBottomAligned: return F("Left-Bottom-aligned"); + case Button_layout_e::NoCaption: return F("No Caption"); + # if ADAGFX_ENABLE_BMP_DISPLAY + case Button_layout_e::Bitmap: return F("Bitmap image"); + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_SLIDER + case Button_layout_e::Slider: return F("Slide control"); + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + } + return F("Unsupported!"); +} + +# endif // if ADAGFX_ENABLE_BUTTON_DRAW + +/***************************************************************************************** + * Show a selector for all available 'Text print mode' options, for use in PLUGIN_WEBFORM_LOAD + ****************************************************************************************/ +void AdaGFXFormTextPrintMode(const __FlashStringHelper *id, + uint8_t selectedIndex) { + const __FlashStringHelper *textModes[] = { // Be sure to use all available modes from enum! + toString(AdaGFXTextPrintMode::ContinueToNextLine), + toString(AdaGFXTextPrintMode::TruncateExceedingMessage), + toString(AdaGFXTextPrintMode::ClearThenTruncate), + toString(AdaGFXTextPrintMode::TruncateExceedingCentered), + }; + const int textModeOptions[] = { + static_cast(AdaGFXTextPrintMode::ContinueToNextLine), + static_cast(AdaGFXTextPrintMode::TruncateExceedingMessage), + static_cast(AdaGFXTextPrintMode::ClearThenTruncate), + static_cast(AdaGFXTextPrintMode::TruncateExceedingCentered), + }; + + addFormSelector(F("Text print Mode"), id, sizeof(textModeOptions) / sizeof(int), textModes, textModeOptions, selectedIndex); +} + +void AdaGFXFormColorDepth(const __FlashStringHelper *id, + uint16_t selectedIndex, + bool enabled) { + # if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + const int colorDepthCount = 7 + 1; + # else // if ADAGFX_SUPPORT_8and16COLOR + const int colorDepthCount = 5 + 1; + # endif // if ADAGFX_SUPPORT_8and16COLOR + # else // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + const int colorDepthCount = 6 + 1; + # else // if ADAGFX_SUPPORT_8and16COLOR + const int colorDepthCount = 4 + 1; + # endif // if ADAGFX_SUPPORT_8and16COLOR + # endif // if ADAGFX_SUPPORT_7COLOR + const __FlashStringHelper *colorDepths[colorDepthCount] = { // Be sure to use all available modes from enum! + toString(static_cast(0)), // include None + toString(AdaGFXColorDepth::Monochrome), + toString(AdaGFXColorDepth::BlackWhiteRed), + toString(AdaGFXColorDepth::BlackWhite2Greyscales), + # if ADAGFX_SUPPORT_7COLOR + toString(AdaGFXColorDepth::SevenColor), + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + toString(AdaGFXColorDepth::EightColor), + toString(AdaGFXColorDepth::SixteenColor), + # endif // if ADAGFX_SUPPORT_8and16COLOR + toString(AdaGFXColorDepth::FullColor) + }; + const int colorDepthOptions[colorDepthCount] = { + 0, + static_cast(AdaGFXColorDepth::Monochrome), + static_cast(AdaGFXColorDepth::BlackWhiteRed), + static_cast(AdaGFXColorDepth::BlackWhite2Greyscales), + # if ADAGFX_SUPPORT_7COLOR + static_cast(AdaGFXColorDepth::SevenColor), + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + static_cast(AdaGFXColorDepth::EightColor), + static_cast(AdaGFXColorDepth::SixteenColor), + # endif // if ADAGFX_SUPPORT_8and16COLOR + static_cast(AdaGFXColorDepth::FullColor) + }; + + addRowLabel_tr_id(F("Display Color-depth"), id); + addSelector(id, colorDepthCount, colorDepths, colorDepthOptions, NULL, selectedIndex, false, enabled); +} + +/***************************************************************************************** + * Show a selector for Rotation options, supported by Adafruit_GFX + ****************************************************************************************/ +void AdaGFXFormRotation(const __FlashStringHelper *id, + uint8_t selectedIndex) { + const __FlashStringHelper *rotationOptions[] = { F("Normal"), F("+90°"), F("+180°"), F("+270°") }; + const int rotationOptionValues[] = { 0, 1, 2, 3 }; + + addFormSelector(F("Rotation"), id, 4, rotationOptions, rotationOptionValues, selectedIndex); +} + +/***************************************************************************************** + * Show a checkbox & note to disable background-fill for text + ****************************************************************************************/ +void AdaGFXFormTextBackgroundFill(const __FlashStringHelper *id, + uint8_t selectedIndex) { + addFormCheckBox(F("Background-fill for text"), id, selectedIndex); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Fill entire line-height with background color.")); + # endif // ifndef LIMIT_BUILD_SIZE +} + +/***************************************************************************************** + * Show a checkbox & note to enable col/row mode for txp, txz and txtfull subcommands + ****************************************************************************************/ +void AdaGFXFormTextColRowMode(const __FlashStringHelper *id, + bool selectedState) { + addFormCheckBox(F("Text Coordinates in col/row"), id, selectedState); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Unchecked: Coordinates in pixels. Applies only to 'txp', 'txz' and 'txtfull' subcommands.")); + # endif // ifndef LIMIT_BUILD_SIZE +} + +/***************************************************************************************** + * Show a checkbox & note to enable -1 px compatibility mode for txp and txtfull subcommands + ****************************************************************************************/ +void AdaGFXFormOnePixelCompatibilityOption(const __FlashStringHelper *id, + uint8_t selectedIndex) { + addFormCheckBox(F("Use -1px offset for txp & txtfull"), id, selectedIndex); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("This is for compatibility with the original plugin implementation.")); + # endif // ifndef LIMIT_BUILD_SIZE +} + +/***************************************************************************************** + * Show 2 input fields for Foreground and Background color, translated to known color names or hex with # prefix + ****************************************************************************************/ +void AdaGFXFormForeAndBackColors(const __FlashStringHelper *foregroundId, + uint16_t foregroundColor, + const __FlashStringHelper *backgroundId, + uint16_t backgroundColor, + AdaGFXColorDepth colorDepth) { + String color = AdaGFXcolorToString(foregroundColor, colorDepth); + + AdaGFXHtmlColorDepthDataList(F("adagfxFGBGcolors"), colorDepth); + addRowLabel(F("Foreground color")); + addTextBox(foregroundId, color, 11, false, false, + EMPTY_STRING, F("") + # if FEATURE_TOOLTIPS + , F("Foreground color") + # endif // if FEATURE_TOOLTIPS + , F("adagfxFGBGcolors") + ); + color = AdaGFXcolorToString(backgroundColor, colorDepth); + addRowLabel(F("Background color")); + addTextBox(backgroundId, color, 11, false, false, + EMPTY_STRING, F("") + # if FEATURE_TOOLTIPS + , F("Background color") + # endif // if FEATURE_TOOLTIPS + , F("adagfxFGBGcolors") + ); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Use Color name, '#RGB565' (# + 1..4 hex nibbles) or '#RRGGBB' (# + 6 hex nibbles RGB color).")); + addFormNote(F("NB: Colors stored as RGB565 value!")); + # else // ifndef LIMIT_BUILD_SIZE + addFormNote(F("Use Color name, # + 1..4 hex RGB565 or # + 6 hex nibbles RGB color.")); + # endif // ifndef LIMIT_BUILD_SIZE +} + +/***************************************************************************************** + * Show a pin selector and percentage 1..100 for Backlight settings + ****************************************************************************************/ +void AdaGFXFormBacklight(const __FlashStringHelper *backlightPinId, + int8_t backlightPin, + const __FlashStringHelper *backlightPercentageId, + uint16_t backlightPercentage) { + addFormPinSelect(PinSelectPurpose::Generic_output, formatGpioName_output_optional(F("Backlight ")), backlightPinId, backlightPin); + + addFormNumericBox(F("Backlight percentage"), backlightPercentageId, backlightPercentage, 0, 100); + addUnit(F("0-100%")); +} + +/***************************************************************************************** + * Show pin selector, inverse option and timeout inputs for Displaybutton settings + ****************************************************************************************/ +void AdaGFXFormDisplayButton(const __FlashStringHelper *buttonPinId, + int8_t buttonPin, + const __FlashStringHelper *buttonInverseId, + bool buttonInverse, + const __FlashStringHelper *displayTimeoutId, + int displayTimeout) { + addFormPinSelect(PinSelectPurpose::Generic_input, F("Display button"), buttonPinId, buttonPin); + + addFormCheckBox(F("Inversed Logic"), buttonInverseId, buttonInverse); + + addFormNumericBox(F("Display Timeout"), displayTimeoutId, displayTimeout, 0); + addUnit(F("0 = off")); +} + +/***************************************************************************************** + * Show a numeric input 1..10 for Font scaling setting + ****************************************************************************************/ +void AdaGFXFormFontScaling(const __FlashStringHelper *fontScalingId, + uint8_t fontScaling, + uint8_t maxScale) { + addFormNumericBox(F("Font scaling"), fontScalingId, fontScaling, 1, maxScale); + String unit = F("1x.."); + + unit += maxScale; + unit += 'x'; + addUnit(unit); +} + +/***************************************************************************************** + * Show a selector for line-spacing setting, supported by Adafruit_GFX + ****************************************************************************************/ +void AdaGFXFormLineSpacing(const __FlashStringHelper *id, + uint8_t selectedIndex) { + String lineSpacings[16]; + int lineSpacingOptions[16]; + + for (uint8_t i = 0; i < 16; ++i) { + if (15 == i) { + # ifndef LIMIT_BUILD_SIZE + lineSpacings[i] = F("Auto, using font height * scaling"); + # else // ifndef LIMIT_BUILD_SIZE + lineSpacings[i] = F("Auto"); + # endif // ifndef LIMIT_BUILD_SIZE + } else { + lineSpacings[i] = i; + } + lineSpacingOptions[i] = i; + } + addFormSelector(F("Linespacing"), id, 16, lineSpacings, lineSpacingOptions, selectedIndex); + addUnit(F("px")); +} + +/**************************************************************************** + * AdaGFXparseTemplate: Replace variables and adjust unicode special characters to Adafruit font + ***************************************************************************/ +String AdaGFXparseTemplate(const String & tmpString, + const uint8_t lineSize, + AdafruitGFX_helper *gfxHelper) { + # if ADAGFX_PARSE_SUBCOMMAND + + String result = tmpString; + + if (nullptr != gfxHelper) { + String trigger = gfxHelper->getTrigger(); + + if (!trigger.isEmpty()) { + int16_t prefixTrigger = result.indexOf(ADAGFX_PARSE_PREFIX); + int16_t postfixTrigger = result.indexOf(ADAGFX_PARSE_POSTFIX, prefixTrigger + 1); + + while ((prefixTrigger > -1) && (postfixTrigger > -1) && (postfixTrigger > prefixTrigger)) { // Might be valid + String subcommand = result.substring(prefixTrigger + ADAGFX_PARSE_POSTFIX_LEN, postfixTrigger); + + if (!subcommand.isEmpty()) { + const String command = strformat(F("%s,%s"), trigger.c_str(), subcommand.c_str()); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLogMove(ADAGFX_LOG_LEVEL, concat(F("AdaGFX: inline cmd: "), command)); + } + # endif // ifndef BUILD_NO_DEBUG + + if (gfxHelper->processCommand(command)) { // Execute command and remove from result incl. pre/postfix + result.remove(prefixTrigger, (postfixTrigger - prefixTrigger) + ADAGFX_PARSE_POSTFIX_LEN); + prefixTrigger = result.indexOf(ADAGFX_PARSE_PREFIX); + postfixTrigger = result.indexOf(ADAGFX_PARSE_POSTFIX, prefixTrigger + 1); + } else { // If the command fails, exit further processing + prefixTrigger = -1; + postfixTrigger = -1; + # ifndef BUILD_NO_DEBUG + addLog(ADAGFX_LOG_LEVEL, F("AdaGFX: inline cmd: unknown")); + # endif // ifndef BUILD_NO_DEBUG + } + } else { + prefixTrigger = -1; + postfixTrigger = -1; + } + } + } + } + result = parseTemplate_padded(result, lineSize); + # else // if ADAGFX_PARSE_SUBCOMMAND + String result = parseTemplate_padded(tmpString, lineSize); + # endif // if ADAGFX_PARSE_SUBCOMMAND + + const char euro[4] = { 0xe2, 0x82, 0xac, 0 }; // Unicode euro symbol + const char euro_ascii[2] = { 0xED, 0 }; // Euro symbol + result.replace(euro, euro_ascii); + + char unicodePrefix = 0xc2; + + if (result.indexOf(unicodePrefix) != -1) { + const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol + const char degree_ascii[2] = { 0xf7, 0 }; // degree symbol + result.replace(degree, degree_ascii); + + const char pound[3] = { 0xc2, 0xa3, 0 }; // Unicode pound symbol + const char pound_ascii[2] = { 0x9C, 0 }; // pound symbol + result.replace(pound, pound_ascii); + + const char yen[3] = { 0xc2, 0xa5, 0 }; // Unicode yen symbol + const char yen_ascii[2] = { 0x9D, 0 }; // yen symbol + result.replace(yen, yen_ascii); + + const char cent[3] = { 0xc2, 0xa2, 0 }; // Unicode cent symbol + const char cent_ascii[2] = { 0x9B, 0 }; // cent symbol + result.replace(cent, cent_ascii); + + const char mu[3] = { 0xc2, 0xb5, 0 }; // Unicode mu/micro (µ) symbol + const char mu_ascii[2] = { 0xe5, 0 }; // mu/micro symbol + result.replace(mu, mu_ascii); + + const char plusmin[3] = { 0xc2, 0xb1, 0 }; // Unicode plusminus symbol + const char plusmin_ascii[2] = { 0xf0, 0 }; // plusminus symbol + result.replace(plusmin, plusmin_ascii); + + const char laquo[3] = { 0xc2, 0xab, 0 }; // Unicode left aquo symbol + const char laquo_ascii[2] = { 0xae, 0 }; // left aquo symbol + result.replace(laquo, laquo_ascii); + + const char raquo[3] = { 0xc2, 0xbb, 0 }; // Unicode right aquote symbol + const char raquo_ascii[2] = { 0xaf, 0 }; // right aquote symbol + result.replace(raquo, raquo_ascii); + + const char half[3] = { 0xc2, 0xbd, 0 }; // Unicode half 1/2 symbol + const char half_ascii[2] = { 0xab, 0 }; // half 1/2 symbol + result.replace(half, half_ascii); + + const char quart[3] = { 0xc2, 0xbc, 0 }; // Unicode quart 1/4 symbol + const char quart_ascii[2] = { 0xac, 0 }; // quart 1/4 symbol + result.replace(quart, quart_ascii); + + const char sup2[3] = { 0xc2, 0xb2, 0 }; // Unicode superscript 2 symbol + const char sup2_ascii[2] = { 0xfc, 0 }; // superscript 2 symbol + result.replace(sup2, sup2_ascii); + + // Unsupported characters, replace by something useful + const char sup1[3] = { 0xc2, 0xb9, 0 }; // Unicode superscript 1 symbol + const char sup1_ascii[2] = { 0x31, 0 }; // regular 1 (missing from font) + result.replace(sup1, sup1_ascii); + + const char sup3[3] = { 0xc2, 0xb3, 0 }; // Unicode superscript 3 symbol + const char sup3_ascii[2] = { 0x33, 0 }; // regular 3 (missing from font) + result.replace(sup3, sup3_ascii); + + const char frac34[3] = { 0xc2, 0xbe, 0 }; // Unicode fraction 3/4 symbol + const char frac34_ascii[2] = { 0x5c, 0 }; // regular \ (missing from font) + result.replace(frac34, frac34_ascii); + delay(0); + } + + unicodePrefix = 0xc3; + + if (result.indexOf(unicodePrefix) != -1) { + // See: https://github.com/letscontrolit/ESPEasy/issues/2081 + + const char umlautAE_uni[3] = { 0xc3, 0x84, 0 }; // Unicode Umlaute AE + const char umlautAE_ascii[2] = { 0x8e, 0 }; // Umlaute A + result.replace(umlautAE_uni, umlautAE_ascii); + + const char umlaut_ae_uni[3] = { 0xc3, 0xa4, 0 }; // Unicode Umlaute ae + const char umlautae_ascii[2] = { 0x84, 0 }; // Umlaute a + result.replace(umlaut_ae_uni, umlautae_ascii); + + const char umlautOE_uni[3] = { 0xc3, 0x96, 0 }; // Unicode Umlaute OE + const char umlautOE_ascii[2] = { 0x99, 0 }; // Umlaute O + result.replace(umlautOE_uni, umlautOE_ascii); + + const char umlaut_oe_uni[3] = { 0xc3, 0xb6, 0 }; // Unicode Umlaute oe + const char umlautoe_ascii[2] = { 0x94, 0 }; // Umlaute o + result.replace(umlaut_oe_uni, umlautoe_ascii); + + const char umlautUE_uni[3] = { 0xc3, 0x9c, 0 }; // Unicode Umlaute UE + const char umlautUE_ascii[2] = { 0x9a, 0 }; // Umlaute U + result.replace(umlautUE_uni, umlautUE_ascii); + + const char umlaut_ue_uni[3] = { 0xc3, 0xbc, 0 }; // Unicode Umlaute ue + const char umlautue_ascii[2] = { 0x81, 0 }; // Umlaute u + result.replace(umlaut_ue_uni, umlautue_ascii); + + const char divide_uni[3] = { 0xc3, 0xb7, 0 }; // Unicode divide symbol + const char divide_ascii[2] = { 0xf5, 0 }; // Divide symbol + result.replace(divide_uni, divide_ascii); + + const char umlaut_sz_uni[3] = { 0xc3, 0x9f, 0 }; // Unicode Umlaute sz + const char umlaut_sz_ascii[2] = { 0xe0, 0 }; // Umlaute B + result.replace(umlaut_sz_uni, umlaut_sz_ascii); + + // Unsupported characters, replace by something useful + const char times[3] = { 0xc3, 0x97, 0 }; // Unicode multiplication symbol + const char times_ascii[2] = { 0x78, 0 }; // regular x (missing from font) + result.replace(times, times_ascii); + delay(0); + } + + // Handle '{0xNN...}' hex values in template, where NN can be any hex value from 01..FF (practically 20..FF). + int16_t hexPrefix = 0; + int16_t hexPostfix; + const String hexSeparators = F(" ,.:;-"); + + while (((hexPrefix = result.indexOf(F("{0x"), hexPrefix)) > -1) && + ((hexPostfix = result.indexOf('}', hexPrefix)) > -1)) { + String replace; + + for (int16_t ci = hexPrefix + 3; ci < hexPostfix - 1; ci += 2) { // Multiple of 2 only + uint32_t hexValue = hexToUL(result.substring(ci, ci + 2)); + + if (hexValue > 0) { + replace += static_cast(hexValue); + } + + while (hexSeparators.indexOf(result.substring(ci + 2, ci + 3)) > -1 && ci < hexPostfix) { + ci++; + } + } + + if (!replace.isEmpty()) { + result.replace(result.substring(hexPrefix, hexPostfix + 1), replace); + } + } + + for (uint16_t l = result.length(); l > 0 && isSpace(result[l - 1]); --l) { // Right-trim + result.remove(l - 1); + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLogMove(ADAGFX_LOG_LEVEL, strformat(F("AdaGFX: parse result: '%s'"), result.c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + return result; +} + +// AdafruitGFX_helper class methods + +/**************************************************************************** + * parameterized constructors + ***************************************************************************/ +AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_GFX *display, + const String & trigger, + const uint16_t res_x, + const uint16_t res_y, + const AdaGFXColorDepth & colorDepth, + const AdaGFXTextPrintMode& textPrintMode, + const uint8_t fontscaling, + const uint16_t fgcolor, + const uint16_t bgcolor, + const bool useValidation, + const bool textBackFill, + const uint8_t defaultFontId) + : _display(display), _trigger(trigger), _res_x(res_x), _res_y(res_y), _colorDepth(colorDepth), + _textPrintMode(textPrintMode), _fontscaling(fontscaling), _fgcolor(fgcolor), _bgcolor(bgcolor), + _useValidation(useValidation), _textBackFill(textBackFill), _defaultFontId(defaultFontId) +{ + addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: GFX Init.")); +} + +# if ADAGFX_ENABLE_BMP_DISPLAY +AdafruitGFX_helper::AdafruitGFX_helper(Adafruit_SPITFT *display, + const String & trigger, + const uint16_t res_x, + const uint16_t res_y, + const AdaGFXColorDepth & colorDepth, + const AdaGFXTextPrintMode& textPrintMode, + const uint8_t fontscaling, + const uint16_t fgcolor, + const uint16_t bgcolor, + const bool useValidation, + const bool textBackFill, + const uint8_t defaultFontId) + : _tft(display), _trigger(trigger), _res_x(res_x), _res_y(res_y), _colorDepth(colorDepth), + _textPrintMode(textPrintMode), _fontscaling(fontscaling), _fgcolor(fgcolor), _bgcolor(bgcolor), + _useValidation(useValidation), _textBackFill(textBackFill), _defaultFontId(defaultFontId) +{ + _display = _tft; + addLog(LOG_LEVEL_INFO, F("AdaGFX_helper: TFT Init.")); +} + +# endif // if ADAGFX_ENABLE_BMP_DISPLAY + +/**************************************************************************** + * common initialization, called from constructors + ***************************************************************************/ +void AdafruitGFX_helper::initialize() { + _trigger.toLowerCase(); // store trigger in lowercase + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLogMove(ADAGFX_LOG_LEVEL, strformat(F("AdaGFX: Init, x: %d, y: %d, colors: %d, trigger: %s, %s"), + _res_x, _res_y, static_cast(_colorDepth), + _trigger.c_str(), getFeatures().c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + _display_x = _res_x; // Store initial resolution + _display_y = _res_y; + + # if ADAGFX_ENABLE_FRAMED_WINDOW + defineWindow(0, 0, _res_x, _res_y, 0, 0); // Add window 0 at rotation 0 + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + if (_fontscaling < 1) { _fontscaling = 1; } + + if (nullptr != _display) { + # if ADAGFX_FONTS_INCLUDED + setFontById(_defaultFontId); + # endif // if ADAGFX_FONTS_INCLUDED + _display->setTextSize(_fontscaling); + _display->setTextColor(_fgcolor, _bgcolor); // initialize text colors + _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); + } +} + +/**************************************************************************** + * Show enabled features of the helper + ***************************************************************************/ +String AdafruitGFX_helper::getFeatures() { + String log = F("Features:" + # if (defined(ADAGFX_USE_ASCIITABLE) && ADAGFX_USE_ASCIITABLE) + " asciitable," + # endif // if (defined(ADAGFX_USE_ASCIITABLE) && ADAGFX_USE_ASCIITABLE) + # if (defined(ADAGFX_ENABLE_EXTRA_CMDS) && ADAGFX_ENABLE_EXTRA_CMDS) + " lm/lmr," + # endif // if (defined(ADAGFX_ENABLE_EXTRA_CMDS) && ADAGFX_ENABLE_EXTRA_CMDS) + # if (defined(ADAGFX_ENABLE_BMP_DISPLAY) && ADAGFX_ENABLE_BMP_DISPLAY) + " bmp," + # endif // if (defined(ADAGFX_ENABLE_BMP_DISPLAY) && ADAGFX_ENABLE_BMP_DISPLAY) + # if (defined(ADAGFX_ENABLE_BUTTON_DRAW) && ADAGFX_ENABLE_BUTTON_DRAW) + " btn," + # endif // if (defined(ADAGFX_ENABLE_BUTTON_DRAW) && ADAGFX_ENABLE_BUTTON_DRAW)` + # if (defined(ADAGFX_ENABLE_FRAMED_WINDOW) && ADAGFX_ENABLE_FRAMED_WINDOW) + " win," + # endif // if (defined(ADAGFX_ENABLE_FRAMED_WINDOW) && ADAGFX_ENABLE_FRAMED_WINDOW) + # if (defined(ADAGFX_ENABLE_GET_CONFIG_VALUE) && ADAGFX_ENABLE_GET_CONFIG_VALUE) + " getconf," + # endif // if (defined(ADAGFX_ENABLE_GET_CONFIG_VALUE) && ADAGFX_ENABLE_GET_CONFIG_VALUE) + ); + + if (log.endsWith(F(","))) { + log.remove(log.length() - 1); + } + return log; +} + +/**************************************************************************** + * getCursorXY: get the current (text) cursor coordinates, either in pixels or cols/rows, depending on related setting + ***************************************************************************/ +void AdafruitGFX_helper::getCursorXY(int16_t& currentX, + int16_t& currentY) { + _lastX = _display->getCursorX(); + _lastY = _display->getCursorY(); + + if (_columnRowMode && (_lastX != 0)) { _lastX /= _fontwidth; } + + if (_columnRowMode && (_lastY != 0)) { _lastY /= _fontheight; } + currentX = _lastX; + currentY = _lastY; +} + +/**************************************************************************** + * setTxtfullCompensation: x and/or y values defined here are subtracted from x and y position + * - set to 1 for x-1/y-1 pixel for P095 and P096 + * - set to 2 for y+1 pixel + * - set to 3 for x+1 pixel + ***************************************************************************/ +void AdafruitGFX_helper::setTxtfullCompensation(uint8_t compensation) { + switch (compensation) { + case 1: // P095 + { + _x_compensation = 1; + _y_compensation = 1; + break; + } + case 2: + { + _x_compensation = 0; + _y_compensation = -1; + break; + } + case 3: + { + _x_compensation = -1; + _y_compensation = 0; + break; + } + default: + { + _x_compensation = 0; + _y_compensation = 0; + break; + } + } +} + +/**************************************************************************** + * invertDisplay(): Store display-inverted state and proxy to _display + ***************************************************************************/ +void AdafruitGFX_helper::invertDisplay(bool i) { + _displayInverted = i; + _display->invertDisplay(_displayInverted); +} + +/**************************************************************************** + * processCommand: Parse string to ,[,...] and execute that command + ***************************************************************************/ +const char adagfx_commands[] PROGMEM = + "txt|txp|txz|txl|txc|txs|txtfull|clear|rot|tpm|" // 0..9 + # if ADAGFX_USE_ASCIITABLE + "asciitable|" + # endif // if ADAGFX_USE_ASCIITABLE + "font|l|lh|lv|" + # if ADAGFX_ENABLE_EXTRA_CMDS + "lm|lmr|" + # endif // if ADAGFX_ENABLE_EXTRA_CMDS + "r|rf|c|" // 10..19 + "cf|t|tf|rr|rrf|px|pxh|pxv|" + # if ADAGFX_ENABLE_BMP_DISPLAY + "bmp|" + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_DRAW + "btn|" // 20..29 + # endif // if ADAGFX_ENABLE_BUTTON_DRAW + # if ADAGFX_ENABLE_FRAMED_WINDOW + "win|defwin|delwin" // 30.. + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW +; +enum class adagfx_commands_e : int8_t { + invalid = -1, + txt = 0, // 0 + txp, + txz, + txl, + txc, + txs, + txtfull, + clear, + rot, + tpm, // 9 + # if ADAGFX_USE_ASCIITABLE + asciitable, // 10 + # endif // if ADAGFX_USE_ASCIITABLE + font, + l, + lh, + lv, + # if ADAGFX_ENABLE_EXTRA_CMDS + lm, + lmr, + # endif // if ADAGFX_ENABLE_EXTRA_CMDS + r, + rf, + c, // 19 + cf, // 20 + t, + tf, + rr, + rrf, + px, + pxh, + pxv, + # if ADAGFX_ENABLE_BMP_DISPLAY + bmp, + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_DRAW + btn, // 29 + # endif // if ADAGFX_ENABLE_BUTTON_DRAW + # if ADAGFX_ENABLE_FRAMED_WINDOW + win, // 30 + defwin, + delwin, + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW +}; +# if ADAGFX_FONTS_INCLUDED + +// *** Don't forget to add the | separator at the end of a (new) font-name! (except for the last one in the list) +const char adagfx_fonts[] PROGMEM = + "default|sevenseg24|sevenseg18|freesans|" + # ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB + "tomthumb|" + # endif // ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB + # endif // ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA + "angelina8prop|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA + # ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO + "novamono8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO + # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE + "unispace8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE + # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC + "unispaceitalic8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC + # ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT + "whiterabbit8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO + "roboto8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED + "robotocond8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO + "robotomono8pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA + "angelina12prop|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA + # ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO + "novamono12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO + # ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG + "repetitionscrolling12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG + # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE + "unispace12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE + # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC + "unispaceitalic12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC + # ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT + "whiterabbit12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO + "roboto12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED + "robotocond12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO + "robotomono12pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS + "amerikasans16pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS + # ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT + "whiterabbit16pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO + "roboto16pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED + "robotocond16pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO + "robotomono16pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT + "whiterabbit18pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B + "sevenseg18b|" + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B + # ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND + "lcd14cond18pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT + "whiterabbit20pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT + # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B + "sevenseg24b|" + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B + # ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND + "lcd14cond24pt|" + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED + ""; + +struct tFontArgs { + constexpr tFontArgs(const GFXfont *f, + uint8_t width, + uint8_t height, + int8_t offset, + bool proportional, + uint8_t fontId) + : _f(f), _width(width), _height(height), _offset(offset), + _proportional(proportional), _fontId(fontId) {} + + const GFXfont *_f; + uint8_t _width; + uint8_t _height; + int8_t _offset; + bool _proportional; + uint8_t _fontId; +}; + +/* *INDENT-OFF* */ +constexpr tFontArgs fontargs[] = +{ + { nullptr, 6, 9, 0, false, 0u }, + { &Seven_Segment24pt7b, 21, 42, 35, true, 1u }, + { &Seven_Segment18pt7b, 16, 33, 26, true, 2u }, + { &FreeSans9pt7b, 10, 16, 12, false, 3u }, + # ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB + { &TomThumb, 5, 6, 5, false, 4u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB + # endif // ifdef ADAGFX_FONTS_EXTRA_5PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA + { &angelina8pt7b, 6, 16, 12, true, 5u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ANGELINA + # ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO + { &NovaMono8pt7b, 9, 16, 12, false, 6u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_NOVAMONO + # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE + { &unispace8pt7b, 13, 24, 20, false, 7u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACE + # ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC + { &unispace_italic8pt7b, 13, 24, 20, false, 8u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC + # ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT + { &whitrabt8pt7b, 10, 16, 12, false, 9u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO + { &Roboto_Regular8pt7b, 10, 16, 12, true, 10u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED + { &RobotoCondensed_Regular8pt7b, 9, 16, 12, true, 11u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO + { &RobotoMono_Regular8pt7b, 10, 16, 12, false, 12u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_8PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA + { &angelina12pt7b, 8, 22, 18, true, 13u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ANGELINA + # ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO + { &NovaMono12pt7b, 13, 26, 22, false, 14u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_NOVAMONO + # ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG + { &RepetitionScrolling12pt7b, 13, 22, 18, false, 15u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG + # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE + { &unispace12pt7b, 18, 30, 26, false, 16u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACE + # ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC + { &unispace_italic12pt7b, 18, 30, 26, false, 17u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC + # ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT + { &whitrabt12pt7b, 13, 20, 16, false, 18u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO + { &Roboto_Regular12pt7b, 13, 20, 20, true, 19u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED + { &RobotoCondensed_Regular12pt7b, 13, 20, 20, true, 20u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO + { &RobotoMono_Regular12pt7b, 13, 20, 20, false, 21u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_12PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS + { &AmerikaSans16pt7b, 17, 30, 26, true, 22u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS + # ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT + { &whitrabt16pt7b, 18, 26, 22, false, 23u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO + { &Roboto_Regular16pt7b, 18, 27, 23, true, 24u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTO + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED + { &RobotoCondensed_Regular16pt7b, 18, 27, 23, true, 25u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED + # ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO + { &RobotoMono_Regular16pt7b, 18, 27, 23, false, 26u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO + # endif // ifdef ADAGFX_FONTS_EXTRA_16PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT + { &whitrabt18pt7b, 21, 30, 26, false, 27u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT + # ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B + { &_7segment18pt7b, 21, 30, 30, false, 28u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B + # ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND + { &LCD14cond18pt7b, 24, 30, 30, false, 29u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_LCD14COND + # endif // ifdef ADAGFX_FONTS_EXTRA_18PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT + { &whitrabt20pt7b, 24, 32, 28, false, 30u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT + # endif // ifdef ADAGFX_FONTS_EXTRA_20PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED + # ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B + { &_7segment24pt7b, 26, 34, 38, false, 31u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B + # ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND + { &LCD14cond24pt7b, 26, 34, 38, false, 32u }, + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_LCD14COND + # endif // ifdef ADAGFX_FONTS_EXTRA_24PT_INCLUDED +}; +/* *INDENT-ON* */ +# endif // if ADAGFX_FONTS_INCLUDED + +String AdaGFXgetFontName(uint8_t fontId, bool includeFontId) { + # if ADAGFX_FONTS_INCLUDED + const uint32_t idx = AdaGFXgetFontIndexForFontId(fontId); + char tmp[30]{}; // Longest name so far is 23 + \0 + String fontName(GetTextIndexed(tmp, sizeof(tmp), idx, adagfx_fonts)); + + if (includeFontId) { + fontName = strformat(F("%s (%d)"), tmp, fontargs[idx]._fontId); + } + return fontName; + # else // if ADAGFX_FONTS_INCLUDED + return EMPTY_STRING; + # endif // if ADAGFX_FONTS_INCLUDED +} + +uint32_t AdaGFXgetFontIndexForFontId(uint8_t fontId) { + # if ADAGFX_FONTS_INCLUDED + constexpr uint32_t font_max = NR_ELEMENTS(fontargs); + + for (uint32_t idx = 0; idx < font_max; ++idx) { + if (fontargs[idx]._fontId == fontId) { + return idx; + } + } + # endif // if ADAGFX_FONTS_INCLUDED + return 0; +} + +void AdaGFXFormDefaultFont(const __FlashStringHelper *id, + uint8_t selectedIndex) { + # if ADAGFX_FONTS_INCLUDED + constexpr uint32_t font_max = NR_ELEMENTS(fontargs); + + addRowLabel_tr_id(F("Default font"), id); + addSelector_Head(id); + + char tmp[30]{}; // Longest name so far is 23 + \0 + + for (uint32_t idx = 0; idx < font_max; ++idx) { + const bool selected = (fontargs[idx]._fontId == selectedIndex); + GetTextIndexed(tmp, sizeof(tmp), idx, adagfx_fonts); + addSelector_Item(strformat(F("%s (%d)"), tmp, fontargs[idx]._fontId), + fontargs[idx]._fontId, + selected); + } + addSelector_Foot(); + # endif // if ADAGFX_FONTS_INCLUDED +} + +# if ADAGFX_FONTS_INCLUDED +void AdafruitGFX_helper::setFontById(uint8_t fontId) { + constexpr int font_max = NR_ELEMENTS(fontargs); + const int font_i = AdaGFXgetFontIndexForFontId(fontId); + + if ((font_i >= 0) && (font_i < font_max)) { + _fontId = fontargs[font_i]._fontId; + _display->setFont(fontargs[font_i]._f); + calculateTextMetrics(fontargs[font_i]._width, + fontargs[font_i]._height, + fontargs[font_i]._offset, + fontargs[font_i]._proportional); + } +} + +# endif // if ADAGFX_FONTS_INCLUDED + +bool AdafruitGFX_helper::processCommand(const String& string) { + bool success = false; + + if ((nullptr == _display) || _trigger.isEmpty()) { return success; } + + const String cmd = parseString(string, 1); // lower case + const String subcommand = parseString(string, 2); + + # if ADAGFX_ENABLE_FRAMED_WINDOW || ADAGFX_ARGUMENT_VALIDATION + uint16_t res_x = _res_x; + uint16_t res_y = _res_y; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW || ADAGFX_ARGUMENT_VALIDATION + uint16_t _xo = 0; + uint16_t _yo = 0; + + # if ADAGFX_ENABLE_FRAMED_WINDOW + getWindowLimits(res_x, res_y); + getWindowOffsets(_xo, _yo); + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + if (!(cmd.equals(_trigger) || + isAdaGFXTrigger(cmd)) || + subcommand.isEmpty()) { return success; } // Only support own trigger, and at least a non=empty subcommand + + std::vector sParams; + std::vector nParams; + uint8_t emptyCount = 0; + int argCount = 0; + bool loop = true; + + while (loop) { // Process all provided arguments + // 0-offset + 1st and 2nd argument used by trigger/subcommand, don't trim off spaces + sParams.push_back(parseStringKeepCaseNoTrim(string, argCount + 3)); + nParams.push_back(0); + validIntFromString(sParams[argCount], nParams[argCount]); + + if (sParams[argCount].isEmpty()) { + emptyCount++; + } else { + emptyCount = 0; // Reset empty counter + } + loop = emptyCount < 3 || argCount <= ADAGFX_PARSE_MAX_ARGS; // Keep picking up arguments until we have the last 3 empty + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + addLog(LOG_LEVEL_DEBUG_DEV, strformat(F(":%d %s"), argCount, sParams[argCount].c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + argCount++; + } + argCount -= emptyCount; // Not counting the empty arguments + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLog(ADAGFX_LOG_LEVEL, strformat(F("AdaGFX: command: %s argCount: %d:%s"), _trigger.c_str(), argCount, string.c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + const int subcommand_i = GetCommandCode(subcommand.c_str(), adagfx_commands); + + if (subcommand_i < 0) { return false; } // Fail fast + + const adagfx_commands_e subcmd = static_cast(subcommand_i); + const bool currentColRowState = _columnRowMode; + + # if ADAGFX_ARGUMENT_VALIDATION + + // Optimize some coordinate checks, with less than 3 occurrences there is no gain + const bool invCoord_0_1 = argCount >= 2 && invalidCoordinates(nParams[0], nParams[1]); + const bool invCoord_2_3 = argCount >= 4 && invalidCoordinates(nParams[2], nParams[3]); + const bool invCoord_0_2_1_3 = argCount >= 4 && invalidCoordinates(nParams[0] + nParams[2], + nParams[1] + nParams[3]); + # endif // if ADAGFX_ARGUMENT_VALIDATION + + switch (subcmd) + { + case adagfx_commands_e::invalid: + break; + case adagfx_commands_e::txt: // txt: Print text at last cursor position, ends at next line! + _display->println(parseStringToEndKeepCaseNoTrim(string, 3)); // Print entire rest of provided line + success = true; + break; + case adagfx_commands_e::txp: // txp: Text position + + if (argCount == 2) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!invalidCoordinates(nParams[0], nParams[1], _columnRowMode)) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if (_columnRowMode) { + _display->setCursor(nParams[0] * _fontwidth + _xo, nParams[1] * _fontheight + _yo); + } else { + _display->setCursor(nParams[0] + _xo - _x_compensation, nParams[1] + _yo - _y_compensation); + } + } + success = true; + } + break; + case adagfx_commands_e::txz: // txz: Text at position + + if (argCount >= 3) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!invalidCoordinates(nParams[0], nParams[1], _columnRowMode)) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + printText(parseStringToEndKeepCaseNoTrim(string, 5).c_str(), + nParams[0] + _xo - _x_compensation, + nParams[1] + _yo - _y_compensation, + _fontscaling, + _fgcolor, + _bgcolor); + } + success = true; + } + break; + case adagfx_commands_e::txl: // txl: Text at line(s) + + if (argCount >= 2) { + uint8_t _line = 0; + uint8_t _column = 0; + uint8_t idx = 0; + setColumnRowMode(true); // this command is by default set to Column/Row mode + + while (idx < argCount && !sParams[idx + 1].isEmpty()) { + if (nParams[idx] > 0) { + _line = nParams[idx]; + } else { + _line++; + } + printText(sParams[idx + 1].c_str(), _column, _line - 1, _fontscaling, _fgcolor, _bgcolor); + idx += 2; + } + setColumnRowMode(currentColRowState); + success = true; + } + break; + case adagfx_commands_e::txc: // txc: Textcolor, fg and opt. bg colors + + if ((argCount == 1) || (argCount == 2)) { + _fgcolor = AdaGFXparseColor(sParams[0], _colorDepth); + + if (argCount == 1) { + _bgcolor = _fgcolor; // Transparent background + _display->setTextColor(_fgcolor); + } else { // argCount=2 + _bgcolor = AdaGFXparseColor(sParams[1], _colorDepth); + _display->setTextColor(_fgcolor, _bgcolor); + } + success = true; + } + break; + case adagfx_commands_e::txs: // txs: Text size = font scaling, 1..10 + + if ((argCount == 1) && (nParams[0] >= 0) && (nParams[0] <= 10)) { + _fontscaling = nParams[0]; + _display->setTextSize(_fontscaling); + calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); + success = true; + } + break; + case adagfx_commands_e::txtfull: // txtfull: Text at position, with size and color + + if ((argCount >= 3) && (argCount <= 8)) { + uint16_t par3color = argCount < 5 || sParams[3].isEmpty() ? _fgcolor : AdaGFXparseColor(sParams[3], _colorDepth); + uint16_t par4color = argCount < 6 || sParams[4].isEmpty() ? _bgcolor : AdaGFXparseColor(sParams[4], _colorDepth); + + # if ADAGFX_ARGUMENT_VALIDATION + + if (!invalidCoordinates(nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + _columnRowMode)) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + success = true; + + switch (argCount) { + case 3: // single text + + printText(sParams[2].c_str(), + nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + _fontscaling, + _fgcolor, + _bgcolor); + break; + case 4: // text + size + + printText(sParams[3].c_str(), + nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + nParams[2], + _fgcolor, + _bgcolor); + break; + case 5: // text + size + color + + printText(sParams[4].c_str(), + nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + nParams[2], + par3color, + par3color); // transparent bg + break; + case 6: // text + size + color + bkcolor + + printText(sParams[5].c_str(), + nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + nParams[2], + par3color, + par4color); + break; + case 7: // 7: text + size + color + bkcolor + printmode + case 8: // as 7 but: + maxwidth + + { + AdaGFXTextPrintMode tmpPrintMode = _textPrintMode; + + if ((nParams[5] >= 0) && (nParams[5] < static_cast(AdaGFXTextPrintMode::MAX))) { + _textPrintMode = static_cast(nParams[5]); + _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); + } + printText(sParams[argCount - 1].c_str(), + nParams[0] - _x_compensation, + nParams[1] - _y_compensation, + nParams[2], + par3color, + par4color, + argCount == 8 ? nParams[argCount - 2] : 0); + + if (_textPrintMode != tmpPrintMode) { + _textPrintMode = tmpPrintMode; + _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); + } + break; + } + default: + success = false; + break; + } + } + } + break; + case adagfx_commands_e::clear: // clear: Clear display + # if ADAGFX_ENABLE_FRAMED_WINDOW + + if (_window == 0) + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + { + _display->fillScreen(argCount == 0 ? _bgcolor : AdaGFXparseColor(sParams[0], _colorDepth)); + } + # if ADAGFX_ENABLE_FRAMED_WINDOW + else { + // logWindows(F("clear ")); // Use for debugging only + uint16_t _w = 0, _h = 0; + getWindowLimits(_w, _h); + _display->fillRect(_xo, _yo, _w, _h, + argCount == 0 ? _bgcolor : AdaGFXparseColor(sParams[0], _colorDepth)); + } + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + success = true; + break; + case adagfx_commands_e::rot: // rot: Rotation + + if ((argCount == 1) && (nParams[0] >= 0) && (nParams[0] <= 3)) { + setRotation(nParams[0]); + success = true; + } + break; + case adagfx_commands_e::tpm: // tpm: Text Print Mode + + if ((argCount == 1) && ((nParams[0] < 0) || (nParams[0] >= static_cast(AdaGFXTextPrintMode::MAX)))) { + _textPrintMode = static_cast(nParams[0]); + _display->setTextWrap(_textPrintMode == AdaGFXTextPrintMode::ContinueToNextLine); + success = true; + } + break; + # if ADAGFX_USE_ASCIITABLE + case adagfx_commands_e::asciitable: // Show ASCII table + { + String line; + const int16_t start = 0x80 + (argCount >= 1 && nParams[0] >= -4 && nParams[0] < 4 ? nParams[0] * 0x20 : 0); + const uint8_t scale = (argCount == 2 && nParams[1] > 0 && nParams[1] <= 10 ? nParams[1] : 2); + const uint8_t currentScale = _fontscaling; + + if (_fontscaling != scale) { // Set fontscaling + _fontscaling = scale; + _display->setTextSize(_fontscaling); + calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); + } + line.reserve(_textcols); + _display->setCursor(0, 0); + int16_t row = 0; + setColumnRowMode(true); + + for (int16_t i = start; i <= 0xFF && row < _textrows; ++i) { + if ((i % 4 == 0) && (line.length() > (_textcols - 8u))) { // 8 = 4x space + char + printText(line.c_str(), 0, row, _fontscaling, _fgcolor, _bgcolor); + line.clear(); + row++; + } + + if (line.isEmpty()) { + line += formatToHex(i, 2); + } + line += ' '; + line += static_cast(((i == 0x0A) || (i == 0x0D) ? 0x20 : i)); // Show a space instead of CR/LF + } + + if (row < _textrows) { + printText(line.c_str(), 0, row, _fontscaling, _fgcolor, _bgcolor); + } + + setColumnRowMode(currentColRowState); // Restore + + if (_fontscaling != currentScale) { // Restore if needed + _fontscaling = currentScale; + _display->setTextSize(_fontscaling); + calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); + } + success = true; + break; + } + # endif // if ADAGFX_USE_ASCIITABLE + case adagfx_commands_e::font: // font: Change font + + # if ADAGFX_FONTS_INCLUDED + + if (argCount == 1) { + int font_i = 0; + sParams[0].toLowerCase(); + + constexpr int font_max = NR_ELEMENTS(fontargs); + + if ((nParams[0] > 0) || equals(sParams[0], F("0"))) { + font_i = AdaGFXgetFontIndexForFontId(nParams[0]); // Set font by fontId + } else { + font_i = GetCommandCode(sParams[0].c_str(), adagfx_fonts); + } + + if ((font_i >= 0) && (font_i < font_max)) { + _fontId = fontargs[font_i]._fontId; + _display->setFont(fontargs[font_i]._f); + calculateTextMetrics(fontargs[font_i]._width, + fontargs[font_i]._height, + fontargs[font_i]._offset, + fontargs[font_i]._proportional); + success = true; + } + } + # endif // if ADAGFX_FONTS_INCLUDED + break; + case adagfx_commands_e::l: // l: Line + + if (argCount == 5) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_0_1 || + invCoord_2_3)) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + _display->drawLine(nParams[0] + _xo, nParams[1] + _yo, nParams[2] + _xo, nParams[3] + _yo, + AdaGFXparseColor(sParams[4], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::lh: // lh: Horizontal line + + if (argCount == 3) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!((nParams[0] < 0) || (nParams[0] > res_x))) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + _display->drawFastHLine(_xo, nParams[0] + _yo, nParams[1], AdaGFXparseColor(sParams[2], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::lv: // lv: Vertical line + + if (argCount == 3) + { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!((nParams[0] < 0) || (nParams[0] > res_y))) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + _display->drawFastVLine(nParams[0] + _xo, _yo, nParams[1], AdaGFXparseColor(sParams[2], _colorDepth)); + success = true; + } + } + break; + # if ADAGFX_ENABLE_EXTRA_CMDS + case adagfx_commands_e::lm: + case adagfx_commands_e::lmr: // lm/lmr: Multi-line, multiple coordinates + + if (argCount >= 5) { + uint16_t mcolor = AdaGFXparseColor(sParams[0], _colorDepth); + bool mloop = true; + uint8_t parCount = 0; + uint8_t optCount = 0; + int cx = -1; + int cy = -1; + bool closeLine = false; + const bool relativeMode = (adagfx_commands_e::lmr == subcmd); // Use Relative mode + success = true; + + while (mloop) { + sParams[optCount] = parseString(string, parCount + 4); // 0-offset + 1st and 2nd cmd-argument and 1 for color argument + + if (!validIntFromString(sParams[optCount], nParams[optCount]) && !sParams[optCount].isEmpty()) { + mcolor = AdaGFXparseColor(sParams[optCount], _colorDepth); // Interpret as a color + + if (optCount > 0) { optCount--; } + } + mloop = !sParams[optCount].isEmpty(); + closeLine = equals(sParams[optCount], 'c'); + + if (mloop) { parCount++; optCount++; } // Next argument + + if ((optCount == 4) || closeLine) { // 0..3 = 4th argument or close the line + if (relativeMode) { + nParams[2] += nParams[0]; + nParams[3] += nParams[1]; + } + # if ADAGFX_ARGUMENT_VALIDATION + + if (invalidCoordinates(nParams[0], nParams[1]) || + invalidCoordinates(nParams[2], nParams[3])) { + success = false; + mloop = false; // break out + } else + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if (closeLine) { + nParams[2] = cx; + nParams[3] = cy; + mloop = false; // Exit after closing the line + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AdaGFX: cmd: lm x/y/x1/y1:%d/%d/%d/%d loop:%c color:%s"), + nParams[0], nParams[1], nParams[2], nParams[3], + mloop ? 'T' : 'f', AdaGFXcolorToString(mcolor, _colorDepth).c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + _display->drawLine(nParams[0] + _xo, nParams[1] + _yo, nParams[2] + _xo, nParams[3] + _yo, mcolor); + + if ((cx == -1) && (cy == -1)) { + cx = nParams[0]; + cy = nParams[1]; + } + nParams[0] = nParams[2]; // Move second set to first set + nParams[1] = nParams[3]; + optCount = 2; // Get second set of arguments only + } + } + } + } + break; + # endif // if ADAGFX_ENABLE_EXTRA_CMDS + case adagfx_commands_e::r: // r: Rectangle + case adagfx_commands_e::rf: // rf: Rectangled, filled + + if ((argCount == 5) || + (argCount == 6)) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_0_1 || + invCoord_0_2_1_3)) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if ((adagfx_commands_e::rf == subcmd) && (argCount == 6)) { + _display->fillRect(nParams[0] + _xo, nParams[1] + _yo, nParams[2], nParams[3], AdaGFXparseColor(sParams[5], _colorDepth)); + } + _display->drawRect(nParams[0] + _xo, nParams[1] + _yo, nParams[2], nParams[3], AdaGFXparseColor(sParams[4], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::c: // c: Circle + case adagfx_commands_e::cf: // cf: Circle, filled + + if ((argCount == 4) || + (argCount == 5)) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_0_1 || + invalidCoordinates(nParams[2], 0))) // Also check radius + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if ((adagfx_commands_e::cf == subcmd) && (argCount == 5)) { + _display->fillCircle(nParams[0] + _xo, nParams[1] + _yo, nParams[2], AdaGFXparseColor(sParams[4], _colorDepth)); + } + _display->drawCircle(nParams[0] + _xo, nParams[1] + _yo, nParams[2], AdaGFXparseColor(sParams[3], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::t: // t: Triangle + case adagfx_commands_e::tf: // tf: Triangle, filled + + if ((argCount == 7) || + (argCount == 8)) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_0_1 || + invCoord_2_3 || + invalidCoordinates(nParams[4], nParams[5]))) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if ((adagfx_commands_e::tf == subcmd) && (argCount == 8)) { + _display->fillTriangle(nParams[0] + _xo, + nParams[1] + _yo, + nParams[2] + _xo, + nParams[3] + _yo, + nParams[4] + _xo, + nParams[5] + _yo, + AdaGFXparseColor(sParams[7], _colorDepth)); + } + _display->drawTriangle(nParams[0] + _xo, + nParams[1] + _yo, + nParams[2] + _xo, + nParams[3] + _yo, + nParams[4] + _xo, + nParams[5] + _yo, + AdaGFXparseColor(sParams[6], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::rr: // rr: Rounded rectangle + case adagfx_commands_e::rrf: // rrf: Rounded rectangle, filled + + if ((argCount == 6) || + (argCount == 7)) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_0_1 || + invCoord_0_2_1_3 || + invalidCoordinates(nParams[4], 0))) // Also check radius + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + if ((adagfx_commands_e::rrf == subcmd) && (argCount == 7)) { + _display->fillRoundRect(nParams[0] + _xo, + nParams[1] + _yo, + nParams[2], + nParams[3], + nParams[4], + AdaGFXparseColor(sParams[6], _colorDepth)); + } + _display->drawRoundRect(nParams[0] + _xo, + nParams[1] + _yo, + nParams[2], + nParams[3], + nParams[4], + AdaGFXparseColor(sParams[5], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::px: // px: Pixel + + if (argCount == 3) { + # if ADAGFX_ARGUMENT_VALIDATION + + if (!invCoord_0_1) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + _display->drawPixel(nParams[0] + _xo, nParams[1] + _yo, AdaGFXparseColor(sParams[2], _colorDepth)); + success = true; + } + } + break; + case adagfx_commands_e::pxh: + case adagfx_commands_e::pxv: // pxh/pxv: Pixels, hor./vert. + + if (argCount > 2) { + // incremented merged loop is smaller than 2 separate loops + # if ADAGFX_ARGUMENT_VALIDATION + + if (!invCoord_0_1) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + _display->startWrite(); + _display->writePixel(nParams[0] + _xo, nParams[1] + _yo, AdaGFXparseColor(sParams[2], _colorDepth)); + loop = true; + uint8_t h = 0; + uint8_t v = 0; + bool isPxh = (adagfx_commands_e::pxh == subcmd); + + if (isPxh) { + h++; + } else { + v++; + } + + while (loop) { + String color = parseString(string, h + v + 5); // 5 = 2 + 3 already parsed merged loop is smaller than 2 separate loops + + if (color.isEmpty() + # if ADAGFX_ARGUMENT_VALIDATION + || invalidCoordinates(nParams[0] + h + _xo, nParams[1] + v + _yo) + # endif // if ADAGFX_ARGUMENT_VALIDATION + ) { + loop = false; + } else { + _display->writePixel(nParams[0] + h + _xo, nParams[1] + v + _yo, AdaGFXparseColor(color, _colorDepth)); + + if (isPxh) { + h++; + } else { + v++; + } + } + delay(0); + } + _display->endWrite(); + success = true; + } + } + break; + # if ADAGFX_ENABLE_BMP_DISPLAY + case adagfx_commands_e::bmp: // bmp,x,y,filename.bmp : show bmp from file + + if ((argCount == 3) && !sParams[2].isEmpty()) { + success = showBmp(sParams[2], nParams[0] + _xo, nParams[1] + _yo); + } + break; + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_DRAW + case adagfx_commands_e::btn: + + if ((argCount >= 8) && (nParams[7] != 0)) { + // btn,state,m,x,y,w,h,id,type[,ONclr,OFFclr,Captionclr,fontscale,ONcaption,OFFcapt,Borderclr,DisabClr,DisabCaptclr],TaskIndex,Group,SelGrp,objectname + // ev: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17,18,19,20,21 + // nP: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16,17,18,19,20 + // : Draw a button + // m=mode: -2 = disabled, -1 = initial, 0 = default + // state: 0 = off, 1 = on, -2 = off + disabled, -1 = on + disabled + // id: < 0 = clear area + // type & 0x0F: 0 = none, 1 = rectangle, 2 = rounded rect., 3 = circle, + // type & 0xF0 = CenterAligned, LeftAligned, TopAligned, RightAligned, BottomAligned, LeftTopAligned, RightTopAligned, + // RightBottomAligned, LeftBottomAligned, NoCaption, Bitmap, Slider (not a button) + // (*clr = color, TaskIndex, Group and SelGrp are ignored) + # if ADAGFX_ARGUMENT_VALIDATION + + if (!(invCoord_2_3 || + invalidCoordinates(nParams[2] + nParams[4], nParams[3] + nParams[5]))) + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + // All checked out OK + // Default values + uint16_t onColor = ADAGFX_BLUE; + uint16_t offColor = ADAGFX_RED; + uint16_t captionColor = ADAGFX_WHITE; + uint8_t fontScale = 0; + uint16_t borderColor = ADAGFX_WHITE; + uint16_t disabledColor = 0x9410; // Medium grey + uint16_t disabledCaptionColor = 0x5A69; // Dark grey + success = true; + + if (!sParams[8].isEmpty()) { onColor = AdaGFXparseColor(sParams[8], _colorDepth); } + + if (!sParams[9].isEmpty()) { offColor = AdaGFXparseColor(sParams[9], _colorDepth); } + + if (!sParams[10].isEmpty()) { captionColor = AdaGFXparseColor(sParams[10], _colorDepth); } + + if ((nParams[11] > 0) && (nParams[11] <= 10)) { fontScale = nParams[11]; } + + if (!sParams[14].isEmpty()) { borderColor = AdaGFXparseColor(sParams[14], _colorDepth); } + + if (!sParams[15].isEmpty()) { disabledColor = AdaGFXparseColor(sParams[15], _colorDepth); } + + if (!sParams[16].isEmpty()) { disabledCaptionColor = AdaGFXparseColor(sParams[16], _colorDepth); } + + uint16_t fillColor = onColor; + uint16_t textColor = captionColor; + const bool clearArea = nParams[7] < 0; + nParams[7] = std::abs(nParams[7]); + + const Button_type_e buttonType = static_cast(nParams[7] & 0x0F); + const Button_layout_e buttonLayout = static_cast(nParams[7] & 0xF0); + + // Check mode & state: -2, -1, 0, 1 to select used colors + # if ADAGFX_ENABLE_BUTTON_SLIDER + + if (buttonLayout == Button_layout_e::Slider) { + if (nParams[1] == -2) { + fillColor = disabledColor; + textColor = disabledCaptionColor; + } else if (clearArea) { + fillColor = _bgcolor; // + borderColor = _bgcolor; + } + } else + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + { + if (nParams[0] == 0) { + fillColor = offColor; + } + + if ((nParams[1] == -2) || (nParams[0] < 0)) { + fillColor = disabledColor; + textColor = disabledCaptionColor; + } else if (clearArea) { + fillColor = _bgcolor; // + borderColor = _bgcolor; + } + } + + // Clear the area? + if ((buttonType != Button_type_e::None) || + clearArea) { + drawButtonShape( + # if ADAGFX_ENABLE_BUTTON_SLIDER + buttonLayout == Button_layout_e::Slider ? Button_type_e::Square : + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + buttonType, // Clear full square for slider + nParams[2] + _xo, nParams[3] + _yo, nParams[4], nParams[5], + _bgcolor, _bgcolor); + } + + // Check button-type bits (mask: 0x0F) to draw correct shape + if (!clearArea) { + drawButtonShape(buttonType, + nParams[2] + _xo, nParams[3] + _yo, nParams[4], nParams[5], + fillColor, borderColor); + } + + // Display caption? (or bitmap) + if (!clearArea && + (buttonLayout != Button_layout_e::NoCaption)) { + int16_t x1, y1; + uint16_t w1, h1, w2, h2; + String newString; + + // Determine alignment parameters + if ((nParams[0] == 1) || (nParams[0] == -1) // 1 = on+enabled, -1 = on+disabled + # if ADAGFX_ENABLE_BUTTON_SLIDER + || (buttonLayout == Button_layout_e::Slider) + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + ) { + newString = sParams[12].isEmpty() ? sParams[6] : sParams[12]; + } else { + newString = sParams[13].isEmpty() ? sParams[6] : sParams[13]; + } + newString = AdaGFXparseTemplate(newString, 20); + + _display->setTextSize(fontScale); // set scaling + _display->getTextBounds(newString, 0, 0, &x1, &y1, &w1, &h1); // get caption length and height in pixels + _display->getTextBounds(F(" "), 0, 0, &x1, &y1, &w2, &h2); // measure space width for little margins + + // Check button-alignment bits (mask 0xF0) for caption placement, modifies the x/y arguments passed! + // Little margin is: from left/right: half of the width of a space, from top/bottom: half of height of the font used + + switch (buttonLayout) { + case Button_layout_e::CenterAligned: + nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically + nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically + break; + case Button_layout_e::LeftAligned: + nParams[2] += w2 / 2; // A little margin from left + nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically + break; + case Button_layout_e::TopAligned: + nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically + nParams[3] += h1 / 2; // A little margin from top + break; + case Button_layout_e::RightAligned: + nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin + nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically + break; + case Button_layout_e::BottomAligned: + nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically + nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin + break; + case Button_layout_e::LeftTopAligned: + nParams[2] += w2 / 2; // A little margin from left + nParams[3] += h1 / 2; // A little margin from top + break; + case Button_layout_e::RightTopAligned: + nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin + nParams[3] += h1 / 2; // A little margin from top + break; + case Button_layout_e::RightBottomAligned: + nParams[2] += (nParams[4] - w1) - w2 / 2; // right-align + a little margin + nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin + break; + case Button_layout_e::LeftBottomAligned: + nParams[2] += w2 / 2; // A little margin from left + nParams[3] += (nParams[5] - h1 * 1.5); // bottom align + a little margin + break; + # if ADAGFX_ENABLE_BMP_DISPLAY + case Button_layout_e::Bitmap: + { // Use ON/OFF caption to specify (full) bitmap filename + if (!newString.isEmpty()) { + int32_t offX = 0; // Allow optional arguments for x and y offset values, usage: + int32_t offY = 0; // [x,[y,]]filename.bmp + + if (newString.indexOf(',') > -1) { + String tmp = parseString(newString, 1); + validIntFromString(tmp, offX); + newString = parseStringToEndKeepCase(newString, 2); + + if (newString.indexOf(',') > -1) { + tmp = parseString(newString, 1); + validIntFromString(tmp, offY); + newString = parseStringToEndKeepCase(newString, 2); + } + } + success = showBmp(newString, nParams[2] + _xo + offX, nParams[3] + _yo + offY); + } else { + success = false; + } + break; + } + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + case Button_layout_e::NoCaption: + # if ADAGFX_ENABLE_BUTTON_SLIDER + case Button_layout_e::Slider: // Nothing to do here (yet) + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + break; + } + + if ((buttonLayout != Button_layout_e::NoCaption) + # if ADAGFX_ENABLE_BUTTON_SLIDER + && (buttonLayout != Button_layout_e::Slider) + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + # if ADAGFX_ENABLE_BMP_DISPLAY + && (buttonLayout != Button_layout_e::Bitmap) + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + ) { + // Set position and colors, then print + _display->setCursor(nParams[2] + _xo, nParams[3] + _yo); + _display->setTextColor(textColor, textColor); // transparent bg results in button color + _display->print(newString); + + // restore colors + _display->setTextColor(_fgcolor, _bgcolor); + } + # if ADAGFX_ENABLE_BUTTON_SLIDER + + if (buttonLayout == Button_layout_e::Slider) { + // 1) Determine direction from w/h + const bool isVertical = nParams[4] < nParams[5]; // width < height + const bool showAsCircle = borderColor == fillColor; + + // determine value and range + int16_t offI2 = 5; // half of indicator width + int16_t offG2 = 3; // half of Gauge width + int16_t offP = 0; // Offset for indicator + int16_t zeroLine = -1; // Draw a range zero-line at this offset? only when >= 0 + int percentage = 0; + float gaugeValue = 0.0f; + int16_t lowRange = 0; + int16_t highRange = 100; + float rangeFrom = 0.0f; + float rangeTo = 0.0f; + float range = 100.0f; // For percentage the range is 100 + bool useRange = false; + bool hasRangeReversed = false; // Range low value left or top, high value right or bottom + + if (!validFloatFromString(newString, gaugeValue)) { + percentage = nParams[0]; // Value as provided + } + + // Have a range? + if (!sParams[13].isEmpty()) { // Off caption can hold range: , + String tmp = parseString(sParams[13], 1); + const bool validFrom = validFloatFromString(tmp, rangeFrom); + tmp = parseString(sParams[13], 2); + + if (validFrom && validFloatFromString(tmp, rangeTo) && + !essentiallyEqual(rangeFrom, 0.0f) && !essentiallyEqual(rangeTo, 0.0f)) { + useRange = true; + lowRange = static_cast(rangeFrom); + highRange = static_cast(rangeTo); + hasRangeReversed = lowRange > highRange; // Range high value left or top, low value right or bottom? + } + } + + // 2) Draw center-line from 0 to 100% + // 3) Draw gauge for used/filled part + // 4) Draw indicator at correct percentage index + if (showAsCircle) { // Circle indicator or full width bar indicator + offI2 = (isVertical ? nParams[4] : nParams[5]) / 4; + } + + if (useRange) { // Calculate range-boundaries + range = abs(max(rangeTo, rangeFrom) - min(rangeFrom, rangeTo)); + + if (gaugeValue > max(rangeTo, rangeFrom)) { + gaugeValue = max(rangeTo, rangeFrom); + } else if (gaugeValue < min(rangeFrom, rangeTo)) { + gaugeValue = min(rangeFrom, rangeTo); + } else { + gaugeValue -= min(rangeFrom, rangeTo); // Give it the correct Offset + } + + if (((lowRange < 0) && (highRange > 0)) || + ((lowRange > 0) && (highRange < 0))) { + zeroLine = map(0, lowRange, highRange, 0, isVertical ? nParams[5] : nParams[4]); + } + } + percentage = static_cast(gaugeValue); + + offP = ((((isVertical ? nParams[5] : nParams[4]) - (2 * offI2)) / range) * percentage) - 1; // keep within button borders + + if (hasRangeReversed) { + offP = (isVertical ? nParams[5] : nParams[4]) - (2 * offI2) - offP - 1; // flip + } + + if (isVertical) { + // centerline + _display->drawLine(nParams[2] + _xo + nParams[4] / 2, nParams[3] + _yo + (nParams[5] - offI2 - 1), + nParams[2] + _xo + nParams[4] / 2, nParams[3] + _yo + offI2, textColor); + + if (zeroLine > -1) { + _display->drawLine(nParams[2] + _xo, nParams[3] + _yo + (nParams[5] - zeroLine - 1), + nParams[2] + _xo + nParams[4], nParams[3] + _yo + (nParams[5] - zeroLine - 1), textColor); + } + + // Gauge + if (hasRangeReversed) { + int16_t bar = nParams[5] - (2 * offI2); + _display->fillRoundRect(nParams[2] + _xo + (nParams[4] / 2) - offG2, nParams[3] + _yo + offI2 + 0, + 2 * offG2, bar - offP, offG2, textColor); + } else { + _display->fillRoundRect(nParams[2] + _xo + (nParams[4] / 2) - offG2, nParams[3] + _yo + (nParams[5] - offI2 - offP - 1), + 2 * offG2, offP, offG2, textColor); + } + + // Indicator/drag-handle + if (showAsCircle) { // Circle indicator + _display->fillCircle(nParams[2] + _xo + nParams[4] / 2, + nParams[3] + _yo + (nParams[5] - offI2 - offP - 2) + (percentage == 100 ? 1 : 0), + trunc(nParams[4] / 4), textColor); + } else { + _display->fillRoundRect(nParams[2] + _xo + 1, + nParams[3] + _yo + (nParams[5] - (2 * offI2) - offP - (hasRangeReversed ? 0 : 1)), + nParams[4] - 2, 2 * offI2, offI2, textColor); + } + } else { // : if !isVertical -> isHorizontal + // centerline + _display->drawLine(nParams[2] + _xo + offI2 + 1, nParams[3] + _yo + nParams[5] / 2, + nParams[2] + _xo + nParams[4] - offI2, nParams[3] + _yo + nParams[5] / 2, textColor); + + if (zeroLine > -1) { + _display->drawLine(nParams[2] + _xo + zeroLine + 1, nParams[3] + _yo, + nParams[2] + _xo + zeroLine + 1, nParams[3] + _yo + nParams[5], textColor); + } + + // Gauge + if (hasRangeReversed) { + int16_t bar = nParams[4] - (2 * offI2); + _display->fillRoundRect(nParams[2] + _xo + offI2 + offP + 2, nParams[3] + _yo + (nParams[5] / 2) - offG2, + bar - offP, offG2 * 2, offG2, textColor); + } else { + _display->fillRoundRect(nParams[2] + _xo + offI2 + 1, nParams[3] + _yo + (nParams[5] / 2) - offG2, + offP, offG2 * 2, offG2, textColor); + } + + // Indicator/drag-handle + if (showAsCircle) { // Circle indicator + _display->fillCircle(nParams[2] + _xo + offP + offI2 + 1 - (percentage == 100 ? 1 : 0), + nParams[3] + _yo + (nParams[5] / 2), + trunc(nParams[5] / 4), textColor); + } else { + _display->fillRoundRect(nParams[2] + _xo + offP + (hasRangeReversed ? 0 : 1), nParams[3] + _yo + 1, + 2 * offI2, nParams[5] - 2, offI2, textColor); + } + } + + // 5) Draw percentage in center if Fontsize > 0 + if (fontScale > 0) { + nParams[2] += (nParams[4] / 2 - w1 / 2); // center horizontically + nParams[3] += (nParams[5] / 2 - h1 / 2); // center vertically + _display->setCursor(nParams[2] + _xo, nParams[3] + _yo); + _display->setTextColor(textColor, fillColor); // regular bg color for readability + _display->print(newString); + } + } + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + + // restore font scaling + _display->setTextSize(_fontscaling); + } + } + } + break; + # endif // if ADAGFX_ENABLE_BUTTON_DRAW + # if ADAGFX_ENABLE_FRAMED_WINDOW + case adagfx_commands_e::win: // win: select window by id + + if ((argCount >= 1) && (argCount <= 2)) { + success = selectWindow(nParams[0], nParams[1]); + } + break; + case adagfx_commands_e::defwin: // defwin: define window + + if ((argCount >= 5) && (argCount <= 6)) { + const int8_t rot = _rotation; + # if ADAGFX_ARGUMENT_VALIDATION + const int16_t curWin = getWindow(); + + if (curWin != 0) { selectWindow(0); } // Validate against raw window coordinates + + if (argCount == 6) { setRotation(nParams[5]); } // Use requested rotation + + if (invCoord_0_1 || + invCoord_0_2_1_3) { + if (curWin != 0) { selectWindow(curWin); } // restore current window + + if (rot != _rotation) { setRotation(rot); } // Restore rotation + } else + # endif // if ADAGFX_ARGUMENT_VALIDATION + { + # if ADAGFX_ARGUMENT_VALIDATION + + if (curWin != 0) { selectWindow(curWin); } // restore current window + # endif // if ADAGFX_ARGUMENT_VALIDATION + + if (nParams[4] > 0) { // Window 0 is the raw window, having the full size, created at initialization of this + success = true; + + // helper instance + # ifndef BUILD_NO_DEBUG + int16_t win = // avoid compiler warning + # endif // ifndef BUILD_NO_DEBUG + defineWindow(nParams[0], + nParams[1], + nParams[2], + nParams[3], + nParams[4], + argCount == 6 ? nParams[5] : _rotation); + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("AdaGFX defined window id: %d"), win)); + } + # endif // ifndef BUILD_NO_DEBUG + + if (rot != _rotation) { setRotation(rot); } // Restore rotation, also update new window + } + } + } + break; + case adagfx_commands_e::delwin: // delwin: delete window, don't delete window 0 + + if ((argCount == 1) && (nParams[0] > 0)) { + // logWindows(F(" deLwin ")); // use for debugging only + + success = deleteWindow(nParams[0]); + } + break; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + } + return success; +} + +/**************************************************************************** + * Get a config value from the plugin + ***************************************************************************/ +# if ADAGFX_ENABLE_GET_CONFIG_VALUE +const char adagfx_getcommands[] PROGMEM = "win|iswin|width|height|length|textheight|rot|txs|tpm" + # if ADAGFX_FONTS_INCLUDED + "|font" + # endif // if ADAGFX_FONTS_INCLUDED +; +enum class adagfx_getcommands_e : int8_t { + invalid = -1, + win = 0, + iswin, + width, + height, + length, + textheight, + rot, + txs, + tpm, + # if ADAGFX_FONTS_INCLUDED + font, + # endif // if ADAGFX_FONTS_INCLUDED +}; + +bool AdafruitGFX_helper::pluginGetConfigValue(String& string) { + bool success = false; + char sep = '.'; + + if ((-1 == string.indexOf(sep)) && (string.indexOf(',') >= 0)) { + sep = ','; + } + String command = parseString(string, 1, sep); + + const int command_i = GetCommandCode(command.c_str(), adagfx_getcommands); + const adagfx_getcommands_e cmd = static_cast(command_i); + + switch (cmd) { + case adagfx_getcommands_e::win: + { // win: get current window id + # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled + string = getWindow(); + success = true; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + break; + } + case adagfx_getcommands_e::iswin: + { // iswin: check if windows exists + # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled + command = parseString(string, 2, sep); + int32_t win = 0; + + if (validIntFromString(command, win)) { + string = validWindow(static_cast(win)); + } else { + string = '0'; + } + success = true; // Always correct, just return 'false' if wrong + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + break; + } + case adagfx_getcommands_e::width: + case adagfx_getcommands_e::height: + // width/height: get window width or height + { + # if ADAGFX_ENABLE_FRAMED_WINDOW // if feature enabled + uint16_t w = 0, h = 0; + getWindowLimits(w, h); + + if (adagfx_getcommands_e::width == cmd) { + string = w; + } else { + string = h; + } + success = true; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + break; + } + case adagfx_getcommands_e::length: + case adagfx_getcommands_e::textheight: + // length/textheight: get text length or height + { + int16_t x1, y1; + uint16_t w1, h1; + String newString = AdaGFXparseTemplate(parseStringToEndKeepCaseNoTrim(string, 2), 0); + _display->getTextBounds(newString, 0, 0, &x1, &y1, &w1, &h1); // Count length and height + + if (adagfx_getcommands_e::length == cmd) { + string = w1; + } else { + string = h1; + } + success = true; + break; + } + case adagfx_getcommands_e::rot: + { // rot: get current rotation setting + string = _rotation; + success = true; + break; + } + case adagfx_getcommands_e::txs: + { // txs: get current text scaling setting + string = _fontscaling; + success = true; + break; + } + case adagfx_getcommands_e::tpm: + { // tpm: get current text print mode setting + string = static_cast(_textPrintMode); + success = true; + break; + } + # if ADAGFX_FONTS_INCLUDED + case adagfx_getcommands_e::font: + string = AdaGFXgetFontName(_fontId); + success = true; + break; + # endif // if ADAGFX_FONTS_INCLUDED + case adagfx_getcommands_e::invalid: + break; + } + + return success; +} + +# endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE + +/**************************************************************************** + * draw a button shape with provided color, can also clear a previously drawn button + ***************************************************************************/ +# if ADAGFX_ENABLE_BUTTON_DRAW +void AdafruitGFX_helper::drawButtonShape(const Button_type_e& buttonType, + const int & x, + const int & y, + const int & w, + const int & h, + const uint16_t & fillColor, + const uint16_t & borderColor) { + switch (buttonType) { + case Button_type_e::Square: // Rectangle + { + _display->fillRect(x, y, w, h, fillColor); + _display->drawRect(x, y, w, h, borderColor); + break; + } + case Button_type_e::Rounded: // Rounded Rectangle + { + int16_t radius = (w + h) / 20; // average 10 % corner radius w/h + _display->fillRoundRect(x, y, w, h, radius, fillColor); + _display->drawRoundRect(x, y, w, h, radius, borderColor); + break; + } + case Button_type_e::Circle: // Circle + { + int16_t radius = (w + h) / 4; // average radius + _display->fillCircle(x + (w / 2), y + (h / 2), radius, fillColor); + _display->drawCircle(x + (w / 2), y + (h / 2), radius, borderColor); + break; + } + case Button_type_e::ArrowLeft: + { // draw: left-center, right-top, right-bottom + _display->fillTriangle(x, y + h / 2, x + w, y, + x + w, y + h, fillColor); + _display->drawTriangle(x, y + h / 2, x + w, y, + x + w, y + h, borderColor); + break; + } + case Button_type_e::ArrowUp: + { // draw: top-center, right-bottom, left-bottom + _display->fillTriangle(x + w / 2, y, x + w, y + h, + x, y + h, fillColor); + _display->drawTriangle(x + w / 2, y, x + w, y + h, + x, y + h, borderColor); + break; + } + case Button_type_e::ArrowRight: + { // draw: left-top, right-center, left-bottom + _display->fillTriangle(x, y, x + w, y + h / 2, + x, y + h, fillColor); + _display->drawTriangle(x, y, x + w, y + h / 2, + x, y + h, borderColor); + break; + } + case Button_type_e::ArrowDown: + { // draw: left-top, right-top, bottom-center + _display->fillTriangle(x, y, x + w, y, + x + w / 2, y + h, fillColor); + _display->drawTriangle(x, y, x + w, y, + x + w / 2, y + h, borderColor); + break; + } + case Button_type_e::None: + break; + } +} + +# endif // if ADAGFX_ENABLE_BUTTON_DRAW + +/**************************************************************************** + * printText: Print text on display at a specific pixel or column/row location + ***************************************************************************/ +void AdafruitGFX_helper::printText(const char *string, + const int16_t & X, + const int16_t & Y, + const uint8_t & textSize, + const uint16_t& color, + uint16_t bkcolor, + const uint16_t& maxWidth) { + int16_t _x = X; + int16_t _y = Y + (_heightOffset * textSize); + uint16_t _w = 0; + int16_t xText = 0; + int16_t yText = 0; + uint16_t wText = 0; + uint16_t wChar = 0; + uint16_t hText = 0; + int16_t oTop = 0; + int16_t oBottom = 0; + int16_t oLeft = 0; + uint16_t xOffset = 0; + uint16_t hChar1 = 0; + uint16_t wChar1 = 0; + String newString = string; + uint16_t res_x = _res_x; + + # if ADAGFX_ENABLE_FRAMED_WINDOW + uint16_t res_y = _res_y; + uint16_t yOffset = 0; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + # if ADAGFX_ENABLE_FRAMED_WINDOW + getWindowLimits(res_x, res_y); + getWindowOffsets(xOffset, yOffset); + _x += xOffset; + _y += yOffset; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + _display->setTextSize(textSize); + _display->getTextBounds(String('A'), 0, 0, &xText, &yText, &wChar1, &hChar1); // Calculate ~1 char height + + if (_columnRowMode) { + _x = X * (_fontwidth * textSize); // We need this multiple times + + if (15 == _lineSpacing) { + _y = (Y * (_fontheight * textSize)) + (_heightOffset * textSize); + } else { + _y = (Y * (hChar1 + _lineSpacing)) + _heightOffset; // Apply explicit line spacing + } + } + + _display->setCursor(_x, _y); + _display->setTextColor(color, bkcolor); + + if (_textPrintMode != AdaGFXTextPrintMode::ContinueToNextLine) { + # if ADAGFX_ENABLE_FRAMED_WINDOW + + if (0 == getWindow()) // Only on Window 0 + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + { + wChar = wChar1; + } + _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Calculate length + + while ((newString.length() > 0) && (((_x - xOffset) + wText) > res_x + wChar)) { + newString.remove(newString.length() - 1); // Cut last character off + _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Re-calculate length + } + } + + _display->getTextBounds(newString, _x, _y, &xText, &yText, &wText, &hText); // Calculate length + + if ((maxWidth > 0) && ((_x - xOffset) + maxWidth <= res_x)) { + res_x = (_x - xOffset) + maxWidth; + _w = maxWidth; + + if ((_textPrintMode == AdaGFXTextPrintMode::TruncateExceedingCentered) && + (maxWidth > wText)) { + oLeft = (_w - (wText + 2 * (xText - _x))) / 2; + } + } else { + _w = wText + 2 * (xText - _x); + } + + if (_textBackFill && (color != bkcolor)) { // Fill extra space above and below text + oTop -= textSize; + oBottom += textSize; + _y += textSize; + } + + if ((_textPrintMode == AdaGFXTextPrintMode::ClearThenTruncate) || + (color != bkcolor)) { // Clear before print + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("printText: clear: _x:%d, oTop:%d, _y:%d, xTx:%d, yTx:%d, wTx:%d," + " hTx:%d, oBot:%d, _res_x/max:%d/%d, str:%s"), + _x, oTop, _y, xText, yText, wText, + hText, oBottom, _res_x, res_x, newString.c_str())); + } + # endif // ifndef BUILD_NO_DEBUG + + if (bkcolor == color) { bkcolor = _bgcolor; } // To get at least the text readable + + if (_textPrintMode == AdaGFXTextPrintMode::ClearThenTruncate) { // oTop is negative so subtract to add... + _display->fillRect(_x + oTop, yText, res_x - (_x - xOffset), hText + oBottom - oTop, bkcolor); // Clear text area to right edge of + // screen + } else { + _display->fillRect(_x + oTop, yText, _w, hText + oBottom - oTop, bkcolor); // Clear text area + } + + delay(0); + } + + _display->setCursor(_x + oLeft, _y); // add left offset to center, _y may be updated + _display->print(newString); +} + +/**************************************************************************** + * getTextSize length and height in pixels + ***************************************************************************/ +uint16_t AdafruitGFX_helper::getTextSize(const String& text, + uint16_t & h) { + int16_t x; + int16_t y; + uint16_t w; + + _display->getTextBounds(text.c_str(), 0, 0, &x, &y, &w, &h); // Count length and height in pixels + return w; +} + +/**************************************************************************** + * color565: convert r, g, b colors to rgb565 (by bit-shifting) + ***************************************************************************/ +uint16_t color565(const uint8_t& red, + const uint8_t& green, + const uint8_t& blue) { + return ((red & 0xF8) << 8) | ((green & 0xFC) << 3) | (blue >> 3); +} + +/**************************************************************************** + * AdaGFXparseColor: translate color name, rgb565 hex #rGgb or rgb hex #RRGGBB to an RGB565 value, + * also applies color reduction to mono(2), duo(3), quadro(4), septo(7), octo(8), quinto(16)-chrome colors + ***************************************************************************/ + +// Parse color string to RGB565 color +// param [in] s : The color string (white, red, ...) +// Param [in] colorDepth: The requiresed color depth, default: FullColor +// param [in] defaultWhite: Return White color if empty, default: true +// return : color (default ADAGFX_WHITE) +const char adagfx_colornames[] PROGMEM = "black|white|inverse|red|yellow|dark|light|green|blue|orange|navy|darkcyan|" + "darkgreen|maroon|purple|olive|lightgrey|darkgrey|cyan|magenta|greenyellow|pink"; +enum class adagfx_colornames_e : int8_t { + invalid = -1, + black = 0, + white, + inverse, + red, + yellow, + dark, + light, + green, + blue, + orange, + navy, + darkcyan, + darkgreen, + maroon, + purple, + olive, + lightgrey, + darkgrey, + cyan, + magenta, + greenyellow, + pink, +}; + +uint16_t AdaGFXparseColor(String & s, + const AdaGFXColorDepth& colorDepth, + const bool emptyIsBlack) { + s.toLowerCase(); + int32_t result = -1; // No result yet + const int color_i = GetCommandCode(s.c_str(), adagfx_colornames); + + const adagfx_colornames_e color = static_cast(color_i); + + if ((colorDepth == AdaGFXColorDepth::Monochrome) || + (colorDepth == AdaGFXColorDepth::BlackWhiteRed) || + (colorDepth == AdaGFXColorDepth::BlackWhite2Greyscales)) { // Only a limited set of colors is supported + switch (color) { + case adagfx_colornames_e::black: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK); + case adagfx_colornames_e::inverse: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE); + case adagfx_colornames_e::yellow: // Synonym for red + case adagfx_colornames_e::red: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED); + case adagfx_colornames_e::dark: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK); + case adagfx_colornames_e::light: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT); + + // case adagfx_colornames_e::white: return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE); + // If we get this far, return the default + default: + return static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE); + } + # if ADAGFX_SUPPORT_7COLOR + } else if (colorDepth == AdaGFXColorDepth::SevenColor) { + switch (color) { + case adagfx_colornames_e::black: result = static_cast(AdaGFX7Colors::ADAGFX7C_BLACK); break; + case adagfx_colornames_e::white: result = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); break; + case adagfx_colornames_e::green: result = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); break; + case adagfx_colornames_e::blue: result = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); break; + case adagfx_colornames_e::red: result = static_cast(AdaGFX7Colors::ADAGFX7C_RED); break; + case adagfx_colornames_e::yellow: result = static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW); break; + case adagfx_colornames_e::orange: result = static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE); break; + default: + break; + } + # endif // if ADAGFX_SUPPORT_7COLOR + } else { // Some predefined colors + switch (color) { + case adagfx_colornames_e::black: result = ADAGFX_BLACK; break; + case adagfx_colornames_e::navy: result = ADAGFX_NAVY; break; + case adagfx_colornames_e::darkgreen: result = ADAGFX_DARKGREEN; break; + case adagfx_colornames_e::darkcyan: result = ADAGFX_DARKCYAN; break; + case adagfx_colornames_e::maroon: result = ADAGFX_MAROON; break; + case adagfx_colornames_e::purple: result = ADAGFX_PURPLE; break; + case adagfx_colornames_e::olive: result = ADAGFX_OLIVE; break; + case adagfx_colornames_e::lightgrey: result = ADAGFX_LIGHTGREY; break; + case adagfx_colornames_e::darkgrey: result = ADAGFX_DARKGREY; break; + case adagfx_colornames_e::blue: result = ADAGFX_BLUE; break; + case adagfx_colornames_e::green: result = ADAGFX_GREEN; break; + case adagfx_colornames_e::cyan: result = ADAGFX_CYAN; break; + case adagfx_colornames_e::red: result = ADAGFX_RED; break; + case adagfx_colornames_e::magenta: result = ADAGFX_MAGENTA; break; + case adagfx_colornames_e::yellow: result = ADAGFX_YELLOW; break; + case adagfx_colornames_e::white: result = ADAGFX_WHITE; break; + case adagfx_colornames_e::orange: result = ADAGFX_ORANGE; break; + case adagfx_colornames_e::greenyellow: result = ADAGFX_GREENYELLOW; break; + case adagfx_colornames_e::pink: result = ADAGFX_PINK; break; + default: + break; + } + } + + // Parse default hex #rgb565 (hex) string (1-4 hex nibbles accepted!) + if ((result == -1) && (s.length() >= 2) && (s.length() <= 5) && (s[0] == '#')) { + result = hexToUL(&s[1]); + } + + // Parse default hex #RRGGBB string (must be 6 hex nibbles!) + if ((result == -1) && (s.length() == 7) && (s[0] == '#')) { + // convrt to long value in base16, then split up into r, g, b values + const uint32_t number = hexToUL(&s[1]); + + // uint32_t r = number >> 16 & 0xFF; + // uint32_t g = number >> 8 & 0xFF; + // uint32_t b = number & 0xFF; + // convert to color565 (as used by adafruit lib) + result = color565(number >> 16 & 0xFF, number >> 8 & 0xFF, number & 0xFF); + } + + if ((result == -1) || (result == ADAGFX_WHITE)) { // Default & don't convert white + # if ADAGFX_SUPPORT_8and16COLOR + + if ( + # if ADAGFX_SUPPORT_7COLOR + (colorDepth >= AdaGFXColorDepth::SevenColor) && + # endif // if ADAGFX_SUPPORT_7COLOR + (colorDepth <= AdaGFXColorDepth::SixteenColor)) { + result = static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK); // Monochrome fallback, compatible 7-color + } else + # endif // if ADAGFX_SUPPORT_8and16COLOR + { + if (emptyIsBlack) { + result = ADAGFX_BLACK; + } else { + result = ADAGFX_WHITE; // Color fallback value + } + } + } else { + // Reduce colors? + switch (colorDepth) { + case AdaGFXColorDepth::Monochrome: + case AdaGFXColorDepth::BlackWhiteRed: + case AdaGFXColorDepth::BlackWhite2Greyscales: + // Unsupported at this point, but compiler needs the cases because of the enum class + break; + # if ADAGFX_SUPPORT_7COLOR + case AdaGFXColorDepth::SevenColor: + result = AdaGFXrgb565ToColor7(result); // Convert + break; + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::EightColor: + result = color565((result >> 11 & 0x1F) / 4, (result >> 5 & 0x3F) / 4, (result & 0x1F) / 4); // reduce colors factor 4 + break; + case AdaGFXColorDepth::SixteenColor: + result = color565((result >> 11 & 0x1F) / 2, (result >> 5 & 0x3F) / 2, (result & 0x1F) / 2); // reduce colors factor 2 + break; + # endif // if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::FullColor: + // No color reduction + break; + } + } + return static_cast(result); +} + +const __FlashStringHelper* AdaGFXcolorToString_internal(const uint16_t & color, + const AdaGFXColorDepth& colorDepth, + bool blackIsEmpty); + +// Add a single optionvalue of a color to a datalist (internal/private) +void AdaGFXaddHtmlDataListColorOptionValue(uint16_t color, + AdaGFXColorDepth colorDepth) { + const __FlashStringHelper *clr = AdaGFXcolorToString_internal(color, colorDepth, false); + + if (!equals(clr, '*')) { + datalistAddValue(clr); + } +} + +/***************************************************************************************** + * Generate a html 'datalist' of the colors available for selected colorDepth, with id provided + ****************************************************************************************/ +void AdaGFXHtmlColorDepthDataList(const __FlashStringHelper *id, + const AdaGFXColorDepth & colorDepth) { + datalistStart(id); + + switch (colorDepth) { + case AdaGFXColorDepth::BlackWhiteRed: + case AdaGFXColorDepth::BlackWhite2Greyscales: + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT), colorDepth); + + // Fall through + case AdaGFXColorDepth::Monochrome: + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE), colorDepth); + break; + # if ADAGFX_SUPPORT_7COLOR + case AdaGFXColorDepth::SevenColor: + { + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_BLACK), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_WHITE), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_GREEN), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_BLUE), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_RED), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW), colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE), colorDepth); + break; + } + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::EightColor: // TODO: Sort out the actual 8/16 color options + case AdaGFXColorDepth::SixteenColor: + # endif // if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::FullColor: + { + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_BLACK, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_NAVY, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKGREEN, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKCYAN, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_MAROON, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_PURPLE, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_OLIVE, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_LIGHTGREY, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_DARKGREY, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_BLUE, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_GREEN, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_CYAN, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_RED, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_MAGENTA, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_YELLOW, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_WHITE, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_ORANGE, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_GREENYELLOW, colorDepth); + AdaGFXaddHtmlDataListColorOptionValue(ADAGFX_PINK, colorDepth); + break; + } + } + datalistFinish(); +} + +/***************************************************************************************** + * Convert an RGB565 color (number) to it's name or the #rgb565 hex string, based on depth + ****************************************************************************************/ +String AdaGFXcolorToString(const uint16_t & color, + const AdaGFXColorDepth& colorDepth, + bool blackIsEmpty) { + String result = AdaGFXcolorToString_internal(color, colorDepth, blackIsEmpty); + + if (equals(result, '*')) { + result = '#'; + result += String(color, HEX); + result.toUpperCase(); + } + return result; +} + +const __FlashStringHelper* AdaGFXcolorToString_internal(const uint16_t & color, + const AdaGFXColorDepth& colorDepth, + bool blackIsEmpty) { + switch (colorDepth) { + case AdaGFXColorDepth::Monochrome: + case AdaGFXColorDepth::BlackWhiteRed: + case AdaGFXColorDepth::BlackWhite2Greyscales: + { + switch (color) { + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_BLACK): return blackIsEmpty ? F("") : F("black"); + case ADAGFX_WHITE: // Fall through + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_WHITE): return F("white"); + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_INVERSE): return F("inverse"); + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_RED): return F("red"); + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_DARK): return F("dark"); + case static_cast(AdaGFXMonoRedGreyscaleColors::ADAGFXEPD_LIGHT): return F("light"); + default: + break; + } + break; + } + # if ADAGFX_SUPPORT_7COLOR + case AdaGFXColorDepth::SevenColor: + { + switch (color) { + case static_cast(AdaGFX7Colors::ADAGFX7C_BLACK): return blackIsEmpty ? F("") : F("black"); + case ADAGFX_WHITE: // Fall through + case static_cast(AdaGFX7Colors::ADAGFX7C_WHITE): return F("white"); + case static_cast(AdaGFX7Colors::ADAGFX7C_GREEN): return F("green"); + case static_cast(AdaGFX7Colors::ADAGFX7C_BLUE): return F("blue"); + case static_cast(AdaGFX7Colors::ADAGFX7C_RED): return F("red"); + case static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW): return F("yellow"); + case static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE): return F("orange"); + default: + break; + } + break; + } + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::EightColor: + case AdaGFXColorDepth::SixteenColor: + # endif // if ADAGFX_SUPPORT_8and16COLOR + case AdaGFXColorDepth::FullColor: + { + switch (color) { + case ADAGFX_BLACK: return blackIsEmpty ? F("") : F("black"); + case ADAGFX_NAVY: return F("navy"); + case ADAGFX_DARKGREEN: return F("darkgreen"); + case ADAGFX_DARKCYAN: return F("darkcyan"); + case ADAGFX_MAROON: return F("maroon"); + case ADAGFX_PURPLE: return F("purple"); + case ADAGFX_OLIVE: return F("olive"); + case ADAGFX_LIGHTGREY: return F("lightgrey"); + case ADAGFX_DARKGREY: return F("darkgrey"); + case ADAGFX_BLUE: return F("blue"); + case ADAGFX_GREEN: return F("green"); + case ADAGFX_CYAN: return F("cyan"); + case ADAGFX_RED: return F("red"); + case ADAGFX_MAGENTA: return F("magenta"); + case ADAGFX_YELLOW: return F("yellow"); + case ADAGFX_WHITE: return F("white"); + case ADAGFX_ORANGE: return F("orange"); + case ADAGFX_GREENYELLOW: return F("greenyellow"); + case ADAGFX_PINK: return F("pink"); + default: + break; + } + break; + } + } + return F("*"); +} + +# if ADAGFX_SUPPORT_7COLOR + +/**************************************************************************** + * AdaGFXrgb565ToColor7: Convert a rgb565 color to the 7 colors supported by 7-color eInk displays + * Borrowed from https://github.com/ZinggJM/GxEPD2 color7() routine + ***************************************************************************/ +uint16_t AdaGFXrgb565ToColor7(const uint16_t& color) { + const uint16_t red = (color & 0xF800); + const uint16_t green = (color & 0x07E0) << 5; + const uint16_t blue = (color & 0x001F) << 11; + uint16_t cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); // Default = white + + if ((red < 0x8000) && (green < 0x8000) && (blue < 0x8000)) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLACK); // black + } + else if ((red >= 0x8000) && (green >= 0x8000) && (blue >= 0x8000)) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_WHITE); // white + } + else if ((red >= 0x8000) && (blue >= 0x8000)) { + if (red > blue) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_RED); + } else { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // red, blue + } + } + else if ((green >= 0x8000) && (blue >= 0x8000)) { + if (green > blue) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); + } else { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // green, blue + } + } + else if ((red >= 0x8000) && (green >= 0x8000)) { + static const uint16_t y2o_lim = ((ADAGFX_YELLOW - ADAGFX_ORANGE) / 2 + (ADAGFX_ORANGE & 0x07E0)) << 5; + + if (green > y2o_lim) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_YELLOW); + } else { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_ORANGE); // yellow, orange + } + } + else if (red >= 0x8000) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_RED); // red + } + else if (green >= 0x8000) { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_GREEN); // green + } + else { + cv7 = static_cast(AdaGFX7Colors::ADAGFX7C_BLUE); // blue + } + return cv7; +} + +# endif // if ADAGFX_SUPPORT_7COLOR + +/**************************************************************************** + * getTextMetrics: Returns the metrics related to current font + ***************************************************************************/ +void AdafruitGFX_helper::getTextMetrics(uint16_t& textcols, + uint16_t& textrows, + uint8_t & fontwidth, + uint8_t & fontheight, + uint8_t & fontscaling, + uint8_t & heightOffset, + uint16_t& xpix, + uint16_t& ypix) { + textcols = _textcols; + textrows = _textrows; + fontwidth = _fontwidth; + fontheight = _fontheight; + fontscaling = _fontscaling; + heightOffset = _heightOffset; + # if ADAGFX_ENABLE_FRAMED_WINDOW + getWindowLimits(xpix, ypix); + # else // if ADAGFX_ENABLE_FRAMED_WINDOW + xpix = _res_x; + ypix = _res_y; + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW +} + +/**************************************************************************** + * getColors: Returns the current text colors + ***************************************************************************/ +void AdafruitGFX_helper::getColors(uint16_t& fgcolor, + uint16_t& bgcolor) { + fgcolor = _fgcolor; + bgcolor = _bgcolor; +} + +/**************************************************************************** + * calculateTextMetrics: Recalculate the text mertics based on supplied font parameters + ***************************************************************************/ +void AdafruitGFX_helper::calculateTextMetrics(const uint8_t fontwidth, + const uint8_t fontheight, + const int8_t heightOffset, + const bool isProportional) { + uint16_t res_x = _res_x; + uint16_t res_y = _res_y; + + # if ADAGFX_ENABLE_FRAMED_WINDOW + getWindowLimits(res_x, res_y); + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + _fontwidth = fontwidth; + _fontheight = fontheight; + _heightOffset = heightOffset; + _isProportional = isProportional; + _textcols = res_x / (_fontwidth * _fontscaling); + _textrows = res_y / ((_fontheight + _heightOffset) * _fontscaling); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLogMove(ADAGFX_LOG_LEVEL, strformat(F("AdaGFX: tr: %s x: %d, y: %d, text columns: %d rows: %d"), + _trigger.c_str(), res_x, res_y, _textcols, _textrows)); + } + # endif // ifndef BUILD_NO_DEBUG +} + +# if ADAGFX_ARGUMENT_VALIDATION + +/**************************************************************************** + * invalidCoordinates: Check if X/Y coordinates stay within the limits of the display, + * default pixel-mode, colRowMode true = character mode. + * If Y == 0 then X is allowed the max. value of the display size. + * *** Returns TRUE when invalid !! *** + ***************************************************************************/ +bool AdafruitGFX_helper::invalidCoordinates(const int X, + const int Y, + const bool colRowMode) { + uint16_t res_x = _res_x; + uint16_t res_y = _res_y; + + # if ADAGFX_ENABLE_FRAMED_WINDOW + getWindowLimits(res_x, res_y); + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(ADAGFX_LOG_LEVEL)) { + addLogMove(ADAGFX_LOG_LEVEL, strformat(F("invalidCoordinates: X:%d/%d Y:%d/%d"), + X, colRowMode ? _textcols : res_x, Y, colRowMode ? _textrows : res_y)); + } + # endif // ifndef BUILD_NO_DEBUG + + if (!_useValidation) { return false; } + + if (colRowMode) { + return !((X >= 0) && (X <= _textcols) && + (Y >= 0) && (Y <= _textrows)); + } else { + if (Y == 0) { // Y == 0: Accept largest x/y size value for x + return !((X >= 0) && (X <= std::max(res_x, res_y))); + } else { + return !((X >= 0) && (X <= res_x) && + (Y >= 0) && (Y <= res_y)); + } + } +} + +# endif // if ADAGFX_ARGUMENT_VALIDATION + +void AdafruitGFX_helper::setValidation(const bool& state) { + _useValidation = state; +} + +/**************************************************************************** + * rotate the display (and all windows) + ***************************************************************************/ +void AdafruitGFX_helper::setRotation(uint8_t m) { + const uint8_t rotation = m & 3; + + _display->setRotation(m); // Set rotation 0/1/2/3 + _rotation = rotation; + + switch (rotation) { + case 0: + case 2: + _res_x = _display_x; + _res_y = _display_y; + break; + case 1: + case 3: + _res_x = _display_y; + _res_y = _display_x; + break; + } + # if ADAGFX_ENABLE_FRAMED_WINDOW + + for (uint8_t i = 0; i < _windows.size(); ++i) { // Swap x/y for all matching windows + switch (rotation) { + case 0: // 0 degrees + _windows[i].top_left.x = _windows[i].org_top_left.x; // All original + _windows[i].top_left.y = _windows[i].org_top_left.y; + _windows[i].width_height.x = _windows[i].org_width_height.x; + _windows[i].width_height.y = _windows[i].org_width_height.y; + break; + case 1: // +90 degrees + _windows[i].top_left.x = _windows[i].org_top_left.y; + _windows[i].top_left.y = _display_x - (_windows[i].org_top_left.x + _windows[i].org_width_height.x); + _windows[i].width_height.x = _windows[i].org_width_height.y; // swapped width/height + _windows[i].width_height.y = _windows[i].org_width_height.x; + break; + case 2: // +180 degrees + _windows[i].top_left.x = _display_x - (_windows[i].org_top_left.x + _windows[i].org_width_height.x); + _windows[i].top_left.y = _display_y - (_windows[i].org_top_left.y + _windows[i].org_width_height.y); + _windows[i].width_height.x = _windows[i].org_width_height.x; + _windows[i].width_height.y = _windows[i].org_width_height.y; + break; + case 3: // +270 degrees + _windows[i].top_left.x = _display_y - (_windows[i].org_top_left.y + _windows[i].org_width_height.y); + _windows[i].top_left.y = _windows[i].org_top_left.x; + _windows[i].width_height.x = _windows[i].org_width_height.y; // swapped width/height + _windows[i].width_height.y = _windows[i].org_width_height.x; + break; + } + _windows[i].rotation = rotation; + } + + // logWindows(F("rot ")); // For debugging only + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + calculateTextMetrics(_fontwidth, _fontheight, _heightOffset, _isProportional); +} + +# if ADAGFX_ENABLE_BMP_DISPLAY + +/**************************************************************************** + * CPA (Copy/paste/adapt) from Adafruit_ImageReader::coreBMP() + * Changes: + * - No 'load to memory' feature + * - No special handling of SD Filesystem/FAT, but File only + * - Adds support for non-SPI displays (like NeoPixel Matrix, and possibly I2C displays, once supported) + ***************************************************************************/ +bool AdafruitGFX_helper::showBmp(const String& filename, + int16_t x, + int16_t y) { + uint32_t offset; // Start of image data in file + uint32_t headerSize; // Indicates BMP version + uint32_t compression = 0; // BMP compression mode + uint32_t colors = 0; // Number of colors in palette + uint32_t rowSize; // >bmpWidth if scanline padding + uint8_t sdbuf[3 * BUFPIXELS]; // BMP read buf (R+G+B/pixel) + + uint32_t destidx = 0; + uint32_t bmpPos = 0; // Next pixel position in file + int bmpWidth; // BMP width & height in pixels + int bmpHeight; + int loadWidth; + int loadHeight; // Region being loaded (clipped) + int loadX; + int loadY; // " + int row; // Current pixel pos. + int col; + uint16_t *quantized = NULL; // 16-bit 5/6/5 color palette + uint16_t tftbuf[BUFPIXELS]; + uint16_t *dest = tftbuf; // TFT working buffer, or NULL if to canvas + int16_t drow = 0; + int16_t dcol = 0; + uint8_t planes; // BMP planes + uint8_t depth; // BMP bit depth + uint8_t r; // Current pixel colors + uint8_t g; + uint8_t b; + uint8_t bitIn = 0; // Bit number for 1-bit data in + + # if ((3 * BUFPIXELS) <= 255) + uint8_t srcidx = sizeof sdbuf; // Current position in sdbuf + # else // if ((3 * BUFPIXELS) <= 255) + uint16_t srcidx = sizeof sdbuf; + # endif // if ((3 * BUFPIXELS) <= 255) + bool flip = true; // BMP is stored bottom-to-top + bool transact = true; // Enable transaction support to work proper with SD czrd, when enabled + bool status = false; // IMAGE_SUCCESS on valid file + + bool canTransact = (nullptr != _tft); + + // If BMP is being drawn off the right or bottom edge of the screen, + // nothing to do here. NOT an error, just a trivial clip operation. + if (_tft && ((x >= _tft->width()) || (y >= _tft->height()))) { + addLog(LOG_LEVEL_INFO, F("showBmp: coordinates off display")); + return false; + } + + // Open requested file on storage + // Search flash file system first, then SD if present + file = tryOpenFile(filename, "r"); + + if (!file) { + addLog(LOG_LEVEL_ERROR, F("showBmp: file not found")); + return false; + } + + // Parse BMP header. 0x4D42 (ASCII 'BM') is the Windows BMP signature. + // There are other values possible in a .BMP file but these are super + // esoteric (e.g. OS/2 struct bitmap array) and NOT supported here! + if (readLE16() == 0x4D42) { // BMP signature + (void)readLE32(); // Read & ignore file size + (void)readLE32(); // Read & ignore creator bytes + offset = readLE32(); // Start of image data + // Read DIB header + headerSize = readLE32(); + bmpWidth = readLE32(); + bmpHeight = readLE32(); + + // If bmpHeight is negative, image is in top-down order. + // This is not canon but has been observed in the wild. + if (bmpHeight < 0) { + bmpHeight = -bmpHeight; + flip = false; + } + planes = readLE16(); + depth = readLE16(); // Bits per pixel + + // Compression mode is present in later BMP versions (default = none) + if (headerSize > 12) { + compression = readLE32(); + (void)readLE32(); // Raw bitmap data size; ignore + (void)readLE32(); // Horizontal resolution, ignore + (void)readLE32(); // Vertical resolution, ignore + colors = readLE32(); // Number of colors in palette, or 0 for 2^depth + (void)readLE32(); // Number of colors used (ignore) + // File position should now be at start of palette (if present) + } + + if (!colors) { + colors = 1 << depth; + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("showBmp: bitmap w:%d, h:%d, dpt:%d, colors:%d, cmp:%d, pl:%d, x:%d, y:%d"), + bmpWidth, bmpHeight, depth, colors, compression, planes, x, y)); + } + # endif // ifndef BUILD_NO_DEBUG + + loadWidth = bmpWidth; + loadHeight = bmpHeight; + loadX = 0; + loadY = 0; + + if (_display) { + // Crop area to be loaded (if destination is TFT) + if (x < 0) { + loadX = -x; + loadWidth += x; + x = 0; + } + + if (y < 0) { + loadY = -y; + loadHeight += y; + y = 0; + } + + if ((x + loadWidth) > _display->width()) { + loadWidth = _display->width() - x; + } + + if ((y + loadHeight) > _display->height()) { + loadHeight = _display->height() - y; + } + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("showBmp: x:%d, y:%d, dw:%d, dh:%d"), + x, y, _display->width(), _display->height())); + } + # endif // ifndef BUILD_NO_DEBUG + + if ((planes == 1) && (compression == 0)) { // Only uncompressed is handled + // BMP rows are padded (if needed) to 4-byte boundary + rowSize = ((depth * bmpWidth + 31) / 32) * 4; + + if ((depth == 24) || (depth == 1)) { // BGR or 1-bit bitmap format + // if (dest) { // Supported format, alloc OK, etc. + status = true; + + if ((loadWidth > 0) && (loadHeight > 0)) { // Clip top/left + _display->startWrite(); // Start SPI (regardless of transact) + + if (canTransact) { + _tft->setAddrWindow(x, y, loadWidth, loadHeight); + } + + if ((depth >= 16) || + (quantized = (uint16_t *)malloc(colors * sizeof(uint16_t)))) { + if (depth < 16) { + // Load and quantize color table + for (uint16_t c = 0; c < colors; ++c) { + b = file.read(); + g = file.read(); + r = file.read(); + (void)file.read(); // Ignore 4th byte + quantized[c] = // -V522 + ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } + } + + for (row = 0; row < loadHeight; ++row) { // For each scanline... + delay(0); // Keep ESP8266 happy + + // Seek to start of scan line. It might seem labor-intensive + // to be doing this on every line, but this method covers a + // lot of gritty details like cropping, flip and scanline + // padding. Also, the seek only takes place if the file + // position actually needs to change (avoids a lot of cluster + // math in SD library). + if (flip) { // Bitmap is stored bottom-to-top order (normal BMP) + bmpPos = offset + (bmpHeight - 1 - (row + loadY)) * rowSize; + } else { // Bitmap is stored top-to-bottom + bmpPos = offset + (row + loadY) * rowSize; + } + + if (depth == 24) { + bmpPos += loadX * 3; + } else { + bmpPos += loadX / 8; + bitIn = 7 - (loadX & 7); + } + + if (file.position() != bmpPos) { // Need seek? + if (transact && canTransact) { + _tft->dmaWait(); + _tft->endWrite(); // End TFT SPI transaction + } + file.seek(bmpPos); // Seek = SD transaction + srcidx = sizeof sdbuf; // Force buffer reload + } + + for (col = 0; col < loadWidth; ++col) { // For each pixel... + if (srcidx >= sizeof sdbuf) { // Time to load more? + if (transact && canTransact) { + _tft->dmaWait(); + _tft->endWrite(); // End TFT SPI transact + } + file.read(sdbuf, sizeof sdbuf); // Load from SD + + if (transact && canTransact) { + _display->startWrite(); // Start TFT SPI transact + } + + if (destidx) { // If buffered TFT data + // Non-blocking writes (DMA) have been temporarily + // disabled until this can be rewritten with two + // alternating 'dest' buffers (else the nonblocking + // data out is overwritten in the dest[] write below). + // tft->writePixels(dest, destidx, false); // Write it + delay(0); + + if (canTransact) { + _tft->writePixels(dest, destidx, true); // Write it + } else { + // loop over buffer + + for (uint16_t p = 0; p < destidx; ++p) { + _display->drawPixel(x + p, y + drow, dest[p]); + } + } + + if (col % 33 == 0) { delay(0); } + destidx = 0; // and reset dest index + } + + srcidx = 0; // Reset bmp buf index + } + + if (depth == 24) { + // Convert each pixel from BMP to 565 format, save in dest + b = sdbuf[srcidx++]; + g = sdbuf[srcidx++]; // -V557 + r = sdbuf[srcidx++]; // -V557 + dest[destidx++] = + ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); + } else { + // Extract 1-bit color index + uint8_t n = (sdbuf[srcidx] >> bitIn) & 1; + + if (!bitIn) { + srcidx++; + bitIn = 7; + } else { + bitIn--; + } + + // Look up in palette, store in tft dest buf + dest[destidx++] = quantized[n]; + } + dcol++; + } // end pixel loop + + if (_tft) { // Drawing to TFT? + delay(0); + + if (destidx) { // Any remainders? + // See notes above re: DMA + _tft->writePixels(dest, destidx, true); // Write it + destidx = 0; // and reset dest index + } + _tft->dmaWait(); + _tft->endWrite(); // update display + } else { + // loop over buffer + if (destidx) { + for (uint16_t p = 0; p < destidx; ++p) { + _display->drawPixel(x + p, y + drow, dest[p]); + + if (p % 100 == 0) { delay(0); } + } + destidx = 0; // and reset dest index + } + } + + drow++; + dcol = 0; + } // end scanline loop + + if (quantized) { + free(quantized); // Palette no longer needed + } + delay(0); + } // end depth>24 or quantized malloc OK + } // end top/left clip + // } // end malloc check + } // end depth check + } // end planes/compression check + + if (status) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("showBmp: Done.")); + # endif // ifndef BUILD_NO_DEBUG + } else { + addLog(LOG_LEVEL_ERROR, F("showBmp: Only uncompressed and 24 or 1 bit color-depth supported.")); + } + } else { // end signature + addLog(LOG_LEVEL_ERROR, F("showBmp: File signature error.")); + } + + file.close(); + return status; // -V680 + + // } +} + +/*! + @brief Reads a little-endian 16-bit unsigned value from currently- + open File, converting if necessary to the microcontroller's + native endianism. (BMP files use little-endian values.) + @return Unsigned 16-bit value, native endianism. + */ +uint16_t AdafruitGFX_helper::readLE16(void) { + // Big-endian or unknown. Byte-by-byte read will perform reversal if needed. + return file.read() | ((uint16_t)file.read() << 8); +} + +/*! + @brief Reads a little-endian 32-bit unsigned value from currently- + open File, converting if necessary to the microcontroller's + native endianism. (BMP files use little-endian values.) + @return Unsigned 32-bit value, native endianism. + */ +uint32_t AdafruitGFX_helper::readLE32(void) { + // Big-endian or unknown. Byte-by-byte read will perform reversal if needed. + return file.read() | ((uint32_t)file.read() << 8) | + ((uint32_t)file.read() << 16) | ((uint32_t)file.read() << 24); +} + +# endif // if ADAGFX_ENABLE_BMP_DISPLAY + +# if ADAGFX_ENABLE_FRAMED_WINDOW + +/**************************************************************************** + * Check if the requested id is a valid window id + ***************************************************************************/ +bool AdafruitGFX_helper::validWindow(const uint8_t& windowId) { + return getWindowIndex(windowId) != -1; +} + +/**************************************************************************** + * Select this window id as the default + ***************************************************************************/ +bool AdafruitGFX_helper::selectWindow(const uint8_t& windowId, + const int8_t & rotation) { + const int16_t result = getWindowIndex(windowId); + + if (result != -1) { + _windowIndex = result; + _window = windowId; + } + return result != -1; +} + +/**************************************************************************** + * Return the index of the windowId in _windows, -1 if not found + ***************************************************************************/ +int16_t AdafruitGFX_helper::getWindowIndex(const int16_t& windowId) { + size_t result = 0; + + for (auto win = _windows.begin(); win != _windows.end(); win++, ++result) { + if ((*win).id == windowId) { + break; + } + } + return result == _windows.size() ? -1 : result; +} + +/**************************************************************************** + * Get the offset for the currently active window + ***************************************************************************/ +void AdafruitGFX_helper::getWindowOffsets(uint16_t& xOffset, + uint16_t& yOffset) { + xOffset = _windows[_windowIndex].top_left.x; + yOffset = _windows[_windowIndex].top_left.y; +} + +/**************************************************************************** + * Get the limits for the currently active window + ***************************************************************************/ +void AdafruitGFX_helper::getWindowLimits(uint16_t& xLimit, + uint16_t& yLimit) { + xLimit = _windows[_windowIndex].width_height.x; + yLimit = _windows[_windowIndex].width_height.y; +} + +/**************************************************************************** + * Define a window and return the ID + ***************************************************************************/ +uint8_t AdafruitGFX_helper::defineWindow(const int16_t& x, + const int16_t& y, + const int16_t& w, + const int16_t& h, + int16_t windowId, + const int8_t & rotation) { + int16_t result = getWindowIndex(windowId); + + if (result < 0) { + result = static_cast(_windows.size()); // previous size + _windows.push_back(tWindowObject()); // add new + + if (windowId < 0) { + windowId = 0; + + for (auto it = _windows.begin(); it != _windows.end(); it++) { + if ((*it).id == windowId) { windowId++; } // Generate a new window id + } + } + _windows[result].id = windowId; + } + _windows[result].top_left.x = x; + _windows[result].top_left.y = y; + _windows[result].width_height.x = w; + _windows[result].width_height.y = h; + + if (rotation >= 0) { + _windows[result].rotation = rotation & 3; + } else { + _windows[result].rotation = _rotation; + } + + // Adjust original coordinate/sizes based on rotation + switch (_windows[result].rotation) { + case 0: // 0 degrees + _windows[result].org_top_left.x = x; // All original + _windows[result].org_top_left.y = y; + _windows[result].org_width_height.x = w; + _windows[result].org_width_height.y = h; + break; + case 1: // +90 degrees + _windows[result].org_top_left.x = _display_x - (y + h); // swapped x/y + _windows[result].org_top_left.y = x; + _windows[result].org_width_height.x = h; // swapped width/height + _windows[result].org_width_height.y = w; + break; + case 2: // +180 degrees + _windows[result].org_top_left.x = _display_x - (x + w); + _windows[result].org_top_left.y = _display_y - (y + h); + _windows[result].org_width_height.x = w; // unchanged + _windows[result].org_width_height.y = h; + break; + case 3: // +270 degrees + _windows[result].org_top_left.x = y; + _windows[result].org_top_left.y = _display_x - (x + w); + _windows[result].org_width_height.x = h; // swapped width/height + _windows[result].org_width_height.y = w; + break; + } + + return _windows[result].id; +} + +/**************************************************************************** + * Remove a window definition + ***************************************************************************/ +bool AdafruitGFX_helper::deleteWindow(const uint8_t& windowId) { + const int16_t result = getWindowIndex(windowId); + + if (result > -1) { + _windows.erase(_windows.begin() + result); + return true; + } + return false; +} + +/**************************************************************************** + * log all current known window definitions + ***************************************************************************/ +void AdafruitGFX_helper::logWindows(const String& prefix) { + # ifndef BUILD_NO_DEBUG + + for (auto it = _windows.begin(); it != _windows.end(); it++) { + addLogMove(LOG_LEVEL_INFO, strformat(F("AdaGFX window %s: %d, x:%d, y:%d, w:%d, h:%d" + ", rot%d, current: %d, org x:%d, y:%d, w:%d, h:%d"), + prefix.c_str(), (*it).id, (*it).top_left.x, (*it).top_left.y, + (*it).width_height.x, (*it).width_height.y, + (*it).rotation, getWindow(), (*it).org_top_left.x, (*it).org_top_left.y, + (*it).org_width_height.x, (*it).org_width_height.y)); + } + # endif // ifndef BUILD_NO_DEBUG +} + +# endif // if ADAGFX_ENABLE_FRAMED_WINDOW + +#endif // ifdef PLUGIN_USES_ADAFRUITGFX diff --git a/src/src/Helpers/AdafruitGFX_helper.h b/src/src/Helpers/AdafruitGFX_helper.h index 0d2ab46ab..d0ebb9f03 100644 --- a/src/src/Helpers/AdafruitGFX_helper.h +++ b/src/src/Helpers/AdafruitGFX_helper.h @@ -1,567 +1,641 @@ -#ifndef HELPERS_ADAFRUITGFX_HELPER_H -#define HELPERS_ADAFRUITGFX_HELPER_H - -#include "../../_Plugin_Helper.h" - -#ifdef PLUGIN_USES_ADAFRUITGFX - -# define ADAGFX_LOG_LEVEL LOG_LEVEL_DEBUG - -/**************************************************************************** - * helper class and functions for displays that use Adafruit_GFX library - ***************************************************************************/ -/************ - * Changelog: - * 2023-02-26 tonhuisman: Use GetCommandCode() / PROGMEM for parsing of commands and colors to reduce .bin size. - * 2022-10-05 tonhuisman: No longer trim off spaces from arguments to commands - * 2022-09-23 tonhuisman: Allow backlight percentage from 0% instead of from 1% to be able to completely turn it off - * 2022-09-12 tonhuisman: Add line-spacing option for Column/Row mode, default set to auto, optional 0..14 pixels line-spacing - * Add line spacing form selector function - * 2022-09-10 tonhuisman: Enable printing partial characters falling off at the right edge of the screen, only when on Window 0 - * 2022-08-25 tonhuisman: Add invertDisplay() functionality, often used for monochrome displays - * 2022-08-23 tonhuisman: Several small improvements, and a few bugfixes - * 2022-08-20 tonhuisman: Add txl subcommand to display text on 1 or more lines, autoincrementing the line nr, - * always in row/column mode. - * Improved argument parsing to allow up to 2 empty arguments between filled arguments - * 2022-06-07 tonhuisman: Code improvements in initialization, move offset calculation to printText() function - * 2022-06-06 tonhuisman: Process any special characters for lenght and textheight values for correct sizing - * 2022-06-05 tonhuisman: Add support for getting config values: win (current window id), iswin (exists?), width & height (current window), - * (text)length and textheight of a provided text, rot (current rotation), txs (fontscaling), tpm (textprintmode) - * 2022-06-04 tonhuisman: Add Window support for drawing and printing within confined areas (windows) - * Always use exact font calculation for determining allowable text length - * 2022-06-02 tonhuisman: Leave out some Notes from UI to save a few bytes from size limited builds - * 2022-05-27 tonhuisman: Change btn subcommand to split state and mode arguments, state = 0/1, -2/-1, mode = -2, -1, 0 - * 2022-05-27 tonhuisman: Fix a few character mappings in AdaGFXparseTemplate, add surrogates for chars not in font - * Add support for {0xNN...} to insert any ascii character in template, supports multiple 2-digit hex values > 00 - * space, comma, dot, colon, semicolon or dash (' ,.:;-') as separators in hex value are allowed - * 2022-05-23 tonhuisman: Fix cast for returned value from AdaGFXparseColor - * Make 8 and 16 color support optional to squeeze a few bytes from size limited builds - * 2022-05-23 tonhuisman: Add changelog, older changes have not been logged. - ***************************************************************************/ - -# include "../Helpers/Numerical.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../ESPEasyCore/ESPEasy_Log.h" - - -# include -# include -# include -# include - -// Used for bmp support -# define BUFPIXELS 200 ///< 200 * 5 = 1000 bytes - -# define ADAGFX_PARSE_MAX_ARGS 7 // Maximum number of arguments needed and supported (corrected) -# ifndef ADAGFX_ARGUMENT_VALIDATION -# define ADAGFX_ARGUMENT_VALIDATION 1 // Validate command arguments -# endif // ifndef ADAGFX_ARGUMENT_VALIDATION -# ifndef ADAGFX_USE_ASCIITABLE -# define ADAGFX_USE_ASCIITABLE 1 // Enable 'asciitable' command (useful for debugging/development) -# endif // ifndef ADAGFX_USE_ASCIITABLE -# ifndef ADAGFX_SUPPORT_7COLOR - -// # define ADAGFX_SUPPORT_7COLOR 1 // Do we support 7-Color displays? -# endif // ifndef ADAGFX_SUPPORT_7COLOR -# ifndef ADAGFX_SUPPORT_8and16COLOR - -// # define ADAGFX_SUPPORT_8and16COLOR 1 // Do we support 8 and 16-Color displays? -# endif // ifndef ADAGFX_SUPPORT_8and16COLOR -# ifndef ADAGFX_FONTS_INCLUDED -# define ADAGFX_FONTS_INCLUDED 1 // 3 extra fonts, also controls enable/disable of below 8pt/12pt fonts -# endif // ifndef ADAGFX_FONTS_INCLUDED -# ifndef ADAGFX_PARSE_SUBCOMMAND -# define ADAGFX_PARSE_SUBCOMMAND 1 // Enable parsing of subcommands (pre/postfix below) to be executed by the helper -# endif // ifndef ADAGFX_PARSE_SUBCOMMAND -# ifndef ADAGFX_ENABLE_EXTRA_CMDS -# define ADAGFX_ENABLE_EXTRA_CMDS 1 // Enable extra subcommands like lm (line-multi) and lmr (line-multi, relative) -# endif // ifndef ADAGFX_ENABLE_EXTRA_CMDS -# ifndef ADAGFX_ENABLE_BMP_DISPLAY -# define ADAGFX_ENABLE_BMP_DISPLAY 1 // Enable subcommands for displaying .bmp files on supported displays (color) -# endif // ifndef ADAGFX_ENABLE_BMP_DISPLAY -# ifndef ADAGFX_ENABLE_BUTTON_DRAW -# define ADAGFX_ENABLE_BUTTON_DRAW 1 // Enable subcommands for displaying button-like shapes -# endif // ifndef ADAGFX_ENABLE_BUTTON_DRAW -# ifndef ADAGFX_ENABLE_FRAMED_WINDOW -# define ADAGFX_ENABLE_FRAMED_WINDOW 1 // Enable framed window features -# endif // ifndef ADAGFX_ENABLE_BUTTON_DRAW -# ifndef ADAGFX_ENABLE_GET_CONFIG_VALUE -# define ADAGFX_ENABLE_GET_CONFIG_VALUE 1 // Enable getting values features -# endif // ifndef ADAGFX_ENABLE_GET_CONFIG_VALUE - -// # define ADAGFX_FONTS_EXTRA_8PT_INCLUDED // 8 extra 8pt fonts, should probably only be enabled in a private custom build, adds ~15.4 kB -// # define ADAGFX_FONTS_EXTRA_12PT_INCLUDED // 9 extra 12pt fonts, should probably only be enabled in a private custom build, adds ~28 kB -// # define ADAGFX_FONTS_EXTRA_16PT_INCLUDED // 5 extra 16pt fonts, should probably only be enabled in a private custom build, adds ~19.9 kB -// # define ADAGFX_FONTS_EXTRA_18PT_INCLUDED // 1 extra 18pt fonts, should probably only be enabled in a private custom build, adds ~4.3 kB -// # define ADAGFX_FONTS_EXTRA_20PT_INCLUDED // 1 extra 20pt fonts, should probably only be enabled in a private custom build, adds ~5.3 kB - -// To enable/disable 8pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_8PT_INCLUDED is defined) -# define ADAGFX_FONTS_EXTRA_8PT_ANGELINA // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_8PT_NOVAMONO -# define ADAGFX_FONTS_EXTRA_8PT_UNISPACE -# define ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC -# define ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT - -// # define ADAGFX_FONTS_EXTRA_8PT_ROBOTO // This font is proportinally spaced! -// # define ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO - -// To enable/disable 12pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_12PT_INCLUDED is defined) -# define ADAGFX_FONTS_EXTRA_12PT_ANGELINA // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_12PT_NOVAMONO -# define ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG -# define ADAGFX_FONTS_EXTRA_12PT_UNISPACE -# define ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC -# define ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT - -// # define ADAGFX_FONTS_EXTRA_12PT_ROBOTO // This font is proportinally spaced! -// # define ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO - -// To enable/disable 16pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_16PT_INCLUDED is defined) -# define ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT - -// # define ADAGFX_FONTS_EXTRA_16PT_ROBOTO // This font is proportinally spaced! -// # define ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED // This font is proportinally spaced! -# define ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO - -// To enable/disable 18pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_18PT_INCLUDED is defined) -# define ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT - -// To enable/disable 20pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_20PT_INCLUDED is defined) -# define ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT - -# ifdef LIMIT_BUILD_SIZE -# ifdef ADAGFX_FONTS_INCLUDED -# undef ADAGFX_FONTS_INCLUDED -# endif // ifdef ADAGFX_FONTS_INCLUDED -# ifdef ADAGFX_ARGUMENT_VALIDATION -# undef ADAGFX_ARGUMENT_VALIDATION -# endif // ifdef ADAGFX_ARGUMENT_VALIDATION -# ifdef ADAGFX_USE_ASCIITABLE -# undef ADAGFX_USE_ASCIITABLE -# endif // ifdef ADAGFX_USE_ASCIITABLE -# ifdef ADAGFX_SUPPORT_8and16COLOR -# undef ADAGFX_SUPPORT_8and16COLOR -# endif // ifdef ADAGFX_SUPPORT_8and16COLOR -// # ifdef ADAGFX_ENABLE_BMP_DISPLAY -// # undef ADAGFX_ENABLE_BMP_DISPLAY -// # endif // ifdef ADAGFX_ENABLE_BMP_DISPLAY -// # ifdef ADAGFX_ENABLE_BUTTON_DRAW -// # undef ADAGFX_ENABLE_BUTTON_DRAW -// # endif // ifdef ADAGFX_ENABLE_BUTTON_DRAW -// # ifdef ADAGFX_ENABLE_FRAMED_WINDOW -// # undef ADAGFX_ENABLE_FRAMED_WINDOW -// # endif // ifdef ADAGFX_ENABLE_FRAMED_WINDOW -// # ifdef ADAGFX_ENABLE_GET_CONFIG_VALUE -// # undef ADAGFX_ENABLE_GET_CONFIG_VALUE -// # endif // ifdef ADAGFX_ENABLE_GET_CONFIG_VALUE -# endif // ifdef LIMIT_BUILD_SIZE - -# ifdef PLUGIN_SET_MAX // Include all fonts in MAX builds -# ifndef ADAGFX_FONTS_EXTRA_8PT_INCLUDED -# define ADAGFX_FONTS_EXTRA_8PT_INCLUDED -# endif // ifndef ADAGFX_FONTS_EXTRA_8PT_INCLUDED -# ifndef ADAGFX_FONTS_EXTRA_12PT_INCLUDED -# define ADAGFX_FONTS_EXTRA_12PT_INCLUDED -# endif // ifndef ADAGFX_FONTS_EXTRA_12PT_INCLUDED -# ifndef ADAGFX_FONTS_EXTRA_16PT_INCLUDED -# define ADAGFX_FONTS_EXTRA_16PT_INCLUDED -# endif // ifndef ADAGFX_FONTS_EXTRA_16PT_INCLUDED -# ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED -# define ADAGFX_FONTS_EXTRA_18PT_INCLUDED -# endif // ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED -# ifndef ADAGFX_FONTS_EXTRA_20PT_INCLUDED -# define ADAGFX_FONTS_EXTRA_20PT_INCLUDED -# endif // ifndef ADAGFX_FONTS_EXTRA_20PT_INCLUDED -# ifndef ADAGFX_SUPPORT_7COLOR -# define ADAGFX_SUPPORT_7COLOR 1 -# endif // ifndef ADAGFX_SUPPORT_7COLOR -# ifndef ADAGFX_SUPPORT_8and16COLOR -# define ADAGFX_SUPPORT_8and16COLOR 1 -# endif // ifndef ADAGFX_SUPPORT_8and16COLOR -# endif // ifdef PLUGIN_SET_MAX - -# define ADAGFX_PARSE_PREFIX F("~") // Subcommand-trigger prefix and postfix strings -# define ADAGFX_PARSE_PREFIX_LEN 1 -# define ADAGFX_PARSE_POSTFIX F("~") // Will be removed before the normal template parsing is done -# define ADAGFX_PARSE_POSTFIX_LEN 1 - -# define ADAGFX_UNIVERSAL_TRIGGER F("adagfx_trigger") // Universal command trigger - -// Color definitions, borrowed from Adafruit_ILI9341.h - -# define ADAGFX_BLACK 0x0000 ///< 0, 0, 0 -# define ADAGFX_NAVY 0x000F ///< 0, 0, 123 -# define ADAGFX_DARKGREEN 0x03E0 ///< 0, 125, 0 -# define ADAGFX_DARKCYAN 0x03EF ///< 0, 125, 123 -# define ADAGFX_MAROON 0x7800 ///< 123, 0, 0 -# define ADAGFX_PURPLE 0x780F ///< 123, 0, 123 -# define ADAGFX_OLIVE 0x7BE0 ///< 123, 125, 0 -# define ADAGFX_LIGHTGREY 0xC618 ///< 198, 195, 198 -# define ADAGFX_DARKGREY 0x7BEF ///< 123, 125, 123 -# define ADAGFX_BLUE 0x001F ///< 0, 0, 255 -# define ADAGFX_GREEN 0x07E0 ///< 0, 255, 0 -# define ADAGFX_CYAN 0x07FF ///< 0, 255, 255 -# define ADAGFX_RED 0xF800 ///< 255, 0, 0 -# define ADAGFX_MAGENTA 0xF81F ///< 255, 0, 255 -# define ADAGFX_YELLOW 0xFFE0 ///< 255, 255, 0 -# define ADAGFX_WHITE 0xFFFF ///< 255, 255, 255 -# define ADAGFX_ORANGE 0xFD20 ///< 255, 165, 0 -# define ADAGFX_GREENYELLOW 0xAFE5 ///< 173, 255, 41 -# define ADAGFX_PINK 0xFC18 ///< 255, 130, 198 - -enum class AdaGFXMonoRedGreyscaleColors: uint16_t { - ADAGFXEPD_BLACK, ///< black color - ADAGFXEPD_WHITE, ///< white color - ADAGFXEPD_INVERSE, ///< invert color - ADAGFXEPD_RED, ///< red color - ADAGFXEPD_DARK, ///< darker color - ADAGFXEPD_LIGHT ///< lighter color -}; - -# if ADAGFX_SUPPORT_7COLOR -enum class AdaGFX7Colors: uint16_t { - ADAGFX7C_BLACK, ///< black color - ADAGFX7C_WHITE, ///< white color - ADAGFX7C_GREEN, ///< green color - ADAGFX7C_BLUE, ///< blue color - ADAGFX7C_RED, ///< red color - ADAGFX7C_YELLOW, ///< yellow color - ADAGFX7C_ORANGE ///< orange color -}; -# endif // if ADAGFX_SUPPORT_7COLOR - -enum class AdaGFXTextPrintMode : uint8_t { - ContinueToNextLine = 0u, - TruncateExceedingMessage = 1u, - ClearThenTruncate = 2u, - TruncateExceedingCentered = 3u, // Should have max. 16 options - - MAX // Keep as last -}; - -# if ADAGFX_SUPPORT_7COLOR -# if ADAGFX_SUPPORT_8and16COLOR -# define ADAGFX_COLORDEPTH_COUNT 7 -# define ADAGFX_MONOCOLORS_COUNT 4 -# else // if ADAGFX_SUPPORT_8and16COLOR -# define ADAGFX_COLORDEPTH_COUNT 5 -# define ADAGFX_MONOCOLORS_COUNT 4 -# endif // if ADAGFX_SUPPORT_8and16COLOR -# else // if ADAGFX_SUPPORT_7COLOR -# if ADAGFX_SUPPORT_8and16COLOR -# define ADAGFX_COLORDEPTH_COUNT 6 -# define ADAGFX_MONOCOLORS_COUNT 3 -# else // if ADAGFX_SUPPORT_8and16COLOR -# define ADAGFX_COLORDEPTH_COUNT 4 -# define ADAGFX_MONOCOLORS_COUNT 3 -# endif // if ADAGFX_SUPPORT_8and16COLOR -# endif // if ADAGFX_SUPPORT_7COLOR -enum class AdaGFXColorDepth : uint16_t { - Monochrome = 2u, // Black & white - BlackWhiteRed = 3u, // Black, white & red (or yellow) - BlackWhite2Greyscales = 4u, // Black, white, lightgrey & darkgrey - # if ADAGFX_SUPPORT_7COLOR - SevenColor = 7u, // Black, white, red, yellow, blue, green, orange - # endif // if ADAGFX_SUPPORT_7COLOR - # if ADAGFX_SUPPORT_8and16COLOR - EightColor = 8u, // 8 regular colors - SixteenColor = 16u, // 16 colors - # endif // if ADAGFX_SUPPORT_8and16COLOR - FullColor = 65535u // 65535 colors (max. supported by RGB565) -}; - -# if ADAGFX_ENABLE_BUTTON_DRAW - -// Only bits 0..3 can be used, masked with: 0x0F -// stored combined with Button_layout_e value -enum class Button_type_e : uint8_t { - None = 0x00, - Square = 0x01, - Rounded = 0x02, - Circle = 0x03, - ArrowLeft = 0x04, - ArrowUp = 0x05, - ArrowRight = 0x06, - ArrowDown = 0x07, - Button_MAX = 8u // must be last value in enum, max possible values: 16 -}; - -// Only bits 4..7 can be used, masked with: 0xF0 -// stored combined with Button_type_e value -enum class Button_layout_e : uint8_t { - CenterAligned = 0x00, - LeftAligned = 0x10, - TopAligned = 0x20, - RightAligned = 0x30, - BottomAligned = 0x40, - LeftTopAligned = 0x50, - RightTopAligned = 0x60, - RightBottomAligned = 0x70, - LeftBottomAligned = 0x80, - NoCaption = 0x90, - Bitmap = 0xA0, - Alignment_MAX = 11u // options-count, max possible values: 16 -}; - -const __FlashStringHelper* toString(const Button_type_e button); -const __FlashStringHelper* toString(const Button_layout_e layout); - -# endif // if ADAGFX_ENABLE_BUTTON_DRAW - -# if ADAGFX_ENABLE_FRAMED_WINDOW - -struct tWindowPoint { - uint16_t x = 0; - uint16_t y = 0; -}; -struct tWindowObject { - tWindowPoint top_left; - tWindowPoint width_height; - tWindowPoint org_top_left; - tWindowPoint org_width_height; - uint8_t id = 0u; - int8_t rotation = 0; -}; -# endif // if ADAGFX_ENABLE_FRAMED_WINDOW - -class AdafruitGFX_helper; // Forward declaration - -// Some generic AdafruitGFX_helper support functions -const __FlashStringHelper* toString(const AdaGFXTextPrintMode& mode); -const __FlashStringHelper* toString(const AdaGFXColorDepth& colorDepth); -void AdaGFXFormTextPrintMode(const __FlashStringHelper *id, - uint8_t selectedIndex); -void AdaGFXFormColorDepth(const __FlashStringHelper *id, - uint16_t selectedIndex, - bool enabled = true); -void AdaGFXFormRotation(const __FlashStringHelper *id, - uint8_t selectedIndex); -void AdaGFXFormTextBackgroundFill(const __FlashStringHelper *id, - uint8_t selectedIndex); -void AdaGFXFormTextColRowMode(const __FlashStringHelper *id, - bool selectedState); -void AdaGFXFormOnePixelCompatibilityOption(const __FlashStringHelper *id, - uint8_t selectedIndex); -void AdaGFXFormForeAndBackColors(const __FlashStringHelper *foregroundId, - uint16_t foregroundColor, - const __FlashStringHelper *backgroundId, - uint16_t backgroundColor, - AdaGFXColorDepth colorDepth = AdaGFXColorDepth::FullColor); -void AdaGFXFormBacklight(const __FlashStringHelper *backlightPinId, - int8_t backlightPin, - const __FlashStringHelper *backlightPercentageId, - uint16_t backlightPercentage); -void AdaGFXFormDisplayButton(const __FlashStringHelper *buttonPinId, - int8_t buttonPin, - const __FlashStringHelper *buttonInverseId, - bool buttonInverse, - const __FlashStringHelper *displayTimeoutId, - int displayTimeout); -void AdaGFXFormFontScaling(const __FlashStringHelper *fontScalingId, - uint8_t fontScaling, - uint8_t maxScale = 10); -String AdaGFXparseTemplate(const String & tmpString, - const uint8_t lineSize, - AdafruitGFX_helper *gfxHelper = nullptr); -uint16_t AdaGFXparseColor(String & s, - const AdaGFXColorDepth& colorDepth = AdaGFXColorDepth::FullColor, - const bool emptyIsBlack = false); // Parse either a color by name, 6 digit hex rrggbb color, - // or 1..4 digit - // #rgb565 color (hex with # prefix) -void AdaGFXHtmlColorDepthDataList(const __FlashStringHelper *id, - const AdaGFXColorDepth & colorDepth); -String AdaGFXcolorToString(const uint16_t & color, - const AdaGFXColorDepth& colorDepth = AdaGFXColorDepth::FullColor, - bool blackIsEmpty = false); -# if ADAGFX_SUPPORT_7COLOR -uint16_t AdaGFXrgb565ToColor7(const uint16_t& color); // Convert rgb565 color to 7-color -# endif // if ADAGFX_SUPPORT_7COLOR -void AdaGFXFormLineSpacing(const __FlashStringHelper *id, - uint8_t selectedIndex); - -class AdafruitGFX_helper { -public: - - AdafruitGFX_helper(Adafruit_GFX *display, - const String & trigger, - const uint16_t res_x, - const uint16_t res_y, - const AdaGFXColorDepth & colorDepth = AdaGFXColorDepth::FullColor, - const AdaGFXTextPrintMode& textPrintMode = AdaGFXTextPrintMode::ContinueToNextLine, - const uint8_t fontscaling = 1, - const uint16_t fgcolor = ADAGFX_WHITE, - const uint16_t bgcolor = ADAGFX_BLACK, - const bool useValidation = true, - const bool textBackFill = false); - # if ADAGFX_ENABLE_BMP_DISPLAY - AdafruitGFX_helper(Adafruit_SPITFT *display, - const String & trigger, - const uint16_t res_x, - const uint16_t res_y, - const AdaGFXColorDepth & colorDepth = AdaGFXColorDepth::FullColor, - const AdaGFXTextPrintMode& textPrintMode = AdaGFXTextPrintMode::ContinueToNextLine, - const uint8_t fontscaling = 1, - const uint16_t fgcolor = ADAGFX_WHITE, - const uint16_t bgcolor = ADAGFX_BLACK, - const bool useValidation = true, - const bool textBackFill = false); - # endif // if ADAGFX_ENABLE_BMP_DISPLAY - virtual ~AdafruitGFX_helper() {} - - String getFeatures(); - - bool processCommand(const String& string); // Parse the string for recognized commands and apply them on the graphics display - - # if ADAGFX_ENABLE_GET_CONFIG_VALUE - bool pluginGetConfigValue(String& string); // Get a config value from the plugin - # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE - - void printText(const char *string, - const int16_t & X, - const int16_t & Y, - const uint8_t & textSize = 0, - const uint16_t& color = ADAGFX_WHITE, - uint16_t bkcolor = ADAGFX_BLACK, - const uint16_t& maxWidth = 0); - void calculateTextMetrics(const uint8_t fontwidth, - const uint8_t fontheight, - const int8_t heightOffset = 0, - const bool isProportional = false); - void getTextMetrics(uint16_t& textcols, - uint16_t& textrows, - uint8_t & fontwidth, - uint8_t & fontheight, - uint8_t & fontscaling, - uint8_t & heightOffset, - uint16_t& xpix, - uint16_t& ypix); - void getColors(uint16_t& fgcolor, - uint16_t& bgcolor); - void getCursorXY(int16_t& currentX, // Get last known (text)cursor position, recalculates to col/row if that - int16_t& currentY); // setting is acive - - void setTxtfullCompensation(uint8_t compensation); // Set to 1 for backward comp. with P095/P096 txtfull subcommands, uses offset -1 - // Set to 2 for extra offset of +1 on y axis - // Set to 3 for extra offset of +1 on x axis - - void setRotation(uint8_t m); // Set the helper-rotation the same as the display object rotation - - void setColumnRowMode(bool state) { // When true, addressing for txp, txtfull commands is in columns/rows, default in - _columnRowMode = state; // pixels NOT compatible with _x_compensation! - } - - void setLineSpacing(int8_t lineSpacing) { // Set inter-line spacing in Column/Row mode - _lineSpacing = lineSpacing & 0xF; // Limited to 0..14 px, 15 = auto, based on fontheight * fontsize - } - - String getTrigger() { // Returns the current trigger - return _trigger; - } - - bool isAdaGFXTrigger(const String& trigger) { - return trigger.equalsIgnoreCase(ADAGFX_UNIVERSAL_TRIGGER); - } - - # if ADAGFX_ENABLE_BMP_DISPLAY - bool showBmp(const String& filename, - int16_t x, - int16_t y); - # endif // if ADAGFX_ENABLE_BMP_DISPLAY - - # if ADAGFX_ENABLE_FRAMED_WINDOW - uint8_t getWindow() { - return _window; - } - - bool validWindow(const uint8_t& windowId); - bool selectWindow(const uint8_t& windowId, - const int8_t & rotation = -1); - uint8_t defineWindow(const int16_t& x, - const int16_t& y, - const int16_t& w, - const int16_t& h, - int16_t windowId = -1, - const int8_t & rotation = -1); - bool deleteWindow(const uint8_t& windowId); - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW - - uint16_t getTextSize(const String& text, - uint16_t & h); // return length and height in pixels using current font - - void setValidation(const bool& state); - bool getValidation() const { - return _useValidation; - } - - void invertDisplay(bool i); - void initialize(); - -private: - - # if ADAGFX_ARGUMENT_VALIDATION - bool invalidCoordinates(const int X, - const int Y, - const bool colRowMode = false); - # endif // if ADAGFX_ARGUMENT_VALIDATION - # if ADAGFX_ENABLE_BUTTON_DRAW - void drawButtonShape(const Button_type_e& buttonType, - const int & x, - const int & y, - const int & w, - const int & h, - const uint16_t & fillColor, - const uint16_t & borderColor); - # endif // if ADAGFX_ENABLE_BUTTON_DRAW - - Adafruit_GFX *_display = nullptr; - Adafruit_SPITFT *_tft = nullptr; - String _trigger; - uint16_t _res_x; - uint16_t _res_y; - AdaGFXColorDepth _colorDepth; - AdaGFXTextPrintMode _textPrintMode; - uint8_t _fontscaling; - uint16_t _fgcolor; - uint16_t _bgcolor; - bool _useValidation; - bool _textBackFill; - uint16_t _textcols = 0; - uint16_t _textrows = 0; - int16_t _lastX = 0; - int16_t _lastY = 0; - uint8_t _fontwidth = 6; // Default font characteristics - uint8_t _fontheight = 10; - int8_t _heightOffset = 0; - bool _isProportional = false; - int8_t _x_compensation = 0; - int8_t _y_compensation = 0; - bool _columnRowMode = false; - int8_t _rotation = 0; - bool _displayInverted = false; - int8_t _lineSpacing = 15; // Default fontheight * fontsize - - uint16_t _display_x = 0; - uint16_t _display_y = 0; - # if ADAGFX_ENABLE_BMP_DISPLAY - uint16_t readLE16(void); - uint32_t readLE32(void); - fs::File file; - # endif // if ADAGFX_ENABLE_BMP_DISPLAY - # if ADAGFX_ENABLE_FRAMED_WINDOW - int16_t getWindowIndex(const int16_t& windowId); - void logWindows(const String& prefix = EMPTY_STRING); - void getWindowOffsets(uint16_t& xOffset, - uint16_t& yOffset); - void getWindowLimits(uint16_t& xLimit, - uint16_t& yLimit); - std::vector_windows; - uint8_t _window = 0; // current window - uint8_t _windowIndex = 0; // current window Index - # endif // if ADAGFX_ENABLE_FRAMED_WINDOW -}; -#endif // ifdef PLUGIN_USES_ADAFRUITGFX - -#endif // ifndef HELPERS_ADAFRUITGFX_HELPER_H +#ifndef HELPERS_ADAFRUITGFX_HELPER_H +#define HELPERS_ADAFRUITGFX_HELPER_H + +#include "../../_Plugin_Helper.h" + +#ifdef PLUGIN_USES_ADAFRUITGFX + +# define ADAGFX_LOG_LEVEL LOG_LEVEL_DEBUG + +/**************************************************************************** + * helper class and functions for displays that use Adafruit_GFX library + ***************************************************************************/ +/************ + * Changelog: + * 2024-05-18 tonhuisman: Change default argument separator for Get Config Value from comma (,) to period (.), with fall-back. + * 2024-05-07 tonhuisman: Correct font related functions, add [#font] to return the currently selected fontname + * Accept numeric font Ids to select a different font: ,font, + * Show font ID in Default font selector + * 2024-04-17 tonhuisman: Add AdaGFXFormDefaultFont() selector and some support functions + * Add default font selection at initialization + * 2024-04-16 tonhuisman: Add font TomThumb, 3x5 pixel font to be used on a NeoMatrix 5x29 display. Disabled by LIMIT_BUILD_SIZE. + * This font is already available via the Adafruit_GFX_Library + * 2023-12-30 tonhuisman: Optimization of font handling, also reducing code-size + * Add some additional 7-segment/LCD-like fonts (18 pt enabled by default for ESP32 builds using this helper) + * - sevenseg18b (7segment 18 pt) (very few non-alphanumeric characters, slightly slanted) + * - sevenseg24b (7segment 24 pt) + * - lcd14cond18pt (LCD 14 segment, condensed, 18 pt) + * - lcd14cond24pt (LCD 14 segment, condensed, 24 pt) + * as sevenseg18 and sevenseg24 are partially proportionally spaced, even in the numeric characters :-( + * 2023-12-29 tonhuisman: Bugfixes: txz and txtfull subcommands didn't properly use the last set FG/BG colors + * 2023-12-12 tonhuisman: Code deduplication and string optimizations to reduce build size + * 2023-02-26 tonhuisman: Use GetCommandCode() / PROGMEM for parsing of commands and colors to reduce .bin size. + * 2022-10-05 tonhuisman: No longer trim off spaces from arguments to commands + * 2022-09-23 tonhuisman: Allow backlight percentage from 0% instead of from 1% to be able to completely turn it off + * 2022-09-12 tonhuisman: Add line-spacing option for Column/Row mode, default set to auto, optional 0..14 pixels line-spacing + * Add line spacing form selector function + * 2022-09-10 tonhuisman: Enable printing partial characters falling off at the right edge of the screen, only when on Window 0 + * 2022-08-25 tonhuisman: Add invertDisplay() functionality, often used for monochrome displays + * 2022-08-23 tonhuisman: Several small improvements, and a few bugfixes + * 2022-08-22 tonhuisman: Improve drawing of slider when using a range, so a reverse range (40,-10) is displayed 'flipped' + * 2022-08-20 tonhuisman: Add txl subcommand to display text on 1 or more lines, autoincrementing the line nr, + * always in row/column mode. + * Improved argument parsing to allow up to 2 empty arguments between filled arguments + * 2022-08-16 tonhuisman: Add drawing of Slide/Gauge controls via btn subcommand, horizontal or vertical depending on width/height ratio + * 2022-08-15 tonhuisman: Add initial support for slide/gauge controls + * 2022-06-07 tonhuisman: Code improvements in initialization, move offset calculation to printText() function + * 2022-06-06 tonhuisman: Process any special characters for lenght and textheight values for correct sizing + * 2022-06-05 tonhuisman: Add support for getting config values: win (current window id), iswin (exists?), width & height (current window), + * (text)length and textheight of a provided text, rot (current rotation), txs (fontscaling), tpm (textprintmode) + * 2022-06-04 tonhuisman: Add Window support for drawing and printing within confined areas (windows) + * Always use exact font calculation for determining allowable text length + * 2022-06-02 tonhuisman: Leave out some Notes from UI to save a few bytes from size limited builds + * 2022-05-27 tonhuisman: Change btn subcommand to split state and mode arguments, state = 0/1, -2/-1, mode = -2, -1, 0 + * 2022-05-27 tonhuisman: Fix a few character mappings in AdaGFXparseTemplate, add surrogates for chars not in font + * Add support for {0xNN...} to insert any ascii character in template, supports multiple 2-digit hex values > 00 + * space, comma, dot, colon, semicolon or dash (' ,.:;-') as separators in hex value are allowed + * 2022-05-23 tonhuisman: Fix cast for returned value from AdaGFXparseColor + * Make 8 and 16 color support optional to squeeze a few bytes from size limited builds + * 2022-05-23 tonhuisman: Add changelog, older changes have not been logged. + ***************************************************************************/ + +# include "../Helpers/Numerical.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../ESPEasyCore/ESPEasy_Log.h" + + +# include +# include +# include +# include + +// Used for bmp support +# define BUFPIXELS 200 ///< 200 * 5 = 1000 bytes + +# define ADAGFX_PARSE_MAX_ARGS 7 // Maximum number of arguments needed and supported (corrected) +# ifndef ADAGFX_ARGUMENT_VALIDATION +# define ADAGFX_ARGUMENT_VALIDATION 1 // Validate command arguments +# endif // ifndef ADAGFX_ARGUMENT_VALIDATION +# ifndef ADAGFX_USE_ASCIITABLE +# define ADAGFX_USE_ASCIITABLE 1 // Enable 'asciitable' command (useful for debugging/development) +# endif // ifndef ADAGFX_USE_ASCIITABLE +# ifndef ADAGFX_SUPPORT_7COLOR + +// # define ADAGFX_SUPPORT_7COLOR 1 // Do we support 7-Color displays? +# endif // ifndef ADAGFX_SUPPORT_7COLOR +# ifndef ADAGFX_SUPPORT_8and16COLOR + +// # define ADAGFX_SUPPORT_8and16COLOR 1 // Do we support 8 and 16-Color displays? +# endif // ifndef ADAGFX_SUPPORT_8and16COLOR +# ifndef ADAGFX_FONTS_INCLUDED +# define ADAGFX_FONTS_INCLUDED 1 // 3 extra fonts, also controls enable/disable of below 8pt/12pt fonts +# endif // ifndef ADAGFX_FONTS_INCLUDED +# ifndef ADAGFX_PARSE_SUBCOMMAND +# define ADAGFX_PARSE_SUBCOMMAND 1 // Enable parsing of subcommands (pre/postfix below) to be executed by the helper +# endif // ifndef ADAGFX_PARSE_SUBCOMMAND +# ifndef ADAGFX_ENABLE_EXTRA_CMDS +# define ADAGFX_ENABLE_EXTRA_CMDS 1 // Enable extra subcommands like lm (line-multi) and lmr (line-multi, relative) +# endif // ifndef ADAGFX_ENABLE_EXTRA_CMDS +# ifndef ADAGFX_ENABLE_BMP_DISPLAY +# define ADAGFX_ENABLE_BMP_DISPLAY 1 // Enable subcommands for displaying .bmp files on supported displays (color) +# endif // ifndef ADAGFX_ENABLE_BMP_DISPLAY +# ifndef ADAGFX_ENABLE_BUTTON_DRAW +# define ADAGFX_ENABLE_BUTTON_DRAW 1 // Enable/disable subcommands for displaying button-like shapes +# endif // ifndef ADAGFX_ENABLE_BUTTON_DRAW +# ifndef ADAGFX_ENABLE_BUTTON_SLIDER +# define ADAGFX_ENABLE_BUTTON_SLIDER 1 // Enable/disable displaying button-shape with slider-actions +# endif // ifndef ADAGFX_ENABLE_BUTTON_SLIDER +# ifndef ADAGFX_ENABLE_FRAMED_WINDOW +# define ADAGFX_ENABLE_FRAMED_WINDOW 1 // Enable framed window features +# endif // ifndef ADAGFX_ENABLE_BUTTON_DRAW +# ifndef ADAGFX_ENABLE_GET_CONFIG_VALUE +# define ADAGFX_ENABLE_GET_CONFIG_VALUE 1 // Enable getting values features +# endif // ifndef ADAGFX_ENABLE_GET_CONFIG_VALUE + +# define ADAGFX_FONTS_EXTRA_5PT_INCLUDED // 1 extra 5pt font, should only be enabled in non-LIMIT_BUILD_SIZE builds, adds ~0.3 kB +// # define ADAGFX_FONTS_EXTRA_8PT_INCLUDED // 8 extra 8pt fonts, should probably only be enabled in a private custom build, adds ~15.4 kB +// # define ADAGFX_FONTS_EXTRA_12PT_INCLUDED // 9 extra 12pt fonts, should probably only be enabled in a private custom build, adds ~28 kB +// # define ADAGFX_FONTS_EXTRA_16PT_INCLUDED // 5 extra 16pt fonts, should probably only be enabled in a private custom build, adds ~19.9 kB +// # define ADAGFX_FONTS_EXTRA_18PT_INCLUDED // 3 extra 18pt fonts, should probably only be enabled in a private custom build, adds ~13.8 kB +// # define ADAGFX_FONTS_EXTRA_20PT_INCLUDED // 1 extra 20pt fonts, should probably only be enabled in a private custom build, adds ~5.3 kB +// # define ADAGFX_FONTS_EXTRA_24PT_INCLUDED // 2 extra 24pt fonts, should probably only be enabled in a private custom build, adds ~11.1 kB + +// To enable/disable 8pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_5PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_5PT_TOMTHUMB + +// To enable/disable 8pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_8PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_8PT_ANGELINA // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_8PT_NOVAMONO +# define ADAGFX_FONTS_EXTRA_8PT_UNISPACE +# define ADAGFX_FONTS_EXTRA_8PT_UNISPACEITALIC +# define ADAGFX_FONTS_EXTRA_8PT_WHITERABBiT + +// # define ADAGFX_FONTS_EXTRA_8PT_ROBOTO // This font is proportinally spaced! +// # define ADAGFX_FONTS_EXTRA_8PT_ROBOTOCONDENSED // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_8PT_ROBOTOMONO + +// To enable/disable 12pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_12PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_12PT_ANGELINA // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_12PT_NOVAMONO +# define ADAGFX_FONTS_EXTRA_12PT_REPETITIONSCROLLiNG +# define ADAGFX_FONTS_EXTRA_12PT_UNISPACE +# define ADAGFX_FONTS_EXTRA_12PT_UNISPACEITALIC +# define ADAGFX_FONTS_EXTRA_12PT_WHITERABBiT + +// # define ADAGFX_FONTS_EXTRA_12PT_ROBOTO // This font is proportinally spaced! +// # define ADAGFX_FONTS_EXTRA_12PT_ROBOTOCONDENSED // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_12PT_ROBOTOMONO + +// To enable/disable 16pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_16PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_16PT_AMERIKASANS // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_16PT_WHITERABBiT + +// # define ADAGFX_FONTS_EXTRA_16PT_ROBOTO // This font is proportinally spaced! +// # define ADAGFX_FONTS_EXTRA_16PT_ROBOTOCONDENSED // This font is proportinally spaced! +# define ADAGFX_FONTS_EXTRA_16PT_ROBOTOMONO + +// To enable/disable 18pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_18PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_18PT_WHITERABBiT +# define ADAGFX_FONTS_EXTRA_18PT_SEVENSEG_B +# define ADAGFX_FONTS_EXTRA_18PT_LCD14COND +# ifndef ESP8266 +# ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# endif // ifndef ESP8266 + +// To enable/disable 20pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_20PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_20PT_WHITERABBiT + +// To enable/disable 24pt fonts separately: (will only be enabled if ADAGFX_FONTS_EXTRA_24PT_INCLUDED is defined) +# define ADAGFX_FONTS_EXTRA_24PT_LCD14COND +# define ADAGFX_FONTS_EXTRA_24PT_SEVENSEG_B + +# ifdef LIMIT_BUILD_SIZE +# if ADAGFX_FONTS_INCLUDED +# undef ADAGFX_FONTS_INCLUDED +# define ADAGFX_FONTS_INCLUDED 0 +# endif // if ADAGFX_FONTS_INCLUDED +# if ADAGFX_ARGUMENT_VALIDATION +# undef ADAGFX_ARGUMENT_VALIDATION +# define ADAGFX_ARGUMENT_VALIDATION 0 +# endif // if ADAGFX_ARGUMENT_VALIDATION +# if ADAGFX_USE_ASCIITABLE +# undef ADAGFX_USE_ASCIITABLE +# define ADAGFX_USE_ASCIITABLE 0 +# endif // if ADAGFX_USE_ASCIITABLE +# if ADAGFX_SUPPORT_8and16COLOR +# undef ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_SUPPORT_8and16COLOR 0 +# endif // if ADAGFX_SUPPORT_8and16COLOR +// # if ADAGFX_ENABLE_BMP_DISPLAY +// # undef ADAGFX_ENABLE_BMP_DISPLAY +// # define ADAGFX_ENABLE_BMP_DISPLAY 0 +// # endif // if ADAGFX_ENABLE_BMP_DISPLAY +// # if ADAGFX_ENABLE_BUTTON_DRAW +// # undef ADAGFX_ENABLE_BUTTON_DRAW +// # define ADAGFX_ENABLE_BUTTON_DRAW 0 +// # endif // if ADAGFX_ENABLE_BUTTON_DRAW +# if ADAGFX_ENABLE_FRAMED_WINDOW +# undef ADAGFX_ENABLE_FRAMED_WINDOW +# define ADAGFX_ENABLE_FRAMED_WINDOW 0 +# endif // if ADAGFX_ENABLE_FRAMED_WINDOW +// # ifdef ADAGFX_ENABLE_GET_CONFIG_VALUE +// # undef ADAGFX_ENABLE_GET_CONFIG_VALUE +// # endif // ifdef ADAGFX_ENABLE_GET_CONFIG_VALUE +# if ADAGFX_ENABLE_BUTTON_SLIDER +# undef ADAGFX_ENABLE_BUTTON_SLIDER +# define ADAGFX_ENABLE_BUTTON_SLIDER 0 // Disable displaying button-shape with slider-actions +# endif // if ADAGFX_ENABLE_BUTTON_SLIDER +# endif // ifdef LIMIT_BUILD_SIZE + +# ifdef PLUGIN_SET_MAX // Include all fonts in MAX builds +# ifndef ADAGFX_FONTS_EXTRA_5PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_5PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_5PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_8PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_8PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_8PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_12PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_12PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_12PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_16PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_16PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_16PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_18PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_20PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_20PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_20PT_INCLUDED +# ifndef ADAGFX_FONTS_EXTRA_24PT_INCLUDED +# define ADAGFX_FONTS_EXTRA_24PT_INCLUDED +# endif // ifndef ADAGFX_FONTS_EXTRA_24PT_INCLUDED +# if !ADAGFX_SUPPORT_7COLOR +# undef ADAGFX_SUPPORT_7COLOR +# define ADAGFX_SUPPORT_7COLOR 1 +# endif // if !ADAGFX_SUPPORT_7COLOR +# if !ADAGFX_SUPPORT_8and16COLOR +# undef ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_SUPPORT_8and16COLOR 1 +# endif // if !ADAGFX_SUPPORT_8and16COLOR +# endif // ifdef PLUGIN_SET_MAX + +# define ADAGFX_PARSE_PREFIX F("~") // Subcommand-trigger prefix and postfix strings +# define ADAGFX_PARSE_PREFIX_LEN 1 +# define ADAGFX_PARSE_POSTFIX F("~") // Will be removed before the normal template parsing is done +# define ADAGFX_PARSE_POSTFIX_LEN 1 + +# define ADAGFX_UNIVERSAL_TRIGGER F("adagfx_trigger") // Universal command trigger + +// Color definitions, borrowed from Adafruit_ILI9341.h + +# define ADAGFX_BLACK 0x0000 ///< 0, 0, 0 +# define ADAGFX_NAVY 0x000F ///< 0, 0, 123 +# define ADAGFX_DARKGREEN 0x03E0 ///< 0, 125, 0 +# define ADAGFX_DARKCYAN 0x03EF ///< 0, 125, 123 +# define ADAGFX_MAROON 0x7800 ///< 123, 0, 0 +# define ADAGFX_PURPLE 0x780F ///< 123, 0, 123 +# define ADAGFX_OLIVE 0x7BE0 ///< 123, 125, 0 +# define ADAGFX_LIGHTGREY 0xC618 ///< 198, 195, 198 +# define ADAGFX_DARKGREY 0x7BEF ///< 123, 125, 123 +# define ADAGFX_BLUE 0x001F ///< 0, 0, 255 +# define ADAGFX_GREEN 0x07E0 ///< 0, 255, 0 +# define ADAGFX_CYAN 0x07FF ///< 0, 255, 255 +# define ADAGFX_RED 0xF800 ///< 255, 0, 0 +# define ADAGFX_MAGENTA 0xF81F ///< 255, 0, 255 +# define ADAGFX_YELLOW 0xFFE0 ///< 255, 255, 0 +# define ADAGFX_WHITE 0xFFFF ///< 255, 255, 255 +# define ADAGFX_ORANGE 0xFD20 ///< 255, 165, 0 +# define ADAGFX_GREENYELLOW 0xAFE5 ///< 173, 255, 41 +# define ADAGFX_PINK 0xFC18 ///< 255, 130, 198 + +enum class AdaGFXMonoRedGreyscaleColors: uint16_t { + ADAGFXEPD_BLACK, ///< black color + ADAGFXEPD_WHITE, ///< white color + ADAGFXEPD_INVERSE, ///< invert color + ADAGFXEPD_RED, ///< red color + ADAGFXEPD_DARK, ///< darker color + ADAGFXEPD_LIGHT ///< lighter color +}; + +# if ADAGFX_SUPPORT_7COLOR +enum class AdaGFX7Colors: uint16_t { + ADAGFX7C_BLACK, ///< black color + ADAGFX7C_WHITE, ///< white color + ADAGFX7C_GREEN, ///< green color + ADAGFX7C_BLUE, ///< blue color + ADAGFX7C_RED, ///< red color + ADAGFX7C_YELLOW, ///< yellow color + ADAGFX7C_ORANGE ///< orange color +}; +# endif // if ADAGFX_SUPPORT_7COLOR + +enum class AdaGFXTextPrintMode : uint8_t { + ContinueToNextLine = 0u, + TruncateExceedingMessage = 1u, + ClearThenTruncate = 2u, + TruncateExceedingCentered = 3u, // Should have max. 16 options + + MAX // Keep as last +}; + +# if ADAGFX_SUPPORT_7COLOR +# if ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_COLORDEPTH_COUNT 7 +# define ADAGFX_MONOCOLORS_COUNT 4 +# else // if ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_COLORDEPTH_COUNT 5 +# define ADAGFX_MONOCOLORS_COUNT 4 +# endif // if ADAGFX_SUPPORT_8and16COLOR +# else // if ADAGFX_SUPPORT_7COLOR +# if ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_COLORDEPTH_COUNT 6 +# define ADAGFX_MONOCOLORS_COUNT 3 +# else // if ADAGFX_SUPPORT_8and16COLOR +# define ADAGFX_COLORDEPTH_COUNT 4 +# define ADAGFX_MONOCOLORS_COUNT 3 +# endif // if ADAGFX_SUPPORT_8and16COLOR +# endif // if ADAGFX_SUPPORT_7COLOR +enum class AdaGFXColorDepth : uint16_t { + Monochrome = 2u, // Black & white + BlackWhiteRed = 3u, // Black, white & red (or yellow) + BlackWhite2Greyscales = 4u, // Black, white, lightgrey & darkgrey + # if ADAGFX_SUPPORT_7COLOR + SevenColor = 7u, // Black, white, red, yellow, blue, green, orange + # endif // if ADAGFX_SUPPORT_7COLOR + # if ADAGFX_SUPPORT_8and16COLOR + EightColor = 8u, // 8 regular colors + SixteenColor = 16u, // 16 colors + # endif // if ADAGFX_SUPPORT_8and16COLOR + FullColor = 65535u // 65535 colors (max. supported by RGB565) +}; + +# if ADAGFX_ENABLE_BUTTON_DRAW + +// Only bits 0..3 can be used, masked with: 0x0F, max possible values: 16 +// stored combined with Button_layout_e value +enum class Button_type_e : uint8_t { + None = 0x00, + Square = 0x01, + Rounded = 0x02, + Circle = 0x03, + ArrowLeft = 0x04, + ArrowUp = 0x05, + ArrowRight = 0x06, + ArrowDown = 0x07, +}; + +// Only bits 4..7 can be used, masked with: 0xF0, max possible values: 16 +// stored combined with Button_type_e value +enum class Button_layout_e : uint8_t { + CenterAligned = 0x00, + LeftAligned = 0x10, + TopAligned = 0x20, + RightAligned = 0x30, + BottomAligned = 0x40, + LeftTopAligned = 0x50, + RightTopAligned = 0x60, + RightBottomAligned = 0x70, + LeftBottomAligned = 0x80, + NoCaption = 0x90, + # if ADAGFX_ENABLE_BMP_DISPLAY + Bitmap = 0xA0, + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_SLIDER + Slider = 0xB0, + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER +}; + +const __FlashStringHelper* toString(const Button_type_e button); +const __FlashStringHelper* toString(const Button_layout_e layout); + +# endif // if ADAGFX_ENABLE_BUTTON_DRAW + +# if ADAGFX_ENABLE_FRAMED_WINDOW + +struct tWindowPoint { + uint16_t x = 0; + uint16_t y = 0; +}; +struct tWindowObject { + tWindowPoint top_left; + tWindowPoint width_height; + tWindowPoint org_top_left; + tWindowPoint org_width_height; + uint8_t id = 0u; + int8_t rotation = 0; +}; +# endif // if ADAGFX_ENABLE_FRAMED_WINDOW + +class AdafruitGFX_helper; // Forward declaration + +// Some generic AdafruitGFX_helper support functions +const __FlashStringHelper* toString(const AdaGFXTextPrintMode& mode); +const __FlashStringHelper* toString(const AdaGFXColorDepth& colorDepth); +void AdaGFXFormTextPrintMode(const __FlashStringHelper *id, + uint8_t selectedIndex); +void AdaGFXFormColorDepth(const __FlashStringHelper *id, + uint16_t selectedIndex, + bool enabled = true); +void AdaGFXFormRotation(const __FlashStringHelper *id, + uint8_t selectedIndex); +void AdaGFXFormTextBackgroundFill(const __FlashStringHelper *id, + uint8_t selectedIndex); +void AdaGFXFormTextColRowMode(const __FlashStringHelper *id, + bool selectedState); +void AdaGFXFormOnePixelCompatibilityOption(const __FlashStringHelper *id, + uint8_t selectedIndex); +void AdaGFXFormForeAndBackColors(const __FlashStringHelper *foregroundId, + uint16_t foregroundColor, + const __FlashStringHelper *backgroundId, + uint16_t backgroundColor, + AdaGFXColorDepth colorDepth = AdaGFXColorDepth::FullColor); +void AdaGFXFormBacklight(const __FlashStringHelper *backlightPinId, + int8_t backlightPin, + const __FlashStringHelper *backlightPercentageId, + uint16_t backlightPercentage); +void AdaGFXFormDisplayButton(const __FlashStringHelper *buttonPinId, + int8_t buttonPin, + const __FlashStringHelper *buttonInverseId, + bool buttonInverse, + const __FlashStringHelper *displayTimeoutId, + int displayTimeout); +void AdaGFXFormFontScaling(const __FlashStringHelper *fontScalingId, + uint8_t fontScaling, + uint8_t maxScale = 10); +String AdaGFXparseTemplate(const String & tmpString, + const uint8_t lineSize, + AdafruitGFX_helper *gfxHelper = nullptr); +uint16_t AdaGFXparseColor(String & s, + const AdaGFXColorDepth& colorDepth = AdaGFXColorDepth::FullColor, + const bool emptyIsBlack = false); // Parse either a color by name, 6 digit hex rrggbb color, + // or 1..4 digit + // #rgb565 color (hex with # prefix) +void AdaGFXHtmlColorDepthDataList(const __FlashStringHelper *id, + const AdaGFXColorDepth & colorDepth); +String AdaGFXcolorToString(const uint16_t & color, + const AdaGFXColorDepth& colorDepth = AdaGFXColorDepth::FullColor, + bool blackIsEmpty = false); +# if ADAGFX_SUPPORT_7COLOR +uint16_t AdaGFXrgb565ToColor7(const uint16_t& color); // Convert rgb565 color to 7-color +# endif // if ADAGFX_SUPPORT_7COLOR +void AdaGFXFormLineSpacing(const __FlashStringHelper *id, + uint8_t selectedIndex); +String AdaGFXgetFontName(uint8_t fontId, + bool includeFontId = false); +uint32_t AdaGFXgetFontIndexForFontId(uint8_t fontId); +void AdaGFXFormDefaultFont(const __FlashStringHelper *id, + uint8_t selectedIndex); + +class AdafruitGFX_helper { +public: + + AdafruitGFX_helper(Adafruit_GFX *display, + const String & trigger, + const uint16_t res_x, + const uint16_t res_y, + const AdaGFXColorDepth & colorDepth = AdaGFXColorDepth::FullColor, + const AdaGFXTextPrintMode& textPrintMode = AdaGFXTextPrintMode::ContinueToNextLine, + const uint8_t fontscaling = 1, + const uint16_t fgcolor = ADAGFX_WHITE, + const uint16_t bgcolor = ADAGFX_BLACK, + const bool useValidation = true, + const bool textBackFill = false, + const uint8_t defaultFontId = 0); + # if ADAGFX_ENABLE_BMP_DISPLAY + AdafruitGFX_helper(Adafruit_SPITFT *display, + const String & trigger, + const uint16_t res_x, + const uint16_t res_y, + const AdaGFXColorDepth & colorDepth = AdaGFXColorDepth::FullColor, + const AdaGFXTextPrintMode& textPrintMode = AdaGFXTextPrintMode::ContinueToNextLine, + const uint8_t fontscaling = 1, + const uint16_t fgcolor = ADAGFX_WHITE, + const uint16_t bgcolor = ADAGFX_BLACK, + const bool useValidation = true, + const bool textBackFill = false, + const uint8_t defaultFontId = 0); + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + virtual ~AdafruitGFX_helper() {} + + String getFeatures(); + + bool processCommand(const String& string); // Parse the string for recognized commands and apply them on the graphics display + + # if ADAGFX_ENABLE_GET_CONFIG_VALUE + bool pluginGetConfigValue(String& string); // Get a config value from the plugin + # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE + + void printText(const char *string, + const int16_t & X, + const int16_t & Y, + const uint8_t & textSize = 0, + const uint16_t& color = ADAGFX_WHITE, + uint16_t bkcolor = ADAGFX_BLACK, + const uint16_t& maxWidth = 0); + void calculateTextMetrics(const uint8_t fontwidth, + const uint8_t fontheight, + const int8_t heightOffset = 0, + const bool isProportional = false); + void getTextMetrics(uint16_t& textcols, + uint16_t& textrows, + uint8_t & fontwidth, + uint8_t & fontheight, + uint8_t & fontscaling, + uint8_t & heightOffset, + uint16_t& xpix, + uint16_t& ypix); + void getColors(uint16_t& fgcolor, + uint16_t& bgcolor); + void getCursorXY(int16_t& currentX, // Get last known (text)cursor position, recalculates to col/row if that + int16_t& currentY); // setting is acive + + void setTxtfullCompensation(uint8_t compensation); // Set to 1 for backward comp. with P095/P096 txtfull subcommands, uses offset -1 + // Set to 2 for extra offset of +1 on y axis + // Set to 3 for extra offset of +1 on x axis + + void setRotation(uint8_t m); // Set the helper-rotation the same as the display object rotation + + void setColumnRowMode(bool state) { // When true, addressing for txp, txtfull commands is in columns/rows, default in + _columnRowMode = state; // pixels NOT compatible with _x_compensation! + } + + void setLineSpacing(int8_t lineSpacing) { // Set inter-line spacing in Column/Row mode + _lineSpacing = lineSpacing & 0xF; // Limited to 0..14 px, 15 = auto, based on fontheight * fontsize + } + + String getTrigger() { // Returns the current trigger + return _trigger; + } + + bool isAdaGFXTrigger(const String& trigger) { + return trigger.equalsIgnoreCase(ADAGFX_UNIVERSAL_TRIGGER); + } + + # if ADAGFX_ENABLE_BMP_DISPLAY + bool showBmp(const String& filename, + int16_t x, + int16_t y); + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + + # if ADAGFX_ENABLE_FRAMED_WINDOW + uint8_t getWindow() const { + return _window; + } + + bool validWindow(const uint8_t& windowId); + bool selectWindow(const uint8_t& windowId, + const int8_t & rotation = -1); + uint8_t defineWindow(const int16_t& x, + const int16_t& y, + const int16_t& w, + const int16_t& h, + int16_t windowId = -1, + const int8_t & rotation = -1); + bool deleteWindow(const uint8_t& windowId); + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW + + # if ADAGFX_FONTS_INCLUDED + void setFontById(uint8_t fontId); + # endif // if ADAGFX_FONTS_INCLUDED + + uint16_t getTextSize(const String& text, + uint16_t & h); // return length and height in pixels using current font + + void setValidation(const bool& state); + bool getValidation() const { + return _useValidation; + } + + void invertDisplay(bool i); + void initialize(); + +private: + + # if ADAGFX_ARGUMENT_VALIDATION + bool invalidCoordinates(const int X, + const int Y, + const bool colRowMode = false); + # endif // if ADAGFX_ARGUMENT_VALIDATION + # if ADAGFX_ENABLE_BUTTON_DRAW + void drawButtonShape(const Button_type_e& buttonType, + const int & x, + const int & y, + const int & w, + const int & h, + const uint16_t & fillColor, + const uint16_t & borderColor); + # endif // if ADAGFX_ENABLE_BUTTON_DRAW + + Adafruit_GFX *_display = nullptr; + Adafruit_SPITFT *_tft = nullptr; + String _trigger; + uint16_t _res_x; + uint16_t _res_y; + AdaGFXColorDepth _colorDepth; + AdaGFXTextPrintMode _textPrintMode; + uint8_t _fontscaling; + uint16_t _fgcolor; + uint16_t _bgcolor; + bool _useValidation; + bool _textBackFill; + uint8_t _defaultFontId; + uint16_t _textcols = 0; + uint16_t _textrows = 0; + int16_t _lastX = 0; + int16_t _lastY = 0; + uint8_t _fontwidth = 6; // Default font characteristics + uint8_t _fontheight = 10; + int8_t _heightOffset = 0; + bool _isProportional = false; + int8_t _x_compensation = 0; + int8_t _y_compensation = 0; + bool _columnRowMode = false; + int8_t _rotation = 0; + bool _displayInverted = false; + int8_t _lineSpacing = 15; // Default fontheight * fontsize + uint8_t _fontId = 0; + + uint16_t _display_x = 0; + uint16_t _display_y = 0; + # if ADAGFX_ENABLE_BMP_DISPLAY + uint16_t readLE16(void); + uint32_t readLE32(void); + fs::File file; + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_FRAMED_WINDOW + int16_t getWindowIndex(const int16_t& windowId); + void logWindows(const String& prefix = EMPTY_STRING); + void getWindowOffsets(uint16_t& xOffset, + uint16_t& yOffset); + void getWindowLimits(uint16_t& xLimit, + uint16_t& yLimit); + std::vector_windows; + uint8_t _window = 0; // current window + uint8_t _windowIndex = 0; // current window Index + # endif // if ADAGFX_ENABLE_FRAMED_WINDOW +}; +#endif // ifdef PLUGIN_USES_ADAFRUITGFX + +#endif // ifndef HELPERS_ADAFRUITGFX_HELPER_H diff --git a/src/src/Helpers/Audio.cpp b/src/src/Helpers/Audio.cpp index abcb543a5..2501f68bd 100644 --- a/src/src/Helpers/Audio.cpp +++ b/src/src/Helpers/Audio.cpp @@ -1,297 +1,300 @@ -#include "../Helpers/Audio.h" - -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../Globals/RamTracker.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/Hardware_PWM.h" - - -/********************************************************************************************\ - Generate a tone of specified frequency on pin - \*********************************************************************************************/ -bool tone_espEasy(int8_t _pin, unsigned int frequency, unsigned long duration) { - if (!validGpio(_pin)) { return false; } - - // Duty cycle can be used as some kind of volume. - if (!set_Gpio_PWM_pct(_pin, 50, frequency)) { return false; } - - if (duration > 0) { - delay(duration); - return set_Gpio_PWM(_pin, 0, frequency); - } - return true; -} - -/********************************************************************************************\ - Play RTTTL string on specified pin - \*********************************************************************************************/ -#if FEATURE_RTTTL -# if FEATURE_ANYRTTTL_LIB -# include -# include -# if FEATURE_RTTTL_EVENTS -# include "../Globals/EventQueue.h" -# include "../Globals/Settings.h" -static bool rtttlPlaying = false; -# endif // if FEATURE_RTTTL_EVENTS -# if FEATURE_ANYRTTTL_ASYNC -static String rtttlMelody; - -void clear_rtttl_melody() { - // The non-blocking play will read from a char pointer. - // So we must stop the playing before changing the string as it could otherwise lead to a crash. - if (anyrtttl::nonblocking::isPlaying()) { // If currently playing, cancel that - addLog(LOG_LEVEL_INFO, F("RTTTL: Cancelling running song...")); - anyrtttl::nonblocking::stop(); - # if FEATURE_RTTTL_EVENTS - - if (Settings.UseRules) { - eventQueue.add(F("RTTTL#Cancelled")); - } - rtttlPlaying = false; - # endif // if FEATURE_RTTTL_EVENTS - } - - rtttlMelody = String(); -} - -void set_rtttl_melody(String& melody) { - clear_rtttl_melody(); - rtttlMelody = melody; -} - -# endif // if FEATURE_ANYRTTTL_ASYNC - - -bool play_rtttl(int8_t _pin, const char *p) { - if (!validGpio(_pin)) { return false; } - - // addLog(LOG_LEVEL_INFO, F("RTTTL: Using AnyRtttl")); - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("play_rtttl")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - anyrtttl::setNoToneFunction(&setInternalGPIOPullupMode); - # if FEATURE_ANYRTTTL_ASYNC - - if (!rtttlMelody.isEmpty()) { - anyrtttl::nonblocking::begin(_pin, rtttlMelody.c_str()); - } else { - anyrtttl::nonblocking::begin(_pin, p); - } - anyrtttl::nonblocking::play(); - # if FEATURE_RTTTL_EVENTS - - if (Settings.UseRules) { - eventQueue.add(F("RTTTL#Started")); - } - rtttlPlaying = true; - # endif // if FEATURE_RTTTL_EVENTS - # else // if FEATURE_ANYRTTTL_ASYNC - anyrtttl::blocking::play(_pin, p); - # endif // if FEATURE_ANYRTTTL_ASYNC - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("play_rtttl2")); - # endif // ifndef BUILD_NO_RAM_TRACKER - return true; -} - -# if FEATURE_ANYRTTTL_ASYNC -void update_rtttl() { - if (anyrtttl::nonblocking::isPlaying()) { - anyrtttl::nonblocking::play(); - } else { - # if FEATURE_RTTTL_EVENTS - - if (rtttlPlaying) { - if (Settings.UseRules) { - eventQueue.add(F("RTTTL#Finished")); - } - rtttlPlaying = false; - } - # endif // if FEATURE_RTTTL_EVENTS - clear_rtttl_melody(); // Release memory - } -} - -# endif // if FEATURE_ANYRTTTL_ASYNC - -# else // if FEATURE_ANYRTTTL_LIB -bool play_rtttl(int8_t _pin, const char *p) -{ - if (!validGpio(_pin)) { return false; } - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("play_rtttl")); - # endif // ifndef BUILD_NO_RAM_TRACKER - # define OCTAVE_OFFSET 0 - - // FIXME: Absolutely no error checking in here - - const int notes[] = { 0, - 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, - 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, - 1047,1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, - 2093,2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951 - }; - - - uint8_t default_dur = 4; - uint8_t default_oct = 6; - int bpm = 63; - int num; - long wholenote; - long duration; - uint8_t note; - uint8_t scale; - - // format: d=N,o=N,b=NNN: - // find the start (skip name, etc) - - while (*p != ':') { - p++; // ignore name - - if (*p == 0) { return false; } - } - p++; // skip ':' - - // get default duration - if (*p == 'd') - { - p++; p++; // skip "d=" - num = 0; - - while (isdigit(*p)) - { - num = (num * 10) + (*p++ - '0'); - } - - if (num > 0) { default_dur = num; } - p++; // skip comma - } - - // get default octave - if (*p == 'o') - { - p++; p++; // skip "o=" - num = *p++ - '0'; - - if ((num >= 3) && (num <= 7)) { default_oct = num; } - p++; // skip comma - } - - // get BPM - if (*p == 'b') - { - p++; p++; // skip "b=" - num = 0; - - while (isdigit(*p)) - { - num = (num * 10) + (*p++ - '0'); - } - bpm = num; - p++; // skip colon - } - - // BPM usually expresses the number of quarter notes per minute - wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds) - - // now begin note loop - while (*p) - { - // first, get note duration, if available - num = 0; - - while (isdigit(*p)) - { - num = (num * 10) + (*p++ - '0'); - } - - if (num) { duration = wholenote / num; } - else { duration = wholenote / default_dur; // we will need to check if we are a dotted note after - } - - // now get the note - switch (*p) - { - case 'c': - note = 1; - break; - case 'd': - note = 3; - break; - case 'e': - note = 5; - break; - case 'f': - note = 6; - break; - case 'g': - note = 8; - break; - case 'a': - note = 10; - break; - case 'b': - note = 12; - break; - case 'p': - default: - note = 0; - } - p++; - - // now, get optional '#' sharp - if (*p == '#') - { - note++; - p++; - } - - // now, get optional '.' dotted note - if (*p == '.') - { - duration += duration / 2; - p++; - } - - // now, get scale - if (isdigit(*p)) - { - scale = *p - '0'; - p++; - } - else - { - scale = default_oct; - } - - scale += OCTAVE_OFFSET; - - if (*p == ',') { - p++; // skip comma for next note (or we may be at the end) - } - - // now play the note - if (note) - { - if (!tone_espEasy(_pin, notes[(scale - 4) * 12 + note], duration)) { - return false; - } - } - else - { - delay(duration / 10); - } - } - setInternalGPIOPullupMode(_pin); // Turn off sound, Arduino _noTone() doesn't do that reliably - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("play_rtttl2")); - # endif // ifndef BUILD_NO_RAM_TRACKER - return true; -} - -# endif // if FEATURE_ANYRTTTL_LIB -#endif // if FEATURE_RTTTL +#include "../Helpers/Audio.h" + +#include "../DataStructs/TimingStats.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../Globals/RamTracker.h" +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/Hardware_PWM.h" + + +/********************************************************************************************\ + Generate a tone of specified frequency on pin + \*********************************************************************************************/ +bool tone_espEasy(int8_t _pin, unsigned int frequency, unsigned long duration) { + if (!validGpio(_pin)) { return false; } + + // Duty cycle can be used as some kind of volume. + if (!set_Gpio_PWM_pct(_pin, 50, frequency)) { return false; } + + if (duration > 0) { + delay(duration); + return set_Gpio_PWM(_pin, 0, frequency); + } + return true; +} + +/********************************************************************************************\ + Play RTTTL string on specified pin + \*********************************************************************************************/ +#if FEATURE_RTTTL +# if FEATURE_ANYRTTTL_LIB +# include +# include +# if FEATURE_RTTTL_EVENTS +# include "../Globals/EventQueue.h" +# include "../Globals/Settings.h" +static bool rtttlPlaying = false; +# endif // if FEATURE_RTTTL_EVENTS +# if FEATURE_ANYRTTTL_ASYNC +static String rtttlMelody; + +void clear_rtttl_melody() { + // The non-blocking play will read from a char pointer. + // So we must stop the playing before changing the string as it could otherwise lead to a crash. + if (anyrtttl::nonblocking::isPlaying()) { // If currently playing, cancel that + addLog(LOG_LEVEL_INFO, F("RTTTL: Cancelling running song...")); + anyrtttl::nonblocking::stop(); + # if FEATURE_RTTTL_EVENTS + + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Cancelled")); + } + rtttlPlaying = false; + # endif // if FEATURE_RTTTL_EVENTS + } + + rtttlMelody = String(); +} + +void set_rtttl_melody(String& melody) { + clear_rtttl_melody(); + rtttlMelody = melody; +} + +# endif // if FEATURE_ANYRTTTL_ASYNC + + +bool play_rtttl(int8_t _pin, const char *p) { + if (!validGpio(_pin)) { return false; } + + // addLog(LOG_LEVEL_INFO, F("RTTTL: Using AnyRtttl")); + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + anyrtttl::setNoToneFunction(&setInternalGPIOPullupMode); + # if FEATURE_ANYRTTTL_ASYNC + + if (!rtttlMelody.isEmpty()) { + anyrtttl::nonblocking::begin(_pin, rtttlMelody.c_str()); + } else { + anyrtttl::nonblocking::begin(_pin, p); + } + anyrtttl::nonblocking::play(); + # if FEATURE_RTTTL_EVENTS + + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Started")); + } + rtttlPlaying = true; + # endif // if FEATURE_RTTTL_EVENTS + # else // if FEATURE_ANYRTTTL_ASYNC + anyrtttl::blocking::play(_pin, p); + # endif // if FEATURE_ANYRTTTL_ASYNC + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl2")); + # endif // ifndef BUILD_NO_RAM_TRACKER + return true; +} + +# if FEATURE_ANYRTTTL_ASYNC +void update_rtttl() { + START_TIMER + if (anyrtttl::nonblocking::isPlaying()) { + anyrtttl::nonblocking::play(); + } else { + # if FEATURE_RTTTL_EVENTS + + if (rtttlPlaying) { + if (Settings.UseRules) { + eventQueue.add(F("RTTTL#Finished")); + } + rtttlPlaying = false; + } + # endif // if FEATURE_RTTTL_EVENTS + clear_rtttl_melody(); // Release memory + } + STOP_TIMER(UPDATE_RTTTL); +} + +# endif // if FEATURE_ANYRTTTL_ASYNC + +# else // if FEATURE_ANYRTTTL_LIB +bool play_rtttl(int8_t _pin, const char *p) +{ + if (!validGpio(_pin)) { return false; } + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl")); + # endif // ifndef BUILD_NO_RAM_TRACKER + # define OCTAVE_OFFSET 0 + + // FIXME: Absolutely no error checking in here + + const int notes[] = { 0, + 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, + 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, + 1047,1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, + 2093,2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951 + }; + + + uint8_t default_dur = 4; + uint8_t default_oct = 6; + int bpm = 63; + int num; + long wholenote; + long duration; + uint8_t note; + uint8_t scale; + + // format: d=N,o=N,b=NNN: + // find the start (skip name, etc) + + while (*p != ':') { + p++; // ignore name + + if (*p == 0) { return false; } + } + p++; // skip ':' + + // get default duration + if (*p == 'd') + { + p++; p++; // skip "d=" + num = 0; + + while (isdigit(*p)) + { + num = (num * 10) + (*p++ - '0'); + } + + if (num > 0) { default_dur = num; } + p++; // skip comma + } + + // get default octave + if (*p == 'o') + { + p++; p++; // skip "o=" + num = *p++ - '0'; + + if ((num >= 3) && (num <= 7)) { default_oct = num; } + p++; // skip comma + } + + // get BPM + if (*p == 'b') + { + p++; p++; // skip "b=" + num = 0; + + while (isdigit(*p)) + { + num = (num * 10) + (*p++ - '0'); + } + bpm = num; + p++; // skip colon + } + + // BPM usually expresses the number of quarter notes per minute + wholenote = (60 * 1000L / bpm) * 4; // this is the time for whole note (in milliseconds) + + // now begin note loop + while (*p) + { + // first, get note duration, if available + num = 0; + + while (isdigit(*p)) + { + num = (num * 10) + (*p++ - '0'); + } + + if (num) { duration = wholenote / num; } + else { duration = wholenote / default_dur; // we will need to check if we are a dotted note after + } + + // now get the note + switch (*p) + { + case 'c': + note = 1; + break; + case 'd': + note = 3; + break; + case 'e': + note = 5; + break; + case 'f': + note = 6; + break; + case 'g': + note = 8; + break; + case 'a': + note = 10; + break; + case 'b': + note = 12; + break; + case 'p': + default: + note = 0; + } + p++; + + // now, get optional '#' sharp + if (*p == '#') + { + note++; + p++; + } + + // now, get optional '.' dotted note + if (*p == '.') + { + duration += duration / 2; + p++; + } + + // now, get scale + if (isdigit(*p)) + { + scale = *p - '0'; + p++; + } + else + { + scale = default_oct; + } + + scale += OCTAVE_OFFSET; + + if (*p == ',') { + p++; // skip comma for next note (or we may be at the end) + } + + // now play the note + if (note) + { + if (!tone_espEasy(_pin, notes[(scale - 4) * 12 + note], duration)) { + return false; + } + } + else + { + delay(duration / 10); + } + } + setInternalGPIOPullupMode(_pin); // Turn off sound, Arduino _noTone() doesn't do that reliably + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("play_rtttl2")); + # endif // ifndef BUILD_NO_RAM_TRACKER + return true; +} + +# endif // if FEATURE_ANYRTTTL_LIB +#endif // if FEATURE_RTTTL diff --git a/src/src/Helpers/CRC_functions.cpp b/src/src/Helpers/CRC_functions.cpp index 3ac6affd3..ba7612162 100644 --- a/src/src/Helpers/CRC_functions.cpp +++ b/src/src/Helpers/CRC_functions.cpp @@ -83,3 +83,26 @@ uint8_t calc_CRC8(const uint8_t *data, size_t length) } return crc; } + +bool calc_CRC8(uint8_t MSB, uint8_t LSB, uint8_t CRC) +{ + /* + * Name : CRC-8 + * Polynomial : 0x31 (x8 + x5 + x4 + 1) + * Initialization : 0xFF + * Reflect input : False + * Reflect output : False + * Final : XOR 0x00 + * Example : CRC8( 0xBE, 0xEF, 0x92) should be true + */ + uint8_t crc = 0xFF; + + for (uint8_t bytenr = 0; bytenr < 2; ++bytenr) { + crc ^= (bytenr == 0) ? MSB : LSB; + + for (uint8_t i = 0; i < 8; ++i) { + crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1; + } + } + return crc == CRC; +} diff --git a/src/src/Helpers/CRC_functions.h b/src/src/Helpers/CRC_functions.h index 236a10de0..dfbf589d8 100644 --- a/src/src/Helpers/CRC_functions.h +++ b/src/src/Helpers/CRC_functions.h @@ -14,5 +14,9 @@ uint32_t calc_CRC32(const uint8_t *data, uint8_t calc_CRC8(const uint8_t *data, size_t length); +bool calc_CRC8(uint8_t MSB, + uint8_t LSB, + uint8_t CRC); + #endif // ifndef HELPERS_CRC_FUNCTIONS_H diff --git a/src/src/Helpers/CUL_interval_filter.cpp b/src/src/Helpers/CUL_interval_filter.cpp new file mode 100644 index 000000000..6cc2d860e --- /dev/null +++ b/src/src/Helpers/CUL_interval_filter.cpp @@ -0,0 +1,101 @@ +#include "../Helpers/CUL_interval_filter.h" + + +#ifdef USES_P094 + +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/TimeZone.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + + +CUL_time_filter_struct::CUL_time_filter_struct(uint32_t checksum, unsigned long UnixTimeExpiration) + : _checksum(checksum), _UnixTimeExpiration(UnixTimeExpiration) {} + +String CUL_interval_filter_getExpiration_log_str(const P094_filter& filter) +{ + const unsigned long expiration = filter.computeUnixTimeExpiration(); + + if ((expiration != 0) && (expiration != 0xFFFFFFFF)) { + struct tm exp_tm; + breakTime(time_zone.toLocal(expiration), exp_tm); + + return concat(F(" Expiration: "), formatDateTimeString(exp_tm)); + } + return EMPTY_STRING; +} + +bool CUL_interval_filter::filter(const mBusPacket_t& packet, const P094_filter& filter) +{ + if (!enabled) { + return true; + } + + if (filter.getFilterWindow() == P094_Filter_Window::None) { + // Will always be rejected, so no need to keep track of the message + return false; + } + + if (filter.getFilterWindow() == P094_Filter_Window::All) { + // Will always be allowed, so no need to keep track of the message + return true; + } + + const uint32_t key = packet.deviceID_to_map_key(); + auto it = _mBusFilterMap.find(key); + + if (it != _mBusFilterMap.end()) { + // Already present + if (node_time.getUnixTime() < it->second._UnixTimeExpiration) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("CUL : Interval filtered: "), packet.toString()); + log += CUL_interval_filter_getExpiration_log_str(filter); + addLogMove(LOG_LEVEL_INFO, log); + } + return false; + } + + if (packet._checksum == it->second._checksum) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("CUL : Interval Same Checksum: "), packet.toString())); + } + return false; + } + + // Has expired, so remove from filter map + _mBusFilterMap.erase(it); + } + + const unsigned long expiration = filter.computeUnixTimeExpiration(); + + CUL_time_filter_struct item(packet._checksum, expiration); + + _mBusFilterMap[key] = item; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("CUL : Add to IntervalFilter: "), packet.toString()); + log += CUL_interval_filter_getExpiration_log_str(filter); + + addLogMove(LOG_LEVEL_INFO, log); + } + + return true; +} + +void CUL_interval_filter::purgeExpired() +{ + auto it = _mBusFilterMap.begin(); + + const unsigned long currentTime = node_time.getUnixTime(); + + for (; it != _mBusFilterMap.end();) { + if (currentTime > it->second._UnixTimeExpiration) { + it = _mBusFilterMap.erase(it); + } else { + ++it; + } + } +} + +#endif // ifdef USES_P094 diff --git a/src/src/Helpers/CUL_interval_filter.h b/src/src/Helpers/CUL_interval_filter.h new file mode 100644 index 000000000..b68f3cb95 --- /dev/null +++ b/src/src/Helpers/CUL_interval_filter.h @@ -0,0 +1,42 @@ +#ifndef DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H +#define DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H + +#include "../../ESPEasy_common.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" +# include "../PluginStructs/P094_Filter.h" + +# include + + +struct CUL_time_filter_struct { + CUL_time_filter_struct() = default; + CUL_time_filter_struct(uint32_t checksum, + unsigned long UnixTimeExpiration); + + uint32_t _checksum{}; + unsigned long _UnixTimeExpiration{}; +}; + +typedef uint32_t mBusSerial; + +typedef std::map mBusFilterMap; + + +struct CUL_interval_filter { + // Return true when packet wasn't already present. + bool filter(const mBusPacket_t& packet, + const P094_filter & filter); + + // Remove packets that have expired. + void purgeExpired(); + + + mBusFilterMap _mBusFilterMap; + + bool enabled = false; +}; + +#endif // ifdef USES_P094 +#endif // ifndef DATASTRUCTS_P094_CUL_TIME_FILTER_STRUCT_H diff --git a/src/src/Helpers/CUL_stats.cpp b/src/src/Helpers/CUL_stats.cpp new file mode 100644 index 000000000..4e790ae39 --- /dev/null +++ b/src/src/Helpers/CUL_stats.cpp @@ -0,0 +1,135 @@ +#include "../Helpers/CUL_stats.h" + +#ifdef USES_P094 + +# include "../ESPEasyCore/ESPEasy_Log.h" +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/CRC_functions.h" +# include "../Helpers/ESPEasy_time_calc.h" +# include "../Helpers/StringConverter.h" + +# include "../WebServer/Markup.h" +# include "../WebServer/HTML_wrappers.h" + +String CUL_Stats::toString(const CUL_Stats_struct& element) const +{ + uint8_t LQI = 0; + const int16_t rssi = mBusPacket_t::decode_LQI_RSSI(element._lqi_rssi, LQI); + + // e.g.: THC.02.12345678;1674030412;1674031412;123;101,-36 + static size_t estimated_length = 52; + + String res; + + res.reserve(estimated_length); + { + auto it = _mBusStatsSourceMap.find(element._sourceHash); + if (element._sourceHash != 0u && it != _mBusStatsSourceMap.end()) { + res += it->second; + } else { + res += '-'; + } + } + res += ';'; + + if (element._id1 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id1); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + if (element._id2 != 0u) { + mBusPacket_header_t deviceID; + deviceID.decode_fromUint64(element._id2); + res += deviceID.toString(); + } else { + res += '-'; + } + res += ';'; + + res += element._UnixTimeFirstSeen; + res += ';'; + res += element._UnixTimeLastSeen; + res += ';'; + res += element._count; + res += ';'; + res += LQI; + res += ';'; + res += rssi; + + if (res.length() > estimated_length) { + estimated_length = res.length(); + } + return res; +} + +bool CUL_Stats::add(const mBusPacket_t& packet) +{ + const CUL_stats_hash sourceHash{}; + return add(packet, packet.deviceID_to_map_key_no_length(), sourceHash); +} + + +bool CUL_Stats::add(const mBusPacket_t& packet, const String& source) +{ + CUL_stats_hash key = packet.deviceID_to_map_key_no_length(); + CUL_stats_hash sourceHash{}; + + if (!source.isEmpty()) { + sourceHash = calc_CRC32((const uint8_t *)(source.c_str()), source.length()); + _mBusStatsSourceMap[sourceHash] = source; + key ^= sourceHash; + } + + return add(packet, key, sourceHash); +} + +bool CUL_Stats::add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash) +{ + if (key == 0) { return false; } + + auto it = _mBusStatsMap.find(key); + + if (it == _mBusStatsMap.end()) { + CUL_Stats_struct tmp; + tmp._count = 1; + tmp._id1 = packet._deviceId1.encode_toUInt64(); + tmp._id2 = packet._deviceId2.encode_toUInt64(); + tmp._lqi_rssi = packet._lqi_rssi; + tmp._UnixTimeFirstSeen = node_time.getLocalUnixTime(); + tmp._UnixTimeLastSeen = tmp._UnixTimeFirstSeen; + tmp._sourceHash = sourceHash; + _mBusStatsMap[key] = tmp; + return true; + } + it->second._count++; + it->second._lqi_rssi = packet._lqi_rssi; + it->second._UnixTimeLastSeen = node_time.getLocalUnixTime(); + return false; +} + +String CUL_Stats::getFront() +{ + auto it = _mBusStatsMap.begin(); + + if (it == _mBusStatsMap.end()) { return EMPTY_STRING; } + const String res = toString(it->second); + + _mBusStatsMap.erase(it); + return res; +} + +void CUL_Stats::toHtml() const +{ + addRowLabel(F("CUL stats")); + + for (auto it = _mBusStatsMap.begin(); it != _mBusStatsMap.end(); ++it) { + addHtml(toString(it->second)); + addHtml(F("
")); + } +} + +#endif // ifdef USES_P094 diff --git a/src/src/Helpers/CUL_stats.h b/src/src/Helpers/CUL_stats.h new file mode 100644 index 000000000..4c131f96e --- /dev/null +++ b/src/src/Helpers/CUL_stats.h @@ -0,0 +1,59 @@ +#ifndef DATASTRUCTS_P094_CUL_STATS_H +#define DATASTRUCTS_P094_CUL_STATS_H + +#include "../../ESPEasy_common.h" +#ifdef USES_P094 + +# include "../DataStructs/mBusPacket.h" + +# include + + +typedef uint64_t mBus_EncodedDeviceID; + +typedef uint32_t CUL_stats_hash; + + +struct CUL_Stats_struct { + mBus_EncodedDeviceID _id1{}; + mBus_EncodedDeviceID _id2{}; + uint32_t _UnixTimeFirstSeen{}; + uint32_t _UnixTimeLastSeen{}; + uint16_t _lqi_rssi{}; + uint16_t _count{}; + CUL_stats_hash _sourceHash{}; +}; + + +typedef std::map mBusStatsMap; +typedef std::map mBusStatsSourceMap; + + +struct CUL_Stats { + // Create a string like this: + // mBus device ID;UNIX time first;UNIX time last;count;LQI;RSSI + // THC.02.12345678;1674030412;1674031412;123;101,-36 + String toString(const CUL_Stats_struct& element) const; + + + // Return true when packet wasn't already present. + bool add(const mBusPacket_t& packet); + bool add(const mBusPacket_t& packet, const String& source); + +private: + + bool add(const mBusPacket_t& packet, CUL_stats_hash key, CUL_stats_hash sourceHash); + +public: + + // Create string from front element and remove from map + String getFront(); + + void toHtml() const; + + mBusStatsMap _mBusStatsMap; + mBusStatsSourceMap _mBusStatsSourceMap; +}; + +#endif // ifdef USES_P094 +#endif // ifndef DATASTRUCTS_P094_CUL_STATS_H diff --git a/src/src/Helpers/Convert.cpp b/src/src/Helpers/Convert.cpp index de20e6ce1..1127858b1 100644 --- a/src/src/Helpers/Convert.cpp +++ b/src/src/Helpers/Convert.cpp @@ -1,205 +1,220 @@ -#include "../Helpers/Convert.h" - -#include "../Helpers/StringConverter.h" - -/*********************************************************************************************\ - Convert bearing in degree to bearing string -\*********************************************************************************************/ -const __FlashStringHelper * getBearing(int degrees) -{ - const __FlashStringHelper* directions[] { - F("N"), - F("NNE"), - F("NE"), - F("ENE"), - F("E"), - F("ESE"), - F("SE"), - F("SSE"), - F("S"), - F("SSW"), - F("SW"), - F("WSW"), - F("W"), - F("WNW"), - F("NW"), - F("NNW") - }; - constexpr size_t nrDirections = NR_ELEMENTS(directions); - const float stepsize = (360.0f / nrDirections); - - if (degrees < 0) { degrees += 360; } // Allow for bearing -360 .. 359 - const size_t bearing_idx = int((degrees + (stepsize / 2.0f)) / stepsize) % nrDirections; - - if (bearing_idx < nrDirections) { - return directions[bearing_idx]; - } - return F(""); -} - -float CelsiusToFahrenheit(float celsius) { - constexpr float ratio = 9.0f / 5.0f; - return celsius * ratio + 32; -} - -int m_secToBeaufort(float m_per_sec) { - // Use ints wit 0.1 m/sec resolution to reduce size. - const uint16_t dm_per_sec = 10 * m_per_sec; - const uint16_t speeds[]{3, 16, 34, 55, 80, 108, 139, 172, 208, 245, 285, 326}; - constexpr int nrElements = NR_ELEMENTS(speeds); - - for (int bft = 0; bft < nrElements; ++bft) { - if (dm_per_sec < speeds[bft]) return bft; - } - return nrElements; -} - -String centimeterToImperialLength(float cm) { - return millimeterToImperialLength(cm * 10.0f); -} - -String millimeterToImperialLength(float mm) { - float inches = mm / 25.4f; - int feet = inches / 12.0f; - - inches = inches - (feet * 12); - String result; - result.reserve(10); - - if (feet != 0) { - result += feet; - result += '\''; - } - result += toString(inches, 1); - result += '"'; - return result; -} - -float minutesToDay(int minutes) { - return minutes / 1440.0f; -} - -String minutesToDayHour(int minutes) { - const int days = minutes / 1440; - const int hours = (minutes % 1440) / 60; - return strformat(F("%dd%02dh"), days, hours); -} - -String minutesToDayHourMinute(int minutes) { - const int days = minutes / 1440; - const int hours = (minutes % 1440) / 60; - const int mins = (minutes % 1440) % 60; - if (days == 0) { - return strformat(F("%02dh%02dm"), hours, mins); - } - return strformat(F("%dd%02dh%02dm"), days, hours, mins); -} - -String minutesToHourColonMinute(int minutes) { - const int hours = (minutes % 1440) / 60; - const int mins = (minutes % 1440) % 60; - - return strformat(F("%02d:%02d"), hours, mins); -} - -String secondsToDayHourMinuteSecond(int seconds) { - const int sec = seconds % 60; - const int minutes = seconds / 60; - const int days = minutes / 1440; - const int min_day = (minutes % 1440); - const int hours = min_day / 60; - const int mins = min_day % 60; - if (days == 0) { - return strformat(F("%02d:%02d:%02d"), hours, mins, sec); - } - return strformat(F("%dT%02d:%02d:%02d"), days, hours, mins, sec); -} - -String format_msec_duration(int64_t duration) { - if (duration < 0ll) { - return concat('-', format_msec_duration(-1ll*duration)); - } - const uint32_t duration_s = duration / 1000ll; - const int32_t duration_ms = duration % 1000ll; - - if (duration_s < 60) { - return strformat( - F("%02d.%03d"), - duration_s, - duration_ms); - } - return strformat( - F("%s.%03d"), - secondsToDayHourMinuteSecond(duration_s).c_str(), - duration_ms); -} - - -// Compute the dew point temperature, given temperature and humidity (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php -// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 -float compute_dew_point_temp(float temperature, float humidity_percentage) { - return powf(humidity_percentage / 100.0f, 0.125f) * - (112.0f + 0.9f*temperature) + 0.1f*temperature - 112.0f; -} - -// Compute the humidity given temperature and dew point temperature (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php -// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 -float compute_humidity_from_dewpoint(float temperature, float dew_temperature) { - return 100.0f * powf((112.0f - 0.1f * temperature + dew_temperature) / - (112.0f + 0.9f * temperature), 8); -} - - - -/********************************************************************************************\ - Compensate air pressure for given altitude (in meters) - \*********************************************************************************************/ -float pressureElevation(float atmospheric, float altitude) { - // Equation taken from BMP180 datasheet (page 16): - // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf - - // Note that using the equation from wikipedia can give bad results - // at high altitude. See this thread for more information: - // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 - return atmospheric / powf(1.0f - (altitude / 44330.0f), 5.255f); -} - -float altitudeFromPressure(float atmospheric, float seaLevel) -{ - // Equation taken from BMP180 datasheet (page 16): - // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf - - // Note that using the equation from wikipedia can give bad results - // at high altitude. See this thread for more information: - // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 - return 44330.0f * (1.0f - powf(atmospheric / seaLevel, 0.1903f)); -} - - - - -/********************************************************************************************\ - In memory convert float to long - \*********************************************************************************************/ -unsigned long float2ul(float f) -{ - unsigned long ul; - - memcpy(&ul, &f, 4); - return ul; -} - -/********************************************************************************************\ - In memory convert long to float - \*********************************************************************************************/ -float ul2float(unsigned long ul) -{ - float f; - - memcpy(&f, &ul, 4); - return f; -} - - +#include "../Helpers/Convert.h" + +#include "../Helpers/StringConverter.h" +#include "../Helpers/ESPEasy_time_calc.h" + +/*********************************************************************************************\ + Convert bearing in degree to bearing string +\*********************************************************************************************/ +const __FlashStringHelper * getBearing(int degrees) +{ + const __FlashStringHelper* directions[] { + F("N"), + F("NNE"), + F("NE"), + F("ENE"), + F("E"), + F("ESE"), + F("SE"), + F("SSE"), + F("S"), + F("SSW"), + F("SW"), + F("WSW"), + F("W"), + F("WNW"), + F("NW"), + F("NNW") + }; + constexpr size_t nrDirections = NR_ELEMENTS(directions); + const float stepsize = (360.0f / nrDirections); + + if (degrees < 0) { degrees += 360; } // Allow for bearing -360 .. 359 + const size_t bearing_idx = int((degrees + (stepsize / 2.0f)) / stepsize) % nrDirections; + + if (bearing_idx < nrDirections) { + return directions[bearing_idx]; + } + return F(""); +} + +float CelsiusToFahrenheit(float celsius) { + constexpr float ratio = 9.0f / 5.0f; + return celsius * ratio + 32; +} + +int m_secToBeaufort(float m_per_sec) { + // Use ints wit 0.1 m/sec resolution to reduce size. + const uint16_t dm_per_sec = 10 * m_per_sec; + const uint16_t speeds[]{3, 16, 34, 55, 80, 108, 139, 172, 208, 245, 285, 326}; + constexpr int nrElements = NR_ELEMENTS(speeds); + + for (int bft = 0; bft < nrElements; ++bft) { + if (dm_per_sec < speeds[bft]) return bft; + } + return nrElements; +} + +String centimeterToImperialLength(float cm) { + return millimeterToImperialLength(cm * 10.0f); +} + +String millimeterToImperialLength(float mm) { + float inches = mm / 25.4f; + int feet = inches / 12.0f; + + inches = inches - (feet * 12); + String result; + result.reserve(10); + + if (feet != 0) { + result += feet; + result += '\''; + } + result += toString(inches, 1); + result += '"'; + return result; +} + +float minutesToDay(int minutes) { + return minutes / 1440.0f; +} + +String minutesToDayHour(int minutes) { + const int days = minutes / 1440; + const int hours = (minutes % 1440) / 60; + return strformat(F("%dd%02dh"), days, hours); +} + +String minutesToDayHourMinute(int minutes) { + const int days = minutes / 1440; + const int hours = (minutes % 1440) / 60; + const int mins = (minutes % 1440) % 60; + if (days == 0) { + return strformat(F("%02dh%02dm"), hours, mins); + } + return strformat(F("%dd%02dh%02dm"), days, hours, mins); +} + +String minutesToHourColonMinute(int minutes) { + const int hours = (minutes % 1440) / 60; + const int mins = (minutes % 1440) % 60; + + return strformat(F("%02d:%02d"), hours, mins); +} + +String secondsToDayHourMinuteSecond(int seconds) { + const int sec = seconds % 60; + const int minutes = seconds / 60; + const int days = minutes / 1440; + const int min_day = (minutes % 1440); + const int hours = min_day / 60; + const int mins = min_day % 60; + if (days == 0) { + return strformat(F("%02d:%02d:%02d"), hours, mins, sec); + } + return strformat(F("%dT%02d:%02d:%02d"), days, hours, mins, sec); +} + +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros) +{ + if (systemMicros < 0ll) { + return concat('-', secondsToDayHourMinuteSecond_ms(-1ll*systemMicros)); + } + + uint32_t usec{}; + const uint32_t seconds = micros_to_sec_usec(systemMicros, usec); + return strformat( + F("%s.%03u"), + secondsToDayHourMinuteSecond(seconds).c_str(), + usec / 1000ul); +} + +String format_msec_duration(int64_t duration) { + if (duration < 0ll) { + return concat('-', format_msec_duration(-1ll*duration)); + } + const uint32_t duration_s = duration / 1000ll; + const int32_t duration_ms = duration % 1000ll; + + if (duration_s < 60) { + return strformat( + F("%02d.%03d"), + duration_s, + duration_ms); + } + return strformat( + F("%s.%03d"), + secondsToDayHourMinuteSecond(duration_s).c_str(), + duration_ms); +} + + +// Compute the dew point temperature, given temperature and humidity (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_dewpoint_temperature.php +// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 +float compute_dew_point_temp(float temperature, float humidity_percentage) { + return powf(humidity_percentage / 100.0f, 0.125f) * + (112.0f + 0.9f*temperature) + 0.1f*temperature - 112.0f; +} + +// Compute the humidity given temperature and dew point temperature (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php +// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 +float compute_humidity_from_dewpoint(float temperature, float dew_temperature) { + return 100.0f * powf((112.0f - 0.1f * temperature + dew_temperature) / + (112.0f + 0.9f * temperature), 8); +} + + + +/********************************************************************************************\ + Compensate air pressure for given altitude (in meters) + \*********************************************************************************************/ +float pressureElevation(float atmospheric, float altitude) { + // Equation taken from BMP180 datasheet (page 16): + // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + + // Note that using the equation from wikipedia can give bad results + // at high altitude. See this thread for more information: + // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 + return atmospheric / powf(1.0f - (altitude / 44330.0f), 5.255f); +} + +float altitudeFromPressure(float atmospheric, float seaLevel) +{ + // Equation taken from BMP180 datasheet (page 16): + // http://www.adafruit.com/datasheets/BST-BMP180-DS000-09.pdf + + // Note that using the equation from wikipedia can give bad results + // at high altitude. See this thread for more information: + // http://forums.adafruit.com/viewtopic.php?f=22&t=58064 + return 44330.0f * (1.0f - powf(atmospheric / seaLevel, 0.1903f)); +} + + + + +/********************************************************************************************\ + In memory convert float to long + \*********************************************************************************************/ +unsigned long float2ul(float f) +{ + unsigned long ul; + + memcpy(&ul, &f, 4); + return ul; +} + +/********************************************************************************************\ + In memory convert long to float + \*********************************************************************************************/ +float ul2float(unsigned long ul) +{ + float f; + + memcpy(&f, &ul, 4); + return f; +} + + diff --git a/src/src/Helpers/Convert.h b/src/src/Helpers/Convert.h index becdc43e3..276c36ad5 100644 --- a/src/src/Helpers/Convert.h +++ b/src/src/Helpers/Convert.h @@ -1,67 +1,68 @@ -#ifndef HELPERS_CONVERT_H -#define HELPERS_CONVERT_H - -#include "../../ESPEasy_common.h" - -/*********************************************************************************************\ - Convert bearing in degree to bearing string -\*********************************************************************************************/ -const __FlashStringHelper * getBearing(int degrees); - -float CelsiusToFahrenheit(float celsius); - -int m_secToBeaufort(float m_per_sec); - -String centimeterToImperialLength(float cm); - -String millimeterToImperialLength(float mm); - -float minutesToDay(int minutes); - -String minutesToDayHour(int minutes); - -String minutesToDayHourMinute(int minutes); - -String minutesToHourColonMinute(int minutes); - -String secondsToDayHourMinuteSecond(int seconds); - -String format_msec_duration(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 -// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 -float compute_dew_point_temp(float temperature, float humidity_percentage); - -// Compute the humidity given temperature and dew point temperature (temp in Celsius) -// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php -// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 -float compute_humidity_from_dewpoint(float temperature, float dew_temperature); - -/********************************************************************************************\ - Compensate air pressure for measured atmospheric - pressure (in hPa) and given altitude (in meters) - \*********************************************************************************************/ -float pressureElevation(float atmospheric, float altitude); - -/********************************************************************************************\ - Calculates the altitude (in meters) from the specified atmospheric - pressure (in hPa), and sea-level pressure (in hPa). - @param seaLevel Sea-level pressure in hPa - @param atmospheric Atmospheric pressure in hPa - \*********************************************************************************************/ -float altitudeFromPressure(float atmospheric, float seaLevel); - -/********************************************************************************************\ - In memory convert float to long - \*********************************************************************************************/ -unsigned long float2ul(float f); - -/********************************************************************************************\ - In memory convert long to float - \*******************************************************************************************/ -float ul2float(unsigned long ul); - - - +#ifndef HELPERS_CONVERT_H +#define HELPERS_CONVERT_H + +#include "../../ESPEasy_common.h" + +/*********************************************************************************************\ + Convert bearing in degree to bearing string +\*********************************************************************************************/ +const __FlashStringHelper * getBearing(int degrees); + +float CelsiusToFahrenheit(float celsius); + +int m_secToBeaufort(float m_per_sec); + +String centimeterToImperialLength(float cm); + +String millimeterToImperialLength(float mm); + +float minutesToDay(int minutes); + +String minutesToDayHour(int minutes); + +String minutesToDayHourMinute(int minutes); + +String minutesToHourColonMinute(int minutes); + +String secondsToDayHourMinuteSecond(int seconds); +String secondsToDayHourMinuteSecond_ms(int64_t systemMicros); + +String format_msec_duration(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 +// Td = (f/100)^(1/8) * (112 + 0.9*T) + 0.1*T - 112 +float compute_dew_point_temp(float temperature, float humidity_percentage); + +// Compute the humidity given temperature and dew point temperature (temp in Celsius) +// Formula: http://www.ajdesigner.com/phphumidity/dewpoint_equation_relative_humidity.php +// f = 100 * ((112 - 0.1*T + Td) / (112 + 0.9 * T))^8 +float compute_humidity_from_dewpoint(float temperature, float dew_temperature); + +/********************************************************************************************\ + Compensate air pressure for measured atmospheric + pressure (in hPa) and given altitude (in meters) + \*********************************************************************************************/ +float pressureElevation(float atmospheric, float altitude); + +/********************************************************************************************\ + Calculates the altitude (in meters) from the specified atmospheric + pressure (in hPa), and sea-level pressure (in hPa). + @param seaLevel Sea-level pressure in hPa + @param atmospheric Atmospheric pressure in hPa + \*********************************************************************************************/ +float altitudeFromPressure(float atmospheric, float seaLevel); + +/********************************************************************************************\ + In memory convert float to long + \*********************************************************************************************/ +unsigned long float2ul(float f); + +/********************************************************************************************\ + In memory convert long to float + \*******************************************************************************************/ +float ul2float(unsigned long ul); + + + #endif // HELPERS_CONVERT_H \ No newline at end of file diff --git a/src/src/Helpers/Dallas1WireHelper.cpp b/src/src/Helpers/Dallas1WireHelper.cpp index 37c625bc1..fb1060226 100644 --- a/src/src/Helpers/Dallas1WireHelper.cpp +++ b/src/src/Helpers/Dallas1WireHelper.cpp @@ -1,1264 +1,1276 @@ -#include "../Helpers/Dallas1WireHelper.h" - -#include "../../_Plugin_Helper.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Misc.h" - -#include "../WebServer/JSON.h" - - -// DEBUG code using logic analyzer for timings -// #define DEBUG_LOGIC_ANALYZER_PIN 27 -// #define DEBUG_LOGIC_ANALYZER_PIN_ERROR 26 - - -// Macros to perform direct access on GPIOs -// Macros written by Paul Stoffregen -// See: https://github.com/PaulStoffregen/OneWire/blob/master/util/ -#include - - -// ESP8266 does work fine without the IRAM attribute -// But ESP32 may benefit from having the code always loaded in RAM. -#ifdef ESP8266 -# define DALLAS_IRAM_ATTR -#endif // ifdef ESP8266 -#ifdef ESP32 -# define DALLAS_IRAM_ATTR IRAM_ATTR -#endif // ifdef ESP32 - - -#include - -unsigned char ROM_NO[8]{ 0 }; -uint8_t LastDiscrepancy{}; -uint8_t LastFamilyDiscrepancy{}; -uint8_t LastDeviceFlag{}; - -int64_t usec_release{}; -int64_t presence_start{}; -int64_t presence_end{}; - - -// References to 1-wire family codes: -// http://owfs.sourceforge.net/simple_family.html -// https://github.com/owfs/owfs-doc/wiki/1Wire-Device-List -const __FlashStringHelper* Dallas_getModel(uint8_t family, const bool hasFixedResolution) { - switch (family) { - case 0x28: return F("DS18B20"); - case 0x3b: return hasFixedResolution ? F("MAX31826") : F("DS1825"); - case 0x22: return F("DS1822"); - case 0x10: return F("DS1820 / DS18S20"); - case 0x42: return F("DS28EA00"); - case 0x1D: return F("DS2423"); // 4k RAM with counter - case 0x01: return F("DS1990A"); // Serial Number iButton - } - return F(""); -} - -String Dallas_format_address(const uint8_t addr[], const bool hasFixedResolution) { - String result; - - result.reserve(40); - - for (uint8_t j = 0; j < 8; j++) - { - appendHexChar(addr[j], result); - - if (j < 7) { result += '-'; } - } - result += F(" ["); - result += Dallas_getModel(addr[0], hasFixedResolution); - result += ']'; - - return result; -} - -uint64_t Dallas_addr_to_uint64(const uint8_t addr[]) { - uint64_t tmpAddr_64 = 0; - - for (uint8_t i = 0; i < 8; ++i) { - tmpAddr_64 *= 256; - tmpAddr_64 += addr[i]; - } - return tmpAddr_64; -} - -void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]) { - uint8_t i = 8; - - while (i > 0) { - --i; - addr[i] = static_cast(value & 0xFF); - value >>= 8; - } -} - -void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) { - if ((gpio_pin_rx == -1) || - (gpio_pin_tx == -1) || - !validTaskIndex(TaskIndex)) { - return; - } - - if (nrVariables >= VARS_PER_TASK) { - nrVariables = VARS_PER_TASK; - } - - std::map addr_task_map; - - for (taskIndex_t task = 0; validTaskIndex(task); ++task) { - if (Dallas_plugin(Settings.getPluginID_for_task(task))) { - uint8_t tmpAddress[8] = { 0 }; - - for (uint8_t var_index = 0; var_index < VARS_PER_TASK; ++var_index) { - Dallas_plugin_get_addr(tmpAddress, task, var_index); - uint64_t tmpAddr_64 = Dallas_addr_to_uint64(tmpAddress); - - if (tmpAddr_64 != 0) { - addr_task_map[tmpAddr_64] = strformat( - F(" (task %d [%s#%s])") - , task + 1 - , getTaskDeviceName(task).c_str() - , getTaskValueName(task, var_index).c_str()); - } - } - } - } - - // find all suitable devices - std::vector scan_res; - std::vector fixed_res; - - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - Dallas_reset_search(); - uint8_t tmpAddress[8]{}; - - while (Dallas_search(tmpAddress, gpio_pin_rx, gpio_pin_tx)) - { - scan_res.push_back(Dallas_addr_to_uint64(tmpAddress)); - bool hasFixedResolution = false; - Dallas_getResolution(tmpAddress, gpio_pin_rx, gpio_pin_tx, hasFixedResolution); - fixed_res.push_back(hasFixedResolution); - } - - for (uint8_t var_index = 0; var_index < nrVariables; ++var_index) { - String rowLabel = F("Device Address"); - - if (nrVariables > 1) { - rowLabel += ' '; - rowLabel += (var_index + 1); - } - addRowLabel(rowLabel); - addSelector_Head(concat(F("dallas_addr"), static_cast(var_index))); - addSelector_Item(F("- None -"), -1, false); // Empty choice - - // get currently saved address - uint8_t savedAddress[8]; - Dallas_plugin_get_addr(savedAddress, TaskIndex, var_index); // Need to fetch only once? - - for (uint8_t index = 0; index < scan_res.size(); ++index) { - uint8_t tmpAddress[8]{}; - Dallas_uint64_to_addr(scan_res[index], tmpAddress); - String option = Dallas_format_address(tmpAddress, fixed_res[index]); - auto it = addr_task_map.find(scan_res[index]); - - if (it != addr_task_map.end()) { - option += it->second; - } - - const bool selected = (memcmp(tmpAddress, savedAddress, 8) == 0); - addSelector_Item(option, index, selected); - } - addSelector_Foot(); - } -} - -void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data) -{ - if (sensor_data.addr == 0) { - return; - } - addRowLabel(F("Address")); - addHtml(sensor_data.get_formatted_address()); - - addRowLabel(F("Resolution")); - addHtmlInt(sensor_data.actual_res); - - if (sensor_data.fixed_resolution) { - addHtml(F(" (fixed)")); - } - - addRowLabel(F("Parasite Powered")); - addHtml(jsonBool(sensor_data.parasitePowered)); - - if (sensor_data.parasitePowered) { - addHtml(F(" ")); - addEnabled(false); - addHtml(F(" Unsupported!")); - } - - addRowLabel(F("Samples Read Success")); - addHtmlInt(sensor_data.read_success); - - addRowLabel(F("Samples Read Init Failed")); - addHtmlInt(sensor_data.start_read_failed); - - addRowLabel(F("Samples Read Retry")); - addHtmlInt(sensor_data.read_retry); - - addRowLabel(F("Samples Read Failed")); - addHtmlInt(sensor_data.read_failed); -} - -void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) -{ - if (gpio_pin_rx == -1 || - gpio_pin_tx == -1 || - !validTaskIndex(TaskIndex)) { - return; - } - - if (nrVariables >= VARS_PER_TASK) { - nrVariables = VARS_PER_TASK; - } - - uint8_t addr[8]{}; - - for (uint8_t var_index = 0; var_index < nrVariables; ++var_index) { - const int selection = getFormItemInt(concat(F("dallas_addr"), static_cast(var_index)), -1); - - if (selection != -1) { - Dallas_scan(selection, addr, gpio_pin_rx, gpio_pin_tx); - Dallas_plugin_set_addr(addr, TaskIndex, var_index); - } - } -} - -bool Dallas_plugin(pluginID_t pluginID) -{ - constexpr pluginID_t PLUGIN_ID_P004_DALLAS_TEMP(4); - constexpr pluginID_t PLUGIN_ID_P080_DALLAS_IBUTTON(80); - constexpr pluginID_t PLUGIN_ID_P100_DS2423_COUNTER(100); - - return (pluginID == PLUGIN_ID_P004_DALLAS_TEMP) || - (pluginID == PLUGIN_ID_P080_DALLAS_IBUTTON) || - (pluginID == PLUGIN_ID_P100_DS2423_COUNTER); -} - -void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) -{ - if (var_index >= 4) { - return; - } - - for (uint8_t x = 0; x < 8; x++) { - uint32_t value = (uint32_t)Cache.getTaskDevicePluginConfigLong(TaskIndex, x); - addr[x] = static_cast((value >> (var_index * 8)) & 0xFF); - } -} - -void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) -{ - if (var_index >= 4) { - return; - } - LoadTaskSettings(TaskIndex); - const uint32_t mask = ~(0xFF << (var_index * 8)); - - for (uint8_t x = 0; x < 8; x++) { - uint32_t value = (uint32_t)ExtraTaskSettings.TaskDevicePluginConfigLong[x]; - value &= mask; - value += (static_cast(addr[x]) << (var_index * 8)); - ExtraTaskSettings.TaskDevicePluginConfigLong[x] = (long)value; - } - Cache.updateExtraTaskSettingsCache(); -} - -/*********************************************************************************************\ - Dallas Scan bus -\*********************************************************************************************/ -uint8_t Dallas_scan(uint8_t getDeviceROM, uint8_t *ROM, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t tmpaddr[8]; - uint8_t devCount = 0; - - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - - Dallas_reset_search(); - - while (Dallas_search(tmpaddr, gpio_pin_rx, gpio_pin_tx)) - { - if (getDeviceROM == devCount) { - for (uint8_t i = 0; i < 8; i++) { - ROM[i] = tmpaddr[i]; - } - } - devCount++; - } - return devCount; -} - -// read power supply -bool Dallas_is_parasite(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return false; - } - Dallas_write(0xB4, gpio_pin_rx, gpio_pin_tx); // read power supply - return !Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); -} - -void Dallas_startConversion(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM - - for (uint8_t i = 0; i < 8; i++) { - Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); - } - Dallas_write(0x44, gpio_pin_rx, gpio_pin_tx); -} - -/*********************************************************************************************\ -* Dallas Read temperature from scratchpad -\*********************************************************************************************/ -bool Dallas_readTemp(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - int16_t DSTemp; - uint8_t ScratchPad[12]; - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return false; - } - Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad - - for (uint8_t i = 0; i < 9; i++) { // read 9 bytes - ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); - } - - bool crc_ok = Dallas_crc8(ScratchPad); - - #ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("DS: SP: "); - - for (uint8_t x = 0; x < 9; x++) - { - if (x != 0) { - log += ','; - } - log += String(ScratchPad[x], HEX); - } - - if (crc_ok) { - log += F(",OK"); - } - - if (Dallas_is_parasite(ROM, gpio_pin_rx, gpio_pin_tx)) { - log += F(",P"); - } - log += ','; - log += ll2String(usec_release, DEC); - log += ','; - log += ll2String(presence_start, DEC); - log += ','; - log += ll2String(presence_end, DEC); - addLogMove(LOG_LEVEL_DEBUG, log); - } - #endif // ifndef BUILD_NO_DEBUG - - if (!crc_ok) - { -#ifdef DEBUG_LOGIC_ANALYZER_PIN_ERROR - - // Toggle the CRC error pin to make it better visible in the logic analyzer trace - static bool error_pin_toggle = false; - error_pin_toggle = !error_pin_toggle; - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN_ERROR, error_pin_toggle ? 1 : 0); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN_ERROR - - - *value = 0; - return false; - } - - if ((ROM[0] == 0x28) // DS18B20 - || (ROM[0] == 0x3b) // DS1825 - || (ROM[0] == 0x22) // DS1822 - || (ROM[0] == 0x42)) // DS28EA00 - { - DSTemp = (ScratchPad[1] << 8) + ScratchPad[0]; - - if (DSTemp == 0x550) { // power-on reset value - return false; - } - *value = (float(DSTemp) * 0.0625f); - } - else if (ROM[0] == 0x10) // DS1820 DS18S20 - { - if (ScratchPad[0] == 0xaa) { // power-on reset value - return false; - } - DSTemp = (ScratchPad[1] << 11) | ScratchPad[0] << 3; - DSTemp = ((DSTemp & 0xfff0) << 3) - 16 + - (((ScratchPad[7] - ScratchPad[6]) << 7) / ScratchPad[7]); - *value = float(DSTemp) * 0.0078125f; - } - return true; -} - -#ifdef USES_P080 -bool Dallas_readiButton(const uint8_t addr[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - // maybe this is needed to trigger the reading - // uint8_t ScratchPad[12]; - - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM - - for (uint8_t i = 0; i < 8; i++) { - Dallas_write(addr[i], gpio_pin_rx, gpio_pin_tx); - } - - Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad - - // for (uint8_t i = 0; i < 9; i++) // read 9 bytes - // ScratchPad[i] = Dallas_read(); - // end maybe this is needed to trigger the reading - - uint8_t tmpaddr[8]; - bool found = false; - - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - String log; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("DS : iButton searching for address: "); - log += Dallas_format_address(addr); - log += F(" found: "); - } - Dallas_reset_search(); - - while (Dallas_search(tmpaddr, gpio_pin_rx, gpio_pin_tx)) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += Dallas_format_address(tmpaddr); - log += ','; - } - - if (memcmp(addr, tmpaddr, 8) == 0) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log += F("Success. Button was found"); - } - found = true; - } - } - addLogMove(LOG_LEVEL_INFO, log); - return found; -} - -#endif // ifdef USES_P080 - -/*********************************************************************************************\ - Dallas read DS2423 counter - Taken from https://github.com/jbechter/arduino-onewire-DS2423 -\*********************************************************************************************/ -#ifdef USES_P100 -# define DS2423_READ_MEMORY_COMMAND 0xa5 -# define DS2423_PAGE_ONE 0xc0 -# define DS2423_PAGE_TWO 0xe0 - -bool Dallas_readCounter(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t counter) -{ - uint8_t data[45]; - - data[0] = DS2423_READ_MEMORY_COMMAND; - data[1] = (counter == 0 ? DS2423_PAGE_ONE : DS2423_PAGE_TWO); - data[2] = 0x01; - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return false; - } - - Dallas_write(data[0], gpio_pin_rx, gpio_pin_tx); - Dallas_write(data[1], gpio_pin_rx, gpio_pin_tx); - Dallas_write(data[2], gpio_pin_rx, gpio_pin_tx); - - for (int j = 3; j < 45; j++) { - data[j] = Dallas_read(gpio_pin_rx, gpio_pin_tx); - } - - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - - uint32_t count = (uint32_t)data[38]; - - for (int j = 37; j >= 35; j--) { - count = (count << 8) + (uint32_t)data[j]; - } - - uint16_t crc = Dallas_crc16(data, 43, 0); - const uint8_t *crcBytes = reinterpret_cast(&crc); - uint8_t crcLo = ~data[43]; - uint8_t crcHi = ~data[44]; - bool error = (crcLo != crcBytes[0]) || (crcHi != crcBytes[1]); - - if (!error) - { - *value = count; - return true; - } - else - { - *value = 0; - return false; - } -} - -#endif // ifdef USES_P100 - -/*********************************************************************************************\ -* Dallas Check for MAX31826 fixed 12 bit resolution, see datasheet page 9 'Memory' -\*********************************************************************************************/ -bool Dallas_check_hasFixedResolution(const uint8_t ROM[8], const uint8_t ScratchPad[12]) { - return (0x3B == ROM[0]) && // MAX31826: Family code 0x3B - (0xFF == ScratchPad[2]) && // All 1s - (0xFF == ScratchPad[3]) && - (0xFF == ScratchPad[5]) && - (0xFF == ScratchPad[6]) && - (0xFF == ScratchPad[7]) && - (0xF0 == (ScratchPad[4] & 0xF0)); // Ignore lower 4 bits used for 'Location' -} - -/*********************************************************************************************\ -* Dallas Get Resolution -\*********************************************************************************************/ -uint8_t Dallas_getResolution(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) { - bool hasFixedResolution; // Ignored - - return Dallas_getResolution(ROM, gpio_pin_rx, gpio_pin_tx, hasFixedResolution); -} - -uint8_t Dallas_getResolution(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx, bool& hasFixedResolution) -{ - // DS1820 and DS18S20 have no resolution configuration register - if (ROM[0] == 0x10) { return 12; } - - uint8_t ScratchPad[12]; - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return 0; - } - Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad - - for (uint8_t i = 0; i < 9; i++) { // read 9 bytes - ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); - } - - if (Dallas_crc8(ScratchPad)) { - if (Dallas_check_hasFixedResolution(ROM, ScratchPad)) { - hasFixedResolution = true; - return 12; - } - - switch (ScratchPad[4]) - { - case 0x7F: // 12 bit - return 12; - - case 0x5F: // 11 bit - return 11; - - case 0x3F: // 10 bit - return 10; - - case 0x1F: // 9 bit - default: - return 9; - } - } - return 0; -} - -/*********************************************************************************************\ -* Dallas Set Resolution -\*********************************************************************************************/ -bool Dallas_setResolution(const uint8_t ROM[8], uint8_t res, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - // DS1820 and DS18S20 have no resolution configuration register - if (ROM[0] == 0x10) { return true; } - - uint8_t ScratchPad[12]; - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { - return false; - } - Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad - - for (uint8_t i = 0; i < 9; i++) { // read 9 bytes - ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); - } - - if (!Dallas_crc8(ScratchPad)) { - addLog(LOG_LEVEL_ERROR, F("DS : Cannot set resolution")); - return false; - } - else - { - if (Dallas_check_hasFixedResolution(ROM, ScratchPad)) { - return true; // Can't change a fixed resolution - } - - uint8_t old_configuration = ScratchPad[4]; - - switch (res) - { - case 12: - ScratchPad[4] = 0x7F; // 12 bits - break; - case 11: - ScratchPad[4] = 0x5F; // 11 bits - break; - case 10: - ScratchPad[4] = 0x3F; // 10 bits - break; - case 9: - default: - ScratchPad[4] = 0x1F; // 9 bits - break; - } - - if (ScratchPad[4] == old_configuration) { - return true; - } - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { return false; } - Dallas_write(0x4E, gpio_pin_rx, gpio_pin_tx); // Write to EEPROM - Dallas_write(ScratchPad[2], gpio_pin_rx, gpio_pin_tx); // high alarm temp - Dallas_write(ScratchPad[3], gpio_pin_rx, gpio_pin_tx); // low alarm temp - Dallas_write(ScratchPad[4], gpio_pin_rx, gpio_pin_tx); // configuration register - - if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { return false; } - - // save the newly written values to eeprom - Dallas_write(0x48, gpio_pin_rx, gpio_pin_tx); - delay(100); // <--- added 20ms delay to allow 10ms long EEPROM write operation (as specified by datasheet) - Dallas_reset(gpio_pin_rx, gpio_pin_tx); - - return true; // new value set - } -} - -/*********************************************************************************************\ -* Dallas Reset -\*********************************************************************************************/ -uint8_t Dallas_reset(int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t retries = 125; - - ISR_noInterrupts(); - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_INPUT(gpio_pin_rx); - } else { - DIRECT_pinWrite(gpio_pin_tx, 1); - } -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - bool success = true; - - do // wait until the wire is high... just in case - { - if (--retries == 0) { - success = false; - } - delayMicroseconds(2); - } - while (!DIRECT_pinRead(gpio_pin_rx) && success); - - usec_release = 0; - presence_start = 0; - presence_end = 0; - - if (success) { -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - - // The master starts a transmission with a reset pulse, - // which pulls the wire to 0 volts for at least 480 µs. - // This resets every slave device on the bus. - DIRECT_pinWrite(gpio_pin_tx, 0); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_OUTPUT(gpio_pin_rx); - } - - delayMicroseconds(480); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_INPUT(gpio_pin_rx); - } else { - DIRECT_pinWrite(gpio_pin_tx, 1); - } -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - - - // After that, any slave device, if present, shows that it exists with a "presence" pulse: - // it holds the bus low for at least 60 µs after the master releases the bus. - // This may take about 30 usec after release for present sensors to pull the line low. - // Sequence: - // - Release => pin high - // - Presence condition start (typ: 30 usec after release) - // - Presence condition end (minimal duration 60 usec, typ: 100 usec) - // - Wait till 480 usec after release. - const uint64_t start = getMicros64(); - int64_t usec_passed = 0; - - bool waiting_for_presence = true; - - while ((usec_passed < 480) && waiting_for_presence) { - usec_passed = usecPassedSince(start); - - const bool pin_state = !!DIRECT_pinRead(gpio_pin_rx); - - if (usec_release == 0) { - if (pin_state) { - // Pin has been released - usec_release = usec_passed; - } - } else if (presence_start == 0) { - if (!pin_state) { - // Presence condition started - presence_start = usec_passed; - } - } else if (presence_end == 0) { - if (pin_state) { - // Presence condition ended - presence_end = usec_passed; - } - } else { - // Set the pin high so we have a clear starting level on the next write. - DIRECT_pinWrite(gpio_pin_tx, 1); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_OUTPUT(gpio_pin_rx); - } - waiting_for_presence = false; - delayMicroseconds(4); - } - delayMicroseconds(2); - } - } - ISR_interrupts(); - - if (presence_end != 0) { - const long presence_duration = presence_end - presence_start; - - if (presence_duration > 60) { return 1; } - } - return 0; -} - -#define FALSE 0 -#define TRUE 1 - -/*********************************************************************************************\ -* Dallas Reset Search -\*********************************************************************************************/ -void Dallas_reset_search() -{ - // reset the search state - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - - for (uint8_t i = 0; i < 8; i++) { - ROM_NO[i] = 0; - } -} - -/*********************************************************************************************\ -* Dallas Search bus -\*********************************************************************************************/ -uint8_t Dallas_search(uint8_t *newAddr, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t id_bit_number; - uint8_t last_zero, rom_byte_number, search_result; - unsigned char rom_byte_mask, search_direction; - - // initialize for search - id_bit_number = 1; - last_zero = 0; - rom_byte_number = 0; - rom_byte_mask = 1; - search_result = 0; - - // if the last call was not the last one - if (!LastDeviceFlag) - { - // 1-Wire reset - if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) - { - // reset the search - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - return FALSE; - } - - // issue the search command - Dallas_write(0xF0, gpio_pin_rx, gpio_pin_tx); - - // loop to do the search - do - { - // read a bit and its complement - const uint8_t id_bit = Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); - const uint8_t cmp_id_bit = Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); - - // check for no devices on 1-wire - if ((id_bit == 1) && (cmp_id_bit == 1)) { - break; - } - else - { - // all devices coupled have 0 or 1 - if (id_bit != cmp_id_bit) { - search_direction = id_bit; // bit write value for search - } - else - { - // if this discrepancy if before the Last Discrepancy - // on a previous next then pick the same as last time - if (id_bit_number < LastDiscrepancy) { - search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); - } - else { - // if equal to last pick 1, if not then pick 0 - search_direction = (id_bit_number == LastDiscrepancy); - } - - // if 0 was picked then record its position in LastZero - if (search_direction == 0) - { - last_zero = id_bit_number; - - // check for Last discrepancy in family - if (last_zero < 9) { - LastFamilyDiscrepancy = last_zero; - } - } - } - - // set or clear the bit in the ROM byte rom_byte_number - // with mask rom_byte_mask - if (search_direction == 1) { - ROM_NO[rom_byte_number] |= rom_byte_mask; - } - else { - ROM_NO[rom_byte_number] &= ~rom_byte_mask; - } - - DIRECT_pinWrite(gpio_pin_tx, 1); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_OUTPUT(gpio_pin_rx); - } - - // serial number search direction write bit - Dallas_write_bit(search_direction, gpio_pin_rx, gpio_pin_tx); - - // increment the byte counter id_bit_number - // and shift the mask rom_byte_mask - id_bit_number++; - rom_byte_mask <<= 1; - - // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask - if (rom_byte_mask == 0) - { - rom_byte_number++; - rom_byte_mask = 1; - } - } - } - while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 - - // if the search was successful then - if (!(id_bit_number < 65)) - { - // search successful so set LastDiscrepancy,LastDeviceFlag,search_result - LastDiscrepancy = last_zero; - - // check for last device - if (LastDiscrepancy == 0) { - LastDeviceFlag = TRUE; - } - - search_result = TRUE; - } - } - - // if no device found then reset counters so next 'search' will be like a first - if (!search_result || !ROM_NO[0]) - { - LastDiscrepancy = 0; - LastDeviceFlag = FALSE; - LastFamilyDiscrepancy = 0; - search_result = FALSE; - } - - for (int i = 0; i < 8; i++) { - newAddr[i] = ROM_NO[i]; - } - - return search_result; -} - -#undef FALSE -#undef TRUE - -/*********************************************************************************************\ -* Dallas Read byte -\*********************************************************************************************/ -uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t bitMask; - uint8_t r = 0; - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - if (Dallas_read_bit(gpio_pin_rx, gpio_pin_tx)) { - r |= bitMask; - } - } - - return r; -} - -/*********************************************************************************************\ -* Dallas Write byte -\*********************************************************************************************/ -void Dallas_write(uint8_t ByteToWrite, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - uint8_t bitMask; - - DIRECT_pinWrite(gpio_pin_tx, 1); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_OUTPUT(gpio_pin_rx); - } -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN - - for (bitMask = 0x01; bitMask; bitMask <<= 1) { - Dallas_write_bit((bitMask & ByteToWrite) ? 1 : 0, gpio_pin_rx, gpio_pin_tx); - } -#ifdef DEBUG_LOGIC_ANALYZER_PIN - - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN -} - -/*********************************************************************************************\ -* Dallas Read bit -\*********************************************************************************************/ -uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (gpio_pin_rx == -1) { return 0; } - - if (gpio_pin_tx == -1) { return 0; } - uint64_t start = 0; - uint8_t r = Dallas_read_bit_ISR(gpio_pin_rx, gpio_pin_tx, start); - - while (usecPassedSince(start) < 70ll) { - // Wait for another 55 usec - // Complete read cycle: - // LOW: 6 usec - // Float: 9 msec - // Read value. Typically the sensor keeps the level low for 27 usec. - // Wait for 55 usec => complete cycle = 6 + 9 + 55 = 70 usec. - } - return r; -} - -uint8_t DALLAS_IRAM_ATTR Dallas_read_bit_ISR( - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - uint64_t& start) -{ - uint8_t r; - - { - ISR_noInterrupts(); - start = getMicros64(); - DIRECT_pinWrite(gpio_pin_tx, 0); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_OUTPUT(gpio_pin_rx); - } - - while (usecPassedSince(start) < 6) { - // Wait for 6 usec - } - const uint64_t startwait = getMicros64(); - - if (gpio_pin_rx == gpio_pin_tx) { - DIRECT_PINMODE_INPUT(gpio_pin_rx); // let pin float, pull up will raise - } else { - DIRECT_pinWrite(gpio_pin_tx, 1); - } - - while (usecPassedSince(startwait) < 9ll) { - // Wait for another 9 usec - } - r = DIRECT_pinRead(gpio_pin_rx); - - ISR_interrupts(); - } - - return r; -} - -/*********************************************************************************************\ -* Dallas Write bit -\*********************************************************************************************/ -void Dallas_write_bit(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (gpio_pin_tx == -1) { return; } - - // Determine times in usec for high and low - // write 1: low 6 usec, high 64 usec - // write 0: low 60 usec, high 10 usec - const long low_time = (v & 1) ? 6 : 60; - const long high_time = (v & 1) ? 64 : 10; - uint64_t start = 0; - - Dallas_write_bit_ISR(v, gpio_pin_rx, gpio_pin_tx, low_time, high_time, start); - - while (usecPassedSince(start) < high_time) { - // output remains high - } -} - -void DALLAS_IRAM_ATTR Dallas_write_bit_ISR(uint8_t v, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - long low_time, - long high_time, - uint64_t& start) -{ - ISR_noInterrupts(); - start = getMicros64(); - DIRECT_pinWrite(gpio_pin_tx, 0); - - while (usecPassedSince(start) < low_time) { - // output remains low - } - start = getMicros64(); - DIRECT_pinWrite(gpio_pin_tx, 1); - ISR_interrupts(); -} - -/*********************************************************************************************\ -* Standard function to initiate addressing a sensor. -\*********************************************************************************************/ -bool Dallas_address_ROM(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) -{ - if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) { return false; } - Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM - - for (uint8_t i = 0; i < 8; i++) { - Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); - } - return true; -} - -/*********************************************************************************************\ -* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] -\*********************************************************************************************/ -bool Dallas_crc8(const uint8_t *addr) -{ - uint8_t crc = 0; - uint8_t len = 8; - - while (len--) - { - uint8_t inbyte = *addr++; // from 0 to 7 - - for (uint8_t i = 8; i; i--) - { - uint8_t mix = (crc ^ inbyte) & 0x01; - crc >>= 1; - - if (mix) { crc ^= 0x8C; } - inbyte >>= 1; - } - } - return crc == *addr; // addr 8 -} - -/*********************************************************************************************\ -* Dallas Calculate CRC16 -\*********************************************************************************************/ -uint16_t Dallas_crc16(const uint8_t *input, uint16_t len, uint16_t crc) -{ - static const uint8_t oddparity[16] = - { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; - - for (uint16_t i = 0; i < len; i++) { - // Even though we're just copying a byte from the input, - // we'll be doing 16-bit computation with it. - uint16_t cdata = input[i]; - cdata = (cdata ^ crc) & 0xff; - crc >>= 8; - - if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) { - crc ^= 0xC001; - } - - cdata <<= 6; - crc ^= cdata; - cdata <<= 1; - crc ^= cdata; - } - - return crc; -} - -Dallas_SensorData::Dallas_SensorData() : - addr(0), value(0.0f), - start_read_failed(0), start_read_retry(0), read_success(0), - read_retry(0), read_failed(0), reinit_count(0), actual_res(0), - measurementActive(false), valueRead(false), - parasitePowered(false), lastReadError(false) -{} - -void Dallas_SensorData::clear() { - addr = 0u; - value = 0.0f; - start_read_failed = 0u; - start_read_retry = 0u; - read_success = 0u; - read_retry = 0u; - read_failed = 0u; - actual_res = 0u; - - - measurementActive = false; - valueRead = false; - parasitePowered = false; - lastReadError = false; -} - -void Dallas_SensorData::set_measurement_inactive() { - measurementActive = false; - value = 0.0f; - valueRead = false; -} - -bool Dallas_SensorData::initiate_read(int8_t gpio_rx, int8_t gpio_tx, int8_t res) { - if (addr == 0) { return false; } - uint8_t tmpaddr[8]; - - Dallas_uint64_to_addr(addr, tmpaddr); - - if (lastReadError) { - if (!check_sensor(gpio_rx, gpio_tx, res)) { - return false; - } - lastReadError = false; - } - - if (!Dallas_address_ROM(tmpaddr, gpio_rx, gpio_tx)) { - ++start_read_retry; - - if (!Dallas_address_ROM(tmpaddr, gpio_rx, gpio_tx)) { - ++start_read_failed; - lastReadError = true; - return false; - } - } - Dallas_write(0x44, gpio_rx, gpio_tx); // Take temperature measurement - return true; -} - -bool Dallas_SensorData::collect_value(int8_t gpio_rx, int8_t gpio_tx) { - if ((addr != 0) && measurementActive) { - uint8_t tmpaddr[8]; - Dallas_uint64_to_addr(addr, tmpaddr); - - if (!Dallas_readTemp(tmpaddr, &value, gpio_rx, gpio_tx)) { - ++read_retry; - - if (!Dallas_readTemp(tmpaddr, &value, gpio_rx, gpio_tx)) { - ++read_failed; - lastReadError = true; - return false; - } - } - - ++read_success; - lastReadError = false; - valueRead = true; - return true; - } - return false; -} - -String Dallas_SensorData::get_formatted_address() const { - if (addr == 0) { return EMPTY_STRING; } - - uint8_t tmpaddr[8]; - - Dallas_uint64_to_addr(addr, tmpaddr); - return Dallas_format_address(tmpaddr, fixed_resolution); -} - -bool Dallas_SensorData::check_sensor(int8_t gpio_rx, int8_t gpio_tx, int8_t res) { - if (addr == 0) { return false; } - uint8_t tmpaddr[8]; - - fixed_resolution = false; // reset - - Dallas_uint64_to_addr(addr, tmpaddr); - - actual_res = Dallas_getResolution(tmpaddr, gpio_rx, gpio_tx, fixed_resolution); - - if (actual_res == 0) { - ++read_failed; - lastReadError = true; - return false; - } - - if ((res != actual_res) && !fixed_resolution) { - if (!Dallas_setResolution(tmpaddr, res, gpio_rx, gpio_tx)) { - return false; - } - actual_res = res; // Update for later use - } - - parasitePowered = Dallas_is_parasite(tmpaddr, gpio_rx, gpio_tx); - return true; -} +#include "../Helpers/Dallas1WireHelper.h" + +#if FEATURE_DALLAS_HELPER + +#include "../../_Plugin_Helper.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Misc.h" + +#include "../WebServer/JSON.h" + + +// DEBUG code using logic analyzer for timings +// #define DEBUG_LOGIC_ANALYZER_PIN 27 +// #define DEBUG_LOGIC_ANALYZER_PIN_ERROR 26 + + +// Macros to perform direct access on GPIOs +// Macros written by Paul Stoffregen +// See: https://github.com/PaulStoffregen/OneWire/blob/master/util/ +#include + + +// ESP8266 does work fine without the IRAM attribute +// But ESP32 may benefit from having the code always loaded in RAM. +#ifdef ESP8266 +# define DALLAS_IRAM_ATTR +#endif // ifdef ESP8266 +#ifdef ESP32 +# define DALLAS_IRAM_ATTR IRAM_ATTR +#endif // ifdef ESP32 + + +#include + +unsigned char ROM_NO[8]{ 0 }; +uint8_t LastDiscrepancy{}; +uint8_t LastFamilyDiscrepancy{}; +uint8_t LastDeviceFlag{}; + +int64_t usec_release{}; +int64_t presence_start{}; +int64_t presence_end{}; + + +// References to 1-wire family codes: +// http://owfs.sourceforge.net/simple_family.html +// https://github.com/owfs/owfs-doc/wiki/1Wire-Device-List +const __FlashStringHelper* Dallas_getModel(uint8_t family, const bool hasFixedResolution) { + switch (family) { + case 0x28: return F("DS18B20"); + case 0x3b: return hasFixedResolution ? F("MAX31826") : F("DS1825"); + case 0x22: return F("DS1822"); + case 0x10: return F("DS1820 / DS18S20"); + case 0x42: return F("DS28EA00"); + case 0x1D: return F("DS2423"); // 4k RAM with counter + case 0x01: return F("DS1990A"); // Serial Number iButton + } + return F("Unknown"); +} + +String Dallas_format_address(const uint8_t addr[], const bool hasFixedResolution) { + String result; + + result.reserve(40); + + for (uint8_t j = 0; j < 8; j++) + { + appendHexChar(addr[j], result); + + if (j < 7) { result += '-'; } + } + result += F(" ["); + result += Dallas_getModel(addr[0], hasFixedResolution); + result += ']'; + + return result; +} + +uint64_t Dallas_addr_to_uint64(const uint8_t addr[]) { + uint64_t tmpAddr_64 = 0; + + for (uint8_t i = 0; i < 8; ++i) { + tmpAddr_64 *= 256; + tmpAddr_64 += addr[i]; + } + return tmpAddr_64; +} + +void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]) { + uint8_t i = 8; + + while (i > 0) { + --i; + addr[i] = static_cast(value & 0xFF); + value >>= 8; + } +} + +void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) { + if ((gpio_pin_rx == -1) || + (gpio_pin_tx == -1) || + !validTaskIndex(TaskIndex)) { + return; + } + + if (nrVariables >= VARS_PER_TASK) { + nrVariables = VARS_PER_TASK; + } + + std::map addr_task_map; + + for (taskIndex_t task = 0; validTaskIndex(task); ++task) { + if (Dallas_plugin(Settings.getPluginID_for_task(task))) { + uint8_t tmpAddress[8] = { 0 }; + + const uint8_t valueCount = getValueCountForTask(task); + + for (uint8_t var_index = 0; var_index < valueCount; ++var_index) { + Dallas_plugin_get_addr(tmpAddress, task, var_index); + uint64_t tmpAddr_64 = Dallas_addr_to_uint64(tmpAddress); + + if (tmpAddr_64 != 0) { + addr_task_map.emplace( + std::make_pair( + tmpAddr_64, + strformat( + F(" (task %d [%s#%s])") + , task + 1 + , getTaskDeviceName(task).c_str() + , Cache.getTaskDeviceValueName(task, var_index).c_str()) + )); + } + } + } + } + + // find all suitable devices + std::vector scan_res; + std::vector fixed_res; + + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + Dallas_reset_search(); + uint8_t tmpAddress[8]{}; + + while (Dallas_search(tmpAddress, gpio_pin_rx, gpio_pin_tx)) + { + scan_res.push_back(Dallas_addr_to_uint64(tmpAddress)); + bool hasFixedResolution = false; + Dallas_getResolution(tmpAddress, gpio_pin_rx, gpio_pin_tx, hasFixedResolution); + fixed_res.push_back(hasFixedResolution); + } + + for (uint8_t var_index = 0; var_index < nrVariables; ++var_index) { + String rowLabel = F("Device Address"); + + if (nrVariables > 1) { + rowLabel += ' '; + rowLabel += (var_index + 1); + } + addRowLabel(rowLabel); + addSelector_Head(concat(F("dallas_addr"), static_cast(var_index))); + addSelector_Item(F("- None -"), -1, false); // Empty choice + + // get currently saved address + uint8_t savedAddress[8]; + Dallas_plugin_get_addr(savedAddress, TaskIndex, var_index); // Need to fetch only once? + + for (uint8_t index = 0; index < scan_res.size(); ++index) { + uint8_t tmpAddress[8]{}; + Dallas_uint64_to_addr(scan_res[index], tmpAddress); + String option = Dallas_format_address(tmpAddress, fixed_res[index]); + auto it = addr_task_map.find(scan_res[index]); + + if (it != addr_task_map.end()) { + option += it->second; + } + + const bool selected = (memcmp(tmpAddress, savedAddress, 8) == 0); + addSelector_Item(option, index, selected); + } + addSelector_Foot(); + } +} + +void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data) +{ + if (sensor_data.addr == 0) { + return; + } + addRowLabel(F("Address")); + addHtml(sensor_data.get_formatted_address()); + + addRowLabel(F("Resolution")); + addHtmlInt(sensor_data.actual_res); + + if (sensor_data.fixed_resolution) { + addHtml(F(" (fixed)")); + } + + addRowLabel(F("Parasite Powered")); + addHtml(jsonBool(sensor_data.parasitePowered)); + + if (sensor_data.parasitePowered) { + addHtml(F(" ")); + addEnabled(false); + addHtml(F(" Unsupported!")); + } + + addRowLabel(F("Samples Read Success")); + addHtmlInt(sensor_data.read_success); + + addRowLabel(F("Samples Read Init Failed")); + addHtmlInt(sensor_data.start_read_failed); + + addRowLabel(F("Samples Read Retry")); + addHtmlInt(sensor_data.read_retry); + + addRowLabel(F("Samples Read Failed")); + addHtmlInt(sensor_data.read_failed); +} + +void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables) +{ + if (gpio_pin_rx == -1 || + gpio_pin_tx == -1 || + !validTaskIndex(TaskIndex)) { + return; + } + + if (nrVariables >= VARS_PER_TASK) { + nrVariables = VARS_PER_TASK; + } + + uint8_t addr[8]{}; + + for (uint8_t var_index = 0; var_index < nrVariables; ++var_index) { + const int selection = getFormItemInt(concat(F("dallas_addr"), static_cast(var_index)), -1); + + if (selection != -1) { + Dallas_scan(selection, addr, gpio_pin_rx, gpio_pin_tx); + Dallas_plugin_set_addr(addr, TaskIndex, var_index); + } + } +} + +bool Dallas_plugin(pluginID_t pluginID) +{ + constexpr pluginID_t PLUGIN_ID_P004_DALLAS_TEMP(4); + constexpr pluginID_t PLUGIN_ID_P080_DALLAS_IBUTTON(80); + constexpr pluginID_t PLUGIN_ID_P100_DS2423_COUNTER(100); + + return (pluginID == PLUGIN_ID_P004_DALLAS_TEMP) || + (pluginID == PLUGIN_ID_P080_DALLAS_IBUTTON) || + (pluginID == PLUGIN_ID_P100_DS2423_COUNTER); +} + +void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) +{ + if (var_index >= 4) { + return; + } + + for (uint8_t x = 0; x < 8; x++) { + uint32_t value = (uint32_t)Cache.getTaskDevicePluginConfigLong(TaskIndex, x); + addr[x] = static_cast((value >> (var_index * 8)) & 0xFF); + } +} + +void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index) +{ + if (var_index >= 4) { + return; + } + LoadTaskSettings(TaskIndex); + const uint32_t mask = ~(0xFF << (var_index * 8)); + + for (uint8_t x = 0; x < 8; x++) { + uint32_t value = (uint32_t)ExtraTaskSettings.TaskDevicePluginConfigLong[x]; + value &= mask; + value += (static_cast(addr[x]) << (var_index * 8)); + ExtraTaskSettings.TaskDevicePluginConfigLong[x] = (long)value; + } + Cache.updateExtraTaskSettingsCache(); +} + +/*********************************************************************************************\ + Dallas Scan bus +\*********************************************************************************************/ +uint8_t Dallas_scan(uint8_t getDeviceROM, uint8_t *ROM, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t tmpaddr[8]; + uint8_t devCount = 0; + + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + + Dallas_reset_search(); + + while (Dallas_search(tmpaddr, gpio_pin_rx, gpio_pin_tx)) + { + if (getDeviceROM == devCount) { + for (uint8_t i = 0; i < 8; i++) { + ROM[i] = tmpaddr[i]; + } + } + devCount++; + } + return devCount; +} + +// read power supply +bool Dallas_is_parasite(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return false; + } + Dallas_write(0xB4, gpio_pin_rx, gpio_pin_tx); // read power supply + return !Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); +} + +void Dallas_startConversion(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM + + for (uint8_t i = 0; i < 8; i++) { + Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); + } + Dallas_write(0x44, gpio_pin_rx, gpio_pin_tx); +} + +/*********************************************************************************************\ +* Dallas Read temperature from scratchpad +\*********************************************************************************************/ +bool Dallas_readTemp(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + int16_t DSTemp; + uint8_t ScratchPad[12]; + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return false; + } + Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad + + for (uint8_t i = 0; i < 9; i++) { // read 9 bytes + ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); + } + + bool crc_ok = Dallas_crc8(ScratchPad); + + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("DS: SP: "); + + for (uint8_t x = 0; x < 9; x++) + { + if (x != 0) { + log += ','; + } + log += String(ScratchPad[x], HEX); + } + + if (crc_ok) { + log += F(",OK"); + } + + if (Dallas_is_parasite(ROM, gpio_pin_rx, gpio_pin_tx)) { + log += F(",P"); + } + log += ','; + log += ll2String(usec_release, DEC); + log += ','; + log += ll2String(presence_start, DEC); + log += ','; + log += ll2String(presence_end, DEC); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifndef BUILD_NO_DEBUG + + if (!crc_ok) + { +#ifdef DEBUG_LOGIC_ANALYZER_PIN_ERROR + + // Toggle the CRC error pin to make it better visible in the logic analyzer trace + static bool error_pin_toggle = false; + error_pin_toggle = !error_pin_toggle; + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN_ERROR, error_pin_toggle ? 1 : 0); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN_ERROR + + + *value = 0; + return false; + } + + if ((ROM[0] == 0x28) // DS18B20 + || (ROM[0] == 0x3b) // DS1825 + || (ROM[0] == 0x22) // DS1822 + || (ROM[0] == 0x42)) // DS28EA00 + { + DSTemp = (ScratchPad[1] << 8) + ScratchPad[0]; + + if (DSTemp == 0x550) { // power-on reset value + return false; + } + *value = (float(DSTemp) * 0.0625f); + } + else if (ROM[0] == 0x10) // DS1820 DS18S20 + { + if (ScratchPad[0] == 0xaa) { // power-on reset value + return false; + } + DSTemp = (ScratchPad[1] << 11) | ScratchPad[0] << 3; + DSTemp = ((DSTemp & 0xfff0) << 3) - 16 + + (((ScratchPad[7] - ScratchPad[6]) << 7) / ScratchPad[7]); + *value = float(DSTemp) * 0.0078125f; + } + return true; +} + +#ifdef USES_P080 +bool Dallas_readiButton(const uint8_t addr[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx, int8_t lastState) +{ + // maybe this is needed to trigger the reading + // uint8_t ScratchPad[12]; + + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM + + for (uint8_t i = 0; i < 8; i++) { + Dallas_write(addr[i], gpio_pin_rx, gpio_pin_tx); + } + + Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad + + // for (uint8_t i = 0; i < 9; i++) // read 9 bytes + // ScratchPad[i] = Dallas_read(); + // end maybe this is needed to trigger the reading + + uint8_t tmpaddr[8]; + bool found = false; + + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + String log; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log = F("DS : iButton searching for address: "); + log += Dallas_format_address(addr); + log += F(" found: "); + } + Dallas_reset_search(); + + while (Dallas_search(tmpaddr, gpio_pin_rx, gpio_pin_tx)) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += Dallas_format_address(tmpaddr); + log += ','; + } + + if (memcmp(addr, tmpaddr, 8) == 0) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F("Success. Button was found"); + } + found = true; + } + } + if ((-1 == lastState) || (lastState != found)) { + addLogMove(LOG_LEVEL_INFO, log); + } + return found; +} + +#endif // ifdef USES_P080 + +/*********************************************************************************************\ + Dallas read DS2423 counter + Taken from https://github.com/jbechter/arduino-onewire-DS2423 +\*********************************************************************************************/ +#ifdef USES_P100 +# define DS2423_READ_MEMORY_COMMAND 0xa5 +# define DS2423_PAGE_ONE 0xc0 +# define DS2423_PAGE_TWO 0xe0 + +bool Dallas_readCounter(const uint8_t ROM[8], float *value, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t counter) +{ + uint8_t data[45]; + + data[0] = DS2423_READ_MEMORY_COMMAND; + data[1] = (counter == 0 ? DS2423_PAGE_ONE : DS2423_PAGE_TWO); + data[2] = 0x01; + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return false; + } + + Dallas_write(data[0], gpio_pin_rx, gpio_pin_tx); + Dallas_write(data[1], gpio_pin_rx, gpio_pin_tx); + Dallas_write(data[2], gpio_pin_rx, gpio_pin_tx); + + for (int j = 3; j < 45; j++) { + data[j] = Dallas_read(gpio_pin_rx, gpio_pin_tx); + } + + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + + uint32_t count = (uint32_t)data[38]; + + for (int j = 37; j >= 35; j--) { + count = (count << 8) + (uint32_t)data[j]; + } + + uint16_t crc = Dallas_crc16(data, 43, 0); + const uint8_t *crcBytes = reinterpret_cast(&crc); + uint8_t crcLo = ~data[43]; + uint8_t crcHi = ~data[44]; + bool error = (crcLo != crcBytes[0]) || (crcHi != crcBytes[1]); + + if (!error) + { + *value = count; + return true; + } + else + { + *value = 0; + return false; + } +} + +#endif // ifdef USES_P100 + +/*********************************************************************************************\ +* Dallas Check for MAX31826 fixed 12 bit resolution, see datasheet page 9 'Memory' +\*********************************************************************************************/ +bool Dallas_check_hasFixedResolution(const uint8_t ROM[8], const uint8_t ScratchPad[12]) { + return (0x3B == ROM[0]) && // MAX31826: Family code 0x3B + (0xFF == ScratchPad[2]) && // All 1s + (0xFF == ScratchPad[3]) && + (0xFF == ScratchPad[5]) && + (0xFF == ScratchPad[6]) && + (0xFF == ScratchPad[7]) && + (0xF0 == (ScratchPad[4] & 0xF0)); // Ignore lower 4 bits used for 'Location' +} + +/*********************************************************************************************\ +* Dallas Get Resolution +\*********************************************************************************************/ +uint8_t Dallas_getResolution(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) { + bool hasFixedResolution; // Ignored + + return Dallas_getResolution(ROM, gpio_pin_rx, gpio_pin_tx, hasFixedResolution); +} + +uint8_t Dallas_getResolution(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx, bool& hasFixedResolution) +{ + // DS1820 and DS18S20 have no resolution configuration register + if (ROM[0] == 0x10) { return 12; } + + uint8_t ScratchPad[12]; + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return 0; + } + Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad + + for (uint8_t i = 0; i < 9; i++) { // read 9 bytes + ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); + } + + if (Dallas_crc8(ScratchPad)) { + if (Dallas_check_hasFixedResolution(ROM, ScratchPad)) { + hasFixedResolution = true; + return 12; + } + + switch (ScratchPad[4]) + { + case 0x7F: // 12 bit + return 12; + + case 0x5F: // 11 bit + return 11; + + case 0x3F: // 10 bit + return 10; + + case 0x1F: // 9 bit + default: + return 9; + } + } + return 0; +} + +/*********************************************************************************************\ +* Dallas Set Resolution +\*********************************************************************************************/ +bool Dallas_setResolution(const uint8_t ROM[8], uint8_t res, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + // DS1820 and DS18S20 have no resolution configuration register + if (ROM[0] == 0x10) { return true; } + + uint8_t ScratchPad[12]; + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { + return false; + } + Dallas_write(0xBE, gpio_pin_rx, gpio_pin_tx); // Read scratchpad + + for (uint8_t i = 0; i < 9; i++) { // read 9 bytes + ScratchPad[i] = Dallas_read(gpio_pin_rx, gpio_pin_tx); + } + + if (!Dallas_crc8(ScratchPad)) { + addLog(LOG_LEVEL_ERROR, F("DS : Cannot set resolution")); + return false; + } + else + { + if (Dallas_check_hasFixedResolution(ROM, ScratchPad)) { + return true; // Can't change a fixed resolution + } + + uint8_t old_configuration = ScratchPad[4]; + + switch (res) + { + case 12: + ScratchPad[4] = 0x7F; // 12 bits + break; + case 11: + ScratchPad[4] = 0x5F; // 11 bits + break; + case 10: + ScratchPad[4] = 0x3F; // 10 bits + break; + case 9: + default: + ScratchPad[4] = 0x1F; // 9 bits + break; + } + + if (ScratchPad[4] == old_configuration) { + return true; + } + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { return false; } + Dallas_write(0x4E, gpio_pin_rx, gpio_pin_tx); // Write to EEPROM + Dallas_write(ScratchPad[2], gpio_pin_rx, gpio_pin_tx); // high alarm temp + Dallas_write(ScratchPad[3], gpio_pin_rx, gpio_pin_tx); // low alarm temp + Dallas_write(ScratchPad[4], gpio_pin_rx, gpio_pin_tx); // configuration register + + if (!Dallas_address_ROM(ROM, gpio_pin_rx, gpio_pin_tx)) { return false; } + + // save the newly written values to eeprom + Dallas_write(0x48, gpio_pin_rx, gpio_pin_tx); + delay(100); // <--- added 20ms delay to allow 10ms long EEPROM write operation (as specified by datasheet) + Dallas_reset(gpio_pin_rx, gpio_pin_tx); + + return true; // new value set + } +} + +/*********************************************************************************************\ +* Dallas Reset +\*********************************************************************************************/ +uint8_t Dallas_reset(int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t retries = 125; + + ISR_noInterrupts(); + +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_INPUT(gpio_pin_rx); + } else { + DIRECT_pinWrite(gpio_pin_tx, 1); + } +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + bool success = true; + + do // wait until the wire is high... just in case + { + if (--retries == 0) { + success = false; + } + delayMicroseconds(2); + } + while (!DIRECT_pinRead(gpio_pin_rx) && success); + + usec_release = 0; + presence_start = 0; + presence_end = 0; + + if (success) { +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + // The master starts a transmission with a reset pulse, + // which pulls the wire to 0 volts for at least 480 µs. + // This resets every slave device on the bus. + DIRECT_pinWrite(gpio_pin_tx, 0); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_OUTPUT(gpio_pin_rx); + } + + delayMicroseconds(480); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_INPUT(gpio_pin_rx); + } else { + DIRECT_pinWrite(gpio_pin_tx, 1); + } +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + + // After that, any slave device, if present, shows that it exists with a "presence" pulse: + // it holds the bus low for at least 60 µs after the master releases the bus. + // This may take about 30 usec after release for present sensors to pull the line low. + // Sequence: + // - Release => pin high + // - Presence condition start (typ: 30 usec after release) + // - Presence condition end (minimal duration 60 usec, typ: 100 usec) + // - Wait till 480 usec after release. + const uint64_t start = getMicros64(); + int64_t usec_passed = 0; + + bool waiting_for_presence = true; + + while ((usec_passed < 480) && waiting_for_presence) { + usec_passed = usecPassedSince(start); + + const bool pin_state = !!DIRECT_pinRead(gpio_pin_rx); + + if (usec_release == 0) { + if (pin_state) { + // Pin has been released + usec_release = usec_passed; + } + } else if (presence_start == 0) { + if (!pin_state) { + // Presence condition started + presence_start = usec_passed; + } + } else if (presence_end == 0) { + if (pin_state) { + // Presence condition ended + presence_end = usec_passed; + } + } else { + // Set the pin high so we have a clear starting level on the next write. + DIRECT_pinWrite(gpio_pin_tx, 1); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_OUTPUT(gpio_pin_rx); + } + waiting_for_presence = false; + delayMicroseconds(4); + } + delayMicroseconds(2); + } + } + ISR_interrupts(); + + if (presence_end != 0) { + const long presence_duration = presence_end - presence_start; + + if (presence_duration > 60) { return 1; } + } + return 0; +} + +#define FALSE 0 +#define TRUE 1 + +/*********************************************************************************************\ +* Dallas Reset Search +\*********************************************************************************************/ +void Dallas_reset_search() +{ + // reset the search state + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + + for (uint8_t i = 0; i < 8; i++) { + ROM_NO[i] = 0; + } +} + +/*********************************************************************************************\ +* Dallas Search bus +\*********************************************************************************************/ +uint8_t Dallas_search(uint8_t *newAddr, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t id_bit_number; + uint8_t last_zero, rom_byte_number, search_result; + unsigned char rom_byte_mask, search_direction; + + // initialize for search + id_bit_number = 1; + last_zero = 0; + rom_byte_number = 0; + rom_byte_mask = 1; + search_result = 0; + + // if the last call was not the last one + if (!LastDeviceFlag) + { + // 1-Wire reset + if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) + { + // reset the search + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + return FALSE; + } + + // issue the search command + Dallas_write(0xF0, gpio_pin_rx, gpio_pin_tx); + + // loop to do the search + do + { + // read a bit and its complement + const uint8_t id_bit = Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); + const uint8_t cmp_id_bit = Dallas_read_bit(gpio_pin_rx, gpio_pin_tx); + + // check for no devices on 1-wire + if ((id_bit == 1) && (cmp_id_bit == 1)) { + break; + } + else + { + // all devices coupled have 0 or 1 + if (id_bit != cmp_id_bit) { + search_direction = id_bit; // bit write value for search + } + else + { + // if this discrepancy if before the Last Discrepancy + // on a previous next then pick the same as last time + if (id_bit_number < LastDiscrepancy) { + search_direction = ((ROM_NO[rom_byte_number] & rom_byte_mask) > 0); + } + else { + // if equal to last pick 1, if not then pick 0 + search_direction = (id_bit_number == LastDiscrepancy); + } + + // if 0 was picked then record its position in LastZero + if (search_direction == 0) + { + last_zero = id_bit_number; + + // check for Last discrepancy in family + if (last_zero < 9) { + LastFamilyDiscrepancy = last_zero; + } + } + } + + // set or clear the bit in the ROM byte rom_byte_number + // with mask rom_byte_mask + if (search_direction == 1) { + ROM_NO[rom_byte_number] |= rom_byte_mask; + } + else { + ROM_NO[rom_byte_number] &= ~rom_byte_mask; + } + + DIRECT_pinWrite(gpio_pin_tx, 1); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_OUTPUT(gpio_pin_rx); + } + + // serial number search direction write bit + Dallas_write_bit(search_direction, gpio_pin_rx, gpio_pin_tx); + + // increment the byte counter id_bit_number + // and shift the mask rom_byte_mask + id_bit_number++; + rom_byte_mask <<= 1; + + // if the mask is 0 then go to new SerialNum byte rom_byte_number and reset mask + if (rom_byte_mask == 0) + { + rom_byte_number++; + rom_byte_mask = 1; + } + } + } + while (rom_byte_number < 8); // loop until through all ROM bytes 0-7 + + // if the search was successful then + if (!(id_bit_number < 65)) + { + // search successful so set LastDiscrepancy,LastDeviceFlag,search_result + LastDiscrepancy = last_zero; + + // check for last device + if (LastDiscrepancy == 0) { + LastDeviceFlag = TRUE; + } + + search_result = TRUE; + } + } + + // if no device found then reset counters so next 'search' will be like a first + if (!search_result || !ROM_NO[0]) + { + LastDiscrepancy = 0; + LastDeviceFlag = FALSE; + LastFamilyDiscrepancy = 0; + search_result = FALSE; + } + + for (int i = 0; i < 8; i++) { + newAddr[i] = ROM_NO[i]; + } + + return search_result; +} + +#undef FALSE +#undef TRUE + +/*********************************************************************************************\ +* Dallas Read byte +\*********************************************************************************************/ +uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t bitMask; + uint8_t r = 0; + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + if (Dallas_read_bit(gpio_pin_rx, gpio_pin_tx)) { + r |= bitMask; + } + } + + return r; +} + +/*********************************************************************************************\ +* Dallas Write byte +\*********************************************************************************************/ +void Dallas_write(uint8_t ByteToWrite, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + uint8_t bitMask; + + DIRECT_pinWrite(gpio_pin_tx, 1); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_OUTPUT(gpio_pin_rx); + } +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + for (bitMask = 0x01; bitMask; bitMask <<= 1) { + Dallas_write_bit((bitMask & ByteToWrite) ? 1 : 0, gpio_pin_rx, gpio_pin_tx); + } +#ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +#endif // ifdef DEBUG_LOGIC_ANALYZER_PIN +} + +/*********************************************************************************************\ +* Dallas Read bit +\*********************************************************************************************/ +uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (gpio_pin_rx == -1) { return 0; } + + if (gpio_pin_tx == -1) { return 0; } + uint64_t start = 0; + uint8_t r = Dallas_read_bit_ISR(gpio_pin_rx, gpio_pin_tx, start); + + while (usecPassedSince(start) < 70ll) { + // Wait for another 55 usec + // Complete read cycle: + // LOW: 6 usec + // Float: 9 msec + // Read value. Typically the sensor keeps the level low for 27 usec. + // Wait for 55 usec => complete cycle = 6 + 9 + 55 = 70 usec. + } + return r; +} + +uint8_t DALLAS_IRAM_ATTR Dallas_read_bit_ISR( + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + uint64_t& start) +{ + uint8_t r; + + { + ISR_noInterrupts(); + start = getMicros64(); + DIRECT_pinWrite(gpio_pin_tx, 0); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_OUTPUT(gpio_pin_rx); + } + + while (usecPassedSince(start) < 6) { + // Wait for 6 usec + } + const uint64_t startwait = getMicros64(); + + if (gpio_pin_rx == gpio_pin_tx) { + DIRECT_PINMODE_INPUT(gpio_pin_rx); // let pin float, pull up will raise + } else { + DIRECT_pinWrite(gpio_pin_tx, 1); + } + + while (usecPassedSince(startwait) < 9ll) { + // Wait for another 9 usec + } + r = DIRECT_pinRead(gpio_pin_rx); + + ISR_interrupts(); + } + + return r; +} + +/*********************************************************************************************\ +* Dallas Write bit +\*********************************************************************************************/ +void Dallas_write_bit(uint8_t v, int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (gpio_pin_tx == -1) { return; } + + // Determine times in usec for high and low + // write 1: low 6 usec, high 64 usec + // write 0: low 60 usec, high 10 usec + const long low_time = (v & 1) ? 6 : 60; + const long high_time = (v & 1) ? 64 : 10; + uint64_t start = 0; + + Dallas_write_bit_ISR(v, gpio_pin_rx, gpio_pin_tx, low_time, high_time, start); + + while (usecPassedSince(start) < high_time) { + // output remains high + } +} + +void DALLAS_IRAM_ATTR Dallas_write_bit_ISR(uint8_t v, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + long low_time, + long high_time, + uint64_t& start) +{ + ISR_noInterrupts(); + start = getMicros64(); + DIRECT_pinWrite(gpio_pin_tx, 0); + + while (usecPassedSince(start) < low_time) { + // output remains low + } + start = getMicros64(); + DIRECT_pinWrite(gpio_pin_tx, 1); + ISR_interrupts(); +} + +/*********************************************************************************************\ +* Standard function to initiate addressing a sensor. +\*********************************************************************************************/ +bool Dallas_address_ROM(const uint8_t ROM[8], int8_t gpio_pin_rx, int8_t gpio_pin_tx) +{ + if (!Dallas_reset(gpio_pin_rx, gpio_pin_tx)) { return false; } + Dallas_write(0x55, gpio_pin_rx, gpio_pin_tx); // Choose ROM + + for (uint8_t i = 0; i < 8; i++) { + Dallas_write(ROM[i], gpio_pin_rx, gpio_pin_tx); + } + return true; +} + +/*********************************************************************************************\ +* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] +\*********************************************************************************************/ +bool Dallas_crc8(const uint8_t *addr) +{ + uint8_t crc = 0; + uint8_t len = 8; + + while (len--) + { + uint8_t inbyte = *addr++; // from 0 to 7 + + for (uint8_t i = 8; i; i--) + { + uint8_t mix = (crc ^ inbyte) & 0x01; + crc >>= 1; + + if (mix) { crc ^= 0x8C; } + inbyte >>= 1; + } + } + return crc == *addr; // addr 8 +} + +/*********************************************************************************************\ +* Dallas Calculate CRC16 +\*********************************************************************************************/ +uint16_t Dallas_crc16(const uint8_t *input, uint16_t len, uint16_t crc) +{ + static const uint8_t oddparity[16] = + { 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0 }; + + for (uint16_t i = 0; i < len; i++) { + // Even though we're just copying a byte from the input, + // we'll be doing 16-bit computation with it. + uint16_t cdata = input[i]; + cdata = (cdata ^ crc) & 0xff; + crc >>= 8; + + if (oddparity[cdata & 0x0F] ^ oddparity[cdata >> 4]) { + crc ^= 0xC001; + } + + cdata <<= 6; + crc ^= cdata; + cdata <<= 1; + crc ^= cdata; + } + + return crc; +} + +Dallas_SensorData::Dallas_SensorData() : + addr(0), value(0.0f), + start_read_failed(0), start_read_retry(0), read_success(0), + read_retry(0), read_failed(0), reinit_count(0), actual_res(0), + measurementActive(false), valueRead(false), + parasitePowered(false), lastReadError(false) +{} + +void Dallas_SensorData::clear() { + addr = 0u; + value = 0.0f; + start_read_failed = 0u; + start_read_retry = 0u; + read_success = 0u; + read_retry = 0u; + read_failed = 0u; + actual_res = 0u; + + + measurementActive = false; + valueRead = false; + parasitePowered = false; + lastReadError = false; +} + +void Dallas_SensorData::set_measurement_inactive() { + measurementActive = false; + value = 0.0f; + valueRead = false; +} + +bool Dallas_SensorData::initiate_read(int8_t gpio_rx, int8_t gpio_tx, int8_t res) { + if (addr == 0) { return false; } + uint8_t tmpaddr[8]; + + Dallas_uint64_to_addr(addr, tmpaddr); + + if (lastReadError) { + if (!check_sensor(gpio_rx, gpio_tx, res)) { + return false; + } + lastReadError = false; + } + + if (!Dallas_address_ROM(tmpaddr, gpio_rx, gpio_tx)) { + ++start_read_retry; + + if (!Dallas_address_ROM(tmpaddr, gpio_rx, gpio_tx)) { + ++start_read_failed; + lastReadError = true; + return false; + } + } + Dallas_write(0x44, gpio_rx, gpio_tx); // Take temperature measurement + return true; +} + +bool Dallas_SensorData::collect_value(int8_t gpio_rx, int8_t gpio_tx) { + if ((addr != 0) && measurementActive) { + uint8_t tmpaddr[8]; + Dallas_uint64_to_addr(addr, tmpaddr); + + if (!Dallas_readTemp(tmpaddr, &value, gpio_rx, gpio_tx)) { + ++read_retry; + + if (!Dallas_readTemp(tmpaddr, &value, gpio_rx, gpio_tx)) { + ++read_failed; + lastReadError = true; + return false; + } + } + + ++read_success; + lastReadError = false; + valueRead = true; + return true; + } + return false; +} + +String Dallas_SensorData::get_formatted_address() const { + if (addr == 0) { return EMPTY_STRING; } + + uint8_t tmpaddr[8]; + + Dallas_uint64_to_addr(addr, tmpaddr); + return Dallas_format_address(tmpaddr, fixed_resolution); +} + +bool Dallas_SensorData::check_sensor(int8_t gpio_rx, int8_t gpio_tx, int8_t res) { + if (addr == 0) { return false; } + uint8_t tmpaddr[8]; + + fixed_resolution = false; // reset + + Dallas_uint64_to_addr(addr, tmpaddr); + + actual_res = Dallas_getResolution(tmpaddr, gpio_rx, gpio_tx, fixed_resolution); + + if (actual_res == 0) { + ++read_failed; + lastReadError = true; + return false; + } + + if ((res != actual_res) && !fixed_resolution) { + if (!Dallas_setResolution(tmpaddr, res, gpio_rx, gpio_tx)) { + return false; + } + actual_res = res; // Update for later use + } + + parasitePowered = Dallas_is_parasite(tmpaddr, gpio_rx, gpio_tx); + return true; +} + +#endif // if FEATURE_DALLAS_HELPER \ No newline at end of file diff --git a/src/src/Helpers/Dallas1WireHelper.h b/src/src/Helpers/Dallas1WireHelper.h index ac6e9ad48..395b34d07 100644 --- a/src/src/Helpers/Dallas1WireHelper.h +++ b/src/src/Helpers/Dallas1WireHelper.h @@ -1,230 +1,234 @@ -#ifndef HELPERS_DALLAS1WIREHELPER_H -#define HELPERS_DALLAS1WIREHELPER_H - -#include "../../ESPEasy_common.h" - -#include "../DataTypes/TaskIndex.h" -#include "../DataTypes/PluginID.h" - - -// Used timings based on Maxim documentation. -// See https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html -// We use the "standard speed" timings, not the "Overdrive speed" - - - - -struct Dallas_SensorData { - Dallas_SensorData(); - - void clear(); - - bool check_sensor(int8_t gpio_rx, - int8_t gpio_tx, - int8_t res); - - void set_measurement_inactive(); - - bool initiate_read(int8_t gpio_rx, - int8_t gpio_tx, - int8_t res); - - bool collect_value(int8_t gpio_rx, - int8_t gpio_tx); - - String get_formatted_address() const; - - uint64_t addr; - float value; - uint32_t start_read_failed; - uint32_t start_read_retry; - uint32_t read_success; - uint32_t read_retry; - uint32_t read_failed; - uint32_t reinit_count; - uint8_t actual_res; - - bool measurementActive = false; - bool valueRead = false; - bool parasitePowered = false; - bool lastReadError = false; - bool fixed_resolution = false; -}; - - - -/*********************************************************************************************\ - Variables used to keep track of scanning the bus - N.B. these should not be shared for simultaneous scans on different pins -\*********************************************************************************************/ -extern unsigned char ROM_NO[8]; -extern uint8_t LastDiscrepancy; -extern uint8_t LastFamilyDiscrepancy; -extern uint8_t LastDeviceFlag; - - -/*********************************************************************************************\ - Timings for diagnostics regarding the reset + presence detection -\*********************************************************************************************/ -extern int64_t usec_release; // Time needed for the line to rise (typ: < 1 usec) -extern int64_t presence_start; // Start presence condition after release by master (typ: 30 usec) -extern int64_t presence_end; // End presence condition (minimal 60 usec, typ: 100 usec) - - -/*********************************************************************************************\ - Format 1-wire address -\*********************************************************************************************/ -const __FlashStringHelper * Dallas_getModel(uint8_t family, const bool hasFixedResolution = false); - -String Dallas_format_address(const uint8_t addr[], const bool hasFixedResolution = false); - -uint64_t Dallas_addr_to_uint64(const uint8_t addr[]); - -void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]); - -void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables = 1); - -void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data); - -void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables = 1); - -bool Dallas_plugin(pluginID_t pluginID); - -// Load ROM address from tasksettings -void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index = 0); - -void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index = 0); - - -/*********************************************************************************************\ - Dallas Scan bus -\*********************************************************************************************/ -uint8_t Dallas_scan(uint8_t getDeviceROM, - uint8_t *ROM, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -// read power supply -bool Dallas_is_parasite(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -void Dallas_startConversion(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas data from scratchpad -\*********************************************************************************************/ -bool Dallas_readTemp(const uint8_t ROM[8], - float *value, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -#ifdef USES_P080 -bool Dallas_readiButton(const uint8_t addr[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); -#endif - -#ifdef USES_P100 -bool Dallas_readCounter(const uint8_t ROM[8], - float *value, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - uint8_t counter); -#endif - -/*********************************************************************************************\ -* Dallas Get Resolution -\*********************************************************************************************/ -uint8_t Dallas_getResolution(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); -uint8_t Dallas_getResolution(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - bool & hasFixedResolution); - -/*********************************************************************************************\ -* Dallas Set Resolution -\*********************************************************************************************/ -bool Dallas_setResolution(const uint8_t ROM[8], - uint8_t res, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Reset -\*********************************************************************************************/ -uint8_t Dallas_reset(int8_t gpio_pin_rx, int8_t gpio_pin_tx); - - -/*********************************************************************************************\ -* Dallas Reset Search -\*********************************************************************************************/ -void Dallas_reset_search(); - -/*********************************************************************************************\ -* Dallas Search bus -\*********************************************************************************************/ -uint8_t Dallas_search(uint8_t *newAddr, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Read byte -\*********************************************************************************************/ -uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Write byte -\*********************************************************************************************/ -void Dallas_write(uint8_t ByteToWrite, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Read bit -* See https://github.com/espressif/arduino-esp32/issues/1335 -\*********************************************************************************************/ -uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx); -uint8_t Dallas_read_bit_ISR(int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint64_t& start); - -/*********************************************************************************************\ -* Dallas Write bit -* See https://github.com/espressif/arduino-esp32/issues/1335 -\*********************************************************************************************/ -void Dallas_write_bit(uint8_t v, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -void Dallas_write_bit_ISR(uint8_t v, - int8_t gpio_pin_rx, - int8_t gpio_pin_tx, - long low_time, - long high_time, - uint64_t &start); - -/*********************************************************************************************\ -* Standard function to initiate addressing a sensor. -\*********************************************************************************************/ -bool Dallas_address_ROM(const uint8_t ROM[8], - int8_t gpio_pin_rx, - int8_t gpio_pin_tx); - -/*********************************************************************************************\ -* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] -\*********************************************************************************************/ -bool Dallas_crc8(const uint8_t *addr); - -/*********************************************************************************************\ -* Dallas Calculate CRC16 -\*********************************************************************************************/ -uint16_t Dallas_crc16(const uint8_t *input, - uint16_t len, - uint16_t crc); - - - -#endif // ifndef HELPERS_DALLAS1WIREHELPER_H +#ifndef HELPERS_DALLAS1WIREHELPER_H +#define HELPERS_DALLAS1WIREHELPER_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_DALLAS_HELPER + +#include "../DataTypes/TaskIndex.h" +#include "../DataTypes/PluginID.h" + + +// Used timings based on Maxim documentation. +// See https://www.maximintegrated.com/en/design/technical-documents/app-notes/1/126.html +// We use the "standard speed" timings, not the "Overdrive speed" + + + + +struct Dallas_SensorData { + Dallas_SensorData(); + + void clear(); + + bool check_sensor(int8_t gpio_rx, + int8_t gpio_tx, + int8_t res); + + void set_measurement_inactive(); + + bool initiate_read(int8_t gpio_rx, + int8_t gpio_tx, + int8_t res); + + bool collect_value(int8_t gpio_rx, + int8_t gpio_tx); + + String get_formatted_address() const; + + uint64_t addr; + float value; + uint32_t start_read_failed; + uint32_t start_read_retry; + uint32_t read_success; + uint32_t read_retry; + uint32_t read_failed; + uint32_t reinit_count; + uint8_t actual_res; + + bool measurementActive = false; + bool valueRead = false; + bool parasitePowered = false; + bool lastReadError = false; + bool fixed_resolution = false; +}; + + + +/*********************************************************************************************\ + Variables used to keep track of scanning the bus + N.B. these should not be shared for simultaneous scans on different pins +\*********************************************************************************************/ +extern unsigned char ROM_NO[8]; +extern uint8_t LastDiscrepancy; +extern uint8_t LastFamilyDiscrepancy; +extern uint8_t LastDeviceFlag; + + +/*********************************************************************************************\ + Timings for diagnostics regarding the reset + presence detection +\*********************************************************************************************/ +extern int64_t usec_release; // Time needed for the line to rise (typ: < 1 usec) +extern int64_t presence_start; // Start presence condition after release by master (typ: 30 usec) +extern int64_t presence_end; // End presence condition (minimal 60 usec, typ: 100 usec) + + +/*********************************************************************************************\ + Format 1-wire address +\*********************************************************************************************/ +const __FlashStringHelper * Dallas_getModel(uint8_t family, const bool hasFixedResolution = false); + +String Dallas_format_address(const uint8_t addr[], const bool hasFixedResolution = false); + +uint64_t Dallas_addr_to_uint64(const uint8_t addr[]); + +void Dallas_uint64_to_addr(uint64_t value, uint8_t addr[]); + +void Dallas_addr_selector_webform_load(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables = 1); + +void Dallas_show_sensor_stats_webform_load(const Dallas_SensorData& sensor_data); + +void Dallas_addr_selector_webform_save(taskIndex_t TaskIndex, int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint8_t nrVariables = 1); + +bool Dallas_plugin(pluginID_t pluginID); + +// Load ROM address from tasksettings +void Dallas_plugin_get_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index = 0); + +void Dallas_plugin_set_addr(uint8_t addr[], taskIndex_t TaskIndex, uint8_t var_index = 0); + + +/*********************************************************************************************\ + Dallas Scan bus +\*********************************************************************************************/ +uint8_t Dallas_scan(uint8_t getDeviceROM, + uint8_t *ROM, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +// read power supply +bool Dallas_is_parasite(const uint8_t ROM[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +void Dallas_startConversion(const uint8_t ROM[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas data from scratchpad +\*********************************************************************************************/ +bool Dallas_readTemp(const uint8_t ROM[8], + float *value, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +#ifdef USES_P080 +bool Dallas_readiButton(const uint8_t addr[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + int8_t lastState = -1); +#endif + +#ifdef USES_P100 +bool Dallas_readCounter(const uint8_t ROM[8], + float *value, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + uint8_t counter); +#endif + +/*********************************************************************************************\ +* Dallas Get Resolution +\*********************************************************************************************/ +uint8_t Dallas_getResolution(const uint8_t ROM[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); +uint8_t Dallas_getResolution(const uint8_t ROM[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + bool & hasFixedResolution); + +/*********************************************************************************************\ +* Dallas Set Resolution +\*********************************************************************************************/ +bool Dallas_setResolution(const uint8_t ROM[8], + uint8_t res, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas Reset +\*********************************************************************************************/ +uint8_t Dallas_reset(int8_t gpio_pin_rx, int8_t gpio_pin_tx); + + +/*********************************************************************************************\ +* Dallas Reset Search +\*********************************************************************************************/ +void Dallas_reset_search(); + +/*********************************************************************************************\ +* Dallas Search bus +\*********************************************************************************************/ +uint8_t Dallas_search(uint8_t *newAddr, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas Read byte +\*********************************************************************************************/ +uint8_t Dallas_read(int8_t gpio_pin_rx, int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas Write byte +\*********************************************************************************************/ +void Dallas_write(uint8_t ByteToWrite, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas Read bit +* See https://github.com/espressif/arduino-esp32/issues/1335 +\*********************************************************************************************/ +uint8_t Dallas_read_bit(int8_t gpio_pin_rx, int8_t gpio_pin_tx); +uint8_t Dallas_read_bit_ISR(int8_t gpio_pin_rx, int8_t gpio_pin_tx, uint64_t& start); + +/*********************************************************************************************\ +* Dallas Write bit +* See https://github.com/espressif/arduino-esp32/issues/1335 +\*********************************************************************************************/ +void Dallas_write_bit(uint8_t v, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +void Dallas_write_bit_ISR(uint8_t v, + int8_t gpio_pin_rx, + int8_t gpio_pin_tx, + long low_time, + long high_time, + uint64_t &start); + +/*********************************************************************************************\ +* Standard function to initiate addressing a sensor. +\*********************************************************************************************/ +bool Dallas_address_ROM(const uint8_t ROM[8], + int8_t gpio_pin_rx, + int8_t gpio_pin_tx); + +/*********************************************************************************************\ +* Dallas Calculate CRC8 and compare it of addr[0-7] and compares it to addr[8] +\*********************************************************************************************/ +bool Dallas_crc8(const uint8_t *addr); + +/*********************************************************************************************\ +* Dallas Calculate CRC16 +\*********************************************************************************************/ +uint16_t Dallas_crc16(const uint8_t *input, + uint16_t len, + uint16_t crc); + + +#endif // if FEATURE_DALLAS_HELPER + +#endif // ifndef HELPERS_DALLAS1WIREHELPER_H diff --git a/src/src/Helpers/ESPEasy_FactoryDefault.cpp b/src/src/Helpers/ESPEasy_FactoryDefault.cpp index 0d9da42b5..2fb7c5862 100644 --- a/src/src/Helpers/ESPEasy_FactoryDefault.cpp +++ b/src/src/Helpers/ESPEasy_FactoryDefault.cpp @@ -1,455 +1,495 @@ -#include "../Helpers/ESPEasy_FactoryDefault.h" - -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" - -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/StorageLayout.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/FactoryDefaultPref.h" -#include "../DataStructs/GpioFactorySettingsStruct.h" - -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../ESPEasyCore/Serial.h" - -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/RTC.h" -#include "../Globals/ResetFactoryDefaultPref.h" -#include "../Globals/SecuritySettings.h" - -#include "../Helpers/_CPlugin_Helper.h" -#include "../Helpers/ESPEasyRTC.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Misc.h" - -#ifdef ESP32 - -// Store in NVS partition -#include "../Helpers/ESPEasy_NVS_Helper.h" - - -// Max. 15 char namespace for ESPEasy Factory Default settings -# define FACTORY_DEFAULT_NVS_NAMESPACE "ESPEasyFacDef" - -# include "../Helpers/StringConverter.h" -#include "../DataStructs/FactoryDefaultPref.h" -#include "../DataStructs/FactoryDefault_UnitName_NVS.h" -#include "../DataStructs/FactoryDefault_WiFi_NVS.h" -#include "../DataStructs/FactoryDefault_Network_NVS.h" -#include "../DataStructs/FactoryDefault_LogConsoleSettings_NVS.h" -# if FEATURE_ALTERNATIVE_CDN_URL -#include "../DataStructs/FactoryDefault_CDN_customurl_NVS.h" -#endif - - -#endif // ifdef ESP32 - - -/********************************************************************************************\ - Reset all settings to factory defaults - \*********************************************************************************************/ -void ResetFactory(bool formatFS) -{ - #ifdef ESP32 - ResetFactoryDefaultPreference.init(); - #endif - bool mustApplySafebootDefaults = false; - - if (ResetFactoryDefaultPreference.getPreference() == 0) - { -#if FEATURE_CUSTOM_PROVISIONING - ResetFactoryDefaultPreference.setDeviceModel(static_cast(DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL)); - ResetFactoryDefaultPreference.fetchRulesTXT(0, DEFAULT_PROVISIONING_FETCH_RULES1); - ResetFactoryDefaultPreference.fetchRulesTXT(1, DEFAULT_PROVISIONING_FETCH_RULES2); - ResetFactoryDefaultPreference.fetchRulesTXT(2, DEFAULT_PROVISIONING_FETCH_RULES3); - ResetFactoryDefaultPreference.fetchRulesTXT(3, DEFAULT_PROVISIONING_FETCH_RULES4); - ResetFactoryDefaultPreference.fetchNotificationDat(DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS); - ResetFactoryDefaultPreference.fetchSecurityDat(DEFAULT_PROVISIONING_FETCH_SECURITY); - ResetFactoryDefaultPreference.fetchConfigDat(DEFAULT_PROVISIONING_FETCH_CONFIG); - ResetFactoryDefaultPreference.fetchProvisioningDat(DEFAULT_PROVISIONING_FETCH_PROVISIONING); - ResetFactoryDefaultPreference.saveURL(DEFAULT_PROVISIONING_SAVE_URL); - ResetFactoryDefaultPreference.storeCredentials(DEFAULT_PROVISIONING_SAVE_CREDENTIALS); -#endif // if FEATURE_CUSTOM_PROVISIONING -#ifdef PLUGIN_BUILD_SAFEBOOT - mustApplySafebootDefaults = true; - ResetFactoryDefaultPreference.keepWiFi(true); - ResetFactoryDefaultPreference.keepNetwork(true); - ResetFactoryDefaultPreference.keepUnitName(true); - ResetFactoryDefaultPreference.keepLogConsoleSettings(true); - ResetFactoryDefaultPreference.keepCustomCdnUrl(true); - - Settings.UseLastWiFiFromRTC(true); -#endif // ifdef PLUGIN_BUILD_SAFEBOOT - - } - - - const GpioFactorySettingsStruct gpio_settings(ResetFactoryDefaultPreference.getDeviceModel()); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("ResetFactory")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Direct Serial is allowed here, since this is only an emergency task. - serialPrint(F("RESET: Resetting factory defaults... using ")); - serialPrint(getDeviceModelString(ResetFactoryDefaultPreference.getDeviceModel())); - serialPrintln(F(" settings")); - process_serialWriteBuffer(); - delay(1000); - - if (readFromRTC()) - { - serialPrint(F("RESET: Warm boot, reset count: ")); - serialPrintln(String(RTC.factoryResetCounter)); - - if (RTC.factoryResetCounter >= 3) - { - serialPrintln(F("RESET: Too many resets, protecting your flash memory (powercycle to solve this)")); - return; - } - } - else - { - serialPrintln(F("RESET: Cold boot")); - initRTC(); - - // TODO TD-er: Store set device model in RTC. - } - - RTC.flashCounter = 0; // reset flashcounter, since we're already counting the number of factory-resets. we dont want to hit a flash-count - // limit during reset. - RTC.factoryResetCounter++; - saveToRTC(); - - if (formatFS) { - // always format on factory reset, in case of corrupt FS - ESPEASY_FS.end(); - serialPrintln(F("RESET: formatting...")); - FS_format(); - serialPrintln(F("RESET: formatting done...")); - process_serialWriteBuffer(); - - if (!ESPEASY_FS.begin()) - { - serialPrintln(F("RESET: FORMAT FS FAILED!")); - return; - } - } - -#if FEATURE_CUSTOM_PROVISIONING - { - MakeProvisioningSettings(ProvisioningSettings); - - if (ProvisioningSettings.get()) { - ProvisioningSettings->setUser(F(DEFAULT_PROVISIONING_USER)); - ProvisioningSettings->setPass(F(DEFAULT_PROVISIONING_PASS)); - ProvisioningSettings->setUrl(F(DEFAULT_PROVISIONING_URL)); - ProvisioningSettings->ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - saveProvisioningSettings(*ProvisioningSettings); - } - } -#endif // if FEATURE_CUSTOM_PROVISIONING - - // pad files with extra zeros for future extensions - InitFile(SettingsType::SettingsFileEnum::FILE_CONFIG_type); - InitFile(SettingsType::SettingsFileEnum::FILE_SECURITY_type); - #if FEATURE_NOTIFIER - InitFile(SettingsType::SettingsFileEnum::FILE_NOTIFICATION_type); - #endif // if FEATURE_NOTIFIER - - InitFile(getRulesFileName(0), 0); - - Settings.clearMisc(); - - if (!ResetFactoryDefaultPreference.keepNTP() || mustApplySafebootDefaults) { - Settings.clearTimeSettings(); - Settings.UseNTP(DEFAULT_USE_NTP); - strcpy_P(Settings.NTPHost, PSTR(DEFAULT_NTP_HOST)); - Settings.TimeZone = DEFAULT_TIME_ZONE; - Settings.DST = DEFAULT_USE_DST; - } - - if (!ResetFactoryDefaultPreference.keepNetwork() || mustApplySafebootDefaults) { - Settings.clearNetworkSettings(); - - // TD-er Reset access control - str2ip(F(DEFAULT_IPRANGE_LOW), SecuritySettings.AllowedIPrangeLow); - str2ip(F(DEFAULT_IPRANGE_HIGH), SecuritySettings.AllowedIPrangeHigh); - SecuritySettings.IPblockLevel = DEFAULT_IP_BLOCK_LEVEL; - - #if DEFAULT_USE_STATIC_IP - str2ip((char *)DEFAULT_IP, Settings.IP); - str2ip((char *)DEFAULT_DNS, Settings.DNS); - str2ip((char *)DEFAULT_GW, Settings.Gateway); - str2ip((char *)DEFAULT_SUBNET, Settings.Subnet); - #endif // if DEFAULT_USE_STATIC_IP - Settings.IncludeHiddenSSID(DEFAULT_WIFI_INCLUDE_HIDDEN_SSID); - } - - Settings.clearNotifications(); - Settings.clearControllers(); - Settings.clearTasks(); - - if (!ResetFactoryDefaultPreference.keepLogConsoleSettings() || mustApplySafebootDefaults) { - Settings.clearLogSettings(); - str2ip((char *)DEFAULT_SYSLOG_IP, Settings.Syslog_IP); - - setLogLevelFor(LOG_TO_SYSLOG, DEFAULT_SYSLOG_LEVEL); - setLogLevelFor(LOG_TO_SERIAL, DEFAULT_SERIAL_LOG_LEVEL); - setLogLevelFor(LOG_TO_WEBLOG, DEFAULT_WEB_LOG_LEVEL); - setLogLevelFor(LOG_TO_SDCARD, DEFAULT_SD_LOG_LEVEL); - Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; - Settings.SyslogPort = DEFAULT_SYSLOG_PORT; - Settings.UseValueLogger = DEFAULT_USE_SD_LOG; - - // FIXME TD-er: Must also keep console settings. - Settings.console_serial_port = DEFAULT_CONSOLE_PORT; - Settings.console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; - Settings.console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; - Settings.console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; - Settings.UseSerial = DEFAULT_USE_SERIAL; - Settings.BaudRate = DEFAULT_SERIAL_BAUD; -} - - if (!ResetFactoryDefaultPreference.keepUnitName() || mustApplySafebootDefaults) { - Settings.clearUnitNameSettings(); - Settings.Unit = UNIT; - strcpy_P(Settings.Name, PSTR(DEFAULT_NAME)); - Settings.UDPPort = DEFAULT_SYNC_UDP_PORT; - } - - if (!ResetFactoryDefaultPreference.keepWiFi() || mustApplySafebootDefaults) { - strcpy_P(SecuritySettings.WifiSSID, PSTR(DEFAULT_SSID)); - strcpy_P(SecuritySettings.WifiKey, PSTR(DEFAULT_KEY)); - strcpy_P(SecuritySettings.WifiSSID2, PSTR(DEFAULT_SSID2)); - strcpy_P(SecuritySettings.WifiKey2, PSTR(DEFAULT_KEY2)); - strcpy_P(SecuritySettings.WifiAPKey, PSTR(DEFAULT_AP_KEY)); - } - strcpy_P(SecuritySettings.Password, PSTR(DEFAULT_ADMIN_PASS)); - - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - - // now we set all parameters that need to be non-zero as default value - - - Settings.PID = ESP_PROJECT_PID; - Settings.Version = VERSION; - Settings.Build = get_build_nr(); - - // Settings.IP_Octet = DEFAULT_IP_OCTET; - // Settings.Delay = DEFAULT_DELAY; - Settings.Pin_i2c_sda = gpio_settings.i2c_sda; - Settings.Pin_i2c_scl = gpio_settings.i2c_scl; - Settings.Pin_status_led = gpio_settings.status_led; - - // Settings.Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; - Settings.Pin_sd_cs = -1; - Settings.Pin_Reset = DEFAULT_PIN_RESET_BUTTON; - Settings.Protocol[0] = DEFAULT_PROTOCOL; - - // Settings.deepSleep_wakeTime = 0; // Sleep disabled - // Settings.CustomCSS = false; - // Settings.InitSPI = DEFAULT_SPI; - - // advanced Settings - // Settings.UseRules = DEFAULT_USE_RULES; - Settings.ControllerEnabled[0] = DEFAULT_CONTROLLER_ENABLED; - Settings.MQTTRetainFlag_unused = DEFAULT_MQTT_RETAIN; - Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; - Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; - - // allow to set default latitude and longitude - #ifdef DEFAULT_LATITUDE - Settings.Latitude = DEFAULT_LATITUDE; - #endif // ifdef DEFAULT_LATITUDE - #ifdef DEFAULT_LONGITUDE - Settings.Longitude = DEFAULT_LONGITUDE; - #endif // ifdef DEFAULT_LONGITUDE - -#ifdef ESP32 - - // Ethernet related settings are never used on ESP8266 - Settings.ETH_Phy_Addr = gpio_settings.eth_phyaddr; - Settings.ETH_Pin_mdc = gpio_settings.eth_mdc; - Settings.ETH_Pin_mdio = gpio_settings.eth_mdio; - Settings.ETH_Pin_power = gpio_settings.eth_power; - Settings.ETH_Phy_Type = gpio_settings.eth_phytype; - Settings.ETH_Clock_Mode = gpio_settings.eth_clock_mode; -#endif // ifdef ESP32 - Settings.NetworkMedium = gpio_settings.network_medium; - - /* - Settings.GlobalSync = DEFAULT_USE_GLOBAL_SYNC; - - Settings.IP_Octet = DEFAULT_IP_OCTET; - Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; - Settings.UseSSDP = DEFAULT_USE_SSDP; - Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; - Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; - */ - - // Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; - - Settings.JSONBoolWithoutQuotes(DEFAULT_JSON_BOOL_WITHOUT_QUOTES); - Settings.EnableTimingStats(DEFAULT_ENABLE_TIMING_STATS); - -#ifdef PLUGIN_DESCR - strcpy_P(Settings.Name, PSTR(PLUGIN_DESCR)); -#endif // ifdef PLUGIN_DESCR - -#ifndef LIMIT_BUILD_SIZE - addPredefinedPlugins(gpio_settings); - addPredefinedRules(gpio_settings); -#endif // ifndef LIMIT_BUILD_SIZE - -#if DEFAULT_CONTROLLER - { - // Place in a scope to have its memory freed ASAP - MakeControllerSettings(ControllerSettings); // -V522 - - if (AllocatedControllerSettings()) { - safe_strncpy(ControllerSettings->Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings->Subscribe)); - safe_strncpy(ControllerSettings->Publish, F(DEFAULT_PUB), sizeof(ControllerSettings->Publish)); - safe_strncpy(ControllerSettings->MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings->MQTTLwtTopic)); - safe_strncpy(ControllerSettings->LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), - sizeof(ControllerSettings->LWTMessageConnect)); - safe_strncpy(ControllerSettings->LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), - sizeof(ControllerSettings->LWTMessageDisconnect)); - str2ip((char *)DEFAULT_SERVER, ControllerSettings->IP); - ControllerSettings->setHostname(F(DEFAULT_SERVER_HOST)); - ControllerSettings->UseDNS = DEFAULT_SERVER_USEDNS; - ControllerSettings->useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); - ControllerSettings->Port = DEFAULT_PORT; - ControllerSettings->ClientTimeout = DEFAULT_CONTROLLER_TIMEOUT; - setControllerUser(0, *ControllerSettings, F(DEFAULT_CONTROLLER_USER)); - setControllerPass(0, *ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); - - SaveControllerSettings(0, *ControllerSettings); - } - } -#endif // if DEFAULT_CONTROLLER - -#ifdef ESP32 - { - ESPEasy_NVS_Helper preferences; - preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE), true); - - if (ResetFactoryDefaultPreference.from_NVS(preferences)) { - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - } - - if (ResetFactoryDefaultPreference.keepUnitName()) - { - FactoryDefault_UnitName_NVS unitNameNVS{}; - unitNameNVS.applyToSettings_from_NVS(preferences); - } - if (ResetFactoryDefaultPreference.keepWiFi()) - { - FactoryDefault_WiFi_NVS wifiNVS{}; - wifiNVS.applyToSettings_from_NVS(preferences); - } - if (ResetFactoryDefaultPreference.keepNetwork()) - { - // Restore Network IP settings - FactoryDefault_Network_NVS network_nvs; - network_nvs.applyToSettings_from_NVS(preferences); - } - if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) - { - // Restore Log and Console settings - FactoryDefault_LogConsoleSettings_NVS log_console_nvs; - log_console_nvs.applyToSettings_from_NVS(preferences); - } - -#if FEATURE_ALTERNATIVE_CDN_URL - if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) { - FactoryDefault_CDN_customurl_NVS::applyToSettings_from_NVS(preferences); - } -#endif - } -#endif - - const bool forFactoryReset = true; - SaveSettings(forFactoryReset); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("ResetFactory2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - serialPrintln(F("RESET: Successful, rebooting. (you might need to press the reset button if you've just flashed the firmware)")); - - // 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 - WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters - reboot(IntendedRebootReason_e::ResetFactory); -} - -/*********************************************************************************************\ - Collect the stored preference for factory default -\*********************************************************************************************/ -void applyFactoryDefaultPref() { - // TODO TD-er: Store it in more places to make it more persistent - Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); - -#ifdef ESP32 - ESPEasy_NVS_Helper preferences; - preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE)); - ResetFactoryDefaultPreference.to_NVS(preferences); - { - FactoryDefault_UnitName_NVS unitNameNVS{}; - if (ResetFactoryDefaultPreference.keepUnitName()) - { - // Store Unit nr and hostname - unitNameNVS.fromSettings_to_NVS(preferences); - } else { - unitNameNVS.clear_from_NVS(preferences); - } - } - { - FactoryDefault_WiFi_NVS wifiNVS{}; - if (ResetFactoryDefaultPreference.keepWiFi()) - { - // Store WiFi credentials - wifiNVS.fromSettings_to_NVS(preferences); - } else { - wifiNVS.clear_from_NVS(preferences); - } - } - { - FactoryDefault_Network_NVS network_nvs{}; - if (ResetFactoryDefaultPreference.keepNetwork()) - { - // Store Network IP settings - network_nvs.fromSettings_to_NVS(preferences); - } else { - network_nvs.clear_from_NVS(preferences); - } - } - { - FactoryDefault_LogConsoleSettings_NVS log_console_nvs{}; - if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) - { - // Store Log and Console settings - log_console_nvs.fromSettings_to_NVS(preferences); - } else { - log_console_nvs.clear_from_NVS(preferences); - } - } -# if FEATURE_ALTERNATIVE_CDN_URL - { - if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) - { - // Store custom CDN - FactoryDefault_CDN_customurl_NVS::fromSettings_to_NVS(preferences); - } else { - FactoryDefault_CDN_customurl_NVS::clear_from_NVS(preferences); - } - } -# endif // if FEATURE_ALTERNATIVE_CDN_URL - - - preferences.end(); -#endif // ifdef ESP32 -} +#include "../Helpers/ESPEasy_FactoryDefault.h" + +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + +#include "../CustomBuild/CompiletimeDefines.h" +#include "../CustomBuild/StorageLayout.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/FactoryDefaultPref.h" +#include "../DataStructs/GpioFactorySettingsStruct.h" + +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../ESPEasyCore/Serial.h" + +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/RTC.h" +#include "../Globals/ResetFactoryDefaultPref.h" +#include "../Globals/SecuritySettings.h" + +#include "../Helpers/_CPlugin_Helper.h" +#include "../Helpers/ESPEasyRTC.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Misc.h" + +#ifdef ESP32 + +// Store in NVS partition +# include "../Helpers/ESPEasy_NVS_Helper.h" + + +// Max. 15 char namespace for ESPEasy Factory Default settings +# define FACTORY_DEFAULT_NVS_NAMESPACE "ESPEasyFacDef" + +# include "../Helpers/StringConverter.h" +# include "../DataStructs/FactoryDefaultPref.h" +# include "../DataStructs/FactoryDefault_UnitName_NVS.h" +# include "../DataStructs/FactoryDefault_WiFi_NVS.h" +# include "../DataStructs/FactoryDefault_Network_NVS.h" +# include "../DataStructs/FactoryDefault_LogConsoleSettings_NVS.h" +# if FEATURE_ALTERNATIVE_CDN_URL +# include "../DataStructs/FactoryDefault_CDN_customurl_NVS.h" +# endif // if FEATURE_ALTERNATIVE_CDN_URL + + +#endif // ifdef ESP32 + + +/********************************************************************************************\ + Reset all settings to factory defaults + \*********************************************************************************************/ +void ResetFactory(bool formatFS) +{ + bool mustApplySafebootDefaults = false; + + #ifdef ESP32 + ESPEasy_NVS_Helper preferences; + + if (!ResetFactoryDefaultPreference.init(preferences)) { + mustApplySafebootDefaults = true; + } + #endif // ifdef ESP32 + + if (ResetFactoryDefaultPreference.getPreference() == 0) + { +#if FEATURE_CUSTOM_PROVISIONING + ResetFactoryDefaultPreference.setDeviceModel(static_cast(DEFAULT_FACTORY_DEFAULT_DEVICE_MODEL)); + #if DEFAULT_PROVISIONING_FETCH_RULES1 + ResetFactoryDefaultPreference.fetchRulesTXT(0, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES2 + ResetFactoryDefaultPreference.fetchRulesTXT(1, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES3 + ResetFactoryDefaultPreference.fetchRulesTXT(2, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_RULES4 + ResetFactoryDefaultPreference.fetchRulesTXT(3, true); + #endif + #if DEFAULT_PROVISIONING_FETCH_NOTIFICATIONS + ResetFactoryDefaultPreference.fetchNotificationDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_SECURITY + ResetFactoryDefaultPreference.fetchSecurityDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_CONFIG + ResetFactoryDefaultPreference.fetchConfigDat(true); + #endif + #if DEFAULT_PROVISIONING_FETCH_PROVISIONING + ResetFactoryDefaultPreference.fetchProvisioningDat(true); + #endif + #if DEFAULT_PROVISIONING_SAVE_URL + ResetFactoryDefaultPreference.saveURL(true); + #endif + #if DEFAULT_PROVISIONING_SAVE_CREDENTIALS + ResetFactoryDefaultPreference.storeCredentials(true); + #endif + #endif + #if DEFAULT_FACTORY_RESET_KEEP_UNIT_NAME + ResetFactoryDefaultPreference.keepUnitName(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_WIFI + ResetFactoryDefaultPreference.keepWiFi(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_NETWORK + ResetFactoryDefaultPreference.keepNetwork(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_NTP_DST + ResetFactoryDefaultPreference.keepNTP(true); + #endif + #if DEFAULT_FACTORY_RESET_KEEP_CONSOLE_LOG + ResetFactoryDefaultPreference.keepLogConsoleSettings(true); + #endif +#ifdef PLUGIN_BUILD_SAFEBOOT + mustApplySafebootDefaults = true; + ResetFactoryDefaultPreference.keepCustomCdnUrl(true); + + Settings.UseLastWiFiFromRTC(true); +#endif // ifdef PLUGIN_BUILD_SAFEBOOT + } + + + const GpioFactorySettingsStruct gpio_settings(ResetFactoryDefaultPreference.getDeviceModel()); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("ResetFactory")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Direct Serial is allowed here, since this is only an emergency task. + serialPrint(F("RESET: Resetting factory defaults... using ")); + serialPrint(getDeviceModelString(ResetFactoryDefaultPreference.getDeviceModel())); + serialPrintln(F(" settings")); + process_serialWriteBuffer(); + delay(1000); + + if (readFromRTC()) + { + serialPrint(F("RESET: Warm boot, reset count: ")); + serialPrintln(String(RTC.factoryResetCounter)); + + if (RTC.factoryResetCounter >= 3) + { + serialPrintln(F("RESET: Too many resets, protecting your flash memory (powercycle to solve this)")); + return; + } + } + else + { + serialPrintln(F("RESET: Cold boot")); + initRTC(); + + // TODO TD-er: Store set device model in RTC. + } + + RTC.flashCounter = 0; // reset flashcounter, since we're already counting the number of factory-resets. we dont want to hit a flash-count + // limit during reset. + RTC.factoryResetCounter++; + saveToRTC(); + + if (formatFS) { + // always format on factory reset, in case of corrupt FS + ESPEASY_FS.end(); + serialPrintln(F("RESET: formatting...")); + FS_format(); + serialPrintln(F("RESET: formatting done...")); + process_serialWriteBuffer(); + + if (!ESPEASY_FS.begin()) + { + serialPrintln(F("RESET: FORMAT FS FAILED!")); + return; + } + } + +#if FEATURE_CUSTOM_PROVISIONING + { + MakeProvisioningSettings(ProvisioningSettings); + + if (ProvisioningSettings.get()) { + ProvisioningSettings->setUser(F(DEFAULT_PROVISIONING_USER)); + ProvisioningSettings->setPass(F(DEFAULT_PROVISIONING_PASS)); + ProvisioningSettings->setUrl(F(DEFAULT_PROVISIONING_URL)); + ProvisioningSettings->ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + saveProvisioningSettings(*ProvisioningSettings); + } + } +#endif // if FEATURE_CUSTOM_PROVISIONING + + // pad files with extra zeros for future extensions + InitFile(SettingsType::SettingsFileEnum::FILE_CONFIG_type); + InitFile(SettingsType::SettingsFileEnum::FILE_SECURITY_type); + #if FEATURE_NOTIFIER + InitFile(SettingsType::SettingsFileEnum::FILE_NOTIFICATION_type); + #endif // if FEATURE_NOTIFIER + + InitFile(getRulesFileName(0), 0); + + Settings.clearMisc(); + + if (!ResetFactoryDefaultPreference.keepNTP() || mustApplySafebootDefaults) { + Settings.clearTimeSettings(); + Settings.UseNTP(DEFAULT_USE_NTP); + strcpy_P(Settings.NTPHost, PSTR(DEFAULT_NTP_HOST)); + Settings.TimeZone = DEFAULT_TIME_ZONE; + Settings.DST = DEFAULT_USE_DST; + } + + if (!ResetFactoryDefaultPreference.keepNetwork() || mustApplySafebootDefaults) { + Settings.clearNetworkSettings(); + + // TD-er Reset access control + str2ip(F(DEFAULT_IPRANGE_LOW), SecuritySettings.AllowedIPrangeLow); + str2ip(F(DEFAULT_IPRANGE_HIGH), SecuritySettings.AllowedIPrangeHigh); + SecuritySettings.IPblockLevel = DEFAULT_IP_BLOCK_LEVEL; + + #if DEFAULT_USE_STATIC_IP + str2ip((char *)DEFAULT_IP, Settings.IP); + str2ip((char *)DEFAULT_DNS, Settings.DNS); + str2ip((char *)DEFAULT_GW, Settings.Gateway); + str2ip((char *)DEFAULT_SUBNET, Settings.Subnet); + #endif // if DEFAULT_USE_STATIC_IP + Settings.IncludeHiddenSSID(DEFAULT_WIFI_INCLUDE_HIDDEN_SSID); + } + + Settings.clearNotifications(); + Settings.clearControllers(); + Settings.clearTasks(); + + if (!ResetFactoryDefaultPreference.keepLogConsoleSettings() || mustApplySafebootDefaults) { + Settings.clearLogSettings(); + str2ip((char *)DEFAULT_SYSLOG_IP, Settings.Syslog_IP); + + setLogLevelFor(LOG_TO_SYSLOG, DEFAULT_SYSLOG_LEVEL); + setLogLevelFor(LOG_TO_SERIAL, DEFAULT_SERIAL_LOG_LEVEL); + setLogLevelFor(LOG_TO_WEBLOG, DEFAULT_WEB_LOG_LEVEL); + setLogLevelFor(LOG_TO_SDCARD, DEFAULT_SD_LOG_LEVEL); + Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; + Settings.SyslogPort = DEFAULT_SYSLOG_PORT; + Settings.UseValueLogger = DEFAULT_USE_SD_LOG; + + // FIXME TD-er: Must also keep console settings. + Settings.console_serial_port = DEFAULT_CONSOLE_PORT; + Settings.console_serial_rxpin = DEFAULT_CONSOLE_PORT_RXPIN; + Settings.console_serial_txpin = DEFAULT_CONSOLE_PORT_TXPIN; + Settings.console_serial0_fallback = DEFAULT_CONSOLE_SER0_FALLBACK; + Settings.UseSerial = DEFAULT_USE_SERIAL; + Settings.BaudRate = DEFAULT_SERIAL_BAUD; + } + + if (!ResetFactoryDefaultPreference.keepUnitName() || mustApplySafebootDefaults) { + Settings.clearUnitNameSettings(); + Settings.Unit = UNIT; + strcpy_P(Settings.Name, PSTR(DEFAULT_NAME)); + Settings.UDPPort = DEFAULT_SYNC_UDP_PORT; + } + + if (!ResetFactoryDefaultPreference.keepWiFi() || mustApplySafebootDefaults) { + strcpy_P(SecuritySettings.WifiSSID, PSTR(DEFAULT_SSID)); + strcpy_P(SecuritySettings.WifiKey, PSTR(DEFAULT_KEY)); + strcpy_P(SecuritySettings.WifiSSID2, PSTR(DEFAULT_SSID2)); + strcpy_P(SecuritySettings.WifiKey2, PSTR(DEFAULT_KEY2)); + strcpy_P(SecuritySettings.WifiAPKey, PSTR(DEFAULT_AP_KEY)); + } + strcpy_P(SecuritySettings.Password, PSTR(DEFAULT_ADMIN_PASS)); + + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + + // now we set all parameters that need to be non-zero as default value + + + Settings.PID = ESP_PROJECT_PID; + Settings.Version = VERSION; + Settings.Build = get_build_nr(); + + // Settings.IP_Octet = DEFAULT_IP_OCTET; + // Settings.Delay = DEFAULT_DELAY; + Settings.Pin_i2c_sda = gpio_settings.i2c_sda; + Settings.Pin_i2c_scl = gpio_settings.i2c_scl; + Settings.Pin_status_led = gpio_settings.status_led; + + // Settings.Pin_status_led_Inversed = DEFAULT_PIN_STATUS_LED_INVERSED; + Settings.Pin_sd_cs = -1; + Settings.Pin_Reset = DEFAULT_PIN_RESET_BUTTON; + Settings.Protocol[0] = DEFAULT_PROTOCOL; + + // Settings.deepSleep_wakeTime = 0; // Sleep disabled + // Settings.CustomCSS = false; + // Settings.InitSPI = DEFAULT_SPI; + + // advanced Settings + // Settings.UseRules = DEFAULT_USE_RULES; + Settings.ControllerEnabled[0] = DEFAULT_CONTROLLER_ENABLED; + Settings.MQTTRetainFlag_unused = DEFAULT_MQTT_RETAIN; + Settings.MessageDelay_unused = DEFAULT_MQTT_DELAY; + Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; + + // allow to set default latitude and longitude + #ifdef DEFAULT_LATITUDE + Settings.Latitude = DEFAULT_LATITUDE; + #endif // ifdef DEFAULT_LATITUDE + #ifdef DEFAULT_LONGITUDE + Settings.Longitude = DEFAULT_LONGITUDE; + #endif // ifdef DEFAULT_LONGITUDE + +#ifdef ESP32 + + // Ethernet related settings are never used on ESP8266 + Settings.ETH_Phy_Addr = gpio_settings.eth_phyaddr; + Settings.ETH_Pin_mdc_cs = gpio_settings.eth_mdc; + Settings.ETH_Pin_mdio_irq = gpio_settings.eth_mdio; + Settings.ETH_Pin_power_rst = gpio_settings.eth_power; + Settings.ETH_Phy_Type = gpio_settings.eth_phytype; + Settings.ETH_Clock_Mode = gpio_settings.eth_clock_mode; +#endif // ifdef ESP32 + Settings.NetworkMedium = gpio_settings.network_medium; + + /* + Settings.GlobalSync = DEFAULT_USE_GLOBAL_SYNC; + + Settings.IP_Octet = DEFAULT_IP_OCTET; + Settings.WDI2CAddress = DEFAULT_WD_IC2_ADDRESS; + Settings.UseSSDP = DEFAULT_USE_SSDP; + Settings.ConnectionFailuresThreshold = DEFAULT_CON_FAIL_THRES; + Settings.WireClockStretchLimit = DEFAULT_I2C_CLOCK_LIMIT; + */ + + // Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; + + Settings.JSONBoolWithoutQuotes(DEFAULT_JSON_BOOL_WITHOUT_QUOTES); + Settings.EnableTimingStats(DEFAULT_ENABLE_TIMING_STATS); + +#ifdef PLUGIN_DESCR + strcpy_P(Settings.Name, PSTR(PLUGIN_DESCR)); +#endif // ifdef PLUGIN_DESCR + +#ifndef LIMIT_BUILD_SIZE + addPredefinedPlugins(gpio_settings); + addPredefinedRules(gpio_settings); +#endif // ifndef LIMIT_BUILD_SIZE + +#if DEFAULT_CONTROLLER + { + // Place in a scope to have its memory freed ASAP + MakeControllerSettings(ControllerSettings); // -V522 + + if (AllocatedControllerSettings()) { + safe_strncpy(ControllerSettings->Subscribe, F(DEFAULT_SUB), sizeof(ControllerSettings->Subscribe)); + safe_strncpy(ControllerSettings->Publish, F(DEFAULT_PUB), sizeof(ControllerSettings->Publish)); + safe_strncpy(ControllerSettings->MQTTLwtTopic, F(DEFAULT_MQTT_LWT_TOPIC), sizeof(ControllerSettings->MQTTLwtTopic)); + safe_strncpy(ControllerSettings->LWTMessageConnect, F(DEFAULT_MQTT_LWT_CONNECT_MESSAGE), + sizeof(ControllerSettings->LWTMessageConnect)); + safe_strncpy(ControllerSettings->LWTMessageDisconnect, F(DEFAULT_MQTT_LWT_DISCONNECT_MESSAGE), + sizeof(ControllerSettings->LWTMessageDisconnect)); + str2ip((char *)DEFAULT_SERVER, ControllerSettings->IP); + ControllerSettings->setHostname(F(DEFAULT_SERVER_HOST)); + ControllerSettings->UseDNS = DEFAULT_SERVER_USEDNS; + ControllerSettings->useExtendedCredentials(DEFAULT_USE_EXTD_CONTROLLER_CREDENTIALS); + ControllerSettings->Port = DEFAULT_PORT; + ControllerSettings->ClientTimeout = DEFAULT_CONTROLLER_TIMEOUT; + setControllerUser(0, *ControllerSettings, F(DEFAULT_CONTROLLER_USER)); + setControllerPass(0, *ControllerSettings, F(DEFAULT_CONTROLLER_PASS)); + + SaveControllerSettings(0, *ControllerSettings); + } + } +#endif // if DEFAULT_CONTROLLER + +#ifdef ESP32 + if (!mustApplySafebootDefaults) + { + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + + if (ResetFactoryDefaultPreference.keepUnitName()) + { + FactoryDefault_UnitName_NVS unitNameNVS{}; + unitNameNVS.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepWiFi()) + { + FactoryDefault_WiFi_NVS wifiNVS{}; + wifiNVS.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepNetwork()) + { + // Restore Network IP settings + FactoryDefault_Network_NVS network_nvs; + network_nvs.applyToSettings_from_NVS(preferences); + } + + if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) + { + // Restore Log and Console settings + FactoryDefault_LogConsoleSettings_NVS log_console_nvs; + log_console_nvs.applyToSettings_from_NVS(preferences); + } + +# if FEATURE_ALTERNATIVE_CDN_URL + + if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) { + FactoryDefault_CDN_customurl_NVS::applyToSettings_from_NVS(preferences); + } +# endif // if FEATURE_ALTERNATIVE_CDN_URL + preferences.end(); + } +#endif // ifdef ESP32 + + const bool forFactoryReset = true; + SaveSettings(forFactoryReset); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("ResetFactory2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + serialPrintln(F("RESET: Successful, rebooting. (you might need to press the reset button if you've just flashed the firmware)")); + + // 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 + WiFi.persistent(false); // Do not use SDK storage of SSID/WPA parameters + reboot(IntendedRebootReason_e::ResetFactory); +} + +/*********************************************************************************************\ + Collect the stored preference for factory default +\*********************************************************************************************/ +void applyFactoryDefaultPref() { + // TODO TD-er: Store it in more places to make it more persistent + Settings.ResetFactoryDefaultPreference = ResetFactoryDefaultPreference.getPreference(); + +#ifdef ESP32 + ESPEasy_NVS_Helper preferences; + preferences.begin(F(FACTORY_DEFAULT_NVS_NAMESPACE)); + ResetFactoryDefaultPreference.to_NVS(preferences); + { + FactoryDefault_UnitName_NVS unitNameNVS{}; + + if (ResetFactoryDefaultPreference.keepUnitName()) + { + // Store Unit nr and hostname + unitNameNVS.fromSettings_to_NVS(preferences); + } else { + unitNameNVS.clear_from_NVS(preferences); + } + } + { + FactoryDefault_WiFi_NVS wifiNVS{}; + + if (ResetFactoryDefaultPreference.keepWiFi()) + { + // Store WiFi credentials + wifiNVS.fromSettings_to_NVS(preferences); + } else { + wifiNVS.clear_from_NVS(preferences); + } + } + { + FactoryDefault_Network_NVS network_nvs{}; + + if (ResetFactoryDefaultPreference.keepNetwork()) + { + // Store Network IP settings + network_nvs.fromSettings_to_NVS(preferences); + } else { + network_nvs.clear_from_NVS(preferences); + } + } + { + FactoryDefault_LogConsoleSettings_NVS log_console_nvs{}; + + if (ResetFactoryDefaultPreference.keepLogConsoleSettings()) + { + // Store Log and Console settings + log_console_nvs.fromSettings_to_NVS(preferences); + } else { + log_console_nvs.clear_from_NVS(preferences); + } + } +# if FEATURE_ALTERNATIVE_CDN_URL + { + if (ResetFactoryDefaultPreference.keepCustomCdnUrl()) + { + // Store custom CDN + FactoryDefault_CDN_customurl_NVS::fromSettings_to_NVS(preferences); + } else { + FactoryDefault_CDN_customurl_NVS::clear_from_NVS(preferences); + } + } +# endif // if FEATURE_ALTERNATIVE_CDN_URL + + + preferences.end(); +#endif // ifdef ESP32 +} diff --git a/src/src/Helpers/ESPEasy_NVS_Helper.cpp b/src/src/Helpers/ESPEasy_NVS_Helper.cpp index 8d5df247b..7b3eecdd5 100644 --- a/src/src/Helpers/ESPEasy_NVS_Helper.cpp +++ b/src/src/Helpers/ESPEasy_NVS_Helper.cpp @@ -29,6 +29,10 @@ void ESPEasy_NVS_Helper::remove(const String& key) bool ESPEasy_NVS_Helper::getPreference(const String& key, String& value) { + if (!_preferences.isKey(key.c_str())) { + return false; + } + value = _preferences.getString(key.c_str()); const bool res = !value.isEmpty(); @@ -50,6 +54,9 @@ void ESPEasy_NVS_Helper::setPreference(const String& key, const String& value) bool ESPEasy_NVS_Helper::getPreference(const String& key, uint32_t& value) { + if (!_preferences.isKey(key.c_str())) { + return false; + } constexpr uint32_t defaultValue = std::numeric_limits::max(); value = _preferences.getUInt(key.c_str(), defaultValue); @@ -75,7 +82,13 @@ void ESPEasy_NVS_Helper::setPreference(const String& key, const uint32_t& value) bool ESPEasy_NVS_Helper::getPreference(const String& key, uint64_t& value) { - constexpr uint64_t defaultValue = std::numeric_limits::max(); + if (!_preferences.isKey(key.c_str())) { + return false; + } + + // Make this a signed value as an erased flash chip only has 0xFF's + // and max uint64 also contains only 0xFF bytes. + constexpr uint64_t defaultValue = std::numeric_limits::max(); value = _preferences.getULong64(key.c_str(), defaultValue); @@ -100,6 +113,10 @@ void ESPEasy_NVS_Helper::setPreference(const String& key, const uint64_t& value) bool ESPEasy_NVS_Helper::getPreference(const String& key, uint8_t *data, size_t length) { + if (!_preferences.isKey(key.c_str())) { + return false; + } + const bool res = _preferences.getBytes(key.c_str(), data, length) == length; addLog(res ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, concat(F("NVS : Load "), key)); diff --git a/src/src/Helpers/ESPEasy_Storage.cpp b/src/src/Helpers/ESPEasy_Storage.cpp index 89124240e..18e620a16 100644 --- a/src/src/Helpers/ESPEasy_Storage.cpp +++ b/src/src/Helpers/ESPEasy_Storage.cpp @@ -12,8 +12,8 @@ #include "../DataTypes/SPI_options.h" #if FEATURE_MQTT -#include "../ESPEasyCore/Controller.h" -#endif +# include "../ESPEasyCore/Controller.h" +#endif // if FEATURE_MQTT #include "../ESPEasyCore/ESPEasy_Log.h" #include "../ESPEasyCore/ESPEasyNetwork.h" #include "../ESPEasyCore/ESPEasyWifi.h" @@ -53,11 +53,11 @@ #if FEATURE_RTC_CACHE_STORAGE # include "../Globals/C016_ControllerCache.h" -#endif +#endif // if FEATURE_RTC_CACHE_STORAGE #ifdef ESP32 -#include -#endif +# include +#endif // ifdef ESP32 #ifdef ESP32 String patch_fname(const String& fname) { @@ -66,7 +66,8 @@ String patch_fname(const String& fname) { } return String('/') + fname; } -#endif + +#endif // ifdef ESP32 /********************************************************************************************\ file system error handling @@ -75,6 +76,7 @@ String patch_fname(const String& fname) { String FileError(int line, const char *fname) { String log = strformat(F("FS : Error while reading/writing %s in %d"), fname, line); + addLog(LOG_LEVEL_ERROR, log); return log; } @@ -95,7 +97,7 @@ String flashGuard() { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("flashGuard")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER if (RTC.flashDayCounter > MAX_FLASHWRITES_PER_DAY) { @@ -125,7 +127,7 @@ String appendToFile(const String& fname, const uint8_t *data, unsigned int size) return EMPTY_STRING; } -bool fileExists(const __FlashStringHelper * fname) +bool fileExists(const __FlashStringHelper *fname) { return fileExists(String(fname)); } @@ -133,33 +135,41 @@ bool fileExists(const __FlashStringHelper * fname) bool fileExists(const String& fname) { #ifdef USE_SECOND_HEAP HeapSelectDram ephemeral; - #endif + #endif // ifdef USE_SECOND_HEAP const String patched_fname = patch_fname(fname); - auto search = Cache.fileExistsMap.find(patched_fname); + auto search = Cache.fileExistsMap.find(patched_fname); + if (search != Cache.fileExistsMap.end()) { return search->second; } bool res = ESPEASY_FS.exists(patched_fname); #if FEATURE_SD + if (!res) { res = SD.exists(patched_fname); } - #endif + #endif // if FEATURE_SD + // Only keep track of existing files or non-existing filenames that may be requested several times. // Not the non-existing files from the cache controller #if FEATURE_RTC_CACHE_STORAGE - if (res || !isCacheFile(patched_fname)) - #endif + + if (res || !isCacheFile(patched_fname)) + #endif // if FEATURE_RTC_CACHE_STORAGE { - Cache.fileExistsMap[patched_fname] = res; + Cache.fileExistsMap.emplace( + std::make_pair( + patched_fname, + res)); } + if (Cache.fileCacheClearMoment == 0) { - if (node_time.timeSource == timeSource_t::No_time_source) { + if (node_time.getTimeSource() == timeSource_t::No_time_source) { // use some random value as we don't have a time yet Cache.fileCacheClearMoment = HwRandom(); } else { - Cache.fileCacheClearMoment = node_time.now(); + Cache.fileCacheClearMoment = node_time.getLocalUnixTime(); } } return res; @@ -168,6 +178,7 @@ bool fileExists(const String& fname) { fs::File tryOpenFile(const String& fname, const String& mode, FileDestination_e destination) { START_TIMER; fs::File f; + if (fname.isEmpty() || equals(fname, '/')) { return f; } @@ -180,15 +191,16 @@ fs::File tryOpenFile(const String& fname, const String& mode, FileDestination_e } clearFileCaches(); } + if ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::FLASH)) { f = ESPEASY_FS.open(patch_fname(fname), mode.c_str()); } - # if FEATURE_SD + #if FEATURE_SD if (!f && ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::SD))) { f = SD.open(patch_fname(fname).c_str(), mode.c_str()); } - # endif // if FEATURE_SD + #endif // if FEATURE_SD STOP_TIMER(TRY_OPEN_FILE); @@ -197,11 +209,13 @@ fs::File tryOpenFile(const String& fname, const String& mode, FileDestination_e bool fileMatchesTaskSettingsType(const String& fname) { const String config_dat_file = patch_fname(getFileName(FileType::CONFIG_DAT)); + return config_dat_file.equalsIgnoreCase(patch_fname(fname)); } bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestination_e destination) { clearFileCaches(); + if (fileExists(fname_old) && !fileExists(fname_new)) { if (fileMatchesTaskSettingsType(fname_old)) { clearAllCaches(); @@ -209,10 +223,12 @@ bool tryRenameFile(const String& fname_old, const String& fname_new, FileDestina clearAllButTaskCaches(); } bool res = false; + if ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::FLASH)) { res = ESPEASY_FS.rename(patch_fname(fname_old), patch_fname(fname_new)); } #if FEATURE_SD && defined(ESP32) // FIXME ESP8266 SDClass doesn't support rename + if (!res && ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::SD))) { res = SD.rename(patch_fname(fname_old), patch_fname(fname_new)); } @@ -226,24 +242,28 @@ bool tryDeleteFile(const String& fname, FileDestination_e destination) { if (fname.length() > 0) { #if FEATURE_RTC_CACHE_STORAGE + if (isCacheFile(fname)) { ControllerCache.closeOpenFiles(); } - #endif + #endif // if FEATURE_RTC_CACHE_STORAGE + if (fileMatchesTaskSettingsType(fname)) { clearAllCaches(); } else { clearAllButTaskCaches(); } bool res = false; + if ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::FLASH)) { res = ESPEASY_FS.remove(patch_fname(fname)); } #if FEATURE_SD + if (!res && ((destination == FileDestination_e::ANY) || (destination == FileDestination_e::SD))) { res = SD.remove(patch_fname(fname)); } - #endif + #endif // if FEATURE_SD // 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. @@ -268,7 +288,7 @@ bool BuildFixes() } #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("BuildFixes")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER serialPrintln(F("\nBuild changed!")); if (Settings.Build < 145) @@ -280,7 +300,7 @@ bool BuildFixes() { #ifdef LIMIT_BUILD_SIZE serialPrintln(F("Fix reset Pin")); - #endif + #endif // ifdef LIMIT_BUILD_SIZE Settings.Pin_Reset = -1; } @@ -289,10 +309,10 @@ bool BuildFixes() // Have to patch settings to make sure no bogus data is being used. #ifdef LIMIT_BUILD_SIZE serialPrintln(F("Fix settings with uninitalized data or corrupted by switching between versions")); - #endif - Settings.UseRTOSMultitasking = false; - Settings.Pin_Reset = -1; - Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; + #endif // ifdef LIMIT_BUILD_SIZE + Settings.UseRTOSMultitasking = false; + Settings.Pin_Reset = -1; + Settings.SyslogFacility = DEFAULT_SYSLOG_FACILITY; Settings.MQTTUseUnitNameAsClientId_unused = DEFAULT_MQTT_USE_UNITNAME_AS_CLIENTID; } @@ -300,27 +320,33 @@ bool BuildFixes() Settings.ResetFactoryDefaultPreference = 0; Settings.OldRulesEngine(DEFAULT_RULES_OLDENGINE); } + if (Settings.Build < 20105) { Settings.I2C_clockSpeed = DEFAULT_I2C_CLOCK_SPEED; } + if (Settings.Build <= 20106) { // ClientID is now defined in the controller settings. #if FEATURE_MQTT controllerIndex_t controller_idx = firstEnabledMQTT_ControllerIndex(); + if (validControllerIndex(controller_idx)) { - MakeControllerSettings(ControllerSettings); //-V522 + MakeControllerSettings(ControllerSettings); // -V522 + if (AllocatedControllerSettings()) { LoadControllerSettings(controller_idx, *ControllerSettings); String clientid; + if (Settings.MQTTUseUnitNameAsClientId_unused) { clientid = F("%sysname%"); + if (Settings.appendUnitToHostname()) { clientid += F("_%unit%"); } } else { - clientid = F("ESPClient_%mac%"); + clientid = F("ESPClient_%mac%"); } safe_strncpy(ControllerSettings->ClientID, clientid, sizeof(ControllerSettings->ClientID)); @@ -331,50 +357,62 @@ bool BuildFixes() } #endif // if FEATURE_MQTT } + if (Settings.Build < 20107) { Settings.WebserverPort = 80; } + if (Settings.Build < 20108) { #ifdef ESP32 - // Ethernet related settings are never used on ESP8266 - Settings.ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; - Settings.ETH_Pin_mdc = DEFAULT_ETH_PIN_MDC; - Settings.ETH_Pin_mdio = DEFAULT_ETH_PIN_MDIO; - Settings.ETH_Pin_power = DEFAULT_ETH_PIN_POWER; - Settings.ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; - Settings.ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; -#endif - Settings.NetworkMedium = DEFAULT_NETWORK_MEDIUM; + + // Ethernet related settings are never used on ESP8266 + Settings.ETH_Phy_Addr = DEFAULT_ETH_PHY_ADDR; + Settings.ETH_Pin_mdc_cs = DEFAULT_ETH_PIN_MDC; + Settings.ETH_Pin_mdio_irq = DEFAULT_ETH_PIN_MDIO; + Settings.ETH_Pin_power_rst = DEFAULT_ETH_PIN_POWER; + Settings.ETH_Phy_Type = DEFAULT_ETH_PHY_TYPE; + Settings.ETH_Clock_Mode = DEFAULT_ETH_CLOCK_MODE; +#endif // ifdef ESP32 + Settings.NetworkMedium = DEFAULT_NETWORK_MEDIUM; } + if (Settings.Build < 20109) { Settings.SyslogPort = 514; } + if (Settings.Build < 20110) { - Settings.I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; + Settings.I2C_clockSpeed_Slow = DEFAULT_I2C_CLOCK_SPEED_SLOW; Settings.I2C_Multiplexer_Type = I2C_MULTIPLEXER_NONE; Settings.I2C_Multiplexer_Addr = -1; + for (taskIndex_t x = 0; x < TASKS_MAX; x++) { Settings.I2C_Multiplexer_Channel[x] = -1; } Settings.I2C_Multiplexer_ResetPin = -1; } + if (Settings.Build < 20111) { #ifdef ESP32 constexpr uint8_t maxStatesesp32 = NR_ELEMENTS(Settings.PinBootStates_ESP32); + for (uint8_t i = 0; i < maxStatesesp32; ++i) { Settings.PinBootStates_ESP32[i] = 0; } - #endif + #endif // ifdef ESP32 } + 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_TX_power = 70; // 70 = 17.5dBm. unit: 0.25 dBm + Settings.WiFi_sensitivity_margin = 3; // Margin in dBm on top of sensitivity. } + if (Settings.Build < 20113) { Settings.NumberExtraWiFiScans = 0; } + if (Settings.Build < 20114) { #ifdef USES_P003 + // P003_Pulse was always using the pull-up, now it is a setting. constexpr pluginID_t PLUGIN_ID_P003_PULSE(3); @@ -383,8 +421,9 @@ bool BuildFixes() Settings.TaskDevicePin1PullUp[taskIndex] = true; } } - #endif + #endif // ifdef USES_P003 } + if (Settings.Build < 20115) { if (Settings.InitSPI != static_cast(SPI_Options_e::UserDefined)) { // User-defined SPI pins set to None Settings.SPI_SCLK_pin = -1; @@ -393,6 +432,7 @@ bool BuildFixes() } } #ifdef USES_P053 + if (Settings.Build < 20116) { // Added PWR button, init to "-none-" constexpr pluginID_t PLUGIN_ID_P053_PMSx003(53); @@ -402,15 +442,16 @@ bool BuildFixes() Settings.TaskDevicePluginConfig[taskIndex][3] = -1; } } + // Remove PeriodicalScanWiFi // Reset to default 0 for future use. - bitWrite(Settings.VariousBits1, 15, 0); + Settings.VariousBits_1.unused_15 = 0; } - #endif + #endif // ifdef USES_P053 // Starting 2022/08/18 // Use get_build_nr() value for settings transitions. - // This value will also be shown when building using PlatformIO, when showing the Compile time defines + // This value will also be shown when building using PlatformIO, when showing the Compile time defines Settings.Build = get_build_nr(); Settings.StructSize = sizeof(Settings); @@ -428,22 +469,29 @@ void fileSystemCheck() { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("fileSystemCheck")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER addLog(LOG_LEVEL_INFO, F("FS : Mounting...")); #if defined(ESP32) && defined(USE_LITTLEFS) - if (getPartionCount(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS) != 0 + + if ((getPartionCount(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS) != 0) && ESPEASY_FS.begin()) -#else +#else // if defined(ESP32) && defined(USE_LITTLEFS) + if (ESPEASY_FS.begin()) -#endif +#endif // if defined(ESP32) && defined(USE_LITTLEFS) { clearAllCaches(); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("FS : Mount successful, used "); - log += SpiffsUsedBytes(); - log += F(" bytes of "); - log += SpiffsTotalBytes(); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat( + F("FS : " +#ifdef USE_LITTLEFS + "LittleFS" +#else // ifdef USE_LITTLEFS + "SPIFFS" +#endif // ifdef USE_LITTLEFS + " mount successful, used %u bytes of %u"), + SpiffsUsedBytes(), SpiffsTotalBytes())); } // Run garbage collection before any file is open. @@ -453,16 +501,17 @@ void fileSystemCheck() --retries; } - fs::File f = tryOpenFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type).c_str(), "r"); - if (f) { - f.close(); + fs::File f = tryOpenFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type), "r"); + + if (f) { + f.close(); } else { ResetFactory(false); } } else { - const __FlashStringHelper * log = F("FS : Mount failed"); + const __FlashStringHelper *log = F("FS : Mount failed"); serialPrintln(log); addLog(LOG_LEVEL_ERROR, log); ResetFactory(); @@ -470,17 +519,18 @@ void fileSystemCheck() } bool FS_format() { - #ifdef USE_LITTLEFS - #ifdef ESP32 - const bool res = ESPEASY_FS.begin(true); - ESPEASY_FS.end(); - return res; - #else - return ESPEASY_FS.format(); - #endif - #else + #ifdef USE_LITTLEFS + # ifdef ESP32 + const bool res = ESPEASY_FS.begin(true); + ESPEASY_FS.end(); + return res; + # else // ifdef ESP32 return ESPEASY_FS.format(); - #endif + # endif // ifdef ESP32 + #else // ifdef USE_LITTLEFS + return ESPEASY_FS.format(); + + #endif // ifdef USE_LITTLEFS } #ifdef ESP32 @@ -489,7 +539,7 @@ bool FS_format() { int getPartionCount(uint8_t pType, uint8_t pSubType) { esp_partition_type_t partitionType = static_cast(pType); - esp_partition_subtype_t subtype = static_cast(pSubType); + esp_partition_subtype_t subtype = static_cast(pSubType); esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, subtype, NULL); int nrPartitions = 0; @@ -502,19 +552,18 @@ int getPartionCount(uint8_t pType, uint8_t pSubType) { return nrPartitions; } - -#endif +#endif // ifdef ESP32 #ifdef ESP8266 bool clearPartition(ESP8266_partition_type ptype) { uint32_t address; - int32_t size; - int32_t sector = getPartitionInfo(ESP8266_partition_type::rf_cal, address, size); + int32_t size; + int32_t sector = getPartitionInfo(ESP8266_partition_type::rf_cal, address, size); + while (size > 0) { - if (!ESP.flashEraseSector(sector)) return false; + if (!ESP.flashEraseSector(sector)) { return false; } ++sector; size -= SPI_FLASH_SEC_SIZE; - } return true; } @@ -527,7 +576,7 @@ bool clearWiFiSDKpartition() { return clearPartition(ESP8266_partition_type::wifi); } -#endif +#endif // ifdef ESP8266 /********************************************************************************************\ @@ -540,9 +589,9 @@ bool GarbageCollection() { START_TIMER; if (ESPEASY_FS.gc()) { -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, F("FS : Success garbage collection")); -#endif +# endif // ifndef BUILD_NO_DEBUG STOP_TIMER(FS_GC_SUCCESS); return true; } @@ -562,8 +611,8 @@ String SaveSettings(bool forFactoryReset) { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveSettings")); - #endif - String err; + #endif // ifndef BUILD_NO_RAM_TRACKER + String err; { Settings.StructSize = sizeof(Settings); @@ -577,21 +626,25 @@ String SaveSettings(bool forFactoryReset) } if (!COMPUTE_STRUCT_CHECKSUM_UPDATE(SettingsStruct, Settings) - /* - computeChecksum( - Settings.md5, - reinterpret_cast(&Settings), - sizeof(SettingsStruct), - offsetof(SettingsStruct, md5)) - */ + + /* + computeChecksum( + Settings.md5, + reinterpret_cast(&Settings), + sizeof(SettingsStruct), + offsetof(SettingsStruct, md5)) + */ ) { - err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type).c_str(), 0, reinterpret_cast(&Settings), sizeof(Settings)); - } -#ifndef BUILD_NO_DEBUG + err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type).c_str(), + 0, + reinterpret_cast(&Settings), + sizeof(Settings)); + } +#ifndef BUILD_NO_DEBUG else { addLog(LOG_LEVEL_INFO, F("Skip saving settings, not changed")); } -#endif +#endif // ifndef BUILD_NO_DEBUG } if (err.length()) { @@ -599,10 +652,11 @@ String SaveSettings(bool forFactoryReset) } #ifndef BUILD_MINIMAL_OTA + // 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 +#endif // ifndef BUILD_MINIMAL_OTA // } @@ -612,7 +666,7 @@ String SaveSettings(bool forFactoryReset) } String SaveSecuritySettings(bool forFactoryReset) { - String err; + String err; SecuritySettings.validate(); memcpy(SecuritySettings.ProgmemMd5, CRCValues.runTimeMD5, 16); @@ -623,23 +677,26 @@ String SaveSecuritySettings(bool forFactoryReset) { if (SecuritySettings.updateChecksum()) { // Settings have changed, save to file. - err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), 0, reinterpret_cast(&SecuritySettings), sizeof(SecuritySettings)); + err = SaveToFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), + 0, + reinterpret_cast(&SecuritySettings), + 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) { + 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(); } } - } + } #ifndef BUILD_NO_DEBUG else { addLog(LOG_LEVEL_INFO, F("Skip saving SecuritySettings, not changed")); } -#endif +#endif // ifndef BUILD_NO_DEBUG // FIXME TD-er: How to check if these have changed? if (forFactoryReset) { @@ -647,44 +704,59 @@ String SaveSecuritySettings(bool forFactoryReset) { } ExtendedControllerCredentials.save(); - if (!forFactoryReset) + + if (!forFactoryReset) { afterloadSettings(); + } return err; } void afterloadSettings() { ExtraTaskSettings.clear(); // make sure these will not contain old settings. + if ((Settings.Version != VERSION) || (Settings.PID != ESP_PROJECT_PID)) { + // Not valid settings, so do not continue + return; + } + // Load ResetFactoryDefaultPreference from provisioning.dat if available. // FIXME TD-er: Must actually move content of Provisioning.dat to NVS and then delete file uint32_t pref_temp = Settings.ResetFactoryDefaultPreference; + #ifdef ESP32 + if (pref_temp == 0) { if (ResetFactoryDefaultPreference.getPreference() == 0) { // Try loading from NVS - ResetFactoryDefaultPreference.init(); + ESPEasy_NVS_Helper preferences; + ResetFactoryDefaultPreference.init(preferences); pref_temp = ResetFactoryDefaultPreference.getPreference(); } } - #endif + #endif // ifdef ESP32 #if FEATURE_CUSTOM_PROVISIONING + if (fileExists(getFileName(FileType::PROVISIONING_DAT))) { MakeProvisioningSettings(ProvisioningSettings); + if (ProvisioningSettings.get()) { loadProvisioningSettings(*ProvisioningSettings); + if (ProvisioningSettings->matchingFlashSize()) { - if (pref_temp == 0 && ProvisioningSettings->ResetFactoryDefaultPreference.getPreference() != 0) + if ((pref_temp == 0) && (ProvisioningSettings->ResetFactoryDefaultPreference.getPreference() != 0)) { pref_temp = ProvisioningSettings->ResetFactoryDefaultPreference.getPreference(); + } } } } - #endif + #endif // if FEATURE_CUSTOM_PROVISIONING // TODO TD-er: Try to get the information from more locations to make it more persistent // Maybe EEPROM location? ResetFactoryDefaultPreference_struct pref(pref_temp); + if (modelMatchingFlashSize(pref.getDeviceModel())) { ResetFactoryDefaultPreference = pref_temp; } @@ -692,11 +764,12 @@ void afterloadSettings() { Scheduler.setEcoMode(Settings.EcoPowerMode()); #ifdef ESP32 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. } @@ -708,15 +781,19 @@ String LoadSettings() clearAllButTaskCaches(); #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER uint8_t oldSettingsChecksum[16] = { 0 }; memcpy(oldSettingsChecksum, Settings.md5, 16); - String err; + String err; - err = LoadFromFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type).c_str(), 0, reinterpret_cast(&Settings), sizeof(SettingsStruct)); + err = + LoadFromFile(SettingsType::getSettingsFileName(SettingsType::Enum::BasicSettings_Type).c_str(), + 0, + reinterpret_cast(&Settings), + sizeof(SettingsStruct)); if (memcmp(oldSettingsChecksum, Settings.md5, 16) != 0) { // File has changed, so need to flush all task caches. @@ -728,33 +805,38 @@ String LoadSettings() } if (!BuildFixes()) { - #ifndef BUILD_NO_DEBUG + if (COMPUTE_STRUCT_CHECKSUM(SettingsStruct, Settings)) { - addLog(LOG_LEVEL_INFO, F("CRC : Settings CRC ...OK")); - } else{ - addLog(LOG_LEVEL_ERROR, F("CRC : Settings CRC ...FAIL")); + addLog(LOG_LEVEL_INFO, concat(F("CRC : Settings CRC"), F("...OK"))); + } else { + addLog(LOG_LEVEL_ERROR, concat(F("CRC : Settings CRC"), F("...FAIL"))); } - #endif + #endif // ifndef BUILD_NO_DEBUG } Settings.validate(); initSerial(); - err = LoadFromFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), 0, reinterpret_cast(&SecuritySettings), sizeof(SecurityStruct)); + err = + LoadFromFile(SettingsType::getSettingsFileName(SettingsType::Enum::SecuritySettings_Type).c_str(), + 0, + reinterpret_cast(&SecuritySettings), + sizeof(SecurityStruct)); #ifndef BUILD_NO_DEBUG + if (SecuritySettings.checksumMatch()) { - addLog(LOG_LEVEL_INFO, F("CRC : SecuritySettings CRC ...OK ")); + addLog(LOG_LEVEL_INFO, concat(F("CRC : SecuritySettings CRC"), F("...OK "))); if (memcmp(SecuritySettings.ProgmemMd5, CRCValues.runTimeMD5, 16) != 0) { addLog(LOG_LEVEL_INFO, F("CRC : binary has changed since last save of Settings")); } } else { - addLog(LOG_LEVEL_ERROR, F("CRC : SecuritySettings CRC ...FAIL")); + addLog(LOG_LEVEL_ERROR, concat(F("CRC : SecuritySettings CRC"), F("...FAIL"))); } -#endif +#endif // ifndef BUILD_NO_DEBUG ExtendedControllerCredentials.load(); @@ -765,8 +847,6 @@ String LoadSettings() return err; } - - /********************************************************************************************\ Disable Plugin, based on bootFailedCount \*********************************************************************************************/ @@ -788,16 +868,16 @@ uint8_t disablePlugin(uint8_t bootFailedCount) { uint8_t disableAllPlugins(uint8_t bootFailedCount) { if (bootFailedCount > 0) { --bootFailedCount; + for (taskIndex_t i = 0; i < TASKS_MAX; ++i) { - // Disable temporarily as unit crashed - // FIXME TD-er: Should this be stored? - Settings.TaskDeviceEnabled[i] = false; + // Disable temporarily as unit crashed + // FIXME TD-er: Should this be stored? + Settings.TaskDeviceEnabled[i] = false; } } return bootFailedCount; } - /********************************************************************************************\ Disable Controller, based on bootFailedCount \*********************************************************************************************/ @@ -817,6 +897,7 @@ uint8_t disableController(uint8_t bootFailedCount) { uint8_t disableAllControllers(uint8_t bootFailedCount) { if (bootFailedCount > 0) { --bootFailedCount; + for (controllerIndex_t i = 0; i < CONTROLLER_MAX; ++i) { Settings.ControllerEnabled[i] = false; } @@ -824,7 +905,6 @@ uint8_t disableAllControllers(uint8_t bootFailedCount) { return bootFailedCount; } - /********************************************************************************************\ Disable Notification, based on bootFailedCount \*********************************************************************************************/ @@ -845,13 +925,15 @@ uint8_t disableNotification(uint8_t bootFailedCount) { uint8_t disableAllNotifications(uint8_t bootFailedCount) { if (bootFailedCount > 0) { --bootFailedCount; + for (uint8_t i = 0; i < NOTIFICATION_MAX; ++i) { - Settings.NotificationEnabled[i] = false; + Settings.NotificationEnabled[i] = false; } } return bootFailedCount; } -#endif + +#endif // if FEATURE_NOTIFIER /********************************************************************************************\ Disable Rules, based on bootFailedCount @@ -864,37 +946,39 @@ uint8_t disableRules(uint8_t bootFailedCount) { return bootFailedCount; } - bool getAndLogSettingsParameters(bool read, SettingsType::Enum settingsType, int index, int& offset, int& max_size) { #ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { String log = read ? F("Read") : F("Write"); - log += F(" settings: "); - log += SettingsType::getSettingsTypeString(settingsType); - log += F(" index: "); - log += index; + log += concat(F(" settings: "), SettingsType::getSettingsTypeString(settingsType)); + log += concat(F(" index: "), index); addLogMove(LOG_LEVEL_DEBUG_DEV, log); } #endif // ifndef BUILD_NO_DEBUG return SettingsType::getSettingsParameters(settingsType, index, offset, max_size); } - /********************************************************************************************\ Load array of Strings from Custom settings Use maxStringLength = 0 to optimize for size (strings will be concatenated) \*********************************************************************************************/ -String LoadStringArray(SettingsType::Enum settingsType, int index, String strings[], uint16_t nrStrings, uint16_t maxStringLength, uint32_t offset_in_block) +String LoadStringArray(SettingsType::Enum settingsType, + int index, + String strings[], + uint16_t nrStrings, + uint16_t maxStringLength, + uint32_t offset_in_block) { int offset, max_size; + if (!SettingsType::getSettingsParameters(settingsType, index, offset, max_size)) { #ifndef BUILD_NO_DEBUG return F("Invalid index for custom settings"); - #else + #else // ifndef BUILD_NO_DEBUG return F("Save error"); - #endif + #endif // ifndef BUILD_NO_DEBUG } const uint32_t bufferSize = 128; @@ -902,7 +986,7 @@ String LoadStringArray(SettingsType::Enum settingsType, int index, String string // FIXME TD-er: For now stack allocated, may need to be heap allocated? if (maxStringLength >= bufferSize) { return F("Max 128 chars allowed"); } - char buffer[bufferSize] = {0}; + char buffer[bufferSize] = { 0 }; String result; uint32_t readPos = offset_in_block; @@ -910,16 +994,16 @@ String LoadStringArray(SettingsType::Enum settingsType, int index, String string uint32_t stringCount = 0; const uint16_t estimatedStringSize = maxStringLength > 0 ? maxStringLength : bufferSize; - String tmpString; + String tmpString; tmpString.reserve(estimatedStringSize); { while (stringCount < nrStrings && static_cast(readPos) < max_size) { const uint32_t readSize = std::min(bufferSize, max_size - readPos); result += LoadFromFile(settingsType, - index, - reinterpret_cast(&buffer), - readSize, - readPos); + index, + reinterpret_cast(&buffer), + readSize, + readPos); for (uint32_t i = 0; i < readSize && stringCount < nrStrings; ++i) { const uint32_t curPos = readPos + i; @@ -946,8 +1030,7 @@ String LoadStringArray(SettingsType::Enum settingsType, int index, String string } if ((!tmpString.isEmpty()) && (stringCount < nrStrings)) { - result += F("Incomplete custom settings for index "); - result += (index + 1); + result += concat(F("Incomplete custom settings for index "), index + 1); move_special(strings[stringCount], std::move(tmpString)); } return result; @@ -957,25 +1040,32 @@ String LoadStringArray(SettingsType::Enum settingsType, int index, String string Save array of Strings from Custom settings Use maxStringLength = 0 to optimize for size (strings will be concatenated) \*********************************************************************************************/ -String SaveStringArray(SettingsType::Enum settingsType, int index, const String strings[], uint16_t nrStrings, uint16_t maxStringLength, uint32_t posInBlock) +String SaveStringArray(SettingsType::Enum settingsType, + int index, + const String strings[], + uint16_t nrStrings, + uint16_t maxStringLength, + uint32_t posInBlock) { // 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)) { #ifndef BUILD_NO_DEBUG return F("Invalid index for custom settings"); - #else + #else // ifndef BUILD_NO_DEBUG return F("Save error"); - #endif + #endif // ifndef BUILD_NO_DEBUG } #ifdef ESP8266 uint16_t bufferSize = 256; - #endif + #endif // ifdef ESP8266 #ifdef ESP32 uint16_t bufferSize = 1024; - #endif + #endif // ifdef ESP32 + if (bufferSize > max_size) { bufferSize = max_size; } @@ -1005,7 +1095,8 @@ String SaveStringArray(SettingsType::Enum settingsType, int index, const String } int bufpos = 0; - for ( ; bufpos < bufferSize && stringCount < nrStrings; ++bufpos) { + + for (; bufpos < bufferSize && stringCount < nrStrings; ++bufpos) { if (stringReadPos == 0) { // We're at the start of a string curStringLength = strings[stringCount].length(); @@ -1046,14 +1137,20 @@ String SaveStringArray(SettingsType::Enum settingsType, int index, const String writePos += bufpos; } + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + + if ((SettingsType::Enum::CustomTaskSettings_Type == settingsType) && + ((writePos - posInBlock) <= DAT_TASKS_CUSTOM_SIZE)) { // Not needed, so can be deleted + DeleteExtendedCustomTaskSettingsFile(settingsType, index); + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + if ((writePos >= max_size) && (stringCount < nrStrings)) { result += F("Error: Not all strings fit in custom settings."); } return result; } - - /********************************************************************************************\ Save Task settings to file system \*********************************************************************************************/ @@ -1061,14 +1158,14 @@ String SaveTaskSettings(taskIndex_t TaskIndex) { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER if (ExtraTaskSettings.TaskIndex != TaskIndex) { #ifndef BUILD_NO_DEBUG return F("SaveTaskSettings taskIndex does not match"); - #else + #else // ifndef BUILD_NO_DEBUG return F("Save error"); - #endif + #endif // ifndef BUILD_NO_DEBUG } START_TIMER @@ -1077,31 +1174,34 @@ String SaveTaskSettings(taskIndex_t TaskIndex) if (!Cache.matchChecksumExtraTaskSettings(TaskIndex, ExtraTaskSettings.computeChecksum())) { // Clear task device value names before saving, will generate again when loading them later. ExtraTaskSettings.clearDefaultTaskDeviceValueNames(); - ExtraTaskSettings.validate(); // Validate before saving will reduce nr of saves as it is more likely to not have changed the next time it will be saved. + ExtraTaskSettings.validate(); // Validate before saving will reduce nr of saves as it is more likely to not have changed the next time + // it will be saved. // Call to validate() may have changed the content, so re-compute the checksum. - // This is how it is now stored, so we can now also update the + // This is how it is now stored, so we can now also update the // ExtraTaskSettings cache. This may prevent a reload. Cache.updateExtraTaskSettingsCache_afterLoad_Save(); err = SaveToFile(SettingsType::Enum::TaskSettings_Type, - TaskIndex, - reinterpret_cast(&ExtraTaskSettings), - sizeof(struct ExtraTaskSettingsStruct)); + TaskIndex, + reinterpret_cast(&ExtraTaskSettings), + sizeof(struct ExtraTaskSettingsStruct)); #if !defined(PLUGIN_BUILD_MINIMAL_OTA) && !defined(ESP8266_1M) + if (err.isEmpty()) { err = checkTaskSettings(TaskIndex); } -#endif +#endif // if !defined(PLUGIN_BUILD_MINIMAL_OTA) && !defined(ESP8266_1M) + + // FIXME TD-er: Is this still needed as it is also cleared on PLUGIN_INIT and PLUGIN_EXIT? UserVar.clear_computed(ExtraTaskSettings.TaskIndex); - } + } #ifndef LIMIT_BUILD_SIZE else { addLog(LOG_LEVEL_INFO, F("Skip saving task settings, not changed")); - } -#endif +#endif // ifndef LIMIT_BUILD_SIZE STOP_TIMER(SAVE_TASK_SETTINGS); return err; } @@ -1114,6 +1214,7 @@ String LoadTaskSettings(taskIndex_t TaskIndex) if (ExtraTaskSettings.TaskIndex == TaskIndex) { return EMPTY_STRING; // already loaded } + if (!validTaskIndex(TaskIndex)) { return EMPTY_STRING; // Un-initialized task index. } @@ -1121,24 +1222,26 @@ String LoadTaskSettings(taskIndex_t TaskIndex) ExtraTaskSettings.clear(); const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(TaskIndex); + if (!validDeviceIndex(DeviceIndex)) { // No need to load from storage, as there is no plugin assigned to this task. ExtraTaskSettings.TaskIndex = TaskIndex; // Needed when an empty task was requested // FIXME TD-er: Do we need to keep a cache of an empty task? - // Maybe better to do this? - Cache.clearTaskCache(TaskIndex); -// Cache.updateExtraTaskSettingsCache_afterLoad_Save(); + // Maybe better to do this? + Cache.clearTaskCache(TaskIndex); + + // Cache.updateExtraTaskSettingsCache_afterLoad_Save(); return EMPTY_STRING; } #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER const String result = LoadFromFile( - SettingsType::Enum::TaskSettings_Type, - TaskIndex, - reinterpret_cast(&ExtraTaskSettings), + SettingsType::Enum::TaskSettings_Type, + TaskIndex, + reinterpret_cast(&ExtraTaskSettings), sizeof(struct ExtraTaskSettingsStruct)); // After loading, some settings may need patching. @@ -1148,13 +1251,12 @@ String LoadTaskSettings(taskIndex_t TaskIndex) // Nr of decimals cannot be configured, so set them to 0 just to be sure. for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { ExtraTaskSettings.TaskDeviceValueDecimals[i] = 0; - } + } } loadDefaultTaskValueNames_ifEmpty(TaskIndex); - + ExtraTaskSettings.validate(); Cache.updateExtraTaskSettingsCache_afterLoad_Save(); - UserVar.clear_computed(ExtraTaskSettings.TaskIndex); STOP_TIMER(LOAD_TASK_SETTINGS); return result; @@ -1166,7 +1268,7 @@ bool _CDN_url_loaded = false; String get_CDN_url_custom() { if (!_CDN_url_loaded) { - String strings[] = {EMPTY_STRING}; + String strings[] = { EMPTY_STRING }; LoadStringArray( SettingsType::Enum::CdnSettings_Type, 0, @@ -1177,9 +1279,10 @@ String get_CDN_url_custom() { return _CDN_url_cache; } -void set_CDN_url_custom(const String &url) { +void set_CDN_url_custom(const String& url) { _CDN_url_cache = url; _CDN_url_cache.trim(); + if (!_CDN_url_cache.isEmpty() && !_CDN_url_cache.endsWith(F("/"))) { _CDN_url_cache.concat('/'); } @@ -1202,6 +1305,7 @@ void set_CDN_url_custom(const String &url) { SettingsType::Enum::CdnSettings_Type, 0, strings, NR_ELEMENTS(strings), 255, 0); } + #endif // if FEATURE_ALTERNATIVE_CDN_URL /********************************************************************************************\ @@ -1211,7 +1315,7 @@ String SaveCustomTaskSettings(taskIndex_t TaskIndex, const uint8_t *memAddress, { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveCustomTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER return SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, TaskIndex, memAddress, datasize, posInBlock); } @@ -1223,18 +1327,14 @@ String SaveCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveCustomTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER return SaveStringArray( SettingsType::Enum::CustomTaskSettings_Type, TaskIndex, strings, nrStrings, maxStringLength, posInBlock); } String getCustomTaskSettingsError(uint8_t varNr) { - String error = F("Error: Text too long for line "); - - error += varNr + 1; - error += '\n'; - return error; + return strformat(F("Error: Text too long for line %d\n"), varNr + 1); } /********************************************************************************************\ @@ -1254,7 +1354,7 @@ String LoadCustomTaskSettings(taskIndex_t TaskIndex, uint8_t *memAddress, int da START_TIMER; #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadCustomTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER String result = LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, TaskIndex, memAddress, datasize, offset_in_block); STOP_TIMER(LOAD_CUSTOM_TASK_STATS); return result; @@ -1269,10 +1369,10 @@ String LoadCustomTaskSettings(taskIndex_t TaskIndex, String strings[], uint16_t START_TIMER; #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadCustomTaskSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER String result = LoadStringArray(SettingsType::Enum::CustomTaskSettings_Type, - TaskIndex, - strings, nrStrings, maxStringLength, offset_in_block); + TaskIndex, + strings, nrStrings, maxStringLength, offset_in_block); STOP_TIMER(LOAD_CUSTOM_TASK_STATS); return result; } @@ -1284,7 +1384,7 @@ String SaveControllerSettings(controllerIndex_t ControllerIndex, ControllerSetti { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveControllerSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER START_TIMER; @@ -1295,16 +1395,16 @@ String SaveControllerSettings(controllerIndex_t ControllerIndex, ControllerSetti if (checksum == (Cache.controllerSettings_checksums[ControllerIndex])) { #ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_INFO, concat(F("Skip saving ControllerSettings: "), checksum.toString())); -#endif +#endif // ifndef BUILD_NO_DEBUG return EMPTY_STRING; } const String res = SaveToFile(SettingsType::Enum::ControllerSettings_Type, ControllerIndex, - reinterpret_cast(&controller_settings), sizeof(controller_settings)); + reinterpret_cast(&controller_settings), sizeof(controller_settings)); Cache.controllerSettings_checksums[ControllerIndex] = checksum; #ifdef ESP32 Cache.setControllerSettings(ControllerIndex, controller_settings); - #endif + #endif // ifdef ESP32 STOP_TIMER(SAVE_CONTROLLER_SETTINGS); return res; @@ -1316,14 +1416,15 @@ String SaveControllerSettings(controllerIndex_t ControllerIndex, ControllerSetti String LoadControllerSettings(controllerIndex_t ControllerIndex, ControllerSettingsStruct& controller_settings) { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadControllerSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER START_TIMER #ifdef ESP32 + if (Cache.getControllerSettings(ControllerIndex, controller_settings)) { STOP_TIMER(LOAD_CONTROLLER_SETTINGS_C); return EMPTY_STRING; } - #endif + #endif // ifdef ESP32 String result = LoadFromFile(SettingsType::Enum::ControllerSettings_Type, ControllerIndex, reinterpret_cast(&controller_settings), sizeof(controller_settings)); @@ -1333,7 +1434,7 @@ String LoadControllerSettings(controllerIndex_t ControllerIndex, ControllerSetti Cache.controllerSettings_checksums[ControllerIndex] = controller_settings.computeChecksum(); #ifdef ESP32 Cache.setControllerSettings(ControllerIndex, controller_settings); - #endif + #endif // ifdef ESP32 STOP_TIMER(LOAD_CONTROLLER_SETTINGS); return result; } @@ -1345,7 +1446,7 @@ String ClearCustomControllerSettings(controllerIndex_t ControllerIndex) { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("ClearCustomControllerSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER // addLog(LOG_LEVEL_DEBUG, F("Clearing custom controller settings")); return ClearInFile(SettingsType::Enum::CustomControllerSettings_Type, ControllerIndex); @@ -1358,7 +1459,7 @@ String SaveCustomControllerSettings(controllerIndex_t ControllerIndex, const uin { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveCustomControllerSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER return SaveToFile(SettingsType::Enum::CustomControllerSettings_Type, ControllerIndex, memAddress, datasize); } @@ -1369,25 +1470,27 @@ String LoadCustomControllerSettings(controllerIndex_t ControllerIndex, uint8_t * { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadCustomControllerSettings")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER return LoadFromFile(SettingsType::Enum::CustomControllerSettings_Type, ControllerIndex, memAddress, datasize); } - #if FEATURE_CUSTOM_PROVISIONING + /********************************************************************************************\ Save Provisioning Settings \*********************************************************************************************/ String saveProvisioningSettings(ProvisioningStruct& ProvisioningSettings) { - String err; + String err; ProvisioningSettings.validate(); memcpy(ProvisioningSettings.ProgmemMd5, CRCValues.runTimeMD5, 16); + if (!COMPUTE_STRUCT_CHECKSUM_UPDATE(ProvisioningStruct, ProvisioningSettings)) { // Settings have changed, save to file. - err = SaveToFile_trunc(getFileName(FileType::PROVISIONING_DAT, 0).c_str(), 0, (uint8_t *)&ProvisioningSettings, sizeof(ProvisioningStruct)); + err = + SaveToFile_trunc(getFileName(FileType::PROVISIONING_DAT, 0).c_str(), 0, (uint8_t *)&ProvisioningSettings, sizeof(ProvisioningStruct)); } return err; } @@ -1397,8 +1500,13 @@ String saveProvisioningSettings(ProvisioningStruct& ProvisioningSettings) \*********************************************************************************************/ String loadProvisioningSettings(ProvisioningStruct& ProvisioningSettings) { - String err = LoadFromFile(getFileName(FileType::PROVISIONING_DAT, 0).c_str(), 0, (uint8_t *)&ProvisioningSettings, sizeof(ProvisioningStruct)); -#ifndef BUILD_NO_DEBUG + String err = LoadFromFile(getFileName(FileType::PROVISIONING_DAT, 0).c_str(), + 0, + (uint8_t *)&ProvisioningSettings, + sizeof(ProvisioningStruct)); + +# ifndef BUILD_NO_DEBUG + if (COMPUTE_STRUCT_CHECKSUM(ProvisioningStruct, ProvisioningSettings)) { addLog(LOG_LEVEL_INFO, F("CRC : ProvisioningSettings CRC ...OK ")); @@ -1410,22 +1518,23 @@ String loadProvisioningSettings(ProvisioningStruct& ProvisioningSettings) else { addLog(LOG_LEVEL_ERROR, F("CRC : ProvisioningSettings CRC ...FAIL")); } -#endif +# endif // ifndef BUILD_NO_DEBUG ProvisioningSettings.validate(); return err; } -#endif +#endif // if FEATURE_CUSTOM_PROVISIONING #if FEATURE_NOTIFIER + /********************************************************************************************\ Save Controller settings to file system \*********************************************************************************************/ String SaveNotificationSettings(int NotificationIndex, const uint8_t *memAddress, int datasize) { - #ifndef BUILD_NO_RAM_TRACKER + # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveNotificationSettings")); - #endif + # endif // ifndef BUILD_NO_RAM_TRACKER return SaveToFile(SettingsType::Enum::NotificationSettings_Type, NotificationIndex, memAddress, datasize); } @@ -1434,9 +1543,9 @@ String SaveNotificationSettings(int NotificationIndex, const uint8_t *memAddress \*********************************************************************************************/ String LoadNotificationSettings(int NotificationIndex, uint8_t *memAddress, int datasize) { - #ifndef BUILD_NO_RAM_TRACKER + # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadNotificationSettings")); - #endif + # endif // ifndef BUILD_NO_RAM_TRACKER return LoadFromFile(SettingsType::Enum::NotificationSettings_Type, NotificationIndex, memAddress, datasize); } @@ -1559,7 +1668,7 @@ String InitFile(const String& fname, int datasize) { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("InitFile")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER FLASH_GUARD(); fs::File f = tryOpenFile(fname, "w"); @@ -1585,7 +1694,7 @@ String InitFile(SettingsType::Enum settingsType) String InitFile(SettingsType::SettingsFileEnum file_type) { - return InitFile(SettingsType::getSettingsFileName(file_type), + return InitFile(SettingsType::getSettingsFileName(file_type), SettingsType::getInitFileSize(file_type)); } @@ -1608,43 +1717,37 @@ String SaveToFile_trunc(const char *fname, int index, const uint8_t *memAddress, String doSaveToFile(const char *fname, int index, const uint8_t *memAddress, int datasize, const char *mode) { #ifndef BUILD_NO_DEBUG -#ifndef ESP32 +# ifndef ESP32 if (allocatedOnStack(memAddress)) { - String log = F("SaveToFile: "); - log += fname; - log += F(" ERROR, Data allocated on stack"); - addLog(LOG_LEVEL_ERROR, log); + addLog(LOG_LEVEL_ERROR, strformat(F("SaveToFile: %s ERROR, Data allocated on stack"), fname)); // return log; // FIXME TD-er: Should this be considered a breaking error? } -#endif // ifndef ESP32 -#endif +# endif // ifndef ESP32 +#endif // ifndef BUILD_NO_DEBUG if (index < 0) { #ifndef BUILD_NO_DEBUG - String log = F("SaveToFile: "); - log += fname; - log += F(" ERROR, invalid position in file"); - #else - String log = F("Save error"); - #endif + const String log = strformat(F("SaveToFile: %s ERROR, invalid position in file"), fname); + #else // ifndef BUILD_NO_DEBUG + const String log = F("Save error"); + #endif // ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, log); return log; } START_TIMER; #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SaveToFile")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER FLASH_GUARD(); - + #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SaveToFile: free stack: "); - log += getCurrentFreeStack(); - addLogMove(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, concat(F("SaveToFile: free stack: "), getCurrentFreeStack())); } - #endif + #endif // ifndef BUILD_NO_DEBUG delay(1); unsigned long timer = millis() + 50; fs::File f = tryOpenFile(fname, mode); @@ -1677,38 +1780,28 @@ String doSaveToFile(const char *fname, int index, const uint8_t *memAddress, int } f.close(); #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(48); - log += F("FILE : Saved "); - log += fname; - log += F(" offset: "); - log += index; - log += F(" size: "); - log += datasize; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("FILE : Saved %s offset: %d size: %d"), fname, index, datasize)); } - #endif + #endif // ifndef BUILD_NO_DEBUG } else { #ifndef BUILD_NO_DEBUG - String log = F("SaveToFile: "); - log += fname; - log += F(" ERROR, Cannot save to file"); - #else - String log = F("Save error"); - #endif + const String log = strformat(F("SaveToFile: %s ERROR, Cannot save to file"), fname); + #else // ifndef BUILD_NO_DEBUG + const String log = F("Save error"); + #endif // ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, log); return log; } STOP_TIMER(SAVEFILE_STATS); #ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SaveToFile: free stack after: "); - log += getCurrentFreeStack(); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("SaveToFile: free stack after: "), getCurrentFreeStack())); } - #endif + #endif // ifndef BUILD_NO_DEBUG // OK return EMPTY_STRING; @@ -1721,12 +1814,10 @@ String ClearInFile(const char *fname, int index, int datasize) { if (index < 0) { #ifndef BUILD_NO_DEBUG - String log = F("ClearInFile: "); - log += fname; - log += F(" ERROR, invalid position in file"); - #else - String log = F("Save error"); - #endif + const String log = strformat(F("ClearInFile: %s ERROR, invalid position in file"), fname); + #else // ifndef BUILD_NO_DEBUG + const String log = F("Save error"); + #endif // ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, log); return log; @@ -1734,7 +1825,7 @@ String ClearInFile(const char *fname, int index, int datasize) #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("ClearInFile")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER FLASH_GUARD(); fs::File f = tryOpenFile(fname, "r+"); @@ -1751,12 +1842,10 @@ String ClearInFile(const char *fname, int index, int datasize) f.close(); } else { #ifndef BUILD_NO_DEBUG - String log = F("ClearInFile: "); - log += fname; - log += F(" ERROR, Cannot save to file"); - #else - String log = F("Save error"); - #endif + const String log = strformat(F("ClearInFile: %s ERROR, Cannot save to file"), fname); + #else // ifndef BUILD_NO_DEBUG + const String log = F("Save error"); + #endif // ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, log); return log; } @@ -1772,12 +1861,10 @@ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int data { if (offset < 0) { #ifndef BUILD_NO_DEBUG - String log = F("LoadFromFile: "); - log += fname; - log += F(" ERROR, invalid position in file"); - #else - String log = F("Load error"); - #endif + const String log = strformat(F("LoadFromFile: %s ERROR, invalid position in file"), fname); + #else // ifndef BUILD_NO_DEBUG + const String log = F("Load error"); + #endif // ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, log); return log; } @@ -1785,14 +1872,15 @@ String LoadFromFile(const char *fname, int offset, uint8_t *memAddress, int data START_TIMER; #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("LoadFromFile")); - #endif - + #endif // ifndef BUILD_NO_RAM_TRACKER + fs::File f = tryOpenFile(fname, "r"); - SPIFFS_CHECK(f, fname); + SPIFFS_CHECK(f, fname); const int fileSize = f.size(); + if (fileSize > offset) { - SPIFFS_CHECK(f.seek(offset, fs::SeekSet), fname); - + SPIFFS_CHECK(f.seek(offset, fs::SeekSet), fname); + if (fileSize < (offset + datasize)) { const int newdatasize = datasize + offset - fileSize; @@ -1862,36 +1950,29 @@ String LoadFromFile(const char *fname, String& data, int offset) \*********************************************************************************************/ String getSettingsFileIndexRangeError(bool read, SettingsType::Enum settingsType, int index) { if (settingsType >= SettingsType::Enum::SettingsType_MAX) { - String error = F("Unknown settingsType: "); - error += static_cast(settingsType); - return error; + return concat(F("Unknown settingsType: "), static_cast(settingsType)); } String error = read ? F("Load") : F("Save"); + #ifndef BUILD_NO_DEBUG error += SettingsType::getSettingsTypeString(settingsType); - error += F(" index out of range: "); - error += index; - #else + error += concat(F(" index out of range: "), index); + #else // ifndef BUILD_NO_DEBUG error += F(" error"); - #endif + #endif // ifndef BUILD_NO_DEBUG return error; } String getSettingsFileDatasizeError(bool read, SettingsType::Enum settingsType, int index, int datasize, int max_size) { String error = read ? F("Load") : F("Save"); + #ifndef BUILD_NO_DEBUG error += SettingsType::getSettingsTypeString(settingsType); - error += '('; - error += index; - error += F(") datasize("); - error += datasize; - error += F(") > max_size("); - error += max_size; - error += ')'; - #else + error += strformat(F("(%d) datasize(%d) > max_size(%d)"), index, datasize, max_size); + #else // ifndef BUILD_NO_DEBUG error += F(" error"); - #endif - + #endif // ifndef BUILD_NO_DEBUG + return error; } @@ -1906,8 +1987,42 @@ String LoadFromFile(SettingsType::Enum settingsType, int index, uint8_t *memAddr if ((datasize + offset_in_block) > max_size) { return getSettingsFileDatasizeError(read, settingsType, index, datasize, max_size); } - const String fname = SettingsType::getSettingsFileName(settingsType); - return LoadFromFile(fname.c_str(), (offset + offset_in_block), memAddress, datasize); + + int dataOffset = 0; + + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + int taskIndex = INVALID_TASK_INDEX; // Use base filename + + if ((SettingsType::Enum::CustomTaskSettings_Type == settingsType) && + ((offset_in_block + datasize) > DAT_TASKS_CUSTOM_SIZE)) { + if (offset_in_block < DAT_TASKS_CUSTOM_SIZE) { // block starts in regular Custom config: Load first part + const String fname = SettingsType::getSettingsFileName(settingsType); + dataOffset = DAT_TASKS_CUSTOM_SIZE - offset_in_block; + const String res = LoadFromFile(fname.c_str(), offset + offset_in_block, memAddress, dataOffset); + + if (!res.isEmpty()) { return res; } // Error occurred? + + datasize -= dataOffset; + offset_in_block = DAT_TASKS_CUSTOM_SIZE; + } + const String fname = SettingsType::getSettingsFileName(settingsType, index); + + if (fileExists(fname)) { // Do we have a task-specific extension stored? + if (offset_in_block >= DAT_TASKS_CUSTOM_SIZE) { + offset_in_block -= DAT_TASKS_CUSTOM_SIZE; + } + offset = 0; + taskIndex = index; // Use task-specific filename + } + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + + const String fname = SettingsType::getSettingsFileName(settingsType + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + , taskIndex + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + ); + return LoadFromFile(fname.c_str(), (offset + offset_in_block), memAddress + dataOffset, datasize); } String SaveToFile(SettingsType::Enum settingsType, int index, const uint8_t *memAddress, int datasize, int posInBlock) { @@ -1921,14 +2036,65 @@ String SaveToFile(SettingsType::Enum settingsType, int index, const uint8_t *mem if ((datasize > max_size) || ((posInBlock + datasize) > max_size)) { return getSettingsFileDatasizeError(read, settingsType, index, datasize, max_size); } - const String fname = SettingsType::getSettingsFileName(settingsType); - if (!fileExists(fname)) { - InitFile(settingsType); + + int dataOffset = 0; + + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + int taskIndex = INVALID_TASK_INDEX; // Use base filename + + if ((SettingsType::Enum::CustomTaskSettings_Type == settingsType) && + (posInBlock + datasize > (DAT_TASKS_CUSTOM_SIZE))) { // max_size already handled above + if (posInBlock < DAT_TASKS_CUSTOM_SIZE) { // Partial in regular config.dat, save that part first + const String fname = SettingsType::getSettingsFileName(settingsType); + dataOffset = (DAT_TASKS_CUSTOM_SIZE - posInBlock); // Bytes to keep 'local' + # ifndef BUILD_NO_DEBUG + const String styp = SettingsType::getSettingsTypeString(settingsType); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("ExtraSaveToFile: %s file: %s size: %d pos: %d"), + styp.c_str(), fname.c_str(), dataOffset, posInBlock)); + } + # endif // ifndef BUILD_NO_DEBUG + const String res = SaveToFile(fname.c_str(), offset + posInBlock, memAddress, dataOffset); + + if (!res.isEmpty()) { return res; } // Error occurred + + datasize -= dataOffset; + posInBlock = 0; + } else { + posInBlock -= DAT_TASKS_CUSTOM_SIZE; + } + offset = 0; // Start of the extension file + taskIndex = index; // Use task-specific filename } -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, concat(F("SaveToFile: "), SettingsType::getSettingsTypeString(settingsType)) + concat(F(" index: "), index)); -#endif - return SaveToFile(fname.c_str(), offset + posInBlock, memAddress, datasize); + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + + const String fname = SettingsType::getSettingsFileName(settingsType + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + , taskIndex + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + ); + + if (!fileExists(fname)) { + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + + if (!validTaskIndex(taskIndex)) { + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + InitFile(settingsType); + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + } else { + InitFile(fname, DAT_TASKS_CUSTOM_EXTENSION_SIZE); // Initialize task-specific file + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + } + #ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("SaveToFile: "), SettingsType::getSettingsTypeString(settingsType)) + + strformat(F(" file: %s task: %d"), fname.c_str(), index + 1)); + } + #endif // ifndef BUILD_NO_DEBUG + return SaveToFile(fname.c_str(), offset + posInBlock, memAddress + dataOffset, datasize); } String ClearInFile(SettingsType::Enum settingsType, int index) { @@ -1938,10 +2104,39 @@ String ClearInFile(SettingsType::Enum settingsType, int index) { if (!getAndLogSettingsParameters(read, settingsType, index, offset, max_size)) { return getSettingsFileIndexRangeError(read, settingsType, index); } + #if FEATURE_EXTENDED_CUSTOM_SETTINGS + + if (SettingsType::Enum::CustomTaskSettings_Type == settingsType) { + max_size = DAT_TASKS_CUSTOM_SIZE; // Don't also wipe the external size inside the config.dat file... + DeleteExtendedCustomTaskSettingsFile(settingsType, index); + } + #endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + const String fname = SettingsType::getSettingsFileName(settingsType); return ClearInFile(fname.c_str(), offset, max_size); } +#if FEATURE_EXTENDED_CUSTOM_SETTINGS +bool DeleteExtendedCustomTaskSettingsFile(SettingsType::Enum settingsType, int index) { + if ((SettingsType::Enum::CustomTaskSettings_Type == settingsType) && validTaskIndex(index)) { + const String fname = SettingsType::getSettingsFileName(settingsType, index); + + if (fileExists(fname)) { + const bool deleted = tryDeleteFile(fname); // Don't need the extension file anymore, so delete it + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("CustomTaskSettings: Removing no longer needed file: "), fname)); + } + # endif // ifndef BUILD_NO_DEBUG + return deleted; + } + } + return false; +} + +#endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + /********************************************************************************************\ Check file system area settings \*********************************************************************************************/ @@ -1949,7 +2144,7 @@ int SpiffsSectors() { #ifndef BUILD_NO_RAM_TRACKER checkRAM(F("SpiffsSectors")); - #endif + #endif // ifndef BUILD_NO_RAM_TRACKER #if defined(ESP8266) # ifdef CORE_POST_2_6_0 uint32_t _sectorStart = ((uint32_t)&_FS_start - 0x40200000) / SPI_FLASH_SEC_SIZE; @@ -1982,6 +2177,7 @@ size_t SpiffsUsedBytes() { size_t SpiffsTotalBytes() { static size_t result = 1; // Do not output 0, this may be used in divisions. + if (result == 1) { #ifdef ESP32 result = ESPEASY_FS.totalBytes(); @@ -1997,9 +2193,10 @@ size_t SpiffsTotalBytes() { size_t SpiffsBlocksize() { static size_t result = 1; + if (result == 1) { #ifdef ESP32 - result = 8192; // Just assume 8k, since we cannot query it + result = 8192; // Just assume 8k, since we cannot query it #endif // ifdef ESP32 #ifdef ESP8266 fs::FSInfo fs_info; @@ -2012,9 +2209,10 @@ size_t SpiffsBlocksize() { size_t SpiffsPagesize() { static size_t result = 1; + if (result == 1) { #ifdef ESP32 - result = 256; // Just assume 256, since we cannot query it + result = 256; // Just assume 256, since we cannot query it #endif // ifdef ESP32 #ifdef ESP8266 fs::FSInfo fs_info; @@ -2026,7 +2224,7 @@ size_t SpiffsPagesize() { } size_t SpiffsFreeSpace() { - int freeSpace = SpiffsTotalBytes() - SpiffsUsedBytes(); + int freeSpace = SpiffsTotalBytes() - SpiffsUsedBytes(); const size_t blocksize = SpiffsBlocksize(); if (freeSpace < static_cast(2 * blocksize)) { @@ -2042,6 +2240,7 @@ bool SpiffsFull() { } #if FEATURE_RTC_CACHE_STORAGE + /********************************************************************************************\ Handling cached data \*********************************************************************************************/ @@ -2049,18 +2248,16 @@ String createCacheFilename(unsigned int count) { String fname; fname.reserve(16); - #ifdef ESP32 + # ifdef ESP32 fname = '/'; - #endif // ifdef ESP32 - fname += F("cache_"); - fname += String(count); - fname += F(".bin"); + # endif // ifdef ESP32 + fname += strformat(F("cache_%d.bin"), count); return fname; } // Match string with an integer between '_' and ".bin" int getCacheFileCountFromFilename(const String& fname) { - if (!isCacheFile(fname)) return -1; + if (!isCacheFile(fname)) { return -1; } int startpos = fname.indexOf('_'); if (startpos < 0) { return -1; } @@ -2087,7 +2284,7 @@ bool getCacheFileCounters(uint16_t& lowest, uint16_t& highest, size_t& filesizeH lowest = 65535; highest = 0; filesizeHighest = 0; -#ifdef ESP8266 +# ifdef ESP8266 fs::Dir dir = ESPEASY_FS.openDir(F("cache")); while (dir.next()) { @@ -2105,8 +2302,8 @@ bool getCacheFileCounters(uint16_t& lowest, uint16_t& highest, size_t& filesizeH } } } -#endif // ESP8266 -#ifdef ESP32 +# endif // ESP8266 +# ifdef ESP32 fs::File root = ESPEASY_FS.open(F("/")); fs::File file = root.openNextFile(); @@ -2114,6 +2311,7 @@ bool getCacheFileCounters(uint16_t& lowest, uint16_t& highest, size_t& filesizeH { if (!file.isDirectory()) { const String fname(file.name()); + if (fname.startsWith(F("/cache")) || fname.startsWith(F("cache"))) { int count = getCacheFileCountFromFilename(fname); @@ -2126,16 +2324,18 @@ bool getCacheFileCounters(uint16_t& lowest, uint16_t& highest, size_t& filesizeH highest = count; filesizeHighest = file.size(); } -#ifndef BUILD_NO_DEBUG +# ifndef BUILD_NO_DEBUG } else { - addLog(LOG_LEVEL_INFO, concat(F("RTC : Cannot get count from: "), fname)); -#endif + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("RTC : Cannot get count from: "), fname)); + } +# endif // ifndef BUILD_NO_DEBUG } } } file = root.openNextFile(); } -#endif // ESP32 +# endif // ESP32 if (lowest <= highest) { return true; @@ -2144,7 +2344,8 @@ bool getCacheFileCounters(uint16_t& lowest, uint16_t& highest, size_t& filesizeH highest = 0; return false; } -#endif + +#endif // if FEATURE_RTC_CACHE_STORAGE /********************************************************************************************\ Get partition table information @@ -2157,9 +2358,7 @@ String getPartitionType(uint8_t pType, uint8_t pSubType) { if (partitionType == ESP_PARTITION_TYPE_APP) { if ((partitionSubType >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN) && (partitionSubType < ESP_PARTITION_SUBTYPE_APP_OTA_MAX)) { - String result = F("OTA partition "); - result += (partitionSubType - ESP_PARTITION_SUBTYPE_APP_OTA_MIN); - return result; + return concat(F("OTA partition "), partitionSubType - ESP_PARTITION_SUBTYPE_APP_OTA_MIN); } switch (partitionSubType) { @@ -2181,35 +2380,23 @@ String getPartitionType(uint8_t pType, uint8_t pSubType) { case ESP_PARTITION_SUBTYPE_DATA_COREDUMP: return F("COREDUMP"); case ESP_PARTITION_SUBTYPE_DATA_ESPHTTPD: return F("ESPHTTPD"); case ESP_PARTITION_SUBTYPE_DATA_FAT: return F("FAT"); - case ESP_PARTITION_SUBTYPE_DATA_SPIFFS: - #ifdef USE_LITTLEFS + case ESP_PARTITION_SUBTYPE_DATA_SPIFFS: + # ifdef USE_LITTLEFS return F("LittleFS"); - #else + # else // ifdef USE_LITTLEFS return F("SPIFFS"); - #endif + # endif // ifdef USE_LITTLEFS default: break; } } - String result = F("Unknown("); - result += partitionSubType; - result += ')'; - return result; + return strformat(F("Unknown(%d)"), partitionSubType); } String getPartitionTableHeader(const String& itemSep, const String& lineEnd) { - String result; + const char *itemSep_str = itemSep.c_str(); - result += F("Address"); - result += itemSep; - result += F("Size"); - result += itemSep; - result += F("Label"); - result += itemSep; - result += F("Partition Type"); - result += itemSep; - result += F("Encrypted"); - result += lineEnd; - return result; + return strformat(F("Address%sSize%sLabel%sPartition Type%sEncrypted%s"), + itemSep_str, itemSep_str, itemSep_str, itemSep_str, lineEnd.c_str()); } String getPartitionTable(uint8_t pType, const String& itemSep, const String& lineEnd) { @@ -2220,16 +2407,18 @@ String getPartitionTable(uint8_t pType, const String& itemSep, const String& lin if (_mypartiterator) { do { const esp_partition_t *_mypart = esp_partition_get(_mypartiterator); - result += formatToHex(_mypart->address); - result += itemSep; - result += formatToHex_decimal(_mypart->size, 1024); - result += itemSep; - result += _mypart->label; - result += itemSep; - result += getPartitionType(_mypart->type, _mypart->subtype); - result += itemSep; - result += (_mypart->encrypted ? F("Yes") : F("-")); - result += lineEnd; + const char *itemSep_str = itemSep.c_str(); + result += strformat(F("%x%s%s%s%s%s%s%s%s%s"), + _mypart->address, + itemSep_str, + formatToHex_decimal(_mypart->size, 1024).c_str(), + itemSep_str, + _mypart->label, + itemSep_str, + getPartitionType(_mypart->type, _mypart->subtype).c_str(), + itemSep_str, + String(_mypart->encrypted ? F("Yes") : F("-")).c_str(), + lineEnd.c_str()); } while ((_mypartiterator = esp_partition_next(_mypartiterator)) != nullptr); } esp_partition_iterator_release(_mypartiterator); @@ -2247,7 +2436,7 @@ String downloadFileType(const String& url, const String& user, const String& pas } String filename = getFileName(filetype, filenr); - String fullUrl = joinUrlFilename(url, filename); + String fullUrl = joinUrlFilename(url, filename); String error; if (ResetFactoryDefaultPreference.deleteFirst()) { @@ -2260,8 +2449,8 @@ String downloadFileType(const String& url, const String& user, const String& pas } } else { if (fileExists(filename)) { - String filename_bak = filename; - filename_bak += F("_bak"); + const String filename_bak = strformat(F("%s_bak"), filename.c_str()); + if (fileExists(filename_bak)) { if (!ResetFactoryDefaultPreference.delete_Bak_Files() || !tryDeleteFile(filename_bak)) { return F("Could not rename to _bak"); @@ -2269,8 +2458,7 @@ String downloadFileType(const String& url, const String& user, const String& pas } // Must download it to a tmp file. - String tmpfile = filename; - tmpfile += F("_tmp"); + const String tmpfile = strformat(F("%s_tmp"), filename.c_str()); if (!downloadFile(fullUrl, tmpfile, user, pass, error)) { return error; @@ -2302,6 +2490,27 @@ String downloadFileType(const String& url, const String& user, const String& pas #endif // if FEATURE_DOWNLOAD +bool validateUploadConfigDat(const uint8_t *buf) { + bool result = false; + struct TempStruct { + unsigned long PID; + int Version; + } Temp; + + for (unsigned int x = 0; x < sizeof(struct TempStruct); x++) { + memcpy(reinterpret_cast(&Temp) + x, &buf[x], 1); + } + #ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("Validate config.dat, Version: %d = %d, PID: %d = %d"), + Temp.Version, VERSION, Temp.PID, ESP_PROJECT_PID)); + #endif // ifndef BUILD_NO_DEBUG + + if ((Temp.Version == VERSION) && (Temp.PID == ESP_PROJECT_PID)) { + result = true; + } + return result; +} + #if FEATURE_CUSTOM_PROVISIONING String downloadFileType(FileType::Enum filetype, unsigned int filenr) @@ -2328,6 +2537,7 @@ String downloadFileType(FileType::Enum filetype, unsigned int filenr) } } String res = downloadFileType(url, user, pass, filetype, filenr); + clearAllCaches(); return res; } diff --git a/src/src/Helpers/ESPEasy_Storage.h b/src/src/Helpers/ESPEasy_Storage.h index 09b157b20..48dbbc31a 100644 --- a/src/src/Helpers/ESPEasy_Storage.h +++ b/src/src/Helpers/ESPEasy_Storage.h @@ -6,6 +6,8 @@ #include "../Helpers/FS_Helper.h" +#include "../CustomBuild/StorageLayout.h" + #include "../DataStructs/ChecksumType.h" #include "../DataStructs/ProvisioningStruct.h" #include "../DataTypes/ESPEasyFileType.h" @@ -195,6 +197,13 @@ String getCustomTaskSettingsError(uint8_t varNr); \*********************************************************************************************/ String ClearCustomTaskSettings(taskIndex_t TaskIndex); +/********************************************************************************************\ + Delete Extended custom task settings file if it exists, with validity checks + \*********************************************************************************************/ +#if FEATURE_EXTENDED_CUSTOM_SETTINGS +bool DeleteExtendedCustomTaskSettingsFile(SettingsType::Enum settingsType, int index); +#endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + /********************************************************************************************\ Load Custom Task settings from file system \*********************************************************************************************/ @@ -367,6 +376,7 @@ String getPartitionTable(uint8_t pType, const String& itemSep, const String& lin #endif // ifdef ESP32 +bool validateUploadConfigDat(const uint8_t *buf); /********************************************************************************************\ Download ESPEasy file types from HTTP server diff --git a/src/src/Helpers/ESPEasy_TouchHandler.cpp b/src/src/Helpers/ESPEasy_TouchHandler.cpp new file mode 100644 index 000000000..b78e5adb8 --- /dev/null +++ b/src/src/Helpers/ESPEasy_TouchHandler.cpp @@ -0,0 +1,2222 @@ +#include "../Helpers/ESPEasy_TouchHandler.h" + +#ifdef PLUGIN_USES_TOUCHHANDLER + +# include "../Commands/ExecuteCommand.h" + +/**************************************************************************** + * toString: Display-value for the touch action + ***************************************************************************/ +# if TOUCH_FEATURE_EXTENDED_TOUCH +const __FlashStringHelper* toString(Touch_action_e action) { + switch (action) { + case Touch_action_e::Default: return F("Default"); + case Touch_action_e::ActivateGroup: return F("Activate Group"); + case Touch_action_e::IncrementGroup: return F("Next Group"); + case Touch_action_e::DecrementGroup: return F("Previous Group"); + case Touch_action_e::IncrementPage: return F("Next Page (+10)"); + case Touch_action_e::DecrementPage: return F("Previous Page (-10)"); + } + return F("Unsupported!"); +} + +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH + +/**************************************************************************** + * toString: Display-value for the swipe action + ***************************************************************************/ +# if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE +const __FlashStringHelper* toString(Swipe_action_e action) { + switch (action) { + case Swipe_action_e::Up: return F("Up"); + case Swipe_action_e::UpRight: return F("Up-Right"); + case Swipe_action_e::Right: return F("Right"); + case Swipe_action_e::RightDown: return F("Right-Down"); + case Swipe_action_e::Down: return F("Down"); + case Swipe_action_e::DownLeft: return F("Down-Left"); + case Swipe_action_e::Left: return F("Left"); + case Swipe_action_e::LeftUp: return F("Left-Up"); + case Swipe_action_e::None: return F("None"); + case Swipe_action_e::SwipeAction_MAX: break; + } + return F("Unknown"); +} + +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +/** + * Constructors + */ +ESPEasy_TouchHandler::ESPEasy_TouchHandler() {} + +ESPEasy_TouchHandler::ESPEasy_TouchHandler(const taskIndex_t & displayTask, + const AdaGFXColorDepth& colorDepth) + : _displayTask(displayTask), _colorDepth(colorDepth) {} + +/** + * Destructor + */ +ESPEasy_TouchHandler::~ESPEasy_TouchHandler() {} + +/** + * Load the touch objects from the settings, and initialize them properly where needed. + */ +void ESPEasy_TouchHandler::loadTouchObjects(struct EventStruct *event) { + # ifdef TOUCH_DEBUG + addLog(LOG_LEVEL_INFO, F("TOUCH DEBUG loadTouchObjects")); + # endif // TOUCH_DEBUG + LoadCustomTaskSettings(event->TaskIndex, settingsArray, TOUCH_ARRAY_SIZE, 0); + + lastObjectIndex = TOUCH_OBJECT_INDEX_START - 1; // START must be > 0!!! + + objectCount = 0; + _buttonGroups.clear(); // Clear groups + _buttonGroups.insert(0u); // Always have group 0 + + for (uint8_t i = TOUCH_OBJECT_INDEX_END; i >= TOUCH_OBJECT_INDEX_START; --i) { + if (!settingsArray[i].isEmpty() && (lastObjectIndex < TOUCH_OBJECT_INDEX_START)) { + lastObjectIndex = i; + objectCount++; // Count actual number of objects + } + } + + // Get calibration and common settings + Touch_Settings.calibrationEnabled = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_CALIBRATION_ENABLED, TOUCH_SETTINGS_SEPARATOR) == 1; + Touch_Settings.logEnabled = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_CALIBRATION_LOG_ENABLED, TOUCH_SETTINGS_SEPARATOR) == 1; + int lSettings = 0; + + bitWrite(lSettings, TOUCH_FLAGS_SEND_XY, TOUCH_TS_SEND_XY); // Defaults initialized + bitWrite(lSettings, TOUCH_FLAGS_SEND_Z, TOUCH_TS_SEND_Z); + bitWrite(lSettings, TOUCH_FLAGS_SEND_OBJECTNAME, TOUCH_TS_SEND_OBJECTNAME); + bitWrite(lSettings, TOUCH_FLAGS_DEDUPLICATE, TOUCH_TS_DEDUPLICATE); + bitWrite(lSettings, TOUCH_FLAGS_INIT_OBJECTEVENT, TOUCH_TS_INIT_OBJECTEVENT); + Touch_Settings.flags = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_FLAGS, TOUCH_SETTINGS_SEPARATOR, lSettings); + Touch_Settings.top_left.x = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], TOUCH_CALIBRATION_TOP_X, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.top_left.y = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], TOUCH_CALIBRATION_TOP_Y, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.bottom_right.x = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_CALIBRATION_BOTTOM_X, + TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.bottom_right.y = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_CALIBRATION_BOTTOM_Y, + TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.debounceMs = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], TOUCH_COMMON_DEBOUNCE_MS, TOUCH_SETTINGS_SEPARATOR, + TOUCH_DEBOUNCE_MILLIS); + # if TOUCH_FEATURE_EXTENDED_TOUCH + Touch_Settings.colorOn = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_ON, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.colorOff = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_OFF, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.colorBorder = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_BORDER, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.colorCaption = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_CAPTION, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.colorDisabled = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_DISABLED, TOUCH_SETTINGS_SEPARATOR); + Touch_Settings.colorDisabledCaption = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_DEF_COLOR_DISABCAPT, TOUCH_SETTINGS_SEPARATOR); + + if ((Touch_Settings.colorOn == 0u) && // Validate and set defaults + (Touch_Settings.colorOff == 0u) && + (Touch_Settings.colorCaption == 0u) && + (Touch_Settings.colorBorder == 0u) && + (Touch_Settings.colorDisabled == 0u) && + (Touch_Settings.colorDisabledCaption == 0u)) { + Touch_Settings.colorOn = TOUCH_DEFAULT_COLOR_ON; + Touch_Settings.colorOff = TOUCH_DEFAULT_COLOR_OFF; + Touch_Settings.colorCaption = TOUCH_DEFAULT_COLOR_CAPTION; + Touch_Settings.colorBorder = TOUCH_DEFAULT_COLOR_BORDER; + Touch_Settings.colorDisabled = TOUCH_DEFAULT_COLOR_DISABLED; + Touch_Settings.colorDisabledCaption = TOUCH_DEFAULT_COLOR_DISABLED_CAPTION; + } + # if TOUCH_FEATURE_SWIPE + Touch_Settings.swipeMinimal = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_SWIPE_MINIMAL, TOUCH_SETTINGS_SEPARATOR, TOUCH_DEF_SWIPE_MINIMAL); + Touch_Settings.swipeMargin = parseStringToInt(settingsArray[TOUCH_CALIBRATION_START], + TOUCH_COMMON_SWIPE_MARGIN, TOUCH_SETTINGS_SEPARATOR, TOUCH_DEF_SWIPE_MARGIN); + # endif // if TOUCH_FEATURE_SWIPE + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + settingsArray[TOUCH_CALIBRATION_START].clear(); // Free a little memory + + // Buffer some settings, mostly for readability, but also to be able to set from write command + _flipped = bitRead(Touch_Settings.flags, TOUCH_FLAGS_ROTATION_FLIPPED); + _deduplicate = bitRead(Touch_Settings.flags, TOUCH_FLAGS_DEDUPLICATE); + + TouchObjects.clear(); + + if (objectCount > 0) { + TouchObjects.reserve(objectCount); + uint8_t t = 0u; + + for (uint8_t i = TOUCH_OBJECT_INDEX_START; i <= lastObjectIndex; ++i) { + if (!settingsArray[i].isEmpty()) { + TouchObjects.push_back(tTouchObjects()); + TouchObjects[t].flags = parseStringToInt(settingsArray[i], TOUCH_OBJECT_FLAGS, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].objectName = parseStringKeepCase(settingsArray[i], TOUCH_OBJECT_NAME, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].top_left.x = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COORD_TOP_X, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].top_left.y = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COORD_TOP_Y, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].width_height.x = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COORD_WIDTH, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].width_height.y = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COORD_HEIGHT, TOUCH_SETTINGS_SEPARATOR); + # if TOUCH_FEATURE_EXTENDED_TOUCH + TouchObjects[t].colorOn = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_ON, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].colorOff = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_OFF, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].colorCaption = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_CAPTION, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].captionOn = parseStringKeepCase(settingsArray[i], TOUCH_OBJECT_CAPTION_ON, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].captionOff = parseStringKeepCase(settingsArray[i], TOUCH_OBJECT_CAPTION_OFF, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].colorBorder = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_BORDER, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].colorDisabled = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_DISABLED, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].colorDisabledCaption = parseStringToInt(settingsArray[i], TOUCH_OBJECT_COLOR_DISABCAPT, TOUCH_SETTINGS_SEPARATOR); + TouchObjects[t].groupFlags = parseStringToInt(settingsArray[i], TOUCH_OBJECT_GROUPFLAGS, TOUCH_SETTINGS_SEPARATOR); + + const uint8_t g = get8BitFromUL(TouchObjects[t].flags, TOUCH_OBJECT_FLAG_GROUP); + + if (!validButtonGroup(g)) { + _buttonGroups.insert(g); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + TouchObjects[t].SurfaceAreas = 0u; // Reset runtime stuff + TouchObjects[t].TouchTimers = 0u; + TouchObjects[t].TouchStates = 0; + + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + + // Check if a slider/gauge with range not including 0 is used, then set starting value closest to 0 + if (bitRead(TouchObjects[t].flags, TOUCH_OBJECT_FLAG_SLIDER) && !TouchObjects[t].captionOff.isEmpty()) { + int16_t _value = 0; + int16_t lowRange = 0; + int16_t highRange = 100; + + if (parseRangeToInt16(TouchObjects[t].captionOff, lowRange, highRange)) { + if (lowRange > highRange) { + if (_value < highRange) { + _value = highRange; + } else if (_value > lowRange) { + _value = lowRange; + } + } else { + if (_value < lowRange) { + _value = lowRange; + } else if (_value > highRange) { + _value = highRange; + } + } + TouchObjects[t].TouchStates = _value; + } + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + + t++; + + settingsArray[i].clear(); // Free a little memory + } + } + } +} + +/** + * init + */ +void ESPEasy_TouchHandler::init(struct EventStruct *event) { + if (!_settingsLoaded) { + loadTouchObjects(event); + _settingsLoaded = true; + } + + # if TOUCH_FEATURE_EXTENDED_TOUCH + _touchIgnored = bitRead(Touch_Settings.flags, TOUCH_FLAGS_IGNORE_TOUCH); + + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME) && + bitRead(Touch_Settings.flags, TOUCH_FLAGS_INIT_OBJECTEVENT)) { + if (_buttonGroups.size() > 1) { // Multiple groups? + displayButtonGroup(event, _buttonGroup, -3); // Clear all displayed groups + } + _buttonGroup = get8BitFromUL(Touch_Settings.flags, TOUCH_FLAGS_INITIAL_GROUP); + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("TOUCH DEBUG group: %d, max group: %d"), _buttonGroup, *_buttonGroups.crbegin())); + } + # endif // ifdef TOUCH_DEBUG + + displayButtonGroup(event, _buttonGroup); // Initialize selected group and group 0 + + # ifdef TOUCH_DEBUG + addLog(LOG_LEVEL_INFO, F("TOUCH DEBUG group done.")); + # endif // ifdef TOUCH_DEBUG + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH +} + +/** + * helper function: use parseString() to read an argument, and convert that to an int value + */ +int ESPEasy_TouchHandler::parseStringToInt(const String & string, + const uint8_t& indexFind, + const char & separator, + const int & defaultValue) { + const String parsed = parseStringKeepCase(string, indexFind, separator); + int32_t result = defaultValue; + + validIntFromString(parsed, result); + + return result; +} + +/** + * Determine if calibration is enabled and usable. + */ +bool ESPEasy_TouchHandler::isCalibrationActive() { + return _useCalibration + && (Touch_Settings.top_left.x != 0 || + Touch_Settings.top_left.y != 0 || + Touch_Settings.bottom_right.x > Touch_Settings.top_left.x || + Touch_Settings.bottom_right.y > Touch_Settings.top_left.y); // Enabled and any value != 0 => Active +} + +/** + * Check within the list of defined objects if we touched one of them. + * Must be in the current button group or in button group 0. + * The smallest matching surface is selected if multiple objects overlap. + * Returns state, sets selectedObjectName to the best matching object name + * and selectedObjectIndex to the index into the TouchObjects vector. + */ +bool ESPEasy_TouchHandler::isValidAndTouchedTouchObject(const int16_t& x, + const int16_t& y, + String & selectedObjectName, + int8_t & selectedObjectIndex) { + uint32_t lastObjectArea = 0u; + bool selected = false; + const uint16_t _x = static_cast(x); + const uint16_t _y = static_cast(y); + + for (size_t objectNr = 0; objectNr < TouchObjects.size(); ++objectNr) { + const uint8_t group = get8BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_GROUP); + + if (!TouchObjects[objectNr].objectName.isEmpty() + && bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED) + && (TouchObjects[objectNr].width_height.x != 0) + && (TouchObjects[objectNr].width_height.y != 0) // Not initial could be valid + && ((group == 0) || (group == _buttonGroup))) { // Group 0 is always active + if (TouchObjects[objectNr].SurfaceAreas == 0) { // Need to calculate the surface area + TouchObjects[objectNr].SurfaceAreas = TouchObjects[objectNr].width_height.x * TouchObjects[objectNr].width_height.y; + } + + if ((TouchObjects[objectNr].top_left.x <= _x) + && (TouchObjects[objectNr].top_left.y <= _y) + && ((TouchObjects[objectNr].width_height.x + TouchObjects[objectNr].top_left.x) >= _x) + && ((TouchObjects[objectNr].width_height.y + TouchObjects[objectNr].top_left.y) >= _y) + && ((lastObjectArea == 0) || + (TouchObjects[objectNr].SurfaceAreas < lastObjectArea))) { // Select smallest area that fits the coordinates + selectedObjectName = TouchObjects[objectNr].objectName; + selectedObjectIndex = objectNr; + lastObjectArea = TouchObjects[objectNr].SurfaceAreas; + selected = true; + } + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, + strformat(F("TOUCH DEBUG Touched: obj: %s,%d,%d,%d,%d surface:%d x,y:%d,%d sel:%s/%d/%c"), + TouchObjects[objectNr].objectName.c_str(), + TouchObjects[objectNr].top_left.x, + TouchObjects[objectNr].top_left.y, + TouchObjects[objectNr].width_height.x, + TouchObjects[objectNr].width_height.y, + TouchObjects[objectNr].SurfaceAreas, + x, + y, + selectedObjectName.c_str(), + selectedObjectIndex, + selected ? 'T' : 'f')); + } + # endif // ifdef TOUCH_DEBUG + } + } + return selected; +} + +/** + * Get either the int value or index of the objectName provided, optionally a button object + */ +int8_t ESPEasy_TouchHandler::getTouchObjectIndex(struct EventStruct *event, + const String & touchObject, + const bool & isButton) { + if (touchObject.isEmpty()) { return -1; } + + int32_t index = -1; + int32_t idx = -1; + + if ((idx = touchObject.indexOf('.')) > -1) { + String part = touchObject.substring(0, idx); + + # if TOUCH_FEATURE_EXTENDED_TOUCH + + int32_t grp = -1; + + if (validIntFromString(part, grp) && validButtonGroup(static_cast(grp), false)) { + part = touchObject.substring(idx + 1); + int32_t btn = -1; + + if (validIntFromString(part, btn)) { + idx = 0; + + for (size_t objectNr = 0; objectNr < TouchObjects.size(); ++objectNr) { + if (!TouchObjects[objectNr].objectName.isEmpty() + && (get8BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_GROUP) == static_cast(grp)) + && (!isButton || bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTON))) { + idx++; + + if (idx == btn) { + return static_cast(objectNr); + } + } + } + } else { + return -1; // Invalid button name + } + } else { + return -1; // Invalid group number + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + + // ATTENTION: Any externally provided objectNumber is 1-based, result is 0-based + if (validIntFromString(touchObject, index) && + (index > 0) && + (index <= static_cast(TouchObjects.size()))) { + return static_cast(index - 1); + } + + for (size_t objectNr = 0; objectNr < TouchObjects.size(); ++objectNr) { + if (!TouchObjects[objectNr].objectName.isEmpty() + && touchObject.equalsIgnoreCase(TouchObjects[objectNr].objectName) + && (!isButton || bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTON))) { + return static_cast(objectNr); + } + } + return -1; +} + +/** + * Set the enabled/disabled state of an object. Will redraw if a button object. + */ +bool ESPEasy_TouchHandler::setTouchObjectState(struct EventStruct *event, + const String & touchObject, + const bool & state) { + if (touchObject.isEmpty()) { return false; } + bool success = false; + + const int8_t objectNr = getTouchObjectIndex(event, touchObject); + + if (objectNr > -1) { + success = true; // Succes if matched object + + if (state != bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED)) { + bitWrite(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED, state); // Store in settings, no save + + // Event when enabling/disabling + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME) && + bitRead(Touch_Settings.flags, TOUCH_FLAGS_INIT_OBJECTEVENT)) { + generateObjectEvent(event, objectNr, + bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_SLIDER) ? + TouchObjects[objectNr].TouchStates : + (TouchObjects[objectNr].TouchStates > 0 ? 1 : 0), + state ? -1 : -2); // Redraw only, no activation + } + } + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log.reserve(72); + log = strformat(F("TOUCH setTouchObjectState: obj: %s/%d"), + touchObject.c_str(), objectNr); + + if (success) { + log += F(", new state: "); + log += state ? F("en") : F("dis"); + log += F("abled."); + } else { + log += F(" failed!"); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifdef TOUCH_DEBUG + } + + return success; +} + +/** + * Get the enabled/disabled state of an object. + */ +int8_t ESPEasy_TouchHandler::getTouchObjectState(struct EventStruct *event, + const String & touchObject) { + if (touchObject.isEmpty()) { return false; } + int8_t result = -1; + const int8_t objectNr = getTouchObjectIndex(event, touchObject); + + if (objectNr > -1) { + result = bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED) ? 1 : 0; + } + return result; +} + +/** + * Set the on/off state of an enabled touch-button object. Will generate an event if so configured. + */ +bool ESPEasy_TouchHandler::setTouchButtonOnOff(struct EventStruct *event, + const String & touchObject, + const bool & state) { + if (touchObject.isEmpty()) { return false; } + bool success = false; + const int8_t objectNr = getTouchObjectIndex(event, touchObject, true); + + if ((objectNr > -1) + && bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED) + && bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTON)) { + success = true; // Always success if matched button + + if (state != TouchObjects[objectNr].TouchStates) { + TouchObjects[objectNr].TouchStates = state; + + // Send event like it was pressed + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME) && + bitRead(Touch_Settings.flags, TOUCH_FLAGS_INIT_OBJECTEVENT)) { + generateObjectEvent(event, objectNr, state ? 1 : 0); + } + } + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log.reserve(72); + log = strformat(F("TOUCH setTouchButtonOnOff: obj: %s/%d, new state: "), + touchObject.c_str(), objectNr); + log += state ? F("on") : F("off"); + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifdef TOUCH_DEBUG + } + return success; +} + +/** + * Get the on/off state of an enabled touch-button object. + */ +int16_t ESPEasy_TouchHandler::getTouchObjectValue(struct EventStruct *event, + const String & touchObject) { + if (touchObject.isEmpty()) { return -1; } + int16_t result = -1; // invalid object + const int8_t objectNr = getTouchObjectIndex(event, touchObject); + + if ((objectNr > -1) + && bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED)) { + if (bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTON)) { + result = TouchObjects[objectNr].TouchStates > 0 && + !bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_INVERTED) ? 1 : 0; + } else { + result = TouchObjects[objectNr].TouchStates; + } + } + return result; +} + +/** + * Set the value of any enabled touch-object. Will generate an event if so configured. + */ +bool ESPEasy_TouchHandler::setTouchObjectValue(struct EventStruct *event, + const String & touchObject, + const int16_t & value) { + if (touchObject.isEmpty()) { return false; } + bool success = false; + const int8_t objectNr = getTouchObjectIndex(event, touchObject, false); + int16_t _value = value; + + if ((objectNr > -1) + && bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED)) { + success = true; // Always success if matched object + + if (_value != TouchObjects[objectNr].TouchStates) { + if (bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + int16_t lowRange = 0; + int16_t highRange = 100; + + if (!TouchObjects[objectNr].captionOff.isEmpty()) { // Off caption can hold range: , + parseRangeToInt16(TouchObjects[objectNr].captionOff, lowRange, highRange); + + if (lowRange > highRange) { + if (_value < highRange) { + _value = highRange; + } else if (_value > lowRange) { + _value = lowRange; + } + } else { + if (_value < lowRange) { + _value = lowRange; + } else if (_value > highRange) { + _value = highRange; + } + } + } + } + TouchObjects[objectNr].TouchStates = _value; + + // Send event like it was pressed + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME) && + bitRead(Touch_Settings.flags, TOUCH_FLAGS_INIT_OBJECTEVENT)) { + generateObjectEvent(event, objectNr, _value); + } + } + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("TOUCH setTouchObjectValue: obj: %s/%d, new value: %d"), + touchObject.c_str(), objectNr, _value)); + } + # endif // ifdef TOUCH_DEBUG + } + return success; +} + +/** + * parseRangeToInt16: get the low and high values of a range and convert to int16_t + */ +bool ESPEasy_TouchHandler::parseRangeToInt16(const String& range, + int16_t & lowRange, + int16_t & highRange) { + float rangeFrom = 0.0f; + float rangeTo = 0.0f; + String tmp = parseString(range, 1); + const bool validFrom = validFloatFromString(tmp, rangeFrom); + + tmp = parseString(range, 2); + + if (validFrom && validFloatFromString(tmp, rangeTo) && + !essentiallyZero(rangeFrom) && !essentiallyZero(rangeTo)) { + lowRange = static_cast(rangeFrom); + highRange = static_cast(rangeTo); + return true; + } + return false; +} + +/** + * mode: -2 = clear buttons in group, -3 = clear all buttongroups, -1 = draw buttons in group, 0 = initialize buttons + */ +# if TOUCH_FEATURE_EXTENDED_TOUCH +void ESPEasy_TouchHandler::displayButtonGroup(struct EventStruct *event, + const int16_t & buttonGroup, + const int8_t & mode) { + for (int8_t objectNr = 0; objectNr < static_cast(TouchObjects.size()); ++objectNr) { + displayButton(event, objectNr, buttonGroup, mode); + + delay(0); + } + + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME)) { + // Send an event #Group,, with the selected group and the mode (-3..0) + eventQueue.add(strformat(F("%s#Group=%d,%d"), + getTaskDeviceName(event->TaskIndex).c_str(), + buttonGroup, + mode)); + } + + delay(0); +} + +/** + * Display a single button, using mode from displayButtonGroup + */ +bool ESPEasy_TouchHandler::displayButton(struct EventStruct *event, + const int8_t & buttonNr, + const int16_t & buttonGroup, + int8_t mode) { + if ((buttonNr < 0) || (buttonNr >= static_cast(TouchObjects.size()))) { return false; } // sanity check + int8_t state = 99; + const int16_t group = get8BitFromUL(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_GROUP); + + # if TOUCH_FEATURE_EXTENDED_TOUCH + Touch_action_e action = static_cast(get4BitFromUL(TouchObjects[buttonNr].groupFlags, TOUCH_OBJECT_GROUP_ACTION)); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + bool isArrow = false; + + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if ((mode > -2) && // Not on clear (-2 and -3) + bitRead(Touch_Settings.flags, TOUCH_FLAGS_AUTO_PAGE_ARROWS) && + ((action == Touch_action_e::DecrementGroup) || // Arrow buttons + (action == Touch_action_e::IncrementGroup) || + (action == Touch_action_e::DecrementPage) || + (action == Touch_action_e::IncrementPage))) { + isArrow = true; + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + if (!TouchObjects[buttonNr].objectName.isEmpty() && + ((bitRead(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED) && (group == 0)) || (group > 0) || isArrow) && + (bitRead(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_BUTTON) || + bitRead(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_SLIDER)) && + (((group == buttonGroup) || (buttonGroup < 0)) || + ((mode != -2) && (group == 0)) || + (mode == -3))) { + // Act like a button, 1 = On, 0 = Off, inversion is handled in generateObjectEvent() + state = TouchObjects[buttonNr].TouchStates; + + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (isArrow) { // Auto-Enable/Disable the arrow buttons + const bool pgupInvert = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + state = 1; // always get ON state! + + if (action == Touch_action_e::DecrementGroup) { // Left arrow + bitWrite(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED, validButtonGroup(buttonGroup - (pgupInvert ? -1 : 1), true)); + } else + if (action == Touch_action_e::IncrementGroup) { // Right arrow + bitWrite(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED, validButtonGroup(buttonGroup + (pgupInvert ? -1 : 1), true)); + } else + if (action == Touch_action_e::DecrementPage) { // Down arrow or Up arrow + bitWrite(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED, validButtonGroup(buttonGroup + (pgupInvert ? 10 : -10), true)); + } else + if (action == Touch_action_e::IncrementPage) { // Up arrow or Down arrow + bitWrite(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED, validButtonGroup(buttonGroup + (pgupInvert ? -10 : 10), true)); + } + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + if (bitRead(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_ENABLED)) { + if (mode == 0) { + mode = -1; + } + } else { + state -= 2; // disabled + } + generateObjectEvent(event, buttonNr, state, mode, mode < 0, mode <= -2 ? -1 : 1); + } + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, + strformat(F("TOUCH: button init, state: %d, group: %d, mode: %d, group: %d, en: %d, object: %d"), + state, + buttonGroup, + mode, + get8BitFromUL(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_GROUP), + bitRead(TouchObjects[buttonNr].flags, TOUCH_OBJECT_FLAG_BUTTON), + buttonNr)); + } + # endif // ifdef TOUCH_DEBUG + return true; +} + +/** + * Check if this is a valid button group, 2022-08-16: default changed to IGNORE group 0! + * When ignoreZero = true will return false for group 0 if the number of groups > 1. + * When ignoreZero = false will return true for group 0 also if the number of groups > 1. + * NB: Group 0 is always available, even without button definitions! + */ +bool ESPEasy_TouchHandler::validButtonGroup(const int16_t& group, + const bool & ignoreZero /* = true*/) { + return _buttonGroups.find(group) != _buttonGroups.end() && + (!ignoreZero || group > 0 || (group == 0 && _buttonGroups.size() == 1)); +} + +# if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +/** + * set button group page via the Swipe event + */ +bool ESPEasy_TouchHandler::handleButtonSwipe(struct EventStruct *event, + const int16_t & swipeValue) { + bool success = false; + const Swipe_action_e swipe = static_cast(swipeValue); + const bool swapped = bitRead(Touch_Settings.flags, TOUCH_FLAGS_SWAP_LEFT_RIGHT); + + if (swipe == Swipe_action_e::Up) { + if (swapped) { + prevButtonPage(event); + } else { + nextButtonPage(event); + } + success = true; + } else if (swipe == Swipe_action_e::Right) { + if (swapped) { + prevButtonGroup(event); + } else { + nextButtonGroup(event); + } + success = true; + } else if (swipe == Swipe_action_e::Down) { + if (swapped) { + nextButtonPage(event); + } else { + prevButtonPage(event); + } + success = true; + } else if ((swipe == Swipe_action_e::Left)) { + if (swapped) { + nextButtonGroup(event); + } else { + prevButtonGroup(event); + } + success = true; + } + return success; +} + +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +/** + * Set the desired button group, must be a known group, previous group will be erased and new group drawn + */ +bool ESPEasy_TouchHandler::setButtonGroup(struct EventStruct *event, + const int16_t & buttonGroup) { + if (validButtonGroup(buttonGroup, false)) { // We want to be able to select group 0 + if (buttonGroup != _buttonGroup) { + displayButtonGroup(event, _buttonGroup, -2); + _buttonGroup = buttonGroup; + displayButtonGroup(event, _buttonGroup, -1); + } + return true; + } + return false; +} + +/** + * Increment button group if that group exists, if max. group > 0 then min. group = 1 + */ +bool ESPEasy_TouchHandler::nextButtonGroup(struct EventStruct *event) { + const bool pgupInvert = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + + if (validButtonGroup(_buttonGroup + (pgupInvert ? -1 : 1))) { + return setButtonGroup(event, _buttonGroup + (pgupInvert ? -1 : 1)); + } + return false; +} + +/** + * Decrement button group if that group exists, if max. group > 0 then min. group = 1 + */ +bool ESPEasy_TouchHandler::prevButtonGroup(struct EventStruct *event) { + const bool pgupInvert = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + + if (validButtonGroup(_buttonGroup - (pgupInvert ? -1 : 1))) { + return setButtonGroup(event, _buttonGroup - (pgupInvert ? -1 : 1)); + } + return false; +} + +/** + * Increment button group by page (+10), if max. group > 0 then min. group = 1 + */ +bool ESPEasy_TouchHandler::nextButtonPage(struct EventStruct *event) { + const bool pgupInvert = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + + if (validButtonGroup(_buttonGroup + (pgupInvert ? -10 : 10))) { + return setButtonGroup(event, _buttonGroup + (pgupInvert ? -10 : 10)); + } + return false; +} + +/** + * Decrement button group by page (+10), if max. group > 0 then min. group = 1 + */ +bool ESPEasy_TouchHandler::prevButtonPage(struct EventStruct *event) { + const bool pgupInvert = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + + if (validButtonGroup(_buttonGroup + (pgupInvert ? 10 : -10))) { + return setButtonGroup(event, _buttonGroup + (pgupInvert ? 10 : -10)); + } + return false; +} + +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH + +/** + * Get the PLUGIN_GET_DEVICEVTYPE, based on the user-selected setting for including Z-axis + */ +uint8_t ESPEasy_TouchHandler::get_device_valuecount(struct EventStruct *event) { + if (!_settingsLoaded) { + loadTouchObjects(event); + _settingsLoaded = true; + } + + return getValueCountFromSensorType( + bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_Z) + ? Sensor_VType::SENSOR_TYPE_TRIPLE + : Sensor_VType::SENSOR_TYPE_DUAL); +} + +/** + * Load the settings onto the webpage + */ +bool ESPEasy_TouchHandler::plugin_webform_load(struct EventStruct *event) { + if (!_settingsLoaded) { + loadTouchObjects(event); + _settingsLoaded = true; + } + + addFormSubHeader(F("Touch configuration")); + + addFormCheckBox(F("Flip rotation 180°"), F("rot_flip"), bitRead(Touch_Settings.flags, TOUCH_FLAGS_ROTATION_FLIPPED)); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Some touchscreens are mounted 180° rotated on the display.")); + # endif // ifndef LIMIT_BUILD_SIZE + + uint8_t choice3 = 0u; + + bitWrite(choice3, TOUCH_FLAGS_SEND_XY, bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_XY)); + bitWrite(choice3, TOUCH_FLAGS_SEND_Z, bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_Z)); + bitWrite(choice3, TOUCH_FLAGS_SEND_OBJECTNAME, bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME)); + { + const __FlashStringHelper *options3[] = + { F("None"), + F("X and Y"), + F("X, Y and Z"), + # if TOUCH_FEATURE_EXTENDED_TOUCH + F("Objectnames and Button groups"), + F("Objectnames, Button groups, X and Y"), + F("Objectnames, Button groups, X, Y and Z") + # else // if TOUCH_FEATURE_EXTENDED_TOUCH + F("Objectnames only"), + F("Objectnames, X and Y"), + F("Objectnames, X, Y and Z") + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + }; + const int optionValues3[] = { 0, 1, 3, 4, 5, 7 }; // Already used as a bitmap! + addFormSelector(F("Events"), F("events"), NR_ELEMENTS(optionValues3), options3, optionValues3, choice3); + + addFormCheckBox(F("Draw buttons when started"), F("initobj"), bitRead(Touch_Settings.flags, TOUCH_FLAGS_INIT_OBJECTEVENT)); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("Needs Objectnames 'Events' to be enabled.")); + # endif // ifndef LIMIT_BUILD_SIZE + } + + addFormCheckBox(F("Prevent duplicate events"), F("dedupe"), bitRead(Touch_Settings.flags, TOUCH_FLAGS_DEDUPLICATE)); + + # if TOUCH_FEATURE_EXTENDED_TOUCH + addFormCheckBox(F("Ignore touch-screen"), F("igntouch"), bitRead(Touch_Settings.flags, TOUCH_FLAGS_IGNORE_TOUCH)); + # ifndef LIMIT_BUILD_SIZE + addFormNote(F("To enable the use of touch-object display-functions only.")); + # endif // ifndef LIMIT_BUILD_SIZE + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + # ifndef LIMIT_BUILD_SIZE + + if (!Settings.UseRules) { + addFormNote(F("Tools / Advanced / Rules must be enabled for events to be fired.")); + } + # endif // ifndef LIMIT_BUILD_SIZE + + addFormSubHeader(F("Calibration")); + + { + const __FlashStringHelper *noYesOptions[2] = { F("No"), F("Yes") }; + const int noYesOptionValues[2] = { 0, 1 }; + addFormSelector(F("Calibrate to screen resolution"), + F("usecalib"), + 2, + noYesOptions, + noYesOptionValues, + Touch_Settings.calibrationEnabled ? 1 : 0, + true); + } + + if (Touch_Settings.calibrationEnabled) { + addRowLabel(F("Calibration")); + html_table(EMPTY_STRING, false); // Sub-table + html_table_header(EMPTY_STRING); + html_table_header(F("x")); + html_table_header(F("y")); + html_table_header(EMPTY_STRING); + html_table_header(F("x")); + html_table_header(F("y")); + + html_TR_TD(); + addHtml(F("Top-left")); + html_TD(); + addNumericBox(F("cal_tl_x"), + Touch_Settings.top_left.x, + 0, + 65535); + html_TD(); + addNumericBox(F("cal_tl_y"), + Touch_Settings.top_left.y, + 0, + 65535); + html_TD(); + addHtml(F("Bottom-right")); + html_TD(); + addNumericBox(F("cal_br_x"), + Touch_Settings.bottom_right.x, + 0, + 65535); + html_TD(); + addNumericBox(F("cal_br_y"), + Touch_Settings.bottom_right.y, + 0, + 65535); + + html_end_table(); + } + + addFormCheckBox(F("Enable logging for calibration"), F("logcalib"), + Touch_Settings.logEnabled); + + addFormSubHeader(F("Object settings")); + + # if TOUCH_FEATURE_EXTENDED_TOUCH + + AdaGFXHtmlColorDepthDataList(F("adagfx65kcolors"), _colorDepth); + + { + String parsed; + addRowLabel(F("Default On/Off button colors")); + html_table(F("sub"), false); // Sub-table + html_table_header(F("ON color")); + html_table_header(F("OFF color")); + html_table_header(F("Border color")); + html_table_header(F("Caption color")); + html_table_header(F("Disabled color")); + html_table_header(F("Disabled caption color")); + + html_TR_TD(); // ON color + parsed = AdaGFXcolorToString(Touch_Settings.colorOn, _colorDepth, true); + addTextBox(getPluginCustomArgName(3000), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("ON color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // OFF color + parsed = AdaGFXcolorToString(Touch_Settings.colorOff, _colorDepth, true); + addTextBox(getPluginCustomArgName(3001), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("OFF color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Border color + parsed = AdaGFXcolorToString(Touch_Settings.colorBorder, _colorDepth, true); + addTextBox(getPluginCustomArgName(3002), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Border color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Caption color + parsed = AdaGFXcolorToString(Touch_Settings.colorCaption, _colorDepth, true); + addTextBox(getPluginCustomArgName(3003), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Caption color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Disabled color + parsed = AdaGFXcolorToString(Touch_Settings.colorDisabled, _colorDepth, true); + addTextBox(getPluginCustomArgName(3004), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Disabled color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Disabled caption color + parsed = AdaGFXcolorToString(Touch_Settings.colorDisabledCaption, _colorDepth, true); + addTextBox(getPluginCustomArgName(3005), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Disabled caption color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_end_table(); + } + { + addFormNumericBox(F("Initial button group"), F("initgrp"), + get8BitFromUL(Touch_Settings.flags, TOUCH_FLAGS_INITIAL_GROUP), 0, TOUCH_MAX_BUTTON_GROUPS + # if TOUCH_FEATURE_TOOLTIPS + , F("Initial group") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + addFormCheckBox(F("Draw buttons via Rules"), F("via_rules"), + bitRead(Touch_Settings.flags, TOUCH_FLAGS_DRAWBTN_VIA_RULES)); + addFormCheckBox(F("Enable/Disable page buttons"), F("pagebtns"), + bitRead(Touch_Settings.flags, TOUCH_FLAGS_AUTO_PAGE_ARROWS)); + addFormCheckBox(F("Navigation Left/Right/Up/ Down menu reversed"), F("pageblw"), + bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU)); + addFormCheckBox(F("Swipe Left/Right/Up/Down menu reversed"), F("swipeswap"), + bitRead(Touch_Settings.flags, TOUCH_FLAGS_SWAP_LEFT_RIGHT)); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + { + addFormNumericBox(F("Debounce delay for On/Off buttons"), F("debounce"), + Touch_Settings.debounceMs, 0, 255); + addUnit(F("0..255 msec.")); + + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + addFormNumericBox(F("Minimal swipe movement"), F("swipemin"), + Touch_Settings.swipeMinimal, 1, 25); + addUnit(F("1..25px")); + + addFormNumericBox(F("Maximum swipe margin"), F("swipemax"), + Touch_Settings.swipeMargin, 5, 250); + addUnit(F("5..250px")); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } + { + addFormSubHeader(F("Touch objects")); + + { + addRowLabel(F("Object")); + + html_table(F("multirow tworow"), false); // Sub-table with alternating highlight per 2 rows + html_table_header(F(" # ")); + html_table_header(F("On")); + html_table_header(F("Objectname")); + html_table_header(F("Top-left x")); + html_table_header(F("Top-left y")); + # if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(F("Button")); + html_table_header(F("Layout")); + html_table_header(F("ON color")); + html_table_header(F("ON caption")); + html_table_header(F("Border color")); + html_table_header(F("Disab. cap. clr")); + html_table_header(F("Touch action")); + # else // if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(F("On/Off button")); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + html_TR(); // New row + html_table_header(EMPTY_STRING); + html_table_header(EMPTY_STRING); + # if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(F("Button-group")); + # else // if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(EMPTY_STRING); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(F("Width")); + html_table_header(F("Height")); + html_table_header(F("Inverted")); + # if TOUCH_FEATURE_EXTENDED_TOUCH + html_table_header(F("Font scale")); + html_table_header(F("OFF color")); + html_table_header(F("OFF caption")); + html_table_header(F("Caption color")); + html_table_header(F("Disabled clr")); + html_table_header(F("Action group")); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + # if TOUCH_FEATURE_EXTENDED_TOUCH + const __FlashStringHelper *buttonTypeOptions[] = { + toString(Button_type_e::None), + toString(Button_type_e::Square), + toString(Button_type_e::Rounded), + toString(Button_type_e::Circle), + toString(Button_type_e::ArrowLeft), + toString(Button_type_e::ArrowUp), + toString(Button_type_e::ArrowRight), + toString(Button_type_e::ArrowDown), + }; + + const int buttonTypeValues[] = { + static_cast(Button_type_e::None), + static_cast(Button_type_e::Square), + static_cast(Button_type_e::Rounded), + static_cast(Button_type_e::Circle), + static_cast(Button_type_e::ArrowLeft), + static_cast(Button_type_e::ArrowUp), + static_cast(Button_type_e::ArrowRight), + static_cast(Button_type_e::ArrowDown), + }; + + const __FlashStringHelper *buttonLayoutOptions[] = { + toString(Button_layout_e::CenterAligned), + toString(Button_layout_e::LeftAligned), + toString(Button_layout_e::TopAligned), + toString(Button_layout_e::RightAligned), + toString(Button_layout_e::BottomAligned), + toString(Button_layout_e::LeftTopAligned), + toString(Button_layout_e::RightTopAligned), + toString(Button_layout_e::LeftBottomAligned), + toString(Button_layout_e::RightBottomAligned), + toString(Button_layout_e::NoCaption), + # if ADAGFX_ENABLE_BMP_DISPLAY + toString(Button_layout_e::Bitmap), + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_SLIDER + toString(Button_layout_e::Slider), + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + }; + + const int buttonLayoutValues[] = { + static_cast(Button_layout_e::CenterAligned), + static_cast(Button_layout_e::LeftAligned), + static_cast(Button_layout_e::TopAligned), + static_cast(Button_layout_e::RightAligned), + static_cast(Button_layout_e::BottomAligned), + static_cast(Button_layout_e::LeftTopAligned), + static_cast(Button_layout_e::RightTopAligned), + static_cast(Button_layout_e::LeftBottomAligned), + static_cast(Button_layout_e::RightBottomAligned), + static_cast(Button_layout_e::NoCaption), + # if ADAGFX_ENABLE_BMP_DISPLAY + static_cast(Button_layout_e::Bitmap), + # endif // if ADAGFX_ENABLE_BMP_DISPLAY + # if ADAGFX_ENABLE_BUTTON_SLIDER + static_cast(Button_layout_e::Slider), + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + }; + + const __FlashStringHelper *touchActionOptions[] = { + toString(Touch_action_e::Default), + toString(Touch_action_e::ActivateGroup), + toString(Touch_action_e::IncrementGroup), + toString(Touch_action_e::DecrementGroup), + toString(Touch_action_e::IncrementPage), + toString(Touch_action_e::DecrementPage), + }; + + const int touchActionValues[] = { + static_cast(Touch_action_e::Default), + static_cast(Touch_action_e::ActivateGroup), + static_cast(Touch_action_e::IncrementGroup), + static_cast(Touch_action_e::DecrementGroup), + static_cast(Touch_action_e::IncrementPage), + static_cast(Touch_action_e::DecrementPage), + }; + + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + const uint8_t maxIdx = std::min(static_cast(TouchObjects.size() + TOUCH_EXTRA_OBJECT_COUNT), TOUCH_MAX_OBJECT_COUNT); + String parsed; + TouchObjects.resize(maxIdx, tTouchObjects()); + + for (int8_t objectNr = 0; objectNr < maxIdx; ++objectNr) { + html_TR_TD(); + addHtml(F(" ")); + addHtmlInt(objectNr + 1); // Arrayindex to objectindex + + html_TD(); + + // Enable new entries + bool enabled = bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_ENABLED) || TouchObjects[objectNr].objectName.isEmpty(); + addCheckBox(getPluginCustomArgName(objectNr + 0), + enabled, false + # if TOUCH_FEATURE_TOOLTIPS + , F("Enabled") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // Name + addTextBox(getPluginCustomArgName(objectNr + 100), + TouchObjects[objectNr].objectName, + TOUCH_MaxObjectNameLength, + false, false, EMPTY_STRING, F("wide")); + html_TD(); // top-x + addNumericBox(getPluginCustomArgName(objectNr + 200), + TouchObjects[objectNr].top_left.x, 0, 65535 + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Top-left x") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // top-y + addNumericBox(getPluginCustomArgName(objectNr + 300), + TouchObjects[objectNr].top_left.y, 0, 65535 + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Top-left y") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // (on/off) button (type) + # if TOUCH_FEATURE_EXTENDED_TOUCH + addSelector(getPluginCustomArgName(objectNr + 800), + sizeof(buttonTypeValues) / sizeof(int), + buttonTypeOptions, + buttonTypeValues, + nullptr, + get4BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTONTYPE), false, true, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Button") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // button alignment + addSelector(getPluginCustomArgName(objectNr + 900), + sizeof(buttonLayoutValues) / sizeof(int), + buttonLayoutOptions, + buttonLayoutValues, + nullptr, + get4BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTONALIGN) << 4, false, true, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Layout") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + # else // if TOUCH_FEATURE_EXTENDED_TOUCH + addCheckBox(getPluginCustomArgName(objectNr + 600), + bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_BUTTON), false + # if TOUCH_FEATURE_TOOLTIPS + , F("On/Off button") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + # if TOUCH_FEATURE_EXTENDED_TOUCH + html_TD(); // ON color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorOn, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1000), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("ON color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // ON Caption + parsed = TouchObjects[objectNr].captionOn; + parsed.replace('_', ' '); + addTextBox(getPluginCustomArgName(objectNr + 1300), + parsed, + TOUCH_MaxCaptionNameLength, + false, + false, + EMPTY_STRING, + F("xwide") + # if TOUCH_FEATURE_TOOLTIPS + , F("ON caption") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // Border color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorBorder, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1700), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Border color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Disabled caption color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorDisabledCaption, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1900), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Disabled caption color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // button action + addSelector(getPluginCustomArgName(objectNr + 2000), + sizeof(touchActionValues) / sizeof(int), + touchActionOptions, + touchActionValues, + nullptr, + get4BitFromUL(TouchObjects[objectNr].groupFlags, TOUCH_OBJECT_GROUP_ACTION), + false, + true, + F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Touch action") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + html_TR_TD(); // Start new row + + html_TD(2); // Start with some blank columns + # if TOUCH_FEATURE_EXTENDED_TOUCH + { + addNumericBox(getPluginCustomArgName(objectNr + 1600), + get8BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_GROUP), 0, TOUCH_MAX_BUTTON_GROUPS + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), strformat(F("Button-group [0..%d]"), TOUCH_MAX_BUTTON_GROUPS) + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + html_TD(); // Width + addNumericBox(getPluginCustomArgName(objectNr + 400), + TouchObjects[objectNr].width_height.x, 0, 65535 + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Width") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // Height + addNumericBox(getPluginCustomArgName(objectNr + 500), + TouchObjects[objectNr].width_height.y, 0, 65535 + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Height") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // inverted + addCheckBox(getPluginCustomArgName(objectNr + 700), + bitRead(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_INVERTED), false + # if TOUCH_FEATURE_TOOLTIPS + , F("Inverted") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + # if TOUCH_FEATURE_EXTENDED_TOUCH + html_TD(); // font scale + addNumericBox(getPluginCustomArgName(objectNr + 1200), + get4BitFromUL(TouchObjects[objectNr].flags, TOUCH_OBJECT_FLAG_FONTSCALE), 0, 10 + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Font scaling [1x..10x]") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // OFF color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorOff, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1100), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("OFF color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // OFF Caption + parsed = TouchObjects[objectNr].captionOff; + parsed.replace('_', ' '); + addTextBox(getPluginCustomArgName(objectNr + 1400), + parsed, + TOUCH_MaxCaptionNameLength, + false, + false, + EMPTY_STRING, + F("xwide") + # if TOUCH_FEATURE_TOOLTIPS + , F("OFF caption") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + html_TD(); // Caption color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorCaption, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1500), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Caption color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Disabled color + parsed = AdaGFXcolorToString(TouchObjects[objectNr].colorDisabled, _colorDepth, true); + addTextBox(getPluginCustomArgName(objectNr + 1800), parsed, TOUCH_MAX_COLOR_INPUTLENGTH, false, false, + EMPTY_STRING, F("widenumber") + # if TOUCH_FEATURE_TOOLTIPS + , F("Disabled color") + # endif // if TOUCH_FEATURE_TOOLTIPS + , F("adagfx65kcolors") + ); + html_TD(); // Action Group + addNumericBox(getPluginCustomArgName(objectNr + 2100), + get8BitFromUL(TouchObjects[objectNr].groupFlags, TOUCH_OBJECT_GROUP_ACTIONGROUP), 0, TOUCH_MAX_BUTTON_GROUPS + # if TOUCH_FEATURE_TOOLTIPS + , F("widenumber"), F("Action group") + # endif // if TOUCH_FEATURE_TOOLTIPS + ); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + html_end_table(); + } + return false; +} + +/** + * Save the settings from the web page to flash + */ +bool ESPEasy_TouchHandler::plugin_webform_save(struct EventStruct *event) { + # ifdef TOUCH_DEBUG + addLog(LOG_LEVEL_INFO, F("TOUCH DEBUG webform_save start.")); + # endif // ifdef TOUCH_DEBUG + String config; + + uint16_t saveSize = 0; + + # if TOUCH_FEATURE_EXTENDED_TOUCH + String colorInput; + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + config.reserve(80); + + uint32_t lSettings = 0u; + const int eventsValue = getFormItemInt(F("events")); + + bitWrite(lSettings, TOUCH_FLAGS_SEND_XY, bitRead(eventsValue, TOUCH_FLAGS_SEND_XY)); + bitWrite(lSettings, TOUCH_FLAGS_SEND_Z, bitRead(eventsValue, TOUCH_FLAGS_SEND_Z)); + bitWrite(lSettings, TOUCH_FLAGS_SEND_OBJECTNAME, bitRead(eventsValue, TOUCH_FLAGS_SEND_OBJECTNAME)); + bitWrite(lSettings, TOUCH_FLAGS_ROTATION_FLIPPED, isFormItemChecked(F("rot_flip"))); + bitWrite(lSettings, TOUCH_FLAGS_DEDUPLICATE, isFormItemChecked(F("dedupe"))); + bitWrite(lSettings, TOUCH_FLAGS_INIT_OBJECTEVENT, isFormItemChecked(F("initobj"))); + # if TOUCH_FEATURE_EXTENDED_TOUCH + set8BitToUL(lSettings, TOUCH_FLAGS_INITIAL_GROUP, getFormItemInt(F("initgrp"))); // Button group + bitWrite(lSettings, TOUCH_FLAGS_DRAWBTN_VIA_RULES, isFormItemChecked(F("via_rules"))); + bitWrite(lSettings, TOUCH_FLAGS_AUTO_PAGE_ARROWS, isFormItemChecked(F("pagebtns"))); + bitWrite(lSettings, TOUCH_FLAGS_PGUP_BELOW_MENU, isFormItemChecked(F("pageblw"))); + bitWrite(lSettings, TOUCH_FLAGS_SWAP_LEFT_RIGHT, isFormItemChecked(F("swipeswap"))); + bitWrite(lSettings, TOUCH_FLAGS_IGNORE_TOUCH, isFormItemChecked(F("igntouch"))); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + config += getFormItemInt(F("usecalib")); // First value should NEVER be empty, or parseString() wil get confused + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(isFormItemChecked(F("logcalib")) ? 1 : 0); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("cal_tl_x"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("cal_tl_y"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("cal_br_x"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("cal_br_y"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("debounce"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += ull2String(lSettings); + # if TOUCH_FEATURE_EXTENDED_TOUCH + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3000)); // Default Color ON + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3001)); // Default Color OFF + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, false)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3002)); // Default Color Border + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, false)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3003)); // Default Color caption + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, false)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3004)); // Default Disabled Color + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(3005)); // Default Disabled Caption Color + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, false)); + # if TOUCH_FEATURE_SWIPE + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("swipemin"))); + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemInt(F("swipemax"))); + # endif // if TOUCH_FEATURE_SWIPE + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + settingsArray[TOUCH_CALIBRATION_START] = config; + saveSize += config.length() + 1; + + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + config.replace(TOUCH_SETTINGS_SEPARATOR, ','); + addLogMove(LOG_LEVEL_INFO, concat(F("Save settings: "), config)); + } + # endif // ifdef TOUCH_DEBUG + + String error; + + for (int8_t objectNr = 0; objectNr < TOUCH_MAX_OBJECT_COUNT; ++objectNr) { + config.clear(); + config += webArg(getPluginCustomArgName(objectNr + 100)); // Name + config.trim(); // Remove leading/trailing whitespace from name + + if (!config.isEmpty()) { // Empty name => skip entry + bool numStart = (config[0] >= '0' && config[0] <= '9'); // Numeric start? + + if (!ExtraTaskSettings.checkInvalidCharInNames(config.c_str()) || + numStart) { // Check for invalid characters in objectname + error += strformat(F("Invalid character in objectname #%d. "), objectNr + 1); + error += numStart ? F("Should not start with a digit.\n") : F("Do not use ',-+/*=^%!#[]{}()' or space.\n"); + } + config += TOUCH_SETTINGS_SEPARATOR; + uint32_t flags = 0u; + bitWrite(flags, TOUCH_OBJECT_FLAG_ENABLED, isFormItemChecked(getPluginCustomArgName(objectNr + 0))); // Enabled + bitWrite(flags, TOUCH_OBJECT_FLAG_INVERTED, isFormItemChecked(getPluginCustomArgName(objectNr + 700))); // Inverted + # if TOUCH_FEATURE_EXTENDED_TOUCH + uint32_t groupFlags = 0u; + const uint8_t buttonType = getFormItemIntCustomArgName(objectNr + 800); + const uint8_t buttonLayout = getFormItemIntCustomArgName(objectNr + 900) >> 4; + set4BitToUL(flags, TOUCH_OBJECT_FLAG_BUTTONTYPE, buttonType); // Buttontype + set4BitToUL(flags, TOUCH_OBJECT_FLAG_BUTTONALIGN, buttonLayout); // Button layout + # if ADAGFX_ENABLE_BUTTON_SLIDER + const bool isSlider = (static_cast(buttonLayout << 4) == Button_layout_e::Slider); + bitWrite(flags, TOUCH_OBJECT_FLAG_SLIDER, isSlider); // Slider + # else // if ADAGFX_ENABLE_BUTTON_SLIDER + const bool isSlider = false; + # endif // if ADAGFX_ENABLE_BUTTON_SLIDER + const bool isButton = (static_cast(buttonType) != Button_type_e::None) && !isSlider; + bitWrite(flags, TOUCH_OBJECT_FLAG_BUTTON, isButton); // On/Off button + set4BitToUL(groupFlags, TOUCH_OBJECT_GROUP_ACTION, getFormItemIntCustomArgName(objectNr + 2000)); // ButtonAction + set8BitToUL(groupFlags, TOUCH_OBJECT_GROUP_ACTIONGROUP, getFormItemIntCustomArgName(objectNr + 2100)); // ActionGroup + set4BitToUL(flags, TOUCH_OBJECT_FLAG_FONTSCALE, getFormItemIntCustomArgName(objectNr + 1200)); // Font scaling + set8BitToUL(flags, TOUCH_OBJECT_FLAG_GROUP, getFormItemIntCustomArgName(objectNr + 1600)); // Button group + # else // if TOUCH_FEATURE_EXTENDED_TOUCH + bitWrite(flags, TOUCH_OBJECT_FLAG_BUTTON, isFormItemChecked(getPluginCustomArgName(objectNr + 600))); // On/Off button + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + // REMARK: Converting the code below to strformat() increases the build by 200 bytes! + config += ull2String(flags); // Flags + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemIntCustomArgName(objectNr + 200)); // Top x + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemIntCustomArgName(objectNr + 300)); // Top y + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemIntCustomArgName(objectNr + 400)); // Bottom x + config += TOUCH_SETTINGS_SEPARATOR; + config += toStringNoZero(getFormItemIntCustomArgName(objectNr + 500)); // Bottom y + + # if TOUCH_FEATURE_EXTENDED_TOUCH + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1000)); // Color ON + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1100)); // Color OFF + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1500)); // Color caption + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; // Caption ON + colorInput = webArg(getPluginCustomArgName(objectNr + 1300)); + colorInput.replace(' ', '_'); // Replace spaces by '_', often cheaper than 2 quotes... + config += wrapWithQuotesIfContainsParameterSeparatorChar(colorInput); + config += TOUCH_SETTINGS_SEPARATOR; // Caption OFF + colorInput = webArg(getPluginCustomArgName(objectNr + 1400)); + colorInput.replace(' ', '_'); // Replace spaces by '_', often cheaper than 2 quotes... + config += wrapWithQuotesIfContainsParameterSeparatorChar(colorInput); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1700)); // Color Border + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1800)); // Disabled Color + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; + colorInput = webArg(getPluginCustomArgName(objectNr + 1900)); // Disabled Caption Color + config += toStringNoZero(AdaGFXparseColor(colorInput, _colorDepth, true)); + config += TOUCH_SETTINGS_SEPARATOR; + config += ull2String(groupFlags); // Group Flags + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + config.trim(); + } + + String endZero; // Trim off and 0 from the end + endZero += TOUCH_SETTINGS_SEPARATOR; + endZero += '0'; + const uint8_t endZeroLen = endZero.length(); + + while (!config.isEmpty() && (config.endsWith(endZero) || config[config.length() - 1] == TOUCH_SETTINGS_SEPARATOR)) { + if (config[config.length() - 1] == TOUCH_SETTINGS_SEPARATOR) { + config.remove(config.length() - 1); + } else { + config.remove(config.length() - endZeroLen, endZeroLen); + } + } + + settingsArray[objectNr + TOUCH_OBJECT_INDEX_START] = config; + saveSize += config.length() + 1; + + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO) && + !config.isEmpty()) { + config.replace(TOUCH_SETTINGS_SEPARATOR, ','); + addLogMove(LOG_LEVEL_INFO, strformat(F("Save touch object #%d settings: %s"), objectNr + 1, config.c_str())); + } + # endif // ifdef TOUCH_DEBUG + } + + if (!error.isEmpty()) { + addLog(LOG_LEVEL_ERROR, error); + addHtmlError(error); + } + + error = SaveCustomTaskSettings(event->TaskIndex, settingsArray, TOUCH_ARRAY_SIZE, 0); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("TOUCH: Save settings, size: "), saveSize)); + } + + if (!error.isEmpty()) { + addLog(LOG_LEVEL_ERROR, error); + addHtmlError(error); + return false; + } + return true; +} + +/** + * Every 20 milliseconds we check if the screen is touched, + * handles button switching, swiping and slider-sliding + */ +bool ESPEasy_TouchHandler::plugin_fifty_per_second(struct EventStruct *event, + const int16_t & x, + const int16_t & y, + const int16_t & ox, + const int16_t & oy, + const int16_t & rx, + const int16_t & ry, + const int16_t & z) { + bool success = false; + + // Avoid event-storms by deduplicating coordinates, ignore z value when no z-event is generated + if (!_deduplicate || + (_deduplicate && ((TOUCH_GET_VALUE_X != x) || (TOUCH_GET_VALUE_Y != y) || + (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_Z) && (TOUCH_GET_VALUE_Z != z))))) { + success = true; + TOUCH_SET_VALUE_X(x); + TOUCH_SET_VALUE_Y(y); + TOUCH_SET_VALUE_Z(z); + } + + if (success && + Touch_Settings.logEnabled && + + // This log is REQUIRED for calibration and setting up objects, so do not make this optional! + loglevelActiveFor(LOG_LEVEL_INFO)) { + // Space before the logged values for readability. Always log the z value even if not used. + addLogMove(LOG_LEVEL_INFO, strformat(F("Touch calibration rx= %d, ry= %d; z= %d, x= %d, y= %d; ox= %d, oy= %d"), + rx, ry, z, x, y, ox, oy)); + } + + // No events to handle if rules not enabled + if (Settings.UseRules) { + if (success && bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_XY)) { // Send events for each touch + sendData(event); // Send X/Y(/Z) event + } + + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_SEND_OBJECTNAME)) { // Send events for objectname if within reach, and swipes + String selectedObjectName; + int8_t selectedObjectIndex = -1; + + if (isValidAndTouchedTouchObject(x, y, selectedObjectName, selectedObjectIndex)) { + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + int16_t delta_x = x - _last_point.x; + int16_t delta_y = y - _last_point.y; + + Swipe_action_e swipe = Swipe_action_e::None; + + if ((std::abs(delta_x) >= Touch_Settings.swipeMargin) || (std::abs(delta_x) <= Touch_Settings.swipeMinimal)) { + delta_x = 0; // Ignore + } + + if ((std::abs(delta_y) >= Touch_Settings.swipeMargin) || (std::abs(delta_y) <= Touch_Settings.swipeMinimal)) { + delta_y = 0; // Ignore + } + + if ((delta_x != 0) || (delta_y != 0)) { + _lastObjectIndex = -2; + + // Swipe, determine direction (from 12 o'clock, clock-wise) + if ((delta_x == 0) && (delta_y < 0)) { // Up + swipe = Swipe_action_e::Up; + } else if ((delta_x > 0) && (delta_y < 0)) { // Up-Right + swipe = Swipe_action_e::UpRight; + } else if ((delta_x > 0) && (delta_y == 0)) { // Right + swipe = Swipe_action_e::Right; + } else if ((delta_x > 0) && (delta_y > 0)) { // Right-Down + swipe = Swipe_action_e::RightDown; + } else if ((delta_x == 0) && (delta_y > 0)) { // Down + swipe = Swipe_action_e::Down; + } else if ((delta_x < 0) && (delta_y > 0)) { // Down-Left + swipe = Swipe_action_e::DownLeft; + } else if ((delta_x < 0) && (delta_y == 0)) { // Left + swipe = Swipe_action_e::Left; + } else if ((delta_x < 0) && (delta_y < 0)) { // Left-Up + swipe = Swipe_action_e::LeftUp; + } + + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, + strformat(F("Touch Swiped, direction: %s, dx: %d, dy: %d"), + String(toString(swipe)).c_str(), delta_x, delta_y)); + } + # endif // ifdef TOUCH_DEBUG + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + + // Not touched yet or too long ago + if ( + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + (swipe == Swipe_action_e::None) && + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + ((TouchObjects[selectedObjectIndex].TouchTimers == 0) || + (TouchObjects[selectedObjectIndex].TouchTimers < (millis() - (1.5 * Touch_Settings.debounceMs))) + )) { + // From now wait the debounce time + TouchObjects[selectedObjectIndex].TouchTimers = millis() + Touch_Settings.debounceMs; + } else { + // Debouncing time elapsed? Swiping/sliding passes through without debounce + + if ( + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + (swipe != Swipe_action_e::None) || + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + (TouchObjects[selectedObjectIndex].TouchTimers <= millis())) { + TouchObjects[selectedObjectIndex].TouchTimers = 0; + + if ( + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + (swipe == Swipe_action_e::None) && + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + (selectedObjectIndex > -1) && bitRead(TouchObjects[selectedObjectIndex].flags, TOUCH_OBJECT_FLAG_BUTTON)) { + // Button touched + _lastObjectIndex = selectedObjectIndex; // Handle on release + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } else if ((swipe != Swipe_action_e::None) && + (selectedObjectIndex > -1) && bitRead(TouchObjects[selectedObjectIndex].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + // Handle slider immediately to move/set absolute position + _lastObjectIndex = -1; // Handled + const bool isVertical = TouchObjects[selectedObjectIndex].width_height.x < TouchObjects[selectedObjectIndex].width_height.y; + int16_t position = 0; + int16_t lowRange = 0; + int16_t highRange = 100; + bool useRange = false; + + if (!TouchObjects[selectedObjectIndex].captionOff.isEmpty()) { // Off caption can hold range: , + useRange = parseRangeToInt16(TouchObjects[selectedObjectIndex].captionOff, lowRange, highRange); + } + + if (isVertical) { + position = (TouchObjects[selectedObjectIndex].top_left.y + TouchObjects[selectedObjectIndex].width_height.y) - y; + position = ceil(position / (TouchObjects[selectedObjectIndex].width_height.y / 100.0)); + } else { + position = x - TouchObjects[selectedObjectIndex].top_left.x; + position = ceil(position / (TouchObjects[selectedObjectIndex].width_height.x / 100.0)); + } + + if (useRange) { // Calculate range-boundaries + position = map(position, 0, 100, lowRange, highRange); + TouchObjects[selectedObjectIndex].TouchStates = position; + } else if (position < lowRange) { + TouchObjects[selectedObjectIndex].TouchStates = lowRange; + } else if (position > highRange) { + TouchObjects[selectedObjectIndex].TouchStates = highRange; + } else { + TouchObjects[selectedObjectIndex].TouchStates = position; + } + + // Reduce the number of events during sliding + if ((TouchObjects[selectedObjectIndex].TouchTimers == 0) || + (TouchObjects[selectedObjectIndex].TouchTimers < millis())) { + generateObjectEvent(event, selectedObjectIndex, TouchObjects[selectedObjectIndex].TouchStates); + TouchObjects[selectedObjectIndex].TouchTimers = millis() + (2 * Touch_Settings.debounceMs); + } else { + _lastObjectIndex = selectedObjectIndex; // Update on touch-release + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } else { // Generic touch event + _lastObjectIndex = -2; // Update on touch-release + _lastObjectName = selectedObjectName; + + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + # ifdef TOUCH_DEBUG + addLogMove(LOG_LEVEL_INFO, + strformat(F("Swiped/touched, object: %s:%s"), _lastObjectName.c_str(), + String(toString(swipe)).c_str())); + # endif // ifdef TOUCH_DEBUG + + if (swipe != Swipe_action_e::None) { + _lastSwipe = swipe; + } + _last_delta_x = delta_x; + _last_delta_y = delta_y; + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } + } + _last_point.x = x; // Save last touchpoint + _last_point.y = y; + _last_point_z.x = z; // Don't want to extend the struct for 1 use + } + } + } + } + + return success; +} + +/** + * Release touch + */ +void ESPEasy_TouchHandler::releaseTouch(struct EventStruct *event) { + if ((_lastObjectIndex > -1) && bitRead(TouchObjects[_lastObjectIndex].flags, TOUCH_OBJECT_FLAG_BUTTON)) { + TouchObjects[_lastObjectIndex].TouchStates = (TouchObjects[_lastObjectIndex].TouchStates > 0 ? 0 : 1); // Flip state + generateObjectEvent(event, _lastObjectIndex, TouchObjects[_lastObjectIndex].TouchStates > 0 ? 1 : 0); + _lastObjectIndex = -1; // Handle only once + } else if ((_lastObjectIndex > -1) && bitRead(TouchObjects[_lastObjectIndex].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + generateObjectEvent(event, _lastObjectIndex, TouchObjects[_lastObjectIndex].TouchStates); + TouchObjects[_lastObjectIndex].TouchTimers = 0; + _lastObjectIndex = -1; // Handle only once + } else if (_lastObjectIndex != -1) { + // Matching object is found, send # event with x, y and z as %eventvalue1/2/3% + String eventCommand; + eventCommand.reserve(48); + eventCommand = getTaskDeviceName(event->TaskIndex); + eventCommand += '#'; + + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + + if (_lastSwipe == Swipe_action_e::None) + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + { + eventCommand += strformat(F("%s=%d,%d,%d"), + _lastObjectName.c_str(), + _last_point.x, + _last_point.y, + _last_point_z.x); + } + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + else { + eventCommand += strformat(F("Swiped=%d,%d,%d"), // Add arguments + static_cast(_lastSwipe), + _last_delta_x, + _last_delta_y); + _lastSwipe = Swipe_action_e::None; + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + eventQueue.addMove(std::move(eventCommand)); + _lastObjectIndex = -1; // Handle only once + } + _stillTouching = false; +} + +/** + * Parse and execute the plugin commands + */ +bool ESPEasy_TouchHandler::plugin_write(struct EventStruct *event, + const String & string) { + bool success = false; + String command; + String subcommand; + String arguments; + uint8_t arg = 3; + + command = parseString(string, 1); + + if (equals(command, F("touch"))) { + arguments.reserve(24); + subcommand = parseString(string, 2); + # ifdef TOUCH_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, strformat(F("TOUCH PLUGIN_WRITE arguments Par1: %d, 2: %d, 3: %d, 4: %d, string: %s"), + event->Par1, event->Par2, event->Par3, event->Par4, string.c_str())); + } + # endif // ifdef TOUCH_DEBUG + + if (equals(subcommand, F("enable"))) { // touch,enable,[,...] : Enable disabled objectname(s) + arguments = parseString(string, arg); + + while (!arguments.isEmpty()) { + success |= setTouchObjectState(event, arguments, true); + arguments = parseString(string, ++arg); + } + } else if (equals(subcommand, F("disable"))) { // touch,disable,[,...] : Disable enabled objectname(s) + arguments = parseString(string, arg); + + while (!arguments.isEmpty()) { + success |= setTouchObjectState(event, arguments, false); + arguments = parseString(string, ++arg); + } + } else if (equals(subcommand, F("on"))) { // touch,on,[,...] : Switch TouchButton(s) on + arguments = parseString(string, arg); + + while (!arguments.isEmpty()) { + success |= setTouchButtonOnOff(event, arguments, true); + arguments = parseString(string, ++arg); + } + } else if (equals(subcommand, F("off"))) { // touch,off,[,...] : Switch TouchButton(s) off + arguments = parseString(string, arg); + + while (!arguments.isEmpty()) { + success |= setTouchButtonOnOff(event, arguments, false); + arguments = parseString(string, ++arg); + } + } else if (equals(subcommand, F("toggle"))) { // touch,toggle,[,...] : Switch TouchButton(s) to the other state + arguments = parseString(string, arg); + + while (!arguments.isEmpty()) { + const int16_t state = getTouchObjectValue(event, arguments); + + if (state > -1) { + success |= setTouchButtonOnOff(event, arguments, state == 0); + } + arguments = parseString(string, ++arg); + } + } else if (equals(subcommand, F("set"))) { // touch,set,, : Set TouchObject value + arguments = parseString(string, arg); + success = setTouchObjectValue(event, arguments, event->Par3); + # if TOUCH_FEATURE_EXTENDED_TOUCH + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } else if (equals(subcommand, F("swipe"))) { // touch,swipe, : Switch button group via swipe value + success = handleButtonSwipe(event, event->Par2); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + } else if (equals(subcommand, F("setgrp"))) { // touch,setgrp, : Activate button group + success = setButtonGroup(event, event->Par2); + } else if (equals(subcommand, F("nextgrp"))) { // touch,nextgrp : next group and Activate + success = nextButtonGroup(event); + } else if (equals(subcommand, F("prevgrp"))) { // touch,prevgrp : previous group and Activate + success = prevButtonGroup(event); + } else if (equals(subcommand, F("nextpage"))) { // touch,nextpage : next page and Activate + success = nextButtonPage(event); + } else if (equals(subcommand, F("prevpage"))) { // touch,prevpage : previous page and Activate + success = prevButtonPage(event); + } else if (equals(subcommand, F("updatebutton"))) { // touch,updatebutton,[,[,]] : Update a button + arguments = parseString(string, 3); + + // Check for a valid button name or number, returns a 0-based index + const int8_t index = getTouchObjectIndex(event, arguments, true); + + if (index > -1) { + const bool hasPar3 = !parseString(string, 4).isEmpty(); + const bool hasPar4 = !parseString(string, 5).isEmpty(); + + if (hasPar4) { + success = displayButton(event, index, event->Par3, event->Par4); + } else if (hasPar3) { + success = displayButton(event, index, event->Par3); + } else { + success = displayButton(event, index); // Use default argument values + } + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + } + return success; +} + +/** + * Handle getting config values from plugin/handler + */ +bool ESPEasy_TouchHandler::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + const String command = parseString(string, 1); + + if (equals(command, F("buttongroup"))) { + string = getButtonGroup(); + success = true; + # if TOUCH_FEATURE_EXTENDED_TOUCH + } else if (equals(command, F("hasgroup"))) { + int32_t group; // We'll be ignoring group 0 if there are multiple button groups + + if (validIntFromString(parseString(string, 2), group)) { + string = validButtonGroup(group, true) ? 1 : 0; + success = true; + } else { + string = '0'; // invalid number = false + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } else if (equals(command, F("enabled"))) { + const int8_t enabled = getTouchObjectState(event, parseStringKeepCase(string, 2)); + + if (enabled > -1) { + string = enabled; + success = true; + } + } else if (equals(command, F("state"))) { + const int16_t state = getTouchObjectValue(event, parseStringKeepCase(string, 2)); + + string = state; + success = true; + # if TOUCH_FEATURE_EXTENDED_TOUCH + } else if (equals(command, F("pagemode"))) { + string = bitRead(Touch_Settings.flags, TOUCH_FLAGS_PGUP_BELOW_MENU); + success = true; + # if TOUCH_FEATURE_SWIPE + } else if (equals(command, F("swipedir"))) { + int32_t state; + + if (validIntFromString(parseString(string, 2), state)) { + string = toString(static_cast(state)); + success = true; + } + # endif // if TOUCH_FEATURE_SWIPE + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + return success; +} + +/** + * generate an event for a touch object + * When a display is configured add x,y coordinate, width,height of the object, objectIndex, and TaskIndex of display + **************************************************************************/ +void ESPEasy_TouchHandler::generateObjectEvent(struct EventStruct *event, + const int8_t & objectIndex, + const int16_t & onOffState, + const int8_t & mode, + const bool & groupSwitch, + const int8_t & factor) { + if ((objectIndex < 0) || // Range check + (objectIndex >= static_cast(TouchObjects.size()))) { + return; + } + delay(0); + String eventCommand; + String extraCommand; + + eventCommand.reserve(120); + extraCommand.reserve(48); + + // Task with added arguments: (%eventvalue#%) + extraCommand += strformat(F("%s#%s="), getTaskDeviceName(event->TaskIndex).c_str(), TouchObjects[objectIndex].objectName.c_str()); + + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_DRAWBTN_VIA_RULES)) { + eventCommand = extraCommand; + } else { // Handle via direct btn commands + if (_displayTask != event->TaskIndex) { // Add arguments for display + // Internal command trigger + eventCommand += strformat(F("[%d].adagfx_trigger,btn,"), _displayTask + 1); + } else { + addLog(LOG_LEVEL_ERROR, F("TOUCH: No valid Display task selected.")); + return; + } + } + + if (bitRead(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + eventCommand += onOffState; // Slider control: pass value as state (1 = state) + extraCommand += onOffState; // duplicate + } else { + if (onOffState < 0) { // Negative value: pass on unaltered (1 = state) + eventCommand += onOffState; + extraCommand += onOffState; // duplicate + } else { // Check for inverted output (1 = state) + if (bitRead(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_INVERTED)) { + eventCommand += onOffState == 1 ? '0' : '1'; // Act like an inverted button, 0 = On, 1 = Off + extraCommand += onOffState == 1 ? '0' : '1'; // Act like an inverted button, 0 = On, 1 = Off // duplicate + } else { + eventCommand += onOffState == 1 ? '1' : '0'; // Act like a button, 1 = On, 0 = Off + extraCommand += onOffState == 1 ? '1' : '0'; // Act like a button, 1 = On, 0 = Off // duplicate + } + } + } + eventCommand += ','; + eventCommand += mode; // (2 = mode) + extraCommand += ','; + extraCommand += mode; // (2 = mode) // duplicate + + if (_displayTask != event->TaskIndex) { // Add arguments for display + eventCommand += strformat( + F(",%d,%d,%d,%d,%d,%d"), + TouchObjects[objectIndex].top_left.x, // (3 = x) + TouchObjects[objectIndex].top_left.y, // (4 = y) + TouchObjects[objectIndex].width_height.x, // (5 = width) + TouchObjects[objectIndex].width_height.y, // (6 = height) + objectIndex + 1, // Adjust to displayed index (7 = id) + // (8 = type + layout, 4+4 bit, side by side) + get8BitFromUL(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_BUTTONTYPE) * factor); + # if TOUCH_FEATURE_EXTENDED_TOUCH + eventCommand += strformat( + F(",%s,%s,%s,%d,"), + AdaGFXcolorToString(TouchObjects[objectIndex].colorOn == 0 + ? Touch_Settings.colorOn + : TouchObjects[objectIndex].colorOn, _colorDepth).c_str(), // (9 = ON color) + AdaGFXcolorToString(TouchObjects[objectIndex].colorOff == 0 + ? Touch_Settings.colorOff + : TouchObjects[objectIndex].colorOff, _colorDepth).c_str(), // (10 = OFF color) + AdaGFXcolorToString(TouchObjects[objectIndex].colorCaption == 0 + ? Touch_Settings.colorCaption + : TouchObjects[objectIndex].colorCaption, _colorDepth).c_str(), // (11 = Caption color) + get4BitFromUL(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_FONTSCALE)); // (12 = Font scaling) + + // (13 = ON caption, default=object name) + String _capt; + + if (TouchObjects[objectIndex].captionOn.isEmpty()) { + if (bitRead(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + _capt = onOffState; // Override caption if not set + } else { + _capt = TouchObjects[objectIndex].objectName; + } + } else { + _capt = TouchObjects[objectIndex].captionOn; + } + _capt.replace('_', ' '); // Replace all '_' by space + eventCommand += wrapWithQuotesIfContainsParameterSeparatorChar(_capt); + eventCommand += ','; // (14 = OFF caption) + + if (TouchObjects[objectIndex].captionOff.isEmpty()) { + if (bitRead(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_SLIDER)) { + _capt = onOffState; // override caption if not set + } else { + _capt = TouchObjects[objectIndex].objectName; + } + } else { + _capt = TouchObjects[objectIndex].captionOff; + } + _capt.replace('_', ' '); // Replace all '_' by space + eventCommand += strformat( + F("%s,%s,%s,%s"), + wrapWithQuotesIfContainsParameterSeparatorChar(_capt).c_str(), + AdaGFXcolorToString(TouchObjects[objectIndex].colorBorder == 0 + ? Touch_Settings.colorBorder + : TouchObjects[objectIndex].colorBorder, _colorDepth).c_str(), // (15 = Border color) + AdaGFXcolorToString(TouchObjects[objectIndex].colorDisabled == 0 + ? Touch_Settings.colorDisabled + : TouchObjects[objectIndex].colorDisabled, _colorDepth).c_str(), // (16 = Disabled color) + AdaGFXcolorToString(TouchObjects[objectIndex].colorDisabledCaption == 0 + ? Touch_Settings.colorDisabledCaption + : TouchObjects[objectIndex].colorDisabledCaption, _colorDepth).c_str()); // (17 = Disabled caption color) + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + eventCommand += strformat( + F(",%d,%d"), + _displayTask + 1, // What TaskIndex? (18) or (9) + get8BitFromUL(TouchObjects[objectIndex].flags, TOUCH_OBJECT_FLAG_GROUP)); // Group (19) or (10) + # if TOUCH_FEATURE_EXTENDED_TOUCH + eventCommand += ','; // Group mode (20) + const uint8_t action = get4BitFromUL(TouchObjects[objectIndex].groupFlags, TOUCH_OBJECT_GROUP_ACTION); + const Touch_action_e actGrp = static_cast(action); + + if (!groupSwitch && (Touch_action_e::Default != actGrp)) { + if (Touch_action_e::ActivateGroup == actGrp) { + eventCommand += get8BitFromUL(TouchObjects[objectIndex].groupFlags, TOUCH_OBJECT_GROUP_ACTIONGROUP); + } else { + eventCommand += (action * -1); // Default is already ignored + } + } else { + eventCommand += -1; // No group to activate + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + } + + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (bitRead(Touch_Settings.flags, TOUCH_FLAGS_DRAWBTN_VIA_RULES)) { + eventQueue.addMove(std::move(eventCommand)); + } else { + eventCommand += ','; + eventCommand += wrapWithQuotesIfContainsParameterSeparatorChar(TouchObjects[objectIndex].objectName); + ExecuteCommand_all({ EventValueSource::Enum::VALUE_SOURCE_RULES, eventCommand }, true); // Simulate like from rules + addLogMove(LOG_LEVEL_INFO, eventCommand); + delay(0); + + // Handle group actions + Touch_action_e action = static_cast(get4BitFromUL(TouchObjects[objectIndex].groupFlags, TOUCH_OBJECT_GROUP_ACTION)); + + if ((onOffState >= 0) && (mode >= 0)) { + if (action == Touch_action_e::Default) { + eventQueue.addMove(std::move(extraCommand)); // Issue the extra command for regular button presses + } else { + switch (action) { + case Touch_action_e::ActivateGroup: + setButtonGroup(event, get8BitFromUL(TouchObjects[objectIndex].groupFlags, TOUCH_OBJECT_GROUP_ACTIONGROUP)); + break; + case Touch_action_e::IncrementGroup: + nextButtonGroup(event); + break; + case Touch_action_e::DecrementGroup: + prevButtonGroup(event); + break; + case Touch_action_e::IncrementPage: + nextButtonPage(event); + break; + case Touch_action_e::DecrementPage: + prevButtonPage(event); + break; + case Touch_action_e::Default: + break; + } + addLogMove(LOG_LEVEL_INFO, concat(F("TOUCH event: "), toString(action))); + } + } + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + delay(0); +} + +#endif // ifdef PLUGIN_USES_TOUCHHANDLER diff --git a/src/src/Helpers/ESPEasy_TouchHandler.h b/src/src/Helpers/ESPEasy_TouchHandler.h new file mode 100644 index 000000000..45c1363bb --- /dev/null +++ b/src/src/Helpers/ESPEasy_TouchHandler.h @@ -0,0 +1,437 @@ +#ifndef HELPERS_ESPEASY_TOUCHHANDLER_H +#define HELPERS_ESPEASY_TOUCHHANDLER_H + +#include "../../_Plugin_Helper.h" +#include "../Helpers/AdafruitGFX_helper.h" + +#ifdef PLUGIN_USES_TOUCHHANDLER +# include + +/***** + * Changelog: + * 2024-03-20 tonhuisman: Change inc/dec* commands to next/prev* commands to more accurately describe their function + * 2024-03-13 tonhuisman: Change PageUp/PageDown reversed option to Navigation Left/Right/Up/Down menu reversed, to also swap the behavior + * of the left and right navigation buttons, like the Up/Down navigation already had. + * 2023-12-31 tonhuisman: Code optimizations reducing .bin size (ESP32) with ~1kB + * 2023-10-01 tonhuisman: Re-implement (fix) switching of X/Y/Z vs X/Y output values not by changing the DeviceVector but using + * PLUGIN_GET_DEVICEVALUECOUNT plugin function. + * 2023-08-15 tonhuisman: Implement Extended CustomTaskSettings, minor improvements + * 2022-09-26 tonhuisman: Fix issue with touch-disable option. Code optimizations, improved log/string handling + * Make Swipe feature part of Extended Touch feature + * 2022-08-27 tonhuisman: Enable identifying an object using the . notation, where groupnr = 0..255, and objectnr + * range starts at 1, in sequential order for the objects in that group + * Add option to disable polling the touch-screen, to be able to only use the object/button draw features + * f.e. when using a device like an M5Stack Core that has buttons below the screen + * 2022-08-22 tonhuisman: Improve Slider range handling so a reverse range (40,-10) can also be used. + * 2022-08-17 tonhuisman: Add support for range (x..y) for sliders + * 2022-08-16 tonhuisman: Changed validButtonGroup() to ignore group 0 by default, and setButtonGroup(), and setgrp subcommand, + * to allow group 0 + * Add setting for swapping (reversing) menu-swipe direction + * 2022-08-15 tonhuisman: Add optional swipe/slider support, add swipe subcommand, add GetConfigValue options and docs + * Replace ifdef *_USE_* defines by if *_FEATURE_* + * 2022-08-13 tonhuisman: Replace _ in object name and on/off captions by space, to ease the use of object name as caption + * On save, any spaces in captions are replaced by _ to avoid using 2 quotes around the value. + * This implies that no underscores wil be shown in captions! + * 2022-06-09 tonhuisman: Change method arguments to const-by-reference where possible for improved compile-time checks + * 2022-06-06 tonhuisman: Move PLUGIN_WRITE handling from P123 + * Move PLUGIN_GET_CONFIG_VALUE handling from P123. + * Add getters for on/off (state) and (enabled), and matching GET_CONFIG_VALUE commands + * Add toggle subcommand for switching enabled on/off buttons to the other state + * Extend on, off, toggle subcommands to support a list of objects + * 2022-06-03 tonhuisman: Change default ON color to blue (from green, too bright/bad contrast with white captions) + * Add options for auto Enable/Disable arrow buttons and invert pgup/pgdn + * Bugfix: Also apply debouncing to non-button objects + * 2022-06-02 tonhuisman: Reduce saved settings size by eliminating 0 and similar unneeded values + * Move Touch minimal touch pressure back to P123 + * 2022-05-26 tonhuisman: Expand Captions to 30 characters + * 2022-05 tonhuisman: Testing, improving, bugfixing. + * 2022-05-23 tonhuisman: Created from refactoring P123 Touch object handling into ESPEasy_TouchHandler + *********************************************************************************************************************/ + +/** + * Commands supported: + * ------------------- + * touch,enable,[,...] : Enable disabled objectname(s) + * touch,disable,[,...] : Disable enabled objectname(s) + * touch,on,[,...] : Switch TouchButton(s) on (must be enabled) + * touch,off,[,...] : Switch TouchButton(s) off (must be enabled) + * touch,set,, : Set TouchObject to value (slider) or 0=off >0=on (must be enabled) + * touch,toggle,[,...] : Switch TouchButton(s) to the other state (must be enabled) + * touch,swipe, : Switch button group according to swipe direction + * touch,setgrp, : Switch to button group + * touch,nextgrp : Switch to next button group + * touch,prevgrp : Switch to previous button group + * touch,nextpage : Switch to next button group page (+10) + * touch,prevpage : Switch to previous button group page (-10) + * touch,updatebutton,[,[,]] : Update a button by name or number + */ +/** + * Get Config Variables supported: [#{,arguments}] + * {,arguments} : Description + * buttongroup : Get current buttongroup + * hasgroup,groupNr : Check if group exists, ignores group 0 + * enabled,objectName|objectNr : Check if object is enabled + * state,objectName|objectNr : Get current object state (buttons: on = 1, off = 0, sliders: value 0..100 (=percentage) or explicit value) + * pagemode : Get the Left/Right/Up/Down menu mode, 0 = normal, 1 = reversed + * swipedir,directionId : Get the name for the swipe direction provided in numeric form + */ + +# define TOUCH_DEBUG // Additional debugging information + +# define TOUCH_FEATURE_TOOLTIPS 1 // Enable/disable tooltips in UI +# define TOUCH_FEATURE_EXTENDED_TOUCH 1 // Enable/disable extended touch settings +# define TOUCH_FEATURE_SWIPE 1 // Enable/disable Swipe support + +# ifdef LIMIT_BUILD_SIZE +# if TOUCH_FEATURE_TOOLTIPS +# undef TOUCH_FEATURE_TOOLTIPS +# define TOUCH_FEATURE_TOOLTIPS 0 +# endif // if TOUCH_FEATURE_TOOLTIPS +# if TOUCH_FEATURE_EXTENDED_TOUCH +# undef TOUCH_FEATURE_EXTENDED_TOUCH +# define TOUCH_FEATURE_EXTENDED_TOUCH 0 +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH +// # if TOUCH_FEATURE_SWIPE +// # undef TOUCH_FEATURE_SWIPE +// # define TOUCH_FEATURE_SWIPE 0 +// # endif // if TOUCH_FEATURE_SWIPE +# endif // ifdef LIMIT_BUILD_SIZE +# ifdef BUILD_NO_DEBUG +# ifdef TOUCH_DEBUG +# undef TOUCH_DEBUG +# endif // ifdef TOUCH_DEBUG +# endif // ifdef BUILD_NO_DEBUG + +# if TOUCH_FEATURE_TOOLTIPS && !FEATURE_TOOLTIPS +# undef TOUCH_FEATURE_TOOLTIPS +# define TOUCH_FEATURE_TOOLTIPS 0 +# endif // if TOUCH_FEATURE_TOOLTIPS && !FEATURE_TOOLTIPS + +// Global Settings flags +# define TOUCH_FLAGS_SEND_XY 0 // Send X and Y coordinate events +# define TOUCH_FLAGS_SEND_Z 1 // Send Z coordinate (pressure) events +# define TOUCH_FLAGS_SEND_OBJECTNAME 2 // Send onjectname events +# define TOUCH_FLAGS_USE_CALIBRATION 3 // Enable calibration entry +# define TOUCH_FLAGS_LOG_CALIBRATION 4 // Enable logging for calibration +# define TOUCH_FLAGS_ROTATION_FLIPPED 5 // Rotation flipped 180 degrees +# define TOUCH_FLAGS_DEDUPLICATE 6 // Avoid duplicate events +# define TOUCH_FLAGS_INIT_OBJECTEVENT 7 // Draw button objects when started +# define TOUCH_FLAGS_INITIAL_GROUP 8 // Initial group to activate, 8 bits +# define TOUCH_FLAGS_DRAWBTN_VIA_RULES 16 // Draw buttons using rule +# define TOUCH_FLAGS_AUTO_PAGE_ARROWS 17 // Automatically enable/disable paging buttons +# define TOUCH_FLAGS_PGUP_BELOW_MENU 18 // Group-page below current menu (reverts Left/Right/Up/Down menu buttons) +# define TOUCH_FLAGS_SWAP_LEFT_RIGHT 19 // Swaps Left and Right, Up and Down swipe directions for menu actions +# define TOUCH_FLAGS_IGNORE_TOUCH 20 // Disable touch, use for object/button features only + +# define TOUCH_GET_VALUE_X UserVar.getFloat(event->TaskIndex, 0) +# define TOUCH_GET_VALUE_Y UserVar.getFloat(event->TaskIndex, 1) +# define TOUCH_GET_VALUE_Z UserVar.getFloat(event->TaskIndex, 2) +# define TOUCH_SET_VALUE_X(N) UserVar.setFloat(event->TaskIndex, 0, N) +# define TOUCH_SET_VALUE_Y(N) UserVar.setFloat(event->TaskIndex, 1, N) +# define TOUCH_SET_VALUE_Z(N) UserVar.setFloat(event->TaskIndex, 2, N) + +# define TOUCH_TS_ROTATION 0 // Rotation 0-3 = 0/90/180/270 degrees +# define TOUCH_TS_SEND_XY true // Enable/Disable X/Y events +# define TOUCH_TS_SEND_Z false // Disable/Enable Z events +# define TOUCH_TS_SEND_OBJECTNAME true // Enable/Disable objectname events +# define TOUCH_TS_USE_CALIBRATION false // Disable/Enable calibration +# define TOUCH_TS_LOG_CALIBRATION true // Enable/Disable calibration logging +# define TOUCH_TS_ROTATION_FLIPPED false // Enable/Disable rotation flipped 180 deg. +# define TOUCH_TS_DEDUPLICATE true // Enable/Disable deduplication of events +# define TOUCH_TS_INIT_OBJECTEVENT true // Enable/Disable drawing of touch objects +# define TOUCH_TS_X_RES 320 // Pixels, should match with the screen it is mounted on +# define TOUCH_TS_Y_RES 480 +# define TOUCH_DEBOUNCE_MILLIS 50 // Debounce delay for On/Off button function +# define TOUCH_DEF_SWIPE_MINIMAL 3 // Minimal swipe pixels +# define TOUCH_DEF_SWIPE_MARGIN 10 // Default swipe margin + +# define TOUCH_MAX_COLOR_INPUTLENGTH 11 // 11 Characters is enough to type in all recognized color names and values +# define TOUCH_MaxObjectNameLength 15 // 15 character objectnames +# define TOUCH_MaxCaptionNameLength 30 // 30 character captions, to allow variable names +# define TOUCH_MAX_CALIBRATION_COUNT 1 // +# define TOUCH_MAX_OBJECT_COUNT 40 // This count of touchobjects should be enough, because of limited + // settings storage, 1024 bytes +# define TOUCH_EXTRA_OBJECT_COUNT 5 // The number of empty objects to show if max not reached +# define TOUCH_ARRAY_SIZE (TOUCH_MAX_OBJECT_COUNT + TOUCH_MAX_CALIBRATION_COUNT) + +# define TOUCH_MAX_BUTTON_GROUPS 255 // Max. allowed button groups + +# define TOUCH_SETTINGS_SEPARATOR '\x02' + +// Settings array field offsets: Calibration +# define TOUCH_CALIBRATION_START 0 // Index into settings array +# define TOUCH_CALIBRATION_ENABLED 1 // Enabled 0/1 (parseString index starts at 1) +# define TOUCH_CALIBRATION_LOG_ENABLED 2 // Calibration Log Enabled 0/1 +# define TOUCH_CALIBRATION_TOP_X 3 // Top X offset (uint16_t) +# define TOUCH_CALIBRATION_TOP_Y 4 // Top Y +# define TOUCH_CALIBRATION_BOTTOM_X 5 // Bottom X +# define TOUCH_CALIBRATION_BOTTOM_Y 6 // Bottom Y +# define TOUCH_COMMON_DEBOUNCE_MS 7 // Debounce milliseconds +# define TOUCH_COMMON_FLAGS 8 // Common flags +# if TOUCH_FEATURE_EXTENDED_TOUCH +# define TOUCH_COMMON_DEF_COLOR_ON 9 // Default Color ON (rgb565, uint16_t) +# define TOUCH_COMMON_DEF_COLOR_OFF 10 // Default Color OFF +# define TOUCH_COMMON_DEF_COLOR_BORDER 11 // Default Color Border +# define TOUCH_COMMON_DEF_COLOR_CAPTION 12 // Default Color Caption +# define TOUCH_COMMON_DEF_COLOR_DISABLED 13 // Default Disabled Color +# define TOUCH_COMMON_DEF_COLOR_DISABCAPT 14 // Default Disabled Caption Color +# define TOUCH_COMMON_SWIPE_MINIMAL 15 // Minimal swipe pixels +# define TOUCH_COMMON_SWIPE_MARGIN 16 // Swipe margin +# else // if TOUCH_FEATURE_EXTENDED_TOUCH +# define TOUCH_COMMON_SWIPE_MINIMAL 9 // Minimal swipe pixels +# define TOUCH_COMMON_SWIPE_MARGIN 10 // Swipe margin +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH + +// Settings array field offsets: Touch objects +# define TOUCH_OBJECT_INDEX_START (TOUCH_CALIBRATION_START + 1) +# define TOUCH_OBJECT_INDEX_END (TOUCH_ARRAY_SIZE - (TOUCH_CALIBRATION_START + 1)) +# define TOUCH_OBJECT_NAME 1 // Name (String 14) (parseString index starts at 1) +# define TOUCH_OBJECT_FLAGS 2 // Flags (uint32_t) +# define TOUCH_OBJECT_COORD_TOP_X 3 // Top X (uint16_t) +# define TOUCH_OBJECT_COORD_TOP_Y 4 // Top Y +# define TOUCH_OBJECT_COORD_WIDTH 5 // Width +# define TOUCH_OBJECT_COORD_HEIGHT 6 // Height +# if TOUCH_FEATURE_EXTENDED_TOUCH +# define TOUCH_OBJECT_COLOR_ON 7 // Color ON (rgb565, uint16_t) +# define TOUCH_OBJECT_COLOR_OFF 8 // Color OFF +# define TOUCH_OBJECT_COLOR_CAPTION 9 // Color Caption +# define TOUCH_OBJECT_CAPTION_ON 10 // Caption ON (String 12, quoted) +# define TOUCH_OBJECT_CAPTION_OFF 11 // Caption OFF (String 12, quoted) +# define TOUCH_OBJECT_COLOR_BORDER 12 // Color Border +# define TOUCH_OBJECT_COLOR_DISABLED 13 // Disabled Color +# define TOUCH_OBJECT_COLOR_DISABCAPT 14 // Disabled Caption Color +# define TOUCH_OBJECT_GROUPFLAGS 15 // Group flags +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH + +# define TOUCH_OBJECT_FLAG_ENABLED 0 // Enabled +# define TOUCH_OBJECT_FLAG_BUTTON 1 // Button behavior +# define TOUCH_OBJECT_FLAG_INVERTED 2 // Inverted button +# define TOUCH_OBJECT_FLAG_FONTSCALE 3 // 4 bits used as button alignment +# define TOUCH_OBJECT_FLAG_BUTTONTYPE 7 // 4 bits used as button type (low 4 bits) +# define TOUCH_OBJECT_FLAG_BUTTONALIGN 11 // 4 bits used as button caption layout (high 4 bits) +# define TOUCH_OBJECT_FLAG_GROUP 16 // 8 bits used as button group +# define TOUCH_OBJECT_FLAG_SLIDER 24 // Slider object + +# define TOUCH_OBJECT_GROUP_ACTIONGROUP 8 // 8 bits used as action group +# define TOUCH_OBJECT_GROUP_ACTION 16 // 4 bits used as action option + +# define TOUCH_DEFAULT_COLOR_ON ADAGFX_BLUE +# define TOUCH_DEFAULT_COLOR_OFF ADAGFX_RED +# define TOUCH_DEFAULT_COLOR_CAPTION ADAGFX_WHITE +# define TOUCH_DEFAULT_COLOR_BORDER ADAGFX_WHITE +# define TOUCH_DEFAULT_COLOR_DISABLED 0x9410 +# define TOUCH_DEFAULT_COLOR_DISABLED_CAPTION 0x5A69 + +// Lets Touchne our own coordinate point +struct tTouch_Point +{ + uint16_t x = 0u; + uint16_t y = 0u; +}; + +// For touch objects we store a name, 2 coordinates, flags and other options +struct tTouchObjects +{ + uint32_t flags = 0u; + uint32_t SurfaceAreas = 0u; + uint32_t TouchTimers = 0u; + tTouch_Point top_left; + tTouch_Point width_height; + int16_t TouchStates = 0; + # if TOUCH_FEATURE_EXTENDED_TOUCH + uint32_t groupFlags = 0u; + uint16_t colorOn = 0u; + uint16_t colorOff = 0u; + uint16_t colorCaption = 0u; + uint16_t colorBorder = 0u; + uint16_t colorDisabled = 0u; + uint16_t colorDisabledCaption = 0u; + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + String objectName; + String captionOn; + String captionOff; +}; + +// Touch actions, max 16! +enum class Touch_action_e : uint8_t { + Default = 0u, + ActivateGroup = 1u, + IncrementGroup = 2u, + DecrementGroup = 3u, + IncrementPage = 4u, + DecrementPage = 5u, +}; + +# if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +// Swipe actions, start at 12 o'çlock, clock-wise +enum class Swipe_action_e : uint8_t { + None = 0u, + Up = 1u, + UpRight = 2u, + Right = 3u, + RightDown = 4u, + Down = 5u, + DownLeft = 6u, + Left = 7u, + LeftUp = 8u, + SwipeAction_MAX = 9u // Last item is count +}; +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +const __FlashStringHelper* toString(Touch_action_e action); + +# if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE +const __FlashStringHelper* toString(Swipe_action_e action); +# endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + +class ESPEasy_TouchHandler { +public: + + ESPEasy_TouchHandler(); + ESPEasy_TouchHandler(const taskIndex_t & displayTask, + const AdaGFXColorDepth& colorDepth); + virtual ~ESPEasy_TouchHandler(); + + void loadTouchObjects(struct EventStruct *event); + void init(struct EventStruct *event); + bool isCalibrationActive(); + bool isValidAndTouchedTouchObject(const int16_t& x, + const int16_t& y, + String & selectedObjectName, + int8_t & selectedObjectIndex); + int8_t getTouchObjectIndex(struct EventStruct *event, + const String & touchObject, + const bool & isButton = false); + bool setTouchObjectState(struct EventStruct *event, + const String & touchObject, + const bool & state); + int8_t getTouchObjectState(struct EventStruct *event, + const String & touchObject); + bool setTouchButtonOnOff(struct EventStruct *event, + const String & touchObject, + const bool & state); + int16_t getTouchObjectValue(struct EventStruct *event, + const String & touchObject); + bool setTouchObjectValue(struct EventStruct *event, + const String & touchObject, + const int16_t & value); + uint8_t get_device_valuecount(struct EventStruct *event); + bool plugin_webform_load(struct EventStruct *event); + bool plugin_webform_save(struct EventStruct *event); + bool plugin_fifty_per_second(struct EventStruct *event, + const int16_t & x, + const int16_t & y, + const int16_t & ox, + const int16_t & oy, + const int16_t & rx, + const int16_t & ry, + const int16_t & z); + bool plugin_write(struct EventStruct *event, + const String & string); + bool plugin_get_config_value(struct EventStruct *event, + String & string); + void releaseTouch(struct EventStruct *event); + int16_t getButtonGroup() { + return _buttonGroup; + } + + # if TOUCH_FEATURE_EXTENDED_TOUCH + bool validButtonGroup(const int16_t& group, + const bool & ignoreZero = true); + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + bool handleButtonSwipe(struct EventStruct *event, + const int16_t & swipeValue); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + bool setButtonGroup(struct EventStruct *event, + const int16_t & buttonGroup); + bool nextButtonGroup(struct EventStruct *event); + bool prevButtonGroup(struct EventStruct *event); + bool nextButtonPage(struct EventStruct *event); + bool prevButtonPage(struct EventStruct *event); + void displayButtonGroup(struct EventStruct *event, + const int16_t & buttonGroup, + const int8_t & mode = 0); + bool displayButton(struct EventStruct *event, + const int8_t & buttonNr, + const int16_t & buttonGroup = -1, + int8_t mode = 0); + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + + bool touchEnabled() { + return !_touchIgnored; + } + +private: + + int parseStringToInt(const String & string, + const uint8_t& indexFind, + const char & separator = ',', + const int & defaultValue = 0); + void generateObjectEvent(struct EventStruct *event, + const int8_t & objectIndex, + const int16_t & onOffState, + const int8_t & mode = 0, + const bool & groupSwitch = false, + const int8_t & factor = 1); + bool parseRangeToInt16(const String& range, + int16_t & lowRange, + int16_t & highRange); + + bool _deduplicate = false; + taskIndex_t _displayTask = INVALID_TASK_INDEX; + AdaGFXColorDepth _colorDepth = AdaGFXColorDepth::FullColor; + int16_t _buttonGroup = 0; + + std::set_buttonGroups; + + bool _settingsLoaded = false; + bool _stillTouching = false; + bool _touchIgnored = false; + + // Used to generate events on touch-release + int8_t _lastObjectIndex = -1; + String _lastObjectName; + tTouch_Point _last_point; + tTouch_Point _last_point_z; // Only used to store z in the x member + # if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + Swipe_action_e _lastSwipe = Swipe_action_e::None; + int16_t _last_delta_x; + int16_t _last_delta_y; + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH && TOUCH_FEATURE_SWIPE + + struct tTouch_Globals + { + uint32_t flags = 0u; + tTouch_Point top_left; + tTouch_Point bottom_right; + # if TOUCH_FEATURE_EXTENDED_TOUCH + uint16_t colorOn = 0u; + uint16_t colorOff = 0u; + uint16_t colorCaption = 0u; + uint16_t colorBorder = 0u; + uint16_t colorDisabled = 0u; + uint16_t colorDisabledCaption = 0u; + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + uint8_t debounceMs = 0u; + uint8_t swipeMargin = 0u; + uint8_t swipeMinimal = 0u; + bool calibrationEnabled = false; + bool logEnabled = false; + }; + + std::vectorTouchObjects; + +public: + + bool _flipped = false; // buffered settings + bool _useCalibration = false; + + tTouch_Globals Touch_Settings; + + String settingsArray[TOUCH_ARRAY_SIZE]; + uint8_t lastObjectIndex = 0u; + uint8_t objectCount = 0u; +}; +#endif // ifdef PLUGIN_USES_TOUCHHANDLER +#endif // ifndef HELPERS_ESPEASY_TOUCHHANDLER_H diff --git a/src/src/Helpers/ESPEasy_checks.cpp b/src/src/Helpers/ESPEasy_checks.cpp index 69a8b909a..07588bc16 100644 --- a/src/src/Helpers/ESPEasy_checks.cpp +++ b/src/src/Helpers/ESPEasy_checks.cpp @@ -34,7 +34,8 @@ #include #ifdef USES_C013 -#include "../DataStructs/C013_p2p_dataStructs.h" +#include "../DataStructs/C013_p2p_SensorDataStruct.h" +#include "../DataStructs/C013_p2p_SensorInfoStruct.h" #endif #ifdef USES_C016 @@ -82,19 +83,19 @@ void run_compiletime_checks() { constexpr unsigned int SettingsStructSize = (316 + 84 * TASKS_MAX); #endif #if FEATURE_CUSTOM_PROVISIONING - check_size(); + check_size(); #endif check_size(); check_size(); #if FEATURE_NOTIFIER - check_size(); + check_size(); #endif // if FEATURE_NOTIFIER check_size(); #if 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 @@ -128,8 +129,8 @@ void run_compiletime_checks() { check_size(); check_size(); #ifdef USES_C013 - check_size(); - check_size(); + check_size(); + check_size(); #endif #ifdef USES_C016 check_size(); @@ -178,7 +179,7 @@ void run_compiletime_checks() { // All settings related to N_TASKS static_assert((200 + TASKS_MAX) == offsetof(SettingsStruct, OLD_TaskDeviceID), ""); // 32-bit alignment, so offset of 2 bytes. - static_assert((200 + (67 * TASKS_MAX)) == offsetof(SettingsStruct, ControllerEnabled), ""); + static_assert((200 + (67 * TASKS_MAX)) == offsetof(SettingsStruct, ControllerEnabled), ""); // Used to compute true offset. //const size_t offset = offsetof(SettingsStruct, ControllerEnabled); @@ -263,7 +264,7 @@ String checkTaskSettings(taskIndex_t taskIndex) { } err += LoadTaskSettings(taskIndex); - #endif + #endif return err; } #endif \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp index 7739ffa6b..389411657 100644 --- a/src/src/Helpers/ESPEasy_time.cpp +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -1,936 +1,1074 @@ -#include "../Helpers/ESPEasy_time.h" - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/TimeSource.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Nodes.h" -#include "../Globals/RTC.h" -#include "../Globals/Settings.h" -#include "../Globals/TimeZone.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/Hardware.h" -#include "../Helpers/Hardware_I2C.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" - -#include "../Helpers/ESPEasy_time_calc.h" - -#include - -#if FEATURE_EXT_RTC -# include -#endif // if FEATURE_EXT_RTC - - -ESPEasy_time::ESPEasy_time() { - memset(&local_tm, 0, sizeof(tm)); - memset(&tsRise, 0, sizeof(tm)); - memset(&tsSet, 0, sizeof(tm)); - memset(&sunRise, 0, sizeof(tm)); - memset(&sunSet, 0, sizeof(tm)); -} - -struct tm ESPEasy_time::addSeconds(const struct tm& ts, int seconds, bool toLocalTime, bool fromLocalTime) const { - unsigned long time = makeTime(ts); - - if (fromLocalTime) { - time = time_zone.fromLocal(time); - } - - time += seconds; - - if (toLocalTime) { - time = time_zone.toLocal(time); - } - struct tm result; - - breakTime(time, result); - return result; -} - -void ESPEasy_time::restoreFromRTC() -{ - static bool firstCall = true; - -#if FEATURE_EXT_RTC - uint32_t unixtime = 0; - if (ExtRTC_get(unixtime)) { - setExternalTimeSource(unixtime, timeSource_t::External_RTC_time_source); - firstCall = false; - return; - } -#endif - - if (firstCall && (RTC.lastSysTime != 0) && (RTC.deepSleepState != 1)) { - firstCall = false; - - // Check to see if we have some kind of believable timestamp - // It should not be before the build time - // Still it makes sense to restore RTC time to get some kind of continuous logging when no time source is available. - // ToDo TD-er: Fix this when time travel appears to be possible - setExternalTimeSource(RTC.lastSysTime, - RTC.lastSysTime < get_build_unixtime() - ? timeSource_t::No_time_source - : timeSource_t::Restore_RTC_time_source); - - // Do not add the current uptime as offset. This will be done when calling now() - lastSyncTime_ms = 0; - initTime(); - } -} - -void ESPEasy_time::setExternalTimeSource(double time, timeSource_t new_timeSource, uint8_t unitnr) { - if ((new_timeSource == timeSource) && (new_timeSource != timeSource_t::Manual_set)) { - // Update from the same type of time source, except when manually adjusting the time - if (timePassedSince(lastSyncTime_ms) < EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC) { - return; - } - } - - if ((timeSource < new_timeSource) && - (new_timeSource != timeSource_t::No_time_source) && - (new_timeSource != timeSource_t::Manual_set)) - { - // New time source is potentially worse than the current one. - if (computeExpectedWander(timeSource, lastSyncTime_ms) < - computeExpectedWander(new_timeSource, millis())) { return; } - } - - if ((new_timeSource == timeSource_t::No_time_source) || - (new_timeSource == timeSource_t::Manual_set) || - (time > get_build_unixtime())) { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Time : Set Ext. Time Source: "); - log += toString(new_timeSource); - log += F(" time: "); - log += static_cast(time); - addLogMove(LOG_LEVEL_INFO, log); - } -#endif // ifndef BUILD_NO_DEBUG - extTimeSource = new_timeSource; - externalUnixTime_d = time; - lastSyncTime_ms = millis(); - timeSource_p2p_unit = unitnr; - initTime(); - } -} - -uint32_t ESPEasy_time::getUnixTime() const -{ - return static_cast(sysTime); -} - -uint32_t ESPEasy_time::getUnixTime(uint32_t& unix_time_frac) const -{ - const uint32_t seconds(getUnixTime()); - double tmp(sysTime); - - tmp -= seconds; - tmp *= 4294967295.0; - unix_time_frac = tmp; - return seconds; -} - -void ESPEasy_time::initTime() -{ - nextSyncTime = 0; - now(); -} - -unsigned long ESPEasy_time::now() { - // calculate number of seconds passed since last call to now() - bool timeSynced = false; - const long msec_passed = timePassedSince(prevMillis); - - sysTime += static_cast(msec_passed) / 1000.0; - prevMillis += msec_passed; - - if (nextSyncTime <= sysTime) { - // nextSyncTime & sysTime are in seconds - double unixTime_d = -1.0; - - bool updatedTime = false; - - if (externalUnixTime_d > 0.0) { - unixTime_d = externalUnixTime_d; - - // Correct for the delay between the last received external time and applying it - unixTime_d += (timePassedSince(lastSyncTime_ms) / 1000.0); - externalUnixTime_d = -1.0; - syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; - updatedTime = true; - timeSource = extTimeSource; - } - - if (!isExternalTimeSource(timeSource) - || (extTimeSource <= timeSource) - || (timePassedSince(lastSyncTime_ms) > static_cast(1000 * syncInterval))) { - if (getNtpTime(unixTime_d)) { - updatedTime = true; - } else { - #if FEATURE_ESPEASY_P2P - double tmp_unixtime_d; - - if (!updatedTime && Nodes.getUnixTime(tmp_unixtime_d, timeSource_p2p_unit)) { - unixTime_d = tmp_unixtime_d; - timeSource = timeSource_t::ESPEASY_p2p_UDP; - updatedTime = true; - syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; - } - #endif // if FEATURE_ESPEASY_P2P - - #if FEATURE_EXT_RTC - uint32_t tmp_unixtime = 0; - if (!updatedTime && - (timeSource > timeSource_t::External_RTC_time_source) && // No need to set from ext RTC more than once. - ExtRTC_get(tmp_unixtime)) { - unixTime_d = tmp_unixtime; - timeSource = timeSource_t::External_RTC_time_source; - updatedTime = true; - syncInterval = 120; // Allow sync in 2 minutes to see if we get some better options from p2p nodes. - } - #endif - } - } - - // Clear the external time source so it has to be set again with updated values. - extTimeSource = timeSource_t::No_time_source; - - if (timeSource != timeSource_t::ESPEASY_p2p_UDP) { timeSource_p2p_unit = 0; } - - if (updatedTime) { - START_TIMER; - const double time_offset = unixTime_d - sysTime - (timePassedSince(prevMillis) / 1000.0); - - if (statusNTPInitialized && (time_offset < 1.0)) { - // Clock instability in ppm - timeWander = ((time_offset * 1000000.0) / timePassedSince(lastTimeWanderCalculation_ms)); - timeWander *= 1000.0f; - } - - prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) - lastTimeWanderCalculation_ms = prevMillis; - - timeSynced = true; - - sysTime = unixTime_d; - - #if FEATURE_EXT_RTC - // External RTC only stores with second resolution. - // Thus to limit the error to +/- 500 ms, round the sysTime instead of just casting it. - ExtRTC_set(static_cast(sysTime + 0.5)); - #endif - { - const unsigned long abs_time_offset_ms = std::abs(time_offset) * 1000; - - if (timeSource == timeSource_t::NTP_time_source) { - // May need to lessen the load on the NTP servers, randomize the sync interval - if (abs_time_offset_ms < 1000) { - // offset is less than 1 second, so we consider it a regular time sync. - if (abs_time_offset_ms < 100) { - // Good clock stability, use 5 - 6 hour interval - syncInterval = HwRandom(18000, 21600); - } else { - // Dynamic interval between 30 minutes ... 5 hours. - syncInterval = 1800000 / abs_time_offset_ms; - } - } else { - syncInterval = 3600; - } - - if (syncInterval <= 3600) { - syncInterval = HwRandom(3600, 4000); - } - } else if (timeSource == timeSource_t::No_time_source) { - syncInterval = 60; - } else { - syncInterval = 3600; - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Time set to "); - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - log += doubleToString(unixTime_d, 3); - #else - log += static_cast(unixTime_d); - #endif - - if ((-86400 < time_offset) && (time_offset < 86400)) { - // Only useful to show adjustment if it is less than a day. - log += F(" Time adjusted by "); - log += static_cast(time_offset * 1000.0); - log += F(" msec. Wander: "); - log += floatToString(timeWander, 1); - log += F(" ppm"); - log += F(" Source: "); - log += toString(timeSource); - } - addLogMove(LOG_LEVEL_INFO, log); - } - - time_zone.applyTimeZone(unixTime_d); - lastSyncTime_ms = millis(); - nextSyncTime = (uint32_t)unixTime_d + syncInterval; - - if (isExternalTimeSource(timeSource)) { - #ifdef USES_ESPEASY_NOW - ESPEasy_now_handler.sendNTPbroadcast(); - #endif // ifdef USES_ESPEASY_NOW - } - STOP_TIMER(SYSTIME_UPDATED); - } - } - RTC.lastSysTime = static_cast(sysTime); - uint32_t localSystime = time_zone.toLocal(sysTime); - breakTime(localSystime, local_tm); - - if (timeSynced) { - calcSunRiseAndSet(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat( - F("Local time: %s"), - getDateTimeString('-', ':', ' ').c_str())); - } - { - // Notify plugins the time has been set. - String dummy; - PluginCall(PLUGIN_TIME_CHANGE, 0, dummy); - } - - if (Settings.UseRules) { - if (statusNTPInitialized) { - eventQueue.add(F("Time#Set")); - } else { - eventQueue.add(F("Time#Initialized")); - } - } - statusNTPInitialized = true; // @giig1967g: setting system variable %isntp% - } - return (unsigned long)localSystime; -} - -bool ESPEasy_time::reportNewMinute() -{ - now(); - - int cur_min = local_tm.tm_min; - - if (!systemTimePresent()) { - // Use millis() to compute some "minute" - cur_min = (millis() / 60000) % 60; - } - - if (cur_min == PrevMinutes) - { - return false; - } - PrevMinutes = cur_min; - return true; -} - -bool ESPEasy_time::systemTimePresent() const { - switch (timeSource) { - case timeSource_t::No_time_source: - case timeSource_t::Restore_RTC_time_source: - break; - case timeSource_t::External_RTC_time_source: - case timeSource_t::GPS_time_source: - case timeSource_t::GPS_PPS_time_source: - case timeSource_t::ESP_now_peer: - case timeSource_t::ESPEASY_p2p_UDP: - case timeSource_t::Manual_set: - return true; - case timeSource_t::NTP_time_source: - return getUnixTime() > get_build_unixtime(); - } - return nextSyncTime > 0 || externalUnixTime_d > get_build_unixtime(); -} - -bool ESPEasy_time::getNtpTime(double& unixTime_d) -{ - if (!Settings.UseNTP() || !NetworkConnected(10)) { - return false; - } - - if (lastNTPSyncTime_ms != 0) { - if (timePassedSince(lastNTPSyncTime_ms) < static_cast(1000 * syncInterval)) { - // Make sure not to flood the NTP servers with requests. - return false; - } - } - START_TIMER; - IPAddress timeServerIP; - String log = F("NTP : NTP host "); - - bool useNTPpool = false; - - if (Settings.NTPHost[0] != 0) { - resolveHostByName(Settings.NTPHost, timeServerIP); - log += Settings.NTPHost; - - // When single set host fails, retry again in 20 seconds - nextSyncTime = sysTime + HwRandom(20, 60); - } else { - // 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); - log += ntpServerName; - - // When pool host fails, retry can be much sooner - nextSyncTime = sysTime + HwRandom(5, 20); - useNTPpool = true; - } - - log += F(" ("); - log += formatIP(timeServerIP); - log += ')'; - - if (!hostReachable(timeServerIP)) { - log += F(" unreachable"); - addLogMove(LOG_LEVEL_INFO, log); - STOP_TIMER(NTP_FAIL); - return false; - } - - WiFiUDP udp; - - if (!beginWiFiUDP_randomPort(udp)) { - return false; - } - - const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message - uint8_t packetBuffer[NTP_PACKET_SIZE]{}; // buffer to hold incoming & outgoing packets - - log += F(" queried"); -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, log); -#endif // ifndef BUILD_NO_DEBUG - - while (udp.parsePacket() > 0) { // discard any previously received packets - } - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - - FeedSW_watchdog(); - - if (udp.beginPacket(timeServerIP, 123) == 0) { // NTP requests are to port 123 - FeedSW_watchdog(); - udp.stop(); - STOP_TIMER(NTP_FAIL); - return false; - } - udp.write(packetBuffer, NTP_PACKET_SIZE); - udp.endPacket(); - - - uint32_t beginWait = millis(); - - while (!timeOutReached(beginWait + 1000)) { - int size = udp.parsePacket(); - int remotePort = udp.remotePort(); - - if ((size >= NTP_PACKET_SIZE) && (remotePort == 123)) { - udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer - - if ((packetBuffer[0] & 0b11000000) == 0b11000000) { - // Leap-Indicator: unknown (clock unsynchronized) - // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLog(LOG_LEVEL_ERROR, strformat( - F("NTP : NTP host (%s) unsynchronized"), - formatIP(timeServerIP).c_str())); - } - - if (!useNTPpool) { - // Does not make sense to try it very often if a single host is used which is not synchronized. - nextSyncTime = sysTime + 120; - } - udp.stop(); - STOP_TIMER(NTP_FAIL); - return false; - } - - // For more detailed info on improving accuracy, see: - // https://github.com/lettier/ntpclient/issues/4#issuecomment-360703503 - // For now, we simply use half the reply time as delay compensation. - - unsigned long secsSince1900; - - // convert four bytes starting at location 40 to a long integer - // TX time is used here. - secsSince1900 = (unsigned long)packetBuffer[40] << 24; - secsSince1900 |= (unsigned long)packetBuffer[41] << 16; - secsSince1900 |= (unsigned long)packetBuffer[42] << 8; - secsSince1900 |= (unsigned long)packetBuffer[43]; - - if (secsSince1900 == 0) { - // No time stamp received - - if (!useNTPpool) { - // Retry again in a minute. - nextSyncTime = sysTime + 60; - } - udp.stop(); - STOP_TIMER(NTP_FAIL); - return false; - } - uint32_t txTm = secsSince1900 - 2208988800UL; - - unsigned long txTm_f; - txTm_f = (unsigned long)packetBuffer[44] << 24; - txTm_f |= (unsigned long)packetBuffer[45] << 16; - txTm_f |= (unsigned long)packetBuffer[46] << 8; - txTm_f |= (unsigned long)packetBuffer[47]; - - // Convert seconds to double - unixTime_d = static_cast(txTm); - - // Add fractional part. - unixTime_d += (static_cast(txTm_f) / 4294967295.0); - - long total_delay = timePassedSince(beginWait); - lastSyncTime_ms = millis(); - - // compensate for the delay by adding half the total delay - // N.B. unixTime_d is in seconds and delay in msec. - double delay_compensation = static_cast(total_delay) / 2000.0; - unixTime_d += delay_compensation; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("NTP : NTP replied: delay "); - log += total_delay; - log += F(" mSec"); -#ifndef LIMIT_BUILD_SIZE - log += F(" Accuracy increased by "); - double fractpart, intpart; - fractpart = modf(unixTime_d, &intpart); - - if (fractpart < delay_compensation) { - // We gained more than 1 second in accuracy - fractpart += 1.0; - } - log += static_cast(fractpart * 1000.0); - log += F(" msec"); -#endif - addLogMove(LOG_LEVEL_INFO, log); - } - udp.stop(); - timeSource = timeSource_t::NTP_time_source; - lastNTPSyncTime_ms = millis(); - CheckRunningServices(); // FIXME TD-er: Sometimes services can only be started after NTP is successful - STOP_TIMER(NTP_SUCCESS); - return true; - } - delay(10); - } - - // Timeout. - if (!useNTPpool) { - // Retry again in a minute. - nextSyncTime = sysTime + 60; - } - -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, F("NTP : No reply")); -#endif // ifndef BUILD_NO_DEBUG - udp.stop(); - STOP_TIMER(NTP_FAIL); - return false; -} - -/************************************************** -* get the timezone-offset string in +/-0000 format -**************************************************/ -String ESPEasy_time::getTimeZoneOffsetString() { - int dif = static_cast((static_cast(now()) - static_cast(getUnixTime())) / 60); // Minutes - char valueString[6] = { 0 }; - String tzoffset; - - // Formatting the timezone-offset string as [+|-]HHMM - if (dif < 0) { - tzoffset += '-'; - } else { - tzoffset += '+'; - } - - dif = abs(dif); - sprintf_P(valueString, PSTR("%02d%02d"), dif / 60, dif % 60); - tzoffset += String(valueString); - return tzoffset; -} -/********************************************************************************************\ - Date/Time string formatters - \*********************************************************************************************/ -String ESPEasy_time::getDateString(char delimiter) const -{ - return formatDateString(local_tm, delimiter); -} - -String ESPEasy_time::getTimeString(char delimiter, bool show_seconds /*=true*/, char hour_prefix /*='\0'*/) const -{ - return formatTimeString(local_tm, delimiter, false, show_seconds, hour_prefix); -} - -String ESPEasy_time::getTimeString_ampm(char delimiter, bool show_seconds /*=true*/, char hour_prefix /*='\0'*/) const -{ - return formatTimeString(local_tm, delimiter, true, show_seconds, hour_prefix); -} - -String ESPEasy_time::getDateTimeString(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { - return formatDateTimeString(local_tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, false); -} - -String ESPEasy_time::getDateTimeString_ampm(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { - return formatDateTimeString(local_tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, true); -} - -/********************************************************************************************\ - Get current time/date - \*********************************************************************************************/ -int ESPEasy_time::year(unsigned long t) -{ - struct tm tmp; - - breakTime(t, tmp); - return 1900 + tmp.tm_year; -} - -int ESPEasy_time::weekday(unsigned long t) -{ - struct tm tmp; - - breakTime(t, tmp); - return tmp.tm_wday; -} - -String ESPEasy_time::weekday_str(int wday) -{ - const String weekDays = F("SunMonTueWedThuFriSat"); - - return weekDays.substring(wday * 3, wday * 3 + 3); -} - -String ESPEasy_time::weekday_str() const -{ - return weekday_str(weekday() - 1); -} - -String ESPEasy_time::month_str(int month) -{ - const String months = F("JanFebMarAprMayJunJulAugSepOctNovDec"); - - return months.substring(month * 3, month * 3 + 3); -} - -String ESPEasy_time::month_str() const -{ - return month_str(month() - 1); -} - -/********************************************************************************************\ - Sunrise/Sunset calculations - \*********************************************************************************************/ -int ESPEasy_time::getSecOffset(const String& format) { - int position_minus = format.indexOf('-'); - int position_plus = format.indexOf('+'); - - if ((position_minus == -1) && (position_plus == -1)) { - return 0; - } - int sign_position = _max(position_minus, position_plus); - int position_percent = format.indexOf('%', sign_position); - - if (position_percent == -1) { - return 0; - } - - int32_t value; - - if (!validIntFromString(format.substring(sign_position, position_percent), value)) { - return 0; - } - - switch (format.charAt(position_percent - 1)) { - case 'm': - case 'M': - return value * 60; - case 'h': - case 'H': - return value * 3600; - } - return value; -} - -String ESPEasy_time::getSunriseTimeString(char delimiter) const { - return formatTimeString(sunRise, delimiter, false, false); -} - -String ESPEasy_time::getSunsetTimeString(char delimiter) const { - return formatTimeString(sunSet, delimiter, false, false); -} - -String ESPEasy_time::getSunriseTimeString(char delimiter, int secOffset) const { - if (secOffset == 0) { - return getSunriseTimeString(delimiter); - } - return formatTimeString(getSunRise(secOffset), delimiter, false, false); -} - -String ESPEasy_time::getSunsetTimeString(char delimiter, int secOffset) const { - if (secOffset == 0) { - return getSunsetTimeString(delimiter); - } - return formatTimeString(getSunSet(secOffset), delimiter, false, false); -} - -float ESPEasy_time::sunDeclination(int doy) { - // Declination of the sun in radians - // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average declinations (2008-2027) - return 0.409526325277017 * sin(0.0169060504029192 * (doy - 80.0856919827619)); -} - -float ESPEasy_time::diurnalArc(float dec, float lat) { - // Duration of the half sun path in hours (time from sunrise to the highest level in the south) - float rad = 0.0174532925f; // = pi/180.0 - float height = -50.0f / 60.0f * rad; - float latRad = lat * rad; - - return 12.0f * acos((sin(height) - sin(latRad) * sin(dec)) / (cos(latRad) * cos(dec))) / M_PI; -} - -float ESPEasy_time::equationOfTime(int doy) { - // Difference between apparent and mean solar time - // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average equation of time (2008-2027) - return -0.170869921174742 * sin(0.0336997028793971 * doy + 0.465419984181394) - 0.129890681040717 * sin( - 0.0178674832556871 * doy - 0.167936777524864); -} - -int ESPEasy_time::dayOfYear(int year, int month, int day) { - // Algorithm borrowed from DateToOrdinal by Ritchie Lawrence, www.commandline.co.uk - int z = 14 - month; - - z /= 12; - int y = year + 4800 - z; - int m = month + 12 * z - 3; - int j = 153 * m + 2; - - j = j / 5 + day + y * 365 + y / 4 - y / 100 + y / 400 - 32045; - y = year + 4799; - int k = y * 365 + y / 4 - y / 100 + y / 400 - 31738; - - return j - k + 1; -} - -void ESPEasy_time::calcSunRiseAndSet() { - int doy = dayOfYear(local_tm.tm_year, local_tm.tm_mon + 1, local_tm.tm_mday); - float eqt = equationOfTime(doy); - float dec = sunDeclination(doy); - float da = diurnalArc(dec, Settings.Latitude); - float rise = 12 - da - eqt; - float set = 12 + da - eqt; - - tsRise.tm_hour = rise; - tsRise.tm_min = (rise - static_cast(rise)) * 60.0f; - tsSet.tm_hour = set; - tsSet.tm_min = (set - static_cast(set)) * 60.0f; - tsRise.tm_mday = tsSet.tm_mday = local_tm.tm_mday; - tsRise.tm_mon = tsSet.tm_mon = local_tm.tm_mon; - tsRise.tm_year = tsSet.tm_year = local_tm.tm_year; - - // Now apply the longitude - int secOffset_longitude = -1.0f * (Settings.Longitude / 15.0f) * 3600; - - tsSet = addSeconds(tsSet, secOffset_longitude, false); - tsRise = addSeconds(tsRise, secOffset_longitude, false); - - breakTime(time_zone.toLocal(makeTime(tsRise)), sunRise); - breakTime(time_zone.toLocal(makeTime(tsSet)), sunSet); -} - -struct tm ESPEasy_time::getSunRise(int secOffset) const { - return addSeconds(tsRise, secOffset, true); -} - -struct tm ESPEasy_time::getSunSet(int secOffset) const { - return addSeconds(tsSet, secOffset, true); -} - -#if FEATURE_EXT_RTC -bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime) -{ - unixtime = 0; - - switch (Settings.ExtTimeSource()) { - case ExtTimeSource_e::None: - return false; - case ExtTimeSource_e::DS1307: - { - I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz - RTC_DS1307 rtc; - - if (!rtc.begin()) { - // Not found - break; - } - - if (!rtc.isrunning()) { - // not running - break; - } - unixtime = rtc.now().unixtime(); - break; - } - case ExtTimeSource_e::DS3231: - { - RTC_DS3231 rtc; - - if (!rtc.begin()) { - // Not found - break; - } - - if (rtc.lostPower()) { - // Cannot get the time from the module - break; - } - unixtime = rtc.now().unixtime(); - break; - } - - case ExtTimeSource_e::PCF8523: - { - RTC_PCF8523 rtc; - - if (!rtc.begin()) { - // Not found - break; - } - - if (rtc.lostPower() || !rtc.initialized() || !rtc.isrunning()) { - // Cannot get the time from the module - break; - } - unixtime = rtc.now().unixtime(); - break; - } - case ExtTimeSource_e::PCF8563: - { - RTC_PCF8563 rtc; - - if (!rtc.begin()) { - // Not found - break; - } - - if (rtc.lostPower() || !rtc.isrunning()) { - // Cannot get the time from the module - break; - } - unixtime = rtc.now().unixtime(); - break; - } - } - - if (unixtime != 0) { - String log = F("ExtRTC: Read external time source: "); - log += unixtime; - addLogMove(LOG_LEVEL_INFO, log); - return true; - } - addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot get time from external time source")); - return false; -} -#endif - -#if FEATURE_EXT_RTC -bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) -{ - if (timeSource >= timeSource_t::External_RTC_time_source) { - // Do not adjust the external RTC time if we already used it as a time source. - // or the new time source is worse than the external RTC time souce. - return true; - } - bool timeAdjusted = false; - - switch (Settings.ExtTimeSource()) { - case ExtTimeSource_e::None: - return false; - case ExtTimeSource_e::DS1307: - { - I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz - RTC_DS1307 rtc; - - if (rtc.begin()) { - rtc.adjust(DateTime(unixtime)); - timeAdjusted = true; - } - break; - } - case ExtTimeSource_e::DS3231: - { - RTC_DS3231 rtc; - - if (rtc.begin()) { - rtc.adjust(DateTime(unixtime)); - timeAdjusted = true; - } - break; - } - - case ExtTimeSource_e::PCF8523: - { - RTC_PCF8523 rtc; - - if (rtc.begin()) { - rtc.adjust(DateTime(unixtime)); - rtc.start(); - timeAdjusted = true; - } - break; - } - case ExtTimeSource_e::PCF8563: - { - RTC_PCF8563 rtc; - - if (rtc.begin()) { - rtc.adjust(DateTime(unixtime)); - rtc.start(); - timeAdjusted = true; - } - break; - } - } - - if (timeAdjusted) { - addLogMove(LOG_LEVEL_INFO, concat( - F("ExtRTC: External time source set to: "), - unixtime)); - return true; - } - addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot set time to external time source")); - return false; -} -#endif \ No newline at end of file +#include "../Helpers/ESPEasy_time.h" + +#include "../../ESPEasy_common.h" + +#include "../../_Plugin_Helper.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/NTP_packet.h" +#include "../DataStructs/TimingStats.h" + +#include "../DataTypes/TimeSource.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../Globals/EventQueue.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Nodes.h" +#include "../Globals/RTC.h" +#include "../Globals/Settings.h" +#include "../Globals/TimeZone.h" + +#ifdef USES_ESPEASY_NOW +# include "../Globals/ESPEasy_now_handler.h" +#endif // ifdef USES_ESPEASY_NOW + + +#include "../Helpers/Convert.h" +#include "../Helpers/Hardware.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" + +#include "../Helpers/ESPEasy_time_calc.h" + +#include + +#if FEATURE_EXT_RTC +# include +#endif // if FEATURE_EXT_RTC + + +ESPEasy_time::ESPEasy_time() { + memset(&local_tm, 0, sizeof(tm)); + memset(&tsRise, 0, sizeof(tm)); + memset(&tsSet, 0, sizeof(tm)); + memset(&sunRise, 0, sizeof(tm)); + memset(&sunSet, 0, sizeof(tm)); +} + +struct tm ESPEasy_time::addSeconds(const struct tm& ts, int seconds, bool toLocalTime, bool fromLocalTime) const { + unsigned long time = makeTime(ts); + + if (fromLocalTime) { + time = time_zone.fromLocal(time); + } + + time += seconds; + + if (toLocalTime) { + time = time_zone.toLocal(time); + } + struct tm result; + + breakTime(time, result); + return result; +} + +void ESPEasy_time::restoreFromRTC() +{ + static bool firstCall = true; + +#if FEATURE_EXT_RTC + uint32_t unixtime = 0; + + if (ExtRTC_get(unixtime)) { + setExternalTimeSource(unixtime, timeSource_t::External_RTC_time_source); + firstCall = false; + return; + } +#endif // if FEATURE_EXT_RTC + + if (firstCall && (RTC.lastSysTime != 0) && (RTC.deepSleepState != 1)) { + firstCall = false; + + // Check to see if we have some kind of believable timestamp + // It should not be before the build time + // Still it makes sense to restore RTC time to get some kind of continuous logging when no time source is available. + // ToDo TD-er: Fix this when time travel appears to be possible + setExternalTimeSource(RTC.lastSysTime + getUptime_in_sec(), + RTC.lastSysTime < get_build_unixtime() + ? timeSource_t::No_time_source + : timeSource_t::Restore_RTC_time_source); + initTime(); + } +} + +bool ESPEasy_time::setExternalTimeSource_withTimeWander( + double new_time, + timeSource_t new_timeSource, + int32_t wander, + uint8_t unitnr) +{ + if ((lastSyncTime_ms != 0) && (new_timeSource == _timeSource)) { + if (new_timeSource != timeSource_t::Manual_set) { + // Update from the same type of time source, except when manually adjusting the time + if (timePassedSince(lastSyncTime_ms) < EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_MSEC) { + return false; + } + } + } + + if ((_timeSource < new_timeSource) && + (new_timeSource != timeSource_t::No_time_source) && + (new_timeSource != timeSource_t::Manual_set)) + { + if (wander < 0) { + wander = computeExpectedWander(new_timeSource); + } + + // New time source is potentially worse than the current one. + if (computeExpectedWander(_timeSource, timePassedSince(lastSyncTime_ms)) < + static_cast(wander)) { + return false; + } + } + + if ((new_timeSource == timeSource_t::No_time_source) || + (new_timeSource == timeSource_t::Manual_set) || + (new_time > get_build_unixtime())) { + if (externalUnixTime_offset_usec == 0) { + const int64_t cur_system_Unixtime_usec = getMicros64() + unixTime_usec_uptime_offset; + const int64_t new_Unixtime_usec = new_time * 1000000.0; + externalUnixTime_offset_usec = new_Unixtime_usec - cur_system_Unixtime_usec; + } + extTimeSource = new_timeSource; + lastSyncTime_ms = millis(); + timeSource_p2p_unit = unitnr; +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("Time : Set Ext. Time Source: %s time: %.3f offset: %s"), + String(toString(new_timeSource)).c_str(), + new_time, + secondsToDayHourMinuteSecond_ms(externalUnixTime_offset_usec).c_str())); + } +#endif // ifndef BUILD_NO_DEBUG + + initTime(); + } + return true; +} + +bool ESPEasy_time::setExternalTimeSource(double new_time, timeSource_t new_timeSource, uint8_t unitnr) { + return setExternalTimeSource_withTimeWander( + new_time, + new_timeSource, + computeExpectedWander(new_timeSource), + unitnr); +} + +uint32_t ESPEasy_time::getUptime_in_sec() const { + return getMicros64() / 1000000ull; +} + +uint32_t ESPEasy_time::getUnixTime() const +{ + const uint64_t unixtime_usec = getMicros64() + unixTime_usec_uptime_offset; + + return static_cast(unixtime_usec / 1000000ull); +} + +uint32_t ESPEasy_time::getUnixTime(uint32_t& unix_time_frac) const +{ + return systemMicros_to_Unixtime(getMicros64(), unix_time_frac); +} + +int64_t ESPEasy_time::Unixtime_to_systemMicros(const uint32_t& unix_time_sec, uint32_t unix_time_frac) const +{ + const int64_t res = + (static_cast(unix_time_sec) * 1000000ll) + + unix_time_frac_to_micros(unix_time_frac); + + if (unixTime_usec_uptime_offset == 0) { + // Time has not been set + return res; + } + return res - unixTime_usec_uptime_offset; +} + +uint32_t ESPEasy_time::systemMicros_to_Unixtime(const int64_t& systemMicros, uint32_t& unix_time_frac) const +{ + return micros_to_sec_time_frac(systemMicros + unixTime_usec_uptime_offset, unix_time_frac); +} + +uint32_t ESPEasy_time::systemMicros_to_Localtime(const int64_t& systemMicros, uint32_t& unix_time_frac) const +{ + return time_zone.toLocal(systemMicros_to_Unixtime(systemMicros, unix_time_frac)); +} + +void ESPEasy_time::initTime() +{ + nextSyncTime = 0; + now_(); +} + +unsigned long ESPEasy_time::getLocalUnixTime() const +{ + return time_zone.toLocal(getUnixTime()); +} + +unsigned long ESPEasy_time::getLocalUnixTime(uint32_t& unix_time_frac) const +{ + return time_zone.toLocal(getUnixTime(unix_time_frac)); +} + +unsigned long ESPEasy_time::now_() { + bool timeSynced = false; + + if (nextSyncTime <= getUptime_in_sec()) { + // nextSyncTime is in seconds + double unixTime_d = -1.0; + + bool updatedTime = false; + + if ((externalUnixTime_offset_usec != 0) && + (extTimeSource != timeSource_t::No_time_source)) { + unixTime_d = getMicros64() + + unixTime_usec_uptime_offset + + externalUnixTime_offset_usec; + unixTime_d /= 1000000.0; + + syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; + updatedTime = true; + _timeSource = extTimeSource; + } else { + if (!isExternalTimeSource(_timeSource) + || (timePassedSince(lastSyncTime_ms) > static_cast(1000 * syncInterval))) + { + externalUnixTime_offset_usec = 0; + + // FIXME TD-er: calls to set external timesource should be done via the scheduler + // Those should then also call setExternalTimeSource, + // which determines whether the newly set time is an improvement + if (getNtpTime(unixTime_d)) { + updatedTime = true; + } else { + #if FEATURE_ESPEASY_P2P + + if (!updatedTime) { + double tmp_unixtime_d{}; + int32_t wander{}; + const timeSource_t tmp_timeSource = Nodes.getUnixTime(tmp_unixtime_d, wander, timeSource_p2p_unit); + + if (tmp_timeSource != timeSource_t::No_time_source) { + // Nodes.getUnixTime does compensate for any delay since the timestamp was received from the p2p node + // thus this can be used here without further compensation. + + // FIXME TD-er: Should check if this is a better time source compared to what we already have, using time wander + + unixTime_d = tmp_unixtime_d; + _timeSource = tmp_timeSource; + updatedTime = true; + syncInterval = EXT_TIME_SOURCE_MIN_UPDATE_INTERVAL_SEC; + } + } + #endif // if FEATURE_ESPEASY_P2P + + #if FEATURE_EXT_RTC + uint32_t tmp_unixtime = 0; + + if (!updatedTime && + (_timeSource > timeSource_t::External_RTC_time_source) && // No need to set from ext RTC more than once. + ExtRTC_get(tmp_unixtime)) { + unixTime_d = tmp_unixtime; + _timeSource = timeSource_t::External_RTC_time_source; + updatedTime = true; + syncInterval = 120; // Allow sync in 2 minutes to see if we get some better options from p2p nodes. + } + #endif // if FEATURE_EXT_RTC + + if (updatedTime && (externalUnixTime_offset_usec == 0)) { + const int64_t cur_system_Unixtime_usec = getMicros64() + unixTime_usec_uptime_offset; + const int64_t new_Unixtime_usec = unixTime_d * 1000000.0; + externalUnixTime_offset_usec = new_Unixtime_usec - cur_system_Unixtime_usec; + } + } + } + } + + // Clear the external time source so it has to be set again with updated values. + extTimeSource = timeSource_t::No_time_source; + + if ((_timeSource != timeSource_t::ESPEASY_p2p_UDP) && + (_timeSource != timeSource_t::ESP_now_peer)) + { + timeSource_p2p_unit = 0; + } + + if (updatedTime) { + START_TIMER; + unixTime_usec_uptime_offset += externalUnixTime_offset_usec; + + constexpr int64_t ten_sec_in_usec = 10 * 1000000ll; + + if ((lastTimeWanderCalculation_ms != 0) && + statusNTPInitialized && + (std::abs(externalUnixTime_offset_usec) < ten_sec_in_usec)) { + // Clock instability in ppm + timeWander = + static_cast(externalUnixTime_offset_usec) / + static_cast(timePassedSince(lastTimeWanderCalculation_ms)); + timeWander *= 1000.0f; + } + + lastTimeWanderCalculation_ms = millis(); + timeSynced = true; + + #if FEATURE_PLUGIN_STATS + + // GMT Wed Jan 01 2020 00:00:00 GMT+0000 + constexpr int64_t unixTime_20200101_usec = 1577836800ll * 1000000ll; + + if (!statusNTPInitialized && (externalUnixTime_offset_usec > unixTime_20200101_usec)) { + if (getUnixTime() > get_build_unixtime()) { + // Update recorded plugin stats timestamps + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); + + if (taskData != nullptr) { + taskData->processTimeSet(externalUnixTime_offset_usec / 1000000.0); + } + } + } + } + + #endif // if FEATURE_PLUGIN_STATS + + #if FEATURE_EXT_RTC + + // External RTC only stores with second resolution. + // Thus to limit the error to +/- 500 ms, round the sysTime instead of just casting it. + ExtRTC_set(static_cast(unixTime_d + 0.5)); + #endif // if FEATURE_EXT_RTC + { + const unsigned long abs_time_offset_ms = std::abs(externalUnixTime_offset_usec) / 1000ll; + + if (_timeSource == timeSource_t::NTP_time_source) { + // May need to lessen the load on the NTP servers, randomize the sync interval + if (abs_time_offset_ms < 1000) { + // offset is less than 1 second, so we consider it a regular time sync. + if (abs_time_offset_ms < 100) { + // Good clock stability, use 5 - 6 hour interval + syncInterval = HwRandom(18000, 21600); + } else { + // Dynamic interval between 30 minutes ... 5 hours. + syncInterval = 1800000 / abs_time_offset_ms; + } + } else { + syncInterval = 3600; + } + + if (syncInterval <= 3600) { + syncInterval = HwRandom(3600, 4000); + } + } else if (_timeSource == timeSource_t::No_time_source) { + syncInterval = 60; + } else { + syncInterval = 3600; + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("Time set to "); + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + log += doubleToString(unixTime_d, 3); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + log += static_cast(unixTime_d); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + + if (std::abs(externalUnixTime_offset_usec / 1000000ll) < 86400ll) { + // Only useful to show adjustment if it is less than a day. + log += strformat( + F(" Time adjusted by %d msec. Wander: %.3f ppm Source: "), + static_cast(externalUnixTime_offset_usec / 1000ll), + timeWander); + log += toString(_timeSource); + } + addLogMove(LOG_LEVEL_INFO, log); + } + + time_zone.applyTimeZone(unixTime_d); + lastSyncTime_ms = millis(); + nextSyncTime = getUptime_in_sec() + syncInterval; + + if (isExternalTimeSource(_timeSource)) { + #ifdef USES_ESPEASY_NOW + ESPEasy_now_handler.sendNTPbroadcast(); + #endif // ifdef USES_ESPEASY_NOW + } + STOP_TIMER(SYSTIME_UPDATED); + externalUnixTime_offset_usec = 0; + } + } + RTC.lastSysTime = getUnixTime(); + uint32_t localSystime = time_zone.toLocal(RTC.lastSysTime); + breakTime(localSystime, local_tm); + + calcSunRiseAndSet(timeSynced); + + if (timeSynced) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("Local time: %s"), + getDateTimeString('-', ':', ' ').c_str())); + } + { + // Notify plugins the time has been set. + String dummy; + PluginCall(PLUGIN_TIME_CHANGE, 0, dummy); + } + + if (Settings.UseRules) { + if (statusNTPInitialized) { + eventQueue.add(F("Time#Set")); + } else { + eventQueue.add(F("Time#Initialized")); + } + } + statusNTPInitialized = true; // @giig1967g: setting system variable %isntp% + } + return (unsigned long)localSystime; +} + +bool ESPEasy_time::reportNewMinute() +{ + now_(); + + int cur_min = local_tm.tm_min; + + if (!systemTimePresent()) { + // Use millis() to compute some "minute" + cur_min = (millis() / 60000) % 60; + } + + if (cur_min == PrevMinutes) + { + return false; + } + PrevMinutes = cur_min; + return true; +} + +bool ESPEasy_time::systemTimePresent() const { + switch (_timeSource) { + case timeSource_t::No_time_source: + case timeSource_t::Restore_RTC_time_source: + break; + case timeSource_t::External_RTC_time_source: + case timeSource_t::GPS_time_source: + case timeSource_t::GPS_PPS_time_source: + case timeSource_t::ESP_now_peer: + case timeSource_t::ESPEASY_p2p_UDP: + case timeSource_t::Manual_set: + return true; + case timeSource_t::NTP_time_source: + break; + } + return getUnixTime() > get_build_unixtime(); +} + +bool ESPEasy_time::getNtpTime(double& unixTime_d) +{ + if (!Settings.UseNTP() || !NetworkConnected(10)) { + return false; + } + + if (lastNTPSyncTime_ms != 0) { + if (timePassedSince(lastNTPSyncTime_ms) < static_cast(1000 * syncInterval)) { + // Make sure not to flood the NTP servers with requests. + return false; + } + } + START_TIMER; + IPAddress timeServerIP; + String log = F("NTP : NTP host "); + + bool useNTPpool = false; + + if (Settings.NTPHost[0] != 0) { + resolveHostByName(Settings.NTPHost, timeServerIP); + log += Settings.NTPHost; + + // When single set host fails, retry again in 20 seconds + nextSyncTime = getUptime_in_sec() + HwRandom(20, 60); + } else { + // 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); + log += ntpServerName; + + // When pool host fails, retry can be much sooner + nextSyncTime = getUptime_in_sec() + HwRandom(5, 20); + useNTPpool = true; + } + + log += F(" ("); + log += formatIP(timeServerIP); + log += ')'; + + if (!hostReachable(timeServerIP)) { + log += F(" unreachable"); + addLogMove(LOG_LEVEL_INFO, log); + STOP_TIMER(NTP_FAIL); + return false; + } + + WiFiUDP udp; + + if (!beginWiFiUDP_randomPort(udp)) { + return false; + } + + NTP_packet ntp_packet; + + log += F(" queried"); +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, log); +#endif // ifndef BUILD_NO_DEBUG + + while (udp.parsePacket() > 0) { // discard any previously received packets + } + + FeedSW_watchdog(); + + if (udp.beginPacket(timeServerIP, 123) == 0) { // NTP requests are to port 123 + FeedSW_watchdog(); + udp.stop(); + STOP_TIMER(NTP_FAIL); + return false; + } + constexpr int NTP_packet_size = sizeof(NTP_packet); + const uint64_t txMicros = getMicros64() + unixTime_usec_uptime_offset; + ntp_packet.setTxTimestamp(txMicros); + udp.write(ntp_packet.data, NTP_packet_size); + udp.endPacket(); + + const uint32_t beginWait = millis(); + +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("NTP : before\n"), ntp_packet.toDebugString())); +#endif // ifndef BUILD_NO_DEBUG + + while (!timeOutReached(beginWait + 1000)) { + const int size = udp.parsePacket(); + const int remotePort = udp.remotePort(); + + if (size >= NTP_packet_size) { + if (remotePort != 123) { +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, concat(F("NTP : Reply from wrong port: "), remotePort)); +#endif // ifndef BUILD_NO_DEBUG + udp.stop(); + STOP_TIMER(NTP_FAIL); + return false; + } + udp.read(ntp_packet.data, NTP_packet_size); // read packet into the buffer + const uint64_t receivedMicros = getMicros64(); + + udp.stop(); + +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("NTP : after\n"), ntp_packet.toDebugString())); +#endif // ifndef BUILD_NO_DEBUG + + if (ntp_packet.isUnsynchronized()) { + // Leap-Indicator: unknown (clock unsynchronized) + // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("NTP : NTP host (%s) unsynchronized"), + formatIP(timeServerIP).c_str())); + } + + if (!useNTPpool) { + // Does not make sense to try it very often if a single host is used which is not synchronized. + nextSyncTime = getUptime_in_sec() + 120; + } + STOP_TIMER(NTP_FAIL); + return false; + } + + // For more detailed info on improving accuracy, see: + // https://github.com/lettier/ntpclient/issues/4#issuecomment-360703503 + + int64_t offset_usec{}; + int64_t roundtripDelay_usec{}; + + if (!ntp_packet.compute_usec( + txMicros, + receivedMicros + unixTime_usec_uptime_offset, + offset_usec, roundtripDelay_usec)) + { +#ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_ERROR, strformat( + F("NTP : NTP error: round-trip delay: %d [ms] offset: %s,\n t0: %s,\n t1: %s,\n t2: %s,\n t3: %s"), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str(), + doubleToString(ntp_packet.getReferenceTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getOriginTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getReceiveTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getTransmitTimestamp_usec() / 1000000.0, 3).c_str() + )); +#else // ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_ERROR, strformat( + F("NTP : NTP error: round-trip delay: %d [ms] offset: %s"), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str() + )); + +#endif // ifndef BUILD_NO_DEBUG + + // Apparently this is not a valid packet + // as the received timestamp is before the origin timestamp + // or no valid timestamps from the NTP server. + nextSyncTime = getUptime_in_sec() + 60; + STOP_TIMER(NTP_FAIL); + return false; + } + + externalUnixTime_offset_usec = offset_usec; + _timeSource = timeSource_t::NTP_time_source; + lastSyncTime_ms = millis(); + lastNTPSyncTime_ms = lastSyncTime_ms; + unixTime_d = getMicros64() + + unixTime_usec_uptime_offset + + externalUnixTime_offset_usec; + unixTime_d /= 1000000.0; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { +#ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, strformat( + F("NTP : NTP replied: delay %d ms round-trip delay: %u ms offset: %s,\n t0: %s,\n t1: %s,\n t2: %s,\n t3: %s"), + timePassedSince(beginWait), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str(), + doubleToString(ntp_packet.getReferenceTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getOriginTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getReceiveTimestamp_usec() / 1000000.0, 3).c_str(), + doubleToString(ntp_packet.getTransmitTimestamp_usec() / 1000000.0, 3).c_str() + )); +#else // ifndef BUILD_NO_DEBUG + addLogMove(LOG_LEVEL_INFO, strformat( + F("NTP : NTP replied: delay %d ms round-trip delay: %u ms offset: %s"), + timePassedSince(beginWait), + static_cast(roundtripDelay_usec / 1000), + secondsToDayHourMinuteSecond_ms(offset_usec).c_str() + )); +#endif // ifndef BUILD_NO_DEBUG + } + CheckRunningServices(); // FIXME TD-er: Sometimes services can only be started after NTP is successful + STOP_TIMER(NTP_SUCCESS); + return true; + } + delay(1); + } + + // Timeout. + if (!useNTPpool) { + // Retry again in a minute. + nextSyncTime = getUptime_in_sec() + 60; + } + +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, F("NTP : No reply")); +#endif // ifndef BUILD_NO_DEBUG + udp.stop(); + STOP_TIMER(NTP_FAIL); + return false; +} + +/************************************************** +* get the timezone-offset string in +/-0000 format +**************************************************/ +String ESPEasy_time::getTimeZoneOffsetString() { + int dif = static_cast((static_cast(getLocalUnixTime()) - static_cast(getUnixTime())) / 60); // Minutes + char valueString[6] = { 0 }; + String tzoffset; + + // Formatting the timezone-offset string as [+|-]HHMM + if (dif < 0) { + tzoffset += '-'; + } else { + tzoffset += '+'; + } + + dif = abs(dif); + sprintf_P(valueString, PSTR("%02d%02d"), dif / 60, dif % 60); + tzoffset += String(valueString); + return tzoffset; +} + +void ESPEasy_time::applyTimeZone() +{ + time_zone.applyTimeZone(getUnixTime()); +} + +/********************************************************************************************\ + Date/Time string formatters + \*********************************************************************************************/ +String ESPEasy_time::getDateString(char delimiter) const +{ + return formatDateString(local_tm, delimiter); +} + +String ESPEasy_time::getTimeString(char delimiter, bool show_seconds /*=true*/, char hour_prefix /*='\0'*/) const +{ + return formatTimeString(local_tm, delimiter, false, show_seconds, hour_prefix); +} + +String ESPEasy_time::getTimeString_ampm(char delimiter, bool show_seconds /*=true*/, char hour_prefix /*='\0'*/) const +{ + return formatTimeString(local_tm, delimiter, true, show_seconds, hour_prefix); +} + +String ESPEasy_time::getDateTimeString(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { + return formatDateTimeString(local_tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, false); +} + +String ESPEasy_time::getDateTimeString_ampm(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { + return formatDateTimeString(local_tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, true); +} + +/********************************************************************************************\ + Get current time/date + \*********************************************************************************************/ +int ESPEasy_time::year(unsigned long t) +{ + struct tm tmp; + + breakTime(t, tmp); + return 1900 + tmp.tm_year; +} + +int ESPEasy_time::weekday(unsigned long t) +{ + struct tm tmp; + + breakTime(t, tmp); + return tmp.tm_wday; +} + +String ESPEasy_time::weekday_str(int wday) +{ + const String weekDays = F("SunMonTueWedThuFriSat"); + + return weekDays.substring(wday * 3, wday * 3 + 3); +} + +String ESPEasy_time::weekday_str() const +{ + return weekday_str(weekday() - 1); +} + +String ESPEasy_time::month_str(int month) +{ + const String months = F("JanFebMarAprMayJunJulAugSepOctNovDec"); + + return months.substring(month * 3, month * 3 + 3); +} + +String ESPEasy_time::month_str() const +{ + return month_str(month() - 1); +} + +/********************************************************************************************\ + Sunrise/Sunset calculations + \*********************************************************************************************/ +int ESPEasy_time::getSecOffset(const String& format) { + int position_minus = format.indexOf('-'); + int position_plus = format.indexOf('+'); + + if ((position_minus == -1) && (position_plus == -1)) { + return 0; + } + int sign_position = _max(position_minus, position_plus); + int position_percent = format.indexOf('%', sign_position); + + if (position_percent == -1) { + return 0; + } + + int32_t value; + + if (!validIntFromString(format.substring(sign_position, position_percent), value)) { + return 0; + } + + switch (format.charAt(position_percent - 1)) { + case 'm': + case 'M': + return value * 60; + case 'h': + case 'H': + return value * 3600; + } + return value; +} + +String ESPEasy_time::getSunriseTimeString(char delimiter) const { + return formatTimeString(sunRise, delimiter, false, false); +} + +String ESPEasy_time::getSunsetTimeString(char delimiter) const { + return formatTimeString(sunSet, delimiter, false, false); +} + +String ESPEasy_time::getSunriseTimeString(char delimiter, int secOffset) const { + if (secOffset == 0) { + return getSunriseTimeString(delimiter); + } + return formatTimeString(getSunRise(secOffset), delimiter, false, false); +} + +String ESPEasy_time::getSunsetTimeString(char delimiter, int secOffset) const { + if (secOffset == 0) { + return getSunsetTimeString(delimiter); + } + return formatTimeString(getSunSet(secOffset), delimiter, false, false); +} + +float ESPEasy_time::sunDeclination(int doy) { + // Declination of the sun in radians + // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average declinations (2008-2027) + return 0.409526325277017 * sin(0.0169060504029192 * (doy - 80.0856919827619)); +} + +float ESPEasy_time::diurnalArc(float dec, float lat) { + // Duration of the half sun path in hours (time from sunrise to the highest level in the south) + float rad = 0.0174532925f; // = pi/180.0 + float height = -50.0f / 60.0f * rad; + float latRad = lat * rad; + + return 12.0f * acos((sin(height) - sin(latRad) * sin(dec)) / (cos(latRad) * cos(dec))) / M_PI; +} + +float ESPEasy_time::equationOfTime(int doy) { + // Difference between apparent and mean solar time + // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average equation of time (2008-2027) + return -0.170869921174742 * sin(0.0336997028793971 * doy + 0.465419984181394) - 0.129890681040717 * sin( + 0.0178674832556871 * doy - 0.167936777524864); +} + +int ESPEasy_time::dayOfYear(int year, int month, int day) { + // Algorithm borrowed from DateToOrdinal by Ritchie Lawrence, www.commandline.co.uk + int z = 14 - month; + + z /= 12; + int y = year + 4800 - z; + int m = month + 12 * z - 3; + int j = 153 * m + 2; + + j = j / 5 + day + y * 365 + y / 4 - y / 100 + y / 400 - 32045; + y = year + 4799; + int k = y * 365 + y / 4 - y / 100 + y / 400 - 31738; + + return j - k + 1; +} + +void ESPEasy_time::calcSunRiseAndSet(bool timeSynced) { + if (!timeSynced && + (tsSet.tm_mday == local_tm.tm_mday)) { + // No need to recalculate if already calculated for this day + return; + } + + const int doy = dayOfYear(local_tm.tm_year, local_tm.tm_mon + 1, local_tm.tm_mday); + const float eqt = equationOfTime(doy); + const float dec = sunDeclination(doy); + const float da = diurnalArc(dec, Settings.Latitude); + const float rise = 12 - da - eqt; + const float set = 12 + da - eqt; + + tsRise.tm_hour = rise; + tsRise.tm_min = (rise - static_cast(rise)) * 60.0f; + tsSet.tm_hour = set; + tsSet.tm_min = (set - static_cast(set)) * 60.0f; + tsRise.tm_mday = tsSet.tm_mday = local_tm.tm_mday; + tsRise.tm_mon = tsSet.tm_mon = local_tm.tm_mon; + tsRise.tm_year = tsSet.tm_year = local_tm.tm_year; + + // Now apply the longitude + const int secOffset_longitude = -1.0f * (Settings.Longitude / 15.0f) * 3600; + + tsSet = addSeconds(tsSet, secOffset_longitude, false); + tsRise = addSeconds(tsRise, secOffset_longitude, false); + + breakTime(time_zone.toLocal(makeTime(tsRise)), sunRise); + breakTime(time_zone.toLocal(makeTime(tsSet)), sunSet); +} + +struct tm ESPEasy_time::getSunRise(int secOffset) const { + return addSeconds(tsRise, secOffset, true); +} + +struct tm ESPEasy_time::getSunSet(int secOffset) const { + return addSeconds(tsSet, secOffset, true); +} + +#if FEATURE_EXT_RTC +bool ESPEasy_time::ExtRTC_get(uint32_t& unixtime) +{ + unixtime = 0; + + switch (Settings.ExtTimeSource()) { + case ExtTimeSource_e::None: + return false; + case ExtTimeSource_e::DS1307: + { + I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz + RTC_DS1307 rtc; + + if (!rtc.begin()) { + // Not found + break; + } + + if (!rtc.isrunning()) { + // not running + break; + } + unixtime = rtc.now().unixtime(); + break; + } + case ExtTimeSource_e::DS3231: + { + RTC_DS3231 rtc; + + if (!rtc.begin()) { + // Not found + break; + } + + if (rtc.lostPower()) { + // Cannot get the time from the module + break; + } + unixtime = rtc.now().unixtime(); + break; + } + + case ExtTimeSource_e::PCF8523: + { + RTC_PCF8523 rtc; + + if (!rtc.begin()) { + // Not found + break; + } + + if (rtc.lostPower() || !rtc.initialized() || !rtc.isrunning()) { + // Cannot get the time from the module + break; + } + unixtime = rtc.now().unixtime(); + break; + } + case ExtTimeSource_e::PCF8563: + { + RTC_PCF8563 rtc; + + if (!rtc.begin()) { + // Not found + break; + } + + if (rtc.lostPower() || !rtc.isrunning()) { + // Cannot get the time from the module + break; + } + unixtime = rtc.now().unixtime(); + break; + } + } + + if (unixtime != 0) { + addLogMove(LOG_LEVEL_INFO, concat( + F("ExtRTC: Read external time source: "), + unixtime)); + return true; + } + addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot get time from external time source")); + return false; +} + +#endif // if FEATURE_EXT_RTC + +#if FEATURE_EXT_RTC +bool ESPEasy_time::ExtRTC_set(uint32_t unixtime) +{ + if (_timeSource >= timeSource_t::External_RTC_time_source) { + // Do not adjust the external RTC time if we already used it as a time source. + // or the new time source is worse than the external RTC time souce. + return true; + } + bool timeAdjusted = false; + + switch (Settings.ExtTimeSource()) { + case ExtTimeSource_e::None: + return false; + case ExtTimeSource_e::DS1307: + { + I2CSelect_Max100kHz_ClockSpeed(); // Only supports upto 100 kHz + RTC_DS1307 rtc; + + if (rtc.begin()) { + rtc.adjust(DateTime(unixtime)); + timeAdjusted = true; + } + break; + } + case ExtTimeSource_e::DS3231: + { + RTC_DS3231 rtc; + + if (rtc.begin()) { + rtc.adjust(DateTime(unixtime)); + timeAdjusted = true; + } + break; + } + + case ExtTimeSource_e::PCF8523: + { + RTC_PCF8523 rtc; + + if (rtc.begin()) { + rtc.adjust(DateTime(unixtime)); + rtc.start(); + timeAdjusted = true; + } + break; + } + case ExtTimeSource_e::PCF8563: + { + RTC_PCF8563 rtc; + + if (rtc.begin()) { + rtc.adjust(DateTime(unixtime)); + rtc.start(); + timeAdjusted = true; + } + break; + } + } + + if (timeAdjusted) { + addLogMove(LOG_LEVEL_INFO, concat( + F("ExtRTC: External time source set to: "), + unixtime)); + return true; + } + addLog(LOG_LEVEL_ERROR, F("ExtRTC: Cannot set time to external time source")); + return false; +} + +#endif // if FEATURE_EXT_RTC diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h index e2f32376b..f85d14357 100644 --- a/src/src/Helpers/ESPEasy_time.h +++ b/src/src/Helpers/ESPEasy_time.h @@ -1,216 +1,253 @@ -#ifndef HELPERS_ESPEASY_TIME_H -#define HELPERS_ESPEASY_TIME_H - -#include "../../ESPEasy_common.h" - -#include "../DataTypes/ESPEasyTimeSource.h" - -#include - - -class ESPEasy_time { -public: - - ESPEasy_time(); - - struct tm addSeconds(const struct tm& ts, - int seconds, - bool toLocalTime, - bool fromLocalTime = false) const; - - // Restore the last known system time - // This may be useful to get some idea of what time it is. - // This way the unit can do things based on local time even when NTP servers may not respond. - // Do not use this when booting from deep sleep. - // Only call this once during boot. - void restoreFromRTC(); - - // Restore the last known system time - // This may be useful to get some idea of what time it is. - // This way the unit can do things based on local time even when NTP servers may not respond. - // Do not use this when booting from deep sleep. - // Only call this once during boot. - void restoreLastKnownUnixTime(unsigned long lastSysTime, - uint8_t deepSleepState); - - void setExternalTimeSource(double time, - timeSource_t source, - uint8_t unitnr = 0); - - // Get unix time in seconds - uint32_t getUnixTime() const; - - // Get unix time in seconds - // @param unix_time_frac The fractional part - uint32_t getUnixTime(uint32_t& unix_time_frac) const; - - void initTime(); - - // Update and get the current systime - unsigned long now(); - - // Update time and return whether the minute has changed since last check. - bool reportNewMinute(); - - bool systemTimePresent() const; - - bool getNtpTime(double& unixTime_d); - - String getTimeZoneOffsetString(); - - /********************************************************************************************\ - Date/Time string formatters - \*********************************************************************************************/ - -public: - - // Format the current Date separated by the given delimiter - // Default date format example: 20161231 (YYYYMMDD) - String getDateString(char delimiter = '\0') const; - - // Formats the current Time - // Default time format example: 235959 (HHMMSS) - String getTimeString(char delimiter = '\0', - bool show_seconds = true, - char hour_prefix = '\0') const; - - String getTimeString_ampm(char delimiter = '\0', - bool show_seconds = true, - char hour_prefix = '\0') const; - - - String getDateTimeString(char dateDelimiter = '-', - char timeDelimiter = ':', - char dateTimeDelimiter = ' ') const; - String getDateTimeString_ampm(char dateDelimiter = '-', - char timeDelimiter = ':', - char dateTimeDelimiter = ' ') const; - - - /********************************************************************************************\ - Get current time/date - \*********************************************************************************************/ - - // Get the year given a Unix time stamp - static int year(unsigned long t); - - // Get the weekday, given a Unix time stamp - static int weekday(unsigned long t); - - // Convert a weekday number (Sun = 1 ... Sat = 7) to a 3 letter string - static String weekday_str(int wday); - - // Convert a month number (Jan = 1 ... Dec = 12) to a 3 letter string - static String month_str(int month); - - - // Get current year. - int year() const - { - return 1900 + local_tm.tm_year; - } - - // Get current month - uint8_t month() const - { - return local_tm.tm_mon + 1; // tm_mon starts at 0 - } - - // Get current day of the month - uint8_t day() const - { - return local_tm.tm_mday; - } - - // Get current hour - uint8_t hour() const - { - return local_tm.tm_hour; - } - - // Get current minute - uint8_t minute() const - { - return local_tm.tm_min; - } - - // Get current second - uint8_t second() const - { - return local_tm.tm_sec; - } - - // day of week, sunday is day 1 - int weekday() const - { - return local_tm.tm_wday; - } - - String weekday_str() const; - - String month_str() const; - - - /********************************************************************************************\ - Sunrise/Sunset calculations - \*********************************************************************************************/ - -public: - - // Compute the offset in seconds of the substring +/-[smh] - static int getSecOffset(const String& format); - String getSunriseTimeString(char delimiter) const; - String getSunsetTimeString(char delimiter) const; - String getSunriseTimeString(char delimiter, - int secOffset) const; - String getSunsetTimeString(char delimiter, - int secOffset) const; - -private: - - static float sunDeclination(int doy); - static float diurnalArc(float dec, - float lat); - static float equationOfTime(int doy); - static int dayOfYear(int year, - int month, - int day); - - void calcSunRiseAndSet(); - struct tm getSunRise(int secOffset) const; - struct tm getSunSet(int secOffset) const; - -#if FEATURE_EXT_RTC -public: - - bool ExtRTC_get(uint32_t& unixtime); - -private: - - bool ExtRTC_set(uint32_t unixtime); -#endif - -public: - - struct tm local_tm; // local time - uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds - double sysTime = 0.0; // Use high resolution double to get better sync between nodes when using NTP - uint32_t prevMillis = 0; - uint32_t nextSyncTime = 0; // Next time to allow time sync against UNIX time (thus seconds) - uint32_t lastSyncTime_ms = 0; - uint32_t lastNTPSyncTime_ms = 0; - double externalUnixTime_d = -1.0; // Used to set time from a source other than NTP. - struct tm tsRise, tsSet; - struct tm sunRise; - struct tm sunSet; - timeSource_t timeSource = timeSource_t::No_time_source; - timeSource_t extTimeSource = timeSource_t::No_time_source; - float timeWander = 0.0f; // Clock instability in ppm - uint32_t lastTimeWanderCalculation_ms = 0; - - uint8_t PrevMinutes = 0; - uint8_t timeSource_p2p_unit = 0; -}; - - -#endif // HELPERS_ESPEASY_TIME_H +#ifndef HELPERS_ESPEASY_TIME_H +#define HELPERS_ESPEASY_TIME_H + +#include "../../ESPEasy_common.h" + +#include "../DataTypes/ESPEasyTimeSource.h" + +#include + + +class ESPEasy_time { +public: + + ESPEasy_time(); + + struct tm addSeconds(const struct tm& ts, + int seconds, + bool toLocalTime, + bool fromLocalTime = false) const; + + // Restore the last known system time + // This may be useful to get some idea of what time it is. + // This way the unit can do things based on local time even when NTP servers may not respond. + // Do not use this when booting from deep sleep. + // Only call this once during boot. + void restoreFromRTC(); + + // Restore the last known system time + // This may be useful to get some idea of what time it is. + // This way the unit can do things based on local time even when NTP servers may not respond. + // Do not use this when booting from deep sleep. + // Only call this once during boot. + void restoreLastKnownUnixTime(unsigned long lastSysTime, + uint8_t deepSleepState); + + bool setExternalTimeSource_withTimeWander(double new_time, + timeSource_t new_timeSource, + int32_t wander, + uint8_t unitnr = 0); + + bool setExternalTimeSource(double new_time, + timeSource_t new_timeSource, + uint8_t unitnr = 0); + + uint32_t getUptime_in_sec() const; + + // Get unix time in seconds + uint32_t getUnixTime() const; + + // Get unix time in seconds + // @param unix_time_frac The fractional part + uint32_t getUnixTime(uint32_t& unix_time_frac) const; + + // Convert the UnixTime to systemMicros + // Returns converted system micros when system time has been set. + // Returns UnixTime in usec when system time has not yet been set, so it can be easily identified. + // Return value is negative if Unix timestamp was before system boot. + int64_t Unixtime_to_systemMicros(const uint32_t& unix_time_sec, + uint32_t unix_time_frac = 0) const; + + // Convert the system micros() to Unix Time. + // Return value is in seconds. + // Returns UnixTime when time has been set, otherwise the seconds part of system micros + uint32_t systemMicros_to_Unixtime(const int64_t& systemMicros, + uint32_t & unix_time_frac) const; + + // Convert the system micros() to Unix Time. + // Return value is in seconds. + // Returns LocalTime when time has been set, otherwise the seconds part of system micros + uint32_t systemMicros_to_Localtime(const int64_t& systemMicros, + uint32_t & unix_time_frac) const; + + void initTime(); + + unsigned long getLocalUnixTime() const; + unsigned long getLocalUnixTime(uint32_t& unix_time_frac) const; + + // Update and get the current systime + unsigned long now_(); + + // Update time and return whether the minute has changed since last check. + bool reportNewMinute(); + + bool systemTimePresent() const; + + bool getNtpTime(double& unixTime_d); + + String getTimeZoneOffsetString(); + + void applyTimeZone(); + + /********************************************************************************************\ + Date/Time string formatters + \*********************************************************************************************/ + +public: + + // Format the current Date separated by the given delimiter + // Default date format example: 20161231 (YYYYMMDD) + String getDateString(char delimiter = '\0') const; + + // Formats the current Time + // Default time format example: 235959 (HHMMSS) + String getTimeString(char delimiter = '\0', + bool show_seconds = true, + char hour_prefix = '\0') const; + + String getTimeString_ampm(char delimiter = '\0', + bool show_seconds = true, + char hour_prefix = '\0') const; + + + String getDateTimeString(char dateDelimiter = '-', + char timeDelimiter = ':', + char dateTimeDelimiter = ' ') const; + String getDateTimeString_ampm(char dateDelimiter = '-', + char timeDelimiter = ':', + char dateTimeDelimiter = ' ') const; + + + /********************************************************************************************\ + Get current time/date + \*********************************************************************************************/ + + // Get the year given a Unix time stamp + static int year(unsigned long t); + + // Get the weekday, given a Unix time stamp + static int weekday(unsigned long t); + + // Convert a weekday number (Sun = 1 ... Sat = 7) to a 3 letter string + static String weekday_str(int wday); + + // Convert a month number (Jan = 1 ... Dec = 12) to a 3 letter string + static String month_str(int month); + + + // Get current year. + int year() const + { + return 1900 + local_tm.tm_year; + } + + // Get current month + uint8_t month() const + { + return local_tm.tm_mon + 1; // tm_mon starts at 0 + } + + // Get current day of the month + uint8_t day() const + { + return local_tm.tm_mday; + } + + // Get current hour + uint8_t hour() const + { + return local_tm.tm_hour; + } + + // Get current minute + uint8_t minute() const + { + return local_tm.tm_min; + } + + // Get current second + uint8_t second() const + { + return local_tm.tm_sec; + } + + // day of week, sunday is day 1 + int weekday() const + { + return local_tm.tm_wday; + } + + String weekday_str() const; + + String month_str() const; + + + /********************************************************************************************\ + Sunrise/Sunset calculations + \*********************************************************************************************/ + +public: + + // Compute the offset in seconds of the substring +/-[smh] + static int getSecOffset(const String& format); + String getSunriseTimeString(char delimiter) const; + String getSunsetTimeString(char delimiter) const; + String getSunriseTimeString(char delimiter, + int secOffset) const; + String getSunsetTimeString(char delimiter, + int secOffset) const; + +private: + + static float sunDeclination(int doy); + static float diurnalArc(float dec, + float lat); + static float equationOfTime(int doy); + static int dayOfYear(int year, + int month, + int day); + + void calcSunRiseAndSet(bool timeSynced); + struct tm getSunRise(int secOffset) const; + struct tm getSunSet(int secOffset) const; + +#if FEATURE_EXT_RTC + +public: + + bool ExtRTC_get(uint32_t& unixtime); + +private: + + bool ExtRTC_set(uint32_t unixtime); +#endif // if FEATURE_EXT_RTC + +public: + + timeSource_t getTimeSource() const { return _timeSource; } + + + struct tm local_tm; // local time + uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds + + uint64_t unixTime_usec_uptime_offset = 0.0; // Use usec resolution to get better sync between nodes when using NTP + uint32_t nextSyncTime = 0; // Next time to allow time sync against UNIX time (thus seconds) + uint32_t lastSyncTime_ms = 0; + uint32_t lastNTPSyncTime_ms = 0; + int64_t externalUnixTime_offset_usec{}; // Computed offset from current systime + struct tm tsRise, tsSet; + struct tm sunRise; + struct tm sunSet; +private: + timeSource_t _timeSource = timeSource_t::No_time_source; + timeSource_t extTimeSource = timeSource_t::No_time_source; +public: + float timeWander = 0.0f; // Clock instability in ppm + uint32_t lastTimeWanderCalculation_ms = 0; + + uint8_t PrevMinutes = 0; + uint8_t timeSource_p2p_unit = 0; +}; + + +#endif // HELPERS_ESPEASY_TIME_H diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp index c6c2e4840..1c6be17a9 100644 --- a/src/src/Helpers/ESPEasy_time_calc.cpp +++ b/src/src/Helpers/ESPEasy_time_calc.cpp @@ -1,355 +1,480 @@ -#include "../Helpers/ESPEasy_time_calc.h" - - -#include - -#include "../Globals/ESPEasy_time.h" -#include "../Helpers/StringConverter.h" - - -#define SECS_PER_MIN (60UL) -#define SECS_PER_HOUR (3600UL) -#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) - - -bool isLeapYear(int year) { - return ((year > 0) && !(year % 4) && ((year % 100) || !(year % 400))); -} - -uint8_t getMonthDays(int year, uint8_t month) { - const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - if (month == 1 && isLeapYear(year)) { - return 29; - } - if (month > 11) { - return 0; - } - return monthDays[month]; -} - -/********************************************************************************************\ - Unix Time computations - \*********************************************************************************************/ - -uint32_t makeTime(const struct tm& tm) { - // assemble time elements into uint32_t - // note year argument is offset from 1970 (see macros in time.h to convert to other formats) - // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 - const int tm_year = tm.tm_year + 1900; - - // seconds from 1970 till 1 jan 00:00:00 of the given year - // tm_year starts at 1900 - uint32_t seconds = 1577836800; // 01/01/2020 @ 12:00am (UTC) - int year = 2020; - if (tm_year < year) { - // Just in case this function is called on old dates - year = 1970; - seconds = 0; - } - - for (; year < tm_year; ++year) { - seconds += SECS_PER_DAY * 365; - if (isLeapYear(year)) { - seconds += SECS_PER_DAY; // add extra days for leap years - } - } - - // add days for this year, months start from 0 - for (int i = 0; i < tm.tm_mon; i++) { - seconds += SECS_PER_DAY * getMonthDays(tm_year, i); - } - seconds += (tm.tm_mday - 1) * SECS_PER_DAY; - seconds += tm.tm_hour * SECS_PER_HOUR; - seconds += tm.tm_min * SECS_PER_MIN; - seconds += tm.tm_sec; - return seconds; -} - -void breakTime(unsigned long timeInput, struct tm& tm) { - uint32_t time = (uint32_t)timeInput; - tm.tm_sec = time % 60; - time /= 60; // now it is minutes - tm.tm_min = time % 60; - time /= 60; // now it is hours - tm.tm_hour = time % 24; - time /= 24; // now it is days - tm.tm_wday = ((time + 4) % 7) + 1; // Sunday is day 1 - - int year = 1970; - unsigned long days = 0; - while ((unsigned)(days += (isLeapYear(year) ? 366 : 365)) <= time) { - year++; - } - tm.tm_year = year - 1900; // tm_year starts at 1900 - - days -= isLeapYear(year) ? 366 : 365; - time -= days; // now it is days in this year, starting at 0 - - uint8_t month = 0; - for (month = 0; month < 12; month++) { - const uint8_t monthLength = getMonthDays(year, month); - if (time >= monthLength) { - time -= monthLength; - } else { - break; - } - } - tm.tm_mon = month; // Jan is month 0 - tm.tm_mday = time + 1; // day of month start at 1 -} - - -String formatDateString(const struct tm& ts, char delimiter) { - // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) - char DateString[20]; // 19 digits plus the null char - const int year = 1900 + ts.tm_year; - if (delimiter == '\0') { - sprintf_P(DateString, PSTR("%4d%02d%02d"), year, ts.tm_mon + 1, ts.tm_mday); - } else { - sprintf_P(DateString, PSTR("%4d%c%02d%c%02d"), year, delimiter, ts.tm_mon + 1, delimiter, ts.tm_mday); - } - return DateString; -} - - -// returns the current Time separated by the given delimiter -// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) -String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds, char hour_prefix /*='\0'*/) -{ - char TimeString[20]; // 19 digits plus the null char - char hour_prefix_s[2] = { 0 }; - - if (am_pm) { - uint8_t hour(ts.tm_hour % 12); - - if (hour == 0) { hour = 12; } - const char a_or_p = ts.tm_hour < 12 ? 'A' : 'P'; - if (hour < 10) { hour_prefix_s[0] = hour_prefix; } - - if (show_seconds) { - if (delimiter == '\0') { - sprintf_P(TimeString, PSTR("%s%d%02d%02d %cM"), - hour_prefix_s, hour, ts.tm_min, ts.tm_sec, a_or_p); - } else { - sprintf_P(TimeString, PSTR("%s%d%c%02d%c%02d %cM"), - hour_prefix_s, hour, delimiter, ts.tm_min, delimiter, ts.tm_sec, a_or_p); - } - } else { - if (delimiter == '\0') { - sprintf_P(TimeString, PSTR("%s%d%02d %cM"), - hour_prefix_s, hour, ts.tm_min, a_or_p); - } else { - sprintf_P(TimeString, PSTR("%s%d%c%02d %cM"), - hour_prefix_s, hour, delimiter, ts.tm_min, a_or_p); - } - } - } else { - if (show_seconds) { - if (delimiter == '\0') { - sprintf_P(TimeString, PSTR("%02d%02d%02d"), - ts.tm_hour, ts.tm_min, ts.tm_sec); - } else { - sprintf_P(TimeString, PSTR("%02d%c%02d%c%02d"), - ts.tm_hour, delimiter, ts.tm_min, delimiter, ts.tm_sec); - } - } else { - if (ts.tm_hour < 10) { hour_prefix_s[0] = hour_prefix; } - if (delimiter == '\0') { - sprintf_P(TimeString, PSTR("%s%d%02d"), - hour_prefix_s, ts.tm_hour, ts.tm_min); - } else { - sprintf_P(TimeString, PSTR("%s%d%c%02d"), - hour_prefix_s, ts.tm_hour, delimiter, ts.tm_min); - } - } - } - return TimeString; -} - - -String formatDateTimeString(const struct tm& ts, char dateDelimiter, char timeDelimiter, char dateTimeDelimiter, bool am_pm) -{ - // if called like this: getDateTimeString('\0', '\0', '\0'); - // it will give back this: 20161231235959 (YYYYMMDDHHMMSS) - String ret = formatDateString(ts, dateDelimiter); - - if (dateTimeDelimiter != '\0') { - ret += dateTimeDelimiter; - } - ret += formatTimeString(ts, timeDelimiter, am_pm, true); - return ret; -} - -/********************************************************************************************\ - Time computations for rules. - \*********************************************************************************************/ - -String timeLong2String(unsigned long lngTime) -{ - unsigned long x = 0; - String time; - - x = (lngTime >> 16) & 0xf; - - if (x == 0x0f) { - x = 0; - } - String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); - time = weekDays.substring(x * 3, x * 3 + 3); - time += ','; - - x = (lngTime >> 12) & 0xf; - - if (x == 0xf) { - time += '*'; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - x = (lngTime >> 8) & 0xf; - - if (x == 0xf) { - time += '*'; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - time += ':'; - - x = (lngTime >> 4) & 0xf; - - if (x == 0xf) { - time += '*'; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - x = (lngTime) & 0xf; - - if (x == 0xf) { - time += '*'; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - return time; -} - - -unsigned long string2TimeLong(const String& str) -{ - // format 0000WWWWAAAABBBBCCCCDDDD - // WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes - - char command[20]; - int w, x, y; - unsigned long a; - { - // Within a scope so the tmpString is only used for copy. - String tmpString(str); - tmpString.toLowerCase(); - tmpString.toCharArray(command, 20); - } - unsigned long lngTime = 0; - String TmpStr1; - - if (GetArgv(command, TmpStr1, 1)) - { - String day = TmpStr1; - String weekDays = F("allsunmontuewedthufrisatwrkwkd"); - y = weekDays.indexOf(TmpStr1) / 3; - - if (y == 0) { - y = 0xf; // wildcard is 0xf - } - lngTime |= (unsigned long)y << 16; - } - - if (GetArgv(command, TmpStr1, 2)) - { - y = 0; - - for (x = TmpStr1.length() - 1; x >= 0; x--) - { - w = TmpStr1[x]; - - if (isDigit(w) || (w == '*')) - { - a = 0xffffffff ^ (0xfUL << y); // create mask to clean nibble position y - lngTime &= a; // maak nibble leeg - - if (w == '*') { - lngTime |= (0xFUL << y); // fill nibble with wildcard value - } - else { - lngTime |= (w - '0') << y; // fill nibble with token - } - y += 4; - } - else - if (w == ':') {} - else - { - break; - } - } - } - #undef TmpStr1Length - return lngTime; -} - - - -/********************************************************************************************\ - Match clock event - \*********************************************************************************************/ -bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet) -{ - unsigned long Mask; - - for (uint8_t y = 0; y < 8; y++) - { - if (((clockSet >> (y * 4)) & 0xf) == 0xf) // if nibble y has the wildcard value 0xf - { - Mask = 0xffffffff ^ (0xFUL << (y * 4)); // Mask to wipe nibble position y. - clockEvent &= Mask; // clear nibble - clockEvent |= (0xFUL << (y * 4)); // fill with wildcard value 0xf - } - } - - if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) - if (node_time.weekday() >= 2 && node_time.weekday() <= 6) // and we have a working day today... - { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 - } - } - - if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) - if (node_time.weekday() == 1 || node_time.weekday() == 7) // and we have a weekend day today... - { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 - } - } - - return (clockEvent == clockSet); -} +#include "../Helpers/ESPEasy_time_calc.h" + + +#include + +#include "../Globals/ESPEasy_time.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/SystemVariables.h" + + +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) + + +uint32_t unix_time_frac_to_millis(uint32_t unix_time_frac) +{ + return static_cast(unix_time_frac) / 4294967.0f; +} + +uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac) +{ + return static_cast(unix_time_frac) / 4294.967f; +} + +uint32_t millis_to_unix_time_frac(uint32_t millis) +{ + return static_cast(millis) * 4294967.0f; +} + +uint32_t micros_to_unix_time_frac(uint32_t micros) +{ + return static_cast(micros) * 4294.967f; +} + +uint32_t micros_to_sec_time_frac(int64_t micros, uint32_t& unix_time_frac) +{ + const uint64_t seconds = static_cast(micros / 1000000ull); + + // Compute modulo usec + unix_time_frac = micros_to_unix_time_frac(micros - (1000000ull * seconds)); + return static_cast(seconds); +} + +uint64_t sec_time_frac_to_Micros(uint32_t seconds, uint32_t time_frac) +{ + return + (static_cast(seconds) * 1000000ull) + + unix_time_frac_to_micros(time_frac); +} + +uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec) +{ + const uint64_t seconds = static_cast(micros / 1000000ull); + + // Compute modulo usec + usec = static_cast(micros - (1000000ull * seconds)); + return static_cast(seconds); +} + +uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, + uint32_t time_frac) +{ + const uint64_t unix_time_usec = sec_time_frac_to_Micros(seconds, time_frac); + const uint64_t cur_usec = getMicros64(); + if (unix_time_usec < cur_usec) + return unix_time_usec; + return unix_time_usec - cur_usec; +} + + +bool isLeapYear(int year) { + return (year > 0) && !(year % 4) && ((year % 100) || !(year % 400)); +} + +uint8_t getMonthDays(int year, uint8_t month) { + const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + if ((month == 1) && isLeapYear(year)) { + return 29; + } + + if (month > 11) { + return 0; + } + return monthDays[month]; +} + +uint8_t getMonthDays(const struct tm& tm) { + return getMonthDays(tm.tm_year + 1900, tm.tm_mon); +} + +/********************************************************************************************\ + Unix Time computations + \*********************************************************************************************/ +uint32_t makeTime(const struct tm& tm) { + // assemble time elements into uint32_t + // note year argument is offset from 1970 (see macros in time.h to convert to other formats) + // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + const int tm_year = tm.tm_year + 1900; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + // tm_year starts at 1900 + uint32_t seconds = 1577836800; // 01/01/2020 @ 12:00am (UTC) + int year = 2020; + + if (tm_year < year) { + // Just in case this function is called on old dates + year = 1970; + seconds = 0; + } + + for (; year < tm_year; ++year) { + seconds += SECS_PER_DAY * 365; + + if (isLeapYear(year)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 0 + for (int i = 0; i < tm.tm_mon; i++) { + seconds += SECS_PER_DAY * getMonthDays(tm_year, i); + } + seconds += (tm.tm_mday - 1) * SECS_PER_DAY; + seconds += tm.tm_hour * SECS_PER_HOUR; + seconds += tm.tm_min * SECS_PER_MIN; + seconds += tm.tm_sec; + return seconds; +} + +void breakTime(unsigned long timeInput, struct tm& tm) { + uint32_t time = (uint32_t)timeInput; + + tm.tm_sec = time % 60; + time /= 60; // now it is minutes + tm.tm_min = time % 60; + time /= 60; // now it is hours + tm.tm_hour = time % 24; + time /= 24; // now it is days + tm.tm_wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + int year = 1970; + unsigned long days = 0; + + while ((unsigned)(days += (isLeapYear(year) ? 366 : 365)) <= time) { + year++; + } + tm.tm_year = year - 1900; // tm_year starts at 1900 + + days -= isLeapYear(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + uint8_t month = 0; + + for (month = 0; month < 12; month++) { + const uint8_t monthLength = getMonthDays(year, month); + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.tm_mon = month; // Jan is month 0 + tm.tm_mday = time + 1; // day of month start at 1 +} + +String formatDateString(const struct tm& ts, char delimiter) { + // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) + char DateString[20]; // 19 digits plus the null char + const int year = 1900 + ts.tm_year; + + if (delimiter == '\0') { + sprintf_P(DateString, PSTR("%4d%02d%02d"), year, ts.tm_mon + 1, ts.tm_mday); + } else { + sprintf_P(DateString, PSTR("%4d%c%02d%c%02d"), year, delimiter, ts.tm_mon + 1, delimiter, ts.tm_mday); + } + return DateString; +} + +String formatTimeString(const struct tm& ts) +{ + return formatTimeString(ts, ':', false, true); +} + +// returns the current Time separated by the given delimiter +// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) +String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds, char hour_prefix /*='\0'*/) +{ + char TimeString[20]; // 19 digits plus the null char + char hour_prefix_s[2] = { 0 }; + + if (am_pm) { + uint8_t hour(ts.tm_hour % 12); + + if (hour == 0) { hour = 12; } + const char a_or_p = ts.tm_hour < 12 ? 'A' : 'P'; + + if (hour < 10) { hour_prefix_s[0] = hour_prefix; } + + if (show_seconds) { + if (delimiter == '\0') { + sprintf_P(TimeString, PSTR("%s%d%02d%02d %cM"), + hour_prefix_s, hour, ts.tm_min, ts.tm_sec, a_or_p); + } else { + sprintf_P(TimeString, PSTR("%s%d%c%02d%c%02d %cM"), + hour_prefix_s, hour, delimiter, ts.tm_min, delimiter, ts.tm_sec, a_or_p); + } + } else { + if (delimiter == '\0') { + sprintf_P(TimeString, PSTR("%s%d%02d %cM"), + hour_prefix_s, hour, ts.tm_min, a_or_p); + } else { + sprintf_P(TimeString, PSTR("%s%d%c%02d %cM"), + hour_prefix_s, hour, delimiter, ts.tm_min, a_or_p); + } + } + } else { + if (show_seconds) { + if (delimiter == '\0') { + sprintf_P(TimeString, PSTR("%02d%02d%02d"), + ts.tm_hour, ts.tm_min, ts.tm_sec); + } else { + sprintf_P(TimeString, PSTR("%02d%c%02d%c%02d"), + ts.tm_hour, delimiter, ts.tm_min, delimiter, ts.tm_sec); + } + } else { + if (ts.tm_hour < 10) { hour_prefix_s[0] = hour_prefix; } + + if (delimiter == '\0') { + sprintf_P(TimeString, PSTR("%s%d%02d"), + hour_prefix_s, ts.tm_hour, ts.tm_min); + } else { + sprintf_P(TimeString, PSTR("%s%d%c%02d"), + hour_prefix_s, ts.tm_hour, delimiter, ts.tm_min); + } + } + } + return TimeString; +} + +String formatDateTimeString(const struct tm& ts, char dateDelimiter, char timeDelimiter, char dateTimeDelimiter, bool am_pm) +{ + // if called like this: getDateTimeString('\0', '\0', '\0'); + // it will give back this: 20161231235959 (YYYYMMDDHHMMSS) + String ret = formatDateString(ts, dateDelimiter); + + if (dateTimeDelimiter != '\0') { + ret += dateTimeDelimiter; + } + ret += formatTimeString(ts, timeDelimiter, am_pm, true); + return ret; +} + +/********************************************************************************************\ + Time computations for rules. + \*********************************************************************************************/ +String timeLong2String(unsigned long lngTime) +{ + unsigned long x = 0; + String time; + + x = (lngTime >> 16) & 0xf; + + if (x == 0x0f) { + x = 0; + } + const String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); + + time = weekDays.substring(x * 3, x * 3 + 3); + time += ','; + + #ifndef PLUGIN_BUILD_MINIMAL_OTA + + if (bitRead(lngTime, 28) || bitRead(lngTime, 29)) { // Sunrise or Sunset handling + time += SystemVariables::toString(bitRead(lngTime, 29) ? SystemVariables::Enum::SUNRISE : SystemVariables::Enum::SUNSET); + + if ((lngTime & 0xffff) > 0) { + time += bitRead(lngTime, 30) ? '-' : '+'; // Sign + time += lngTime & 0xffff; + const String hms = F("smh"); + const uint8_t idx = (lngTime >> 26) & 0x3; // 0/1/2 = s/m/h + time += hms.substring(idx, idx + 1); + } + time += '%'; + return time; + } + #endif // ifndef PLUGIN_BUILD_MINIMAL_OTA + + x = (lngTime >> 12) & 0xf; + + if (x == 0xf) { + time += '*'; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + x = (lngTime >> 8) & 0xf; + + if (x == 0xf) { + time += '*'; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + time += ':'; + + x = (lngTime >> 4) & 0xf; + + if (x == 0xf) { + time += '*'; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + x = (lngTime) & 0xf; + + if (x == 0xf) { + time += '*'; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + return time; +} + +unsigned long string2TimeLong(const String& str) +{ + // format 0NRSHM000000WWWWAAAABBBBCCCCDDDD + // WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes + // N = Negative offset, R = sunRise, S = sunSet, H = offset hours, M = offset minutes -> AAAA..DDDD = offset (default: seconds, binary + // stored, not bcd) + + char command[20]; + int w, x, y; + unsigned long a; + { + // Within a scope so the tmpString is only used for copy. + String tmpString(str); + tmpString.toLowerCase(); + tmpString.toCharArray(command, 20); + } + unsigned long lngTime = 0; + String TmpStr1; + + if (GetArgv(command, TmpStr1, 1)) + { + String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); // Deduplicated string takes a little less memory... + weekDays.toLowerCase(); + + y = weekDays.indexOf(TmpStr1) / 3; + + if (y == 0) { + y = 0xf; // wildcard is 0xf + } + lngTime |= (unsigned long)y << 16; + } + + if (GetArgv(command, TmpStr1, 2)) + { + y = 0; + + #ifndef PLUGIN_BUILD_MINIMAL_OTA + + uint8_t off = TmpStr1.startsWith(SystemVariables::toString(SystemVariables::Enum::SUNRISE)) ? 8u : 0u; + + if (off || TmpStr1.startsWith(SystemVariables::toString(SystemVariables::Enum::SUNSET))) { + if (off == 0) { off = 7; } + int lperc = TmpStr1.indexOf('%', off); + + if (lperc >= off) { // Valid variable used, having a second % sign? + bitSet(lngTime, 21u + off); // bit 28 = sunset, bit 29 = sunrise + int delta = ESPEasy_time::getSecOffset(TmpStr1.substring(off, lperc + 1)); + const bool isSeconds = 's' == TmpStr1[lperc - 1]; + + if (delta < 0) { + bitSet(lngTime, 30); // bit 30 = negative offset + delta *= -1; + } + + if (delta > 86400) { // > 24 hours = invalid, reset + delta = 0; + bitClear(lngTime, 30); + } else if (isSeconds && (delta < 32768)) { // Seconds, explicitly, max. delta we can store + } else if (delta >= 3600) { // Hours + delta /= 3600; + bitSet(lngTime, 27); // Bit 27: Stored as Hours + } else if (delta >= 60) { // Minutes + delta /= 60; + bitSet(lngTime, 26); // Bit 26: Stored as Minutes + } // else: Stored as seconds + lngTime += delta; // Store offset + return lngTime; + } + } + #endif // ifndef PLUGIN_BUILD_MINIMAL_OTA + + for (x = TmpStr1.length() - 1; x >= 0; x--) + { + w = TmpStr1[x]; + + if (isDigit(w) || (w == '*')) + { + a = 0xffffffff ^ (0xfUL << y); // create mask to clean nibble position y + lngTime &= a; // maak nibble leeg + + if (w == '*') { + lngTime |= (0xFUL << y); // fill nibble with wildcard value + } + else { + lngTime |= (w - '0') << y; // fill nibble with token + } + y += 4; + } + else + if (w == ':') {} + else + { + break; + } + } + } + #undef TmpStr1Length + return lngTime; +} + +/********************************************************************************************\ + Match clock event + \*********************************************************************************************/ +bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet) +{ + unsigned long Mask; + + for (uint8_t y = 0; y < 8; y++) + { + if (((clockSet >> (y * 4)) & 0xf) == 0xf) // if nibble y has the wildcard value 0xf + { + Mask = 0xffffffff ^ (0xFUL << (y * 4)); // Mask to wipe nibble position y. + clockEvent &= Mask; // clear nibble + clockEvent |= (0xFUL << (y * 4)); // fill with wildcard value 0xf + } + } + + if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) + if ((node_time.weekday() >= 2) && (node_time.weekday() <= 6)) // and we have a working day today... + { + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 + } + } + + if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) + if ((node_time.weekday() == 1) || (node_time.weekday() == 7)) // and we have a weekend day today... + { + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 + } + } + + return clockEvent == clockSet; +} diff --git a/src/src/Helpers/ESPEasy_time_calc.h b/src/src/Helpers/ESPEasy_time_calc.h index 03cad2433..4fbe4e3a6 100644 --- a/src/src/Helpers/ESPEasy_time_calc.h +++ b/src/src/Helpers/ESPEasy_time_calc.h @@ -1,111 +1,126 @@ -#ifndef HELPERS_ESPEASY_TIME_CALC_H -#define HELPERS_ESPEASY_TIME_CALC_H - -#include "../../ESPEasy_common.h" - -inline uint64_t getMicros64() { - #ifdef ESP8266 - return micros64(); - #endif - #ifdef ESP32 - return esp_timer_get_time(); - #endif -} - - -/********************************************************************************************\ - Simple time computations. - \*********************************************************************************************/ - -// Return the time difference as a signed value, taking into account the timers may overflow. -// Returned timediff is between -24.9 days and +24.9 days. -// Returned value is positive when "next" is after "prev" -inline int32_t timeDiff(const unsigned long prev, const unsigned long next) { - return ((int32_t) (next - prev)); -} - -inline int64_t timeDiff64(uint64_t prev, uint64_t next) { - return ((int64_t) (next - prev)); -} - -// Compute the number of milliSeconds passed since timestamp given. -// N.B. value can be negative if the timestamp has not yet been reached. -inline long timePassedSince(const uint32_t& timestamp) { - return timeDiff(timestamp, millis()); -} - -inline int64_t usecPassedSince(volatile uint64_t& timestamp) { - return timeDiff64(timestamp, getMicros64()); -} - -inline int64_t usecPassedSince(const uint64_t& timestamp) { - return timeDiff64(timestamp, getMicros64()); -} - -inline int64_t usecPassedSince(uint64_t& timestamp) { //-V669 - return timeDiff64(timestamp, getMicros64()); -} - -// Check if a certain timeout has been reached. -inline bool timeOutReached(unsigned long timer) { - return timePassedSince(timer) >= 0; -} - -inline bool usecTimeOutReached(const uint64_t& timer) { - return usecPassedSince(timer) >= 0; -} - - - -/********************************************************************************************\ - Unix Time computations - \*********************************************************************************************/ -bool isLeapYear(int year); - -// Get number of days in a month. -// Month starts at 0 for January. -uint8_t getMonthDays(int year, uint8_t month); - -uint32_t makeTime(const struct tm& tm); - -void breakTime(unsigned long timeInput, struct tm& tm); - -/********************************************************************************************\ - Unix Time formatting - \*********************************************************************************************/ - -// Format given Date separated by the given delimiter -// date format example with '-' delimiter: 2016-12-31 (YYYY-MM-DD) -String formatDateString(const struct tm& ts, char delimiter); - -// returns the current Time separated by the given delimiter -// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) -String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds, char hour_prefix = '\0'); - -// returns the current Date and Time separated by the given delimiter -// if called like this: getDateTimeString('\0', '\0', '\0'); -// it will give back this: 20161231235959 (YYYYMMDDHHMMSS) -String formatDateTimeString(const struct tm& ts, char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ', bool am_pm = false); - - -/********************************************************************************************\ - Time computations for rules. - \*********************************************************************************************/ - -// format 0000WWWWAAAABBBBCCCCDDDD -// WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes - -// Convert a 32 bit integer into a string like "Sun,12:30" -String timeLong2String(unsigned long lngTime); - -// Convert a string like "Sun,12:30" into a 32 bit integer -unsigned long string2TimeLong(const String& str); - - -/********************************************************************************************\ - Match clock event - \*********************************************************************************************/ -bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet); - - +#ifndef HELPERS_ESPEASY_TIME_CALC_H +#define HELPERS_ESPEASY_TIME_CALC_H + +#include "../../ESPEasy_common.h" + +inline uint64_t getMicros64() { + #ifdef ESP8266 + return micros64(); + #endif + #ifdef ESP32 + return esp_timer_get_time(); + #endif +} + + +/********************************************************************************************\ + Simple time computations. + \*********************************************************************************************/ + +// Return the time difference as a signed value, taking into account the timers may overflow. +// Returned timediff is between -24.9 days and +24.9 days. +// Returned value is positive when "next" is after "prev" +inline int32_t timeDiff(const unsigned long prev, const unsigned long next) { + return ((int32_t) (next - prev)); +} + +inline int64_t timeDiff64(uint64_t prev, uint64_t next) { + return ((int64_t) (next - prev)); +} + +// Compute the number of milliSeconds passed since timestamp given. +// N.B. value can be negative if the timestamp has not yet been reached. +inline long timePassedSince(const uint32_t& timestamp) { + return timeDiff(timestamp, millis()); +} + +inline int64_t usecPassedSince(volatile uint64_t& timestamp) { + return timeDiff64(timestamp, getMicros64()); +} + +inline int64_t usecPassedSince(const uint64_t& timestamp) { + return timeDiff64(timestamp, getMicros64()); +} + +inline int64_t usecPassedSince(uint64_t& timestamp) { //-V669 + return timeDiff64(timestamp, getMicros64()); +} + +// Check if a certain timeout has been reached. +inline bool timeOutReached(unsigned long timer) { + return timePassedSince(timer) >= 0; +} + +inline bool usecTimeOutReached(const uint64_t& timer) { + return usecPassedSince(timer) >= 0; +} + +uint32_t unix_time_frac_to_millis(uint32_t unix_time_frac); +uint32_t unix_time_frac_to_micros(uint32_t unix_time_frac); +uint32_t millis_to_unix_time_frac(uint32_t millis); +uint32_t micros_to_unix_time_frac(uint32_t micros); + +uint32_t micros_to_sec_time_frac(int64_t micros, uint32_t& unix_time_frac); +uint64_t sec_time_frac_to_Micros(uint32_t seconds, uint32_t time_frac); + +uint32_t micros_to_sec_usec(int64_t micros, uint32_t& usec); + +uint64_t sec_time_frac_to_uptime_offset_usec(const uint32_t& seconds, + uint32_t time_frac = 0); + +/********************************************************************************************\ + Unix Time computations + \*********************************************************************************************/ +bool isLeapYear(int year); + +// Get number of days in a month. +// Month starts at 0 for January. +uint8_t getMonthDays(int year, uint8_t month); +uint8_t getMonthDays(const struct tm& tm); + +uint32_t makeTime(const struct tm& tm); + +void breakTime(unsigned long timeInput, struct tm& tm); + +/********************************************************************************************\ + Unix Time formatting + \*********************************************************************************************/ + +// Format given Date separated by the given delimiter +// date format example with '-' delimiter: 2016-12-31 (YYYY-MM-DD) +String formatDateString(const struct tm& ts, char delimiter); + +// returns the given Time formatted like this 23:59:59 (HH:MM:SS) +String formatTimeString(const struct tm& ts); + +// returns the given Time separated by the given delimiter +// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) +String formatTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds, char hour_prefix = '\0'); + +// returns the given Date and Time separated by the given delimiter +// if called like this: getDateTimeString('\0', '\0', '\0'); +// it will give back this: 20161231235959 (YYYYMMDDHHMMSS) +String formatDateTimeString(const struct tm& ts, char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ', bool am_pm = false); + + +/********************************************************************************************\ + Time computations for rules. + \*********************************************************************************************/ + +// format 0000WWWWAAAABBBBCCCCDDDD +// WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes + +// Convert a 32 bit integer into a string like "Sun,12:30" +String timeLong2String(unsigned long lngTime); + +// Convert a string like "Sun,12:30" into a 32 bit integer +unsigned long string2TimeLong(const String& str); + + +/********************************************************************************************\ + Match clock event + \*********************************************************************************************/ +bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet); + + #endif // HELPERS_ESPEASY_TIME_CALC_H \ No newline at end of file diff --git a/src/src/Helpers/Hardware.cpp b/src/src/Helpers/Hardware.cpp index 45c2b9b39..45c31875e 100644 --- a/src/src/Helpers/Hardware.cpp +++ b/src/src/Helpers/Hardware.cpp @@ -1,1030 +1,1033 @@ -#include "../Helpers/Hardware.h" - -#include "../Commands/GPIO.h" -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataTypes/SPI_options.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasy_Log.h" - -#include "../Globals/Device.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ExtraTaskSettings.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" -#include "../Globals/GlobalMapPortStatus.h" - -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/Hardware_I2C.h" -#include "../Helpers/I2C_access.h" -#include "../Helpers/Misc.h" -#include "../Helpers/PortStatus.h" -#include "../Helpers/StringConverter.h" - - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - -// #include "../../ESPEasy-Globals.h" - -#ifdef ESP32 - # include - # include - # include - # include - # include - - # if ESP_IDF_VERSION_MAJOR == 4 - # if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 - # include - # include - # include - # elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 - # include - # include - # include - # elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 - # include - # include - # elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - # include - # include - # include - # else // if CONFIG_IDF_TARGET_ESP32S3 - # error Target CONFIG_IDF_TARGET is not supported - # endif // if CONFIG_IDF_TARGET_ESP32S3 - # else // ESP32 IDF 5.x and later - # include - # include - # include - # endif // if ESP_IDF_VERSION_MAJOR == 4 - - -# if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 - # define HAS_HALL_EFFECT_SENSOR 0 - # define HAS_TOUCH_GPIO 1 -# elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 - # define HAS_HALL_EFFECT_SENSOR 0 - # define HAS_TOUCH_GPIO 1 -# elif CONFIG_IDF_TARGET_ESP32C6 // ESP32-C6 - # define HAS_HALL_EFFECT_SENSOR 0 - # define HAS_TOUCH_GPIO 0 -# elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 - # define HAS_HALL_EFFECT_SENSOR 0 - # define HAS_TOUCH_GPIO 0 -# elif CONFIG_IDF_TARGET_ESP32C2 // ESP32-C2 - # define HAS_HALL_EFFECT_SENSOR 0 - # define HAS_TOUCH_GPIO 0 -# elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 - # if ESP_IDF_VERSION_MAJOR < 5 - # define HAS_HALL_EFFECT_SENSOR 1 - # else // if ESP_IDF_VERSION_MAJOR < 5 - -// Support for Hall Effect sensor was removed in ESP_IDF 5.x - # define HAS_HALL_EFFECT_SENSOR 0 - # endif // if ESP_IDF_VERSION_MAJOR < 5 - # define HAS_TOUCH_GPIO 1 -# else // if CONFIG_IDF_TARGET_ESP32S3 - # error Target CONFIG_IDF_TARGET is not supported -# endif // if CONFIG_IDF_TARGET_ESP32S3 - - -# ifndef HAS_TOUCH_GPIO -# define HAS_TOUCH_GPIO 0 -# endif // ifndef HAS_TOUCH_GPIO - - -# if ESP_IDF_VERSION_MAJOR >= 5 - -# include -# include -# include -# include - -// #include - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - -# include "../Helpers/Hardware_ADC_cali.h" - -#endif // ifdef ESP32 - - -#if FEATURE_SD -# include -#endif // if FEATURE_SD - - -#include - - -# define GPIO_PLUGIN_ID 1 - -/********************************************************************************************\ - * Initialize specific hardware settings (only global ones, others are set through devices) - \*********************************************************************************************/ -void hardwareInit() -{ - // set GPIO pins state if not set to default - bool hasPullUp, hasPullDown; - - for (int gpio = 0; gpio <= MAX_GPIO; ++gpio) { - const bool serialPinConflict = isSerialConsolePin(gpio); - - if (!serialPinConflict) { - const uint32_t key = createKey(PLUGIN_GPIO, gpio); - #ifdef ESP32 - checkAndClearPWM(key); - #endif // ifdef ESP32 - - if (getGpioPullResistor(gpio, hasPullUp, hasPullDown)) { - PinBootState bootState = Settings.getPinBootState(gpio); - #if FEATURE_ETHERNET -/* - if (Settings.ETH_Pin_power == gpio) - { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("ETH : Reset ETH module on pin "); - log += Settings.ETH_Pin_power; - addLog(LOG_LEVEL_INFO, log); - } - bootState = PinBootState::Output_low; - } -*/ - #endif // if FEATURE_ETHERNET - - #ifdef ESP32 - if (bootState != PinBootState::Default_state) { - gpio_reset_pin(static_cast(gpio)); - } - #endif - - switch (bootState) - { - case PinBootState::Default_state: - // At startup, pins are configured as INPUT - break; - case PinBootState::Output_low: - createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); - GPIO_Write(PLUGIN_GPIO, gpio, LOW, PIN_MODE_OUTPUT); - - // setPinState(1, gpio, PIN_MODE_OUTPUT, LOW); - break; - case PinBootState::Output_high: - createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); - GPIO_Write(PLUGIN_GPIO, gpio, HIGH, PIN_MODE_OUTPUT); - - // setPinState(1, gpio, PIN_MODE_OUTPUT, HIGH); - break; - case PinBootState::Input_pullup: - - if (hasPullUp) { - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLUP, 0); - pinMode(gpio, INPUT_PULLUP); - } - break; - case PinBootState::Input_pulldown: - - if (hasPullDown) { - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLDOWN, 0); - - #ifdef ESP8266 - - if (gpio == 16) { - pinMode(gpio, INPUT_PULLDOWN_16); - } - #endif // ifdef ESP8266 - #ifdef ESP32 - pinMode(gpio, INPUT_PULLDOWN); - #endif // ifdef ESP32 - } - break; - case PinBootState::Input: - createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT, 0); - pinMode(gpio, INPUT); - break; - } - } - } - } - - if (getGpioPullResistor(Settings.Pin_Reset, hasPullUp, hasPullDown)) { - if (hasPullUp) { - pinMode(Settings.Pin_Reset, INPUT_PULLUP); - } - } - - initI2C(); - - #if FEATURE_PLUGIN_PRIORITY - String dummy; - PluginCall(PLUGIN_PRIORITY_INIT_ALL, nullptr, dummy); - #endif // if FEATURE_PLUGIN_PRIORITY - - // SPI Init - if (Settings.isSPI_valid()) - { - SPI.setHwCs(false); - - // MFD: for ESP32 enable the SPI on HSPI as the default is VSPI - #ifdef ESP32 - - const SPI_Options_e SPI_selection = static_cast(Settings.InitSPI); - - switch (SPI_selection) { -#ifdef ESP32_CLASSIC - case SPI_Options_e::Hspi: - { - SPI.begin(HSPI_SCLK, HSPI_MISO, HSPI_MOSI); // HSPI - break; - } -#endif - case SPI_Options_e::UserDefined: - { - SPI.begin(Settings.SPI_SCLK_pin, - Settings.SPI_MISO_pin, - Settings.SPI_MOSI_pin); // User-defined SPI - break; - } - case SPI_Options_e::Vspi_Fspi: - { - SPI.begin(); // Default SPI bus - break; - } - case SPI_Options_e::None: - break; - } - #else // ifdef ESP32 - SPI.begin(); - #endif // ifdef ESP32 - addLog(LOG_LEVEL_INFO, F("INIT : SPI Init (without CS)")); - } - else - { - addLog(LOG_LEVEL_INFO, F("INIT : SPI not enabled")); - } - -#if FEATURE_SD - - if (Settings.Pin_sd_cs >= 0) - { - if (SD.begin(Settings.Pin_sd_cs)) - { - addLog(LOG_LEVEL_INFO, F("SD : Init OK")); - } - else - { - SD.end(); - addLog(LOG_LEVEL_ERROR, F("SD : Init failed")); - } - } -#endif // if FEATURE_SD -} - - -void checkResetFactoryPin() { - static uint8_t factoryResetCounter = 0; - - if (Settings.Pin_Reset == -1) { - return; - } - - if (digitalRead(Settings.Pin_Reset) == 0) { // active low reset pin - factoryResetCounter++; // just count every second - } - else - { // reset pin released - if (factoryResetCounter > 9) { - // factory reset and reboot - ResetFactory(); - } - - if (factoryResetCounter > 3) { - // normal reboot - reboot(IntendedRebootReason_e::ResetFactoryPinActive); - } - factoryResetCounter = 0; // count was < 3, reset counter - } -} - -#ifdef ESP8266 -int lastADCvalue = 0; - -int espeasy_analogRead(int pin) { - if (!WiFiEventData.wifiConnectInProgress) { - #if FEATURE_ADC_VCC - lastADCvalue = ESP.getVcc(); - #else - lastADCvalue = analogRead(A0); - #endif // if FEATURE_ADC_VCC - } - return lastADCvalue; -} - -#endif // ifdef ESP8266 - -float mapADCtoFloat(float float_value, - float adc1, - float adc2, - float out1, - float out2) -{ - if (!approximatelyEqual(adc1, adc2)) - { - const float normalized = (float_value - adc1) / (adc2 - adc1); - float_value = normalized * (out2 - out1) + out1; - } - return float_value; -} - - -#ifdef ESP32 - -// ESP32 ADC calibration datatypes. - - -// FIXME TD-er: For now keep a local array of the adc calibration -#if ESP_IDF_VERSION_MAJOR < 5 -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]; -#else -Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]; -#endif - - -void initADC() { - for (size_t atten = 0; atten < NR_ELEMENTS(ESP32_ADC_cali); ++atten) { - if (!ESP32_ADC_cali[atten].initialized()) { - // FIXME TD-er: For now fake some pin which is connected to ADC1 - #ifdef ESP32_CLASSIC - const int pin = 36; - #else - const int pin = 1; - #endif - ESP32_ADC_cali[atten].init(pin, static_cast(atten)); - } - } -} - -float applyADCFactoryCalibration(float raw_value, adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].applyFactoryCalibration(raw_value); - } - return raw_value; -} - -bool hasADC_factory_calibration() { - return ESP32_ADC_cali[0].useFactoryCalibration(); -} - -const __FlashStringHelper* getADC_factory_calibration_type() -{ - return ESP32_ADC_cali[0].getADC_factory_calibration_type(); -} - -float getADC_factory_calibrated_min(adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].getMinOut(); - } - return 0.0f; -} - -float getADC_factory_calibrated_max(adc_atten_t attenuation) -{ - if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { - return ESP32_ADC_cali[attenuation].getMaxOut(); - } - return MAX_ADC_VALUE; -} - -int getADC_num_for_gpio(int pin) { - int ch; - - return getADC_num_for_gpio(pin, ch); -} - -int getADC_num_for_gpio(int pin, int& channel) -{ - int adc, t; - - if (getADC_gpio_info(pin, adc, channel, t)) { - return adc; - } - return -1; -} - -int espeasy_analogRead(int pin, bool readAsTouch) { - int value = 0; - int adc, ch, t; - - if (getADC_gpio_info(pin, adc, ch, t)) { - bool canread = false; - - switch (adc) { - case 0: - # if HAS_HALL_EFFECT_SENSOR - value = hallRead(); - # endif // if HAS_HALL_EFFECT_SENSOR - break; - case 1: - canread = true; - break; - case 2: -#if ESP_IDF_VERSION_MAJOR < 5 - if (WiFi.getMode() == WIFI_OFF) { - // See: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#configuration-and-reading-adc - // ADC2 is shared with WiFi, so don't read ADC2 when WiFi is on. - canread = true; - } -#else - canread = true; -#endif - break; - } - - if (canread) { - if (readAsTouch && (t >= 0)) { - # if HAS_TOUCH_GPIO - value = touchRead(pin); - # endif // if HAS_TOUCH_GPIO - } else { - value = analogRead(pin); - } - } - } - return value; -} - - -int getCPU_MaxFreqMHz() -{ -#if CONFIG_IDF_TARGET_ESP32 - return static_cast(efuse_hal_get_rated_freq_mhz()); -#elif CONFIG_IDF_TARGET_ESP32C2 - return 120; -#elif CONFIG_IDF_TARGET_ESP32C3 - return 160; -#elif CONFIG_IDF_TARGET_ESP32C6 - return 160; -#elif CONFIG_IDF_TARGET_ESP32H2 - //IDF-6570 - return 96; -#elif CONFIG_IDF_TARGET_ESP32P4 - return 400; -#elif CONFIG_IDF_TARGET_ESP32S2 - return 240; -#elif CONFIG_IDF_TARGET_ESP32S3 - return 240; - -# else - # error Target CONFIG_IDF_TARGET is not supported - return 160; -# endif -} - -int getCPU_MinFreqMHz() -{ - // TODO TD-er: May differ on some ESPs and also some allow less but only without WiFi - return 80; -} - - -#endif // ifdef ESP32 - - - -/*********************************************************************************************\ -* High entropy hardware random generator -* Thanks to DigitalAlchemist -\*********************************************************************************************/ - -#if ESP_IDF_VERSION_MAJOR >= 5 -#include -#endif - -uint32_t HwRandom() { -#if ESP_IDF_VERSION_MAJOR >= 5 - // See for more info on the HW RNG: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/random.html - return esp_random(); -#else - -// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c -// https://github.com/arendst/Tasmota/blob/1e6b78a957be538cf494f0e2dc49060d1cb0fe8b/tasmota/support_esp.ino#L805 -#if ESP8266 - - // https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator - # define _RAND_ADDR 0x3FF20E44UL -#endif // ESP8266 -#ifdef ESP32 - # define _RAND_ADDR 0x3FF75144UL -#endif // ESP32 - static uint32_t last_ccount = 0; - uint32_t ccount; - uint32_t result = 0; - - do { - ccount = ESP.getCycleCount(); - result ^= *(volatile uint32_t *)_RAND_ADDR; // -V566 - } while (ccount - last_ccount < 64); - last_ccount = ccount; - return result ^ *(volatile uint32_t *)_RAND_ADDR; // -V566 -#undef _RAND_ADDR -#endif -} - -long HwRandom(long howbig) { - if(howbig == 0) { - return 0; - } - return HwRandom() % howbig; -} - -long HwRandom(long howsmall, long howbig) { - if(howsmall >= howbig) { - return howsmall; - } - long diff = howbig - howsmall; - return HwRandom(diff) + howsmall; -} - -#ifdef ESP8266 -void readBootCause() { - lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; - const rst_info *resetInfo = ESP.getResetInfoPtr(); - - if (resetInfo != nullptr) { - switch (resetInfo->reason) { - // normal startup by power on - case REASON_DEFAULT_RST: lastBootCause = BOOT_CAUSE_COLD_BOOT; break; - - // hardware watch dog reset - case REASON_WDT_RST: lastBootCause = BOOT_CAUSE_EXT_WD; break; - - // exception reset, GPIO status won’t change - case REASON_EXCEPTION_RST: lastBootCause = BOOT_CAUSE_EXCEPTION; break; - - // software watch dog reset, GPIO status won’t change - case REASON_SOFT_WDT_RST: lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; - - // software restart ,system_restart , GPIO status won’t change - case REASON_SOFT_RESTART: lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; - - // wake up from deep-sleep - case REASON_DEEP_SLEEP_AWAKE: lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; - - // external system reset - case REASON_EXT_SYS_RST: lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; - default: - break; - } - } -} - -#endif // ifdef ESP8266 - -#ifdef ESP32 -void readBootCause() { - lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; - - #ifdef ESP32S2 - - 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ - } - - - -#elif defined(ESP32S3) - 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch 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 POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ - } - - -#elif defined(ESP32C2) - 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; /**<3, Deep Sleep reset digital core*/ - case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ - case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ - case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - 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 SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, super watchdog reset digital core and rtc module*/ - case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<19, glitch reset digital core and rtc module*/ - case EFUSE_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<20, efuse reset digital core*/ - case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ - } - - -#elif defined(ESP32C3) - 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch 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 POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ - } - -#elif defined(ESP32C6) - 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(ESP32_CLASSIC) - 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_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ - case OWDT_RESET : lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; /**<4, Legacy watch dog reset digital core*/ - case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ - case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core*/ - 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ - case TGWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group reset CPU*/ - case 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 EXT_CPU_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<14, for APP CPU, reseted by PRO 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*/ - } - - # else - - static_assert(false, "Implement processor architecture"); - - #endif -} - -#endif // ifdef ESP32 - - -/********************************************************************************************\ - Hardware specific configurations - \*********************************************************************************************/ -const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { - switch (model) { - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: return F("Sonoff"); - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: return F("Shelly"); - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - #ifdef ESP32_CLASSIC - return F("Olimex"); - #endif // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_wESP32: - #ifdef ESP32_CLASSIC - return F("wESP32"); - #endif // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_WT32_ETH01: - #ifdef ESP32_CLASSIC - return F("WT32-ETH01"); - #endif // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: break; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F(""); -} - -const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) -{ - switch (model) { -#if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) - case DeviceModel::DeviceModel_Sonoff_Basic: return F(" Basic"); - case DeviceModel::DeviceModel_Sonoff_TH1x: return F(" TH1x"); - case DeviceModel::DeviceModel_Sonoff_S2x: return F(" S2x"); - case DeviceModel::DeviceModel_Sonoff_TouchT1: return F(" TouchT1"); - case DeviceModel::DeviceModel_Sonoff_TouchT2: return F(" TouchT2"); - case DeviceModel::DeviceModel_Sonoff_TouchT3: return F(" TouchT3"); - case DeviceModel::DeviceModel_Sonoff_4ch: return F(" 4ch"); - case DeviceModel::DeviceModel_Sonoff_POW: return F(" POW"); - case DeviceModel::DeviceModel_Sonoff_POWr2: return F(" POW-r2"); - case DeviceModel::DeviceModel_Shelly1: return F("1"); - case DeviceModel::DeviceModel_ShellyPLUG_S: return F(" PLUG S"); -#else // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: - return F("default"); -#endif // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) -#ifdef ESP32_CLASSIC - 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"); -#else // ifdef ESP32_CLASSIC - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - case DeviceModel::DeviceModel_wESP32: - case DeviceModel::DeviceModel_WT32_ETH01: -#endif // ifdef ESP32_CLASSIC - - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: return F("default"); - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return F(""); -} - -String getDeviceModelString(DeviceModel model) { - return concat( - getDeviceModelBrandString(model), - getDeviceModelTypeString(model)); -} - -bool modelMatchingFlashSize(DeviceModel model) { -#if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) - const uint32_t size_MB = getFlashRealSizeInBytes() >> 20; -#endif // if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) - - // TD-er: This also checks for ESP8266/ESP8285/ESP32_CLASSIC - switch (model) { - case DeviceModel::DeviceModel_Sonoff_Basic: - case DeviceModel::DeviceModel_Sonoff_TH1x: - case DeviceModel::DeviceModel_Sonoff_S2x: - case DeviceModel::DeviceModel_Sonoff_TouchT1: - case DeviceModel::DeviceModel_Sonoff_TouchT2: - case DeviceModel::DeviceModel_Sonoff_TouchT3: - case DeviceModel::DeviceModel_Sonoff_4ch: -#ifdef ESP8266 - return size_MB == 1; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - case DeviceModel::DeviceModel_Sonoff_POW: - case DeviceModel::DeviceModel_Sonoff_POWr2: -#ifdef ESP8266 - return size_MB == 4; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - case DeviceModel::DeviceModel_Shelly1: - case DeviceModel::DeviceModel_ShellyPLUG_S: -#ifdef ESP8266 - return size_MB == 2; -#else // ifdef ESP8266 - return false; -#endif // ifdef ESP8266 - - // These Olimex boards all have Ethernet - case DeviceModel::DeviceModel_Olimex_ESP32_PoE: - case DeviceModel::DeviceModel_Olimex_ESP32_EVB: - case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: - case DeviceModel::DeviceModel_wESP32: - case DeviceModel::DeviceModel_WT32_ETH01: -#if defined(ESP32_CLASSIC) && FEATURE_ETHERNET - return size_MB == 4; -#else // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET - return false; -#endif // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET - - case DeviceModel::DeviceModel_default: - case DeviceModel::DeviceModel_MAX: - return true; - - // Do not use default: as this allows the compiler to detect any missing cases. - } - return true; -} - -void setFactoryDefault(DeviceModel model) { - ResetFactoryDefaultPreference.setDeviceModel(model); -} - -/********************************************************************************************\ - Add pre defined plugins and rules. - \*********************************************************************************************/ -void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { - setTaskDevice_to_TaskIndex(PLUGIN_GPIO, taskIndex); - const int pins[] = { gpio, -1, -1 }; - - setBasicTaskValues( - taskIndex, - 0, // taskdevicetimer - true, // enabled - name, // name - pins); - Settings.TaskDevicePin1PullUp[taskIndex] = true; - - if (activeLow) { - Settings.TaskDevicePluginConfig[taskIndex][2] = 1; // PLUGIN_001_BUTTON_TYPE_PUSH_ACTIVE_LOW; - } - Settings.TaskDevicePluginConfig[taskIndex][3] = 1; // "Send Boot state" checked. -} - -void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings) { - taskIndex_t taskIndex = 0; - - for (int i = 0; i < 4; ++i) { - if (gpio_settings.button[i] >= 0) { - String label = F("Button"); - label += (i + 1); - addSwitchPlugin(taskIndex, gpio_settings.button[i], label, true); - ++taskIndex; - } - - if (gpio_settings.relais[i] >= 0) { - String label = F("Relay"); - label += (i + 1); - addSwitchPlugin(taskIndex, gpio_settings.relais[i], label, false); - ++taskIndex; - } - } -} - -void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { - Settings.UseRules = true; - String fileName; - - #if defined(ESP32) - fileName += '/'; - #endif // if defined(ESP32) - fileName += F("rules1.txt"); - String rule = F("on ButtonBNR#state do\n if [RelayBNR#state]=0\n gpio,GNR,1\n else\n gpio,GNR,0\n endif\nendon\n"); - rule.replace(F("BNR"), String(buttonNumber)); - rule.replace(F("GNR"), String(relay_gpio)); - String result = appendLineToFile(fileName, rule); - - if (result.length() > 0) { - addLogMove(LOG_LEVEL_ERROR, result); - } -} - -void addPredefinedRules(const GpioFactorySettingsStruct& gpio_settings) { - for (int i = 0; i < 4; ++i) { - if ((gpio_settings.button[i] >= 0) && (gpio_settings.relais[i] >= 0)) { - addButtonRelayRule((i + 1), gpio_settings.relais[i]); - } - } -} - - -// ******************************************************************************** -// change of device: cleanup old device and reset default settings -// ******************************************************************************** -void setTaskDevice_to_TaskIndex(pluginID_t taskdevicenumber, taskIndex_t taskIndex) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - // let the plugin do its cleanup by calling PLUGIN_EXIT with this TaskIndex - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - taskClear(taskIndex, false); // clear settings, but do not save - ClearCustomTaskSettings(taskIndex); - - Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; -// Settings.getPluginID_for_task(taskIndex) = taskdevicenumber; - - if (validPluginID_fullcheck(taskdevicenumber)) // set default values if a new device has been selected - { - // FIXME TD-er: Must check if this is working (e.g. need to set nr. decimals?) - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - - // NOTE: do not enable task by default. allow user to enter sensible valus first and let him enable it when ready. - PluginCall(PLUGIN_SET_DEFAULTS, &TempEvent, dummy); - PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); // the plugin should populate ExtraTaskSettings with its default values. - } else { - // New task is empty task, thus save config now. - taskClear(taskIndex, true); // clear settings, and save - } -} - -// ******************************************************************************** -// Initialize task with some default values applicable for almost all tasks -// ******************************************************************************** -void setBasicTaskValues(taskIndex_t taskIndex, unsigned long taskdevicetimer, - bool enabled, const String& name, const int pins[3]) { - if (!validTaskIndex(taskIndex)) { return; } - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - if (!validDeviceIndex(DeviceIndex)) { return; } - - LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date - - if (taskdevicetimer > 0) { - Settings.TaskDeviceTimer[taskIndex] = taskdevicetimer; - } else { - if (!Device[DeviceIndex].TimerOptional) { // Set default delay, unless it's optional... - Settings.TaskDeviceTimer[taskIndex] = Settings.Delay; - } - else { - Settings.TaskDeviceTimer[taskIndex] = 0; - } - } - Settings.TaskDeviceEnabled[taskIndex] = enabled; - //Settings.TaskDeviceEnabled[taskIndex].enabled = enabled; - safe_strncpy(ExtraTaskSettings.TaskDeviceName, name.c_str(), sizeof(ExtraTaskSettings.TaskDeviceName)); - - // FIXME TD-er: Check for valid GPIO pin (and -1 for "not set") - Settings.TaskDevicePin1[taskIndex] = pins[0]; - Settings.TaskDevicePin2[taskIndex] = pins[1]; - Settings.TaskDevicePin3[taskIndex] = pins[2]; -} +#include "../Helpers/Hardware.h" + +#include "../Commands/GPIO.h" +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataTypes/SPI_options.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasy_Log.h" + +#include "../Globals/Device.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ExtraTaskSettings.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" +#include "../Globals/GlobalMapPortStatus.h" + +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/Hardware_I2C.h" +#include "../Helpers/I2C_access.h" +#include "../Helpers/Misc.h" +#include "../Helpers/PortStatus.h" +#include "../Helpers/StringConverter.h" + + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + +// #include "../../ESPEasy-Globals.h" + +#ifdef ESP32 + # include + # include + # include + # include + # include + + # if ESP_IDF_VERSION_MAJOR == 4 + # if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 + # include + # include + # include + # elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 + # include + # include + # include + # elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 + # include + # include + # elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 + # include + # include + # include + # else // if CONFIG_IDF_TARGET_ESP32S3 + # error Target CONFIG_IDF_TARGET is not supported + # endif // if CONFIG_IDF_TARGET_ESP32S3 + # else // ESP32 IDF 5.x and later + # include + # include + # include + # endif // if ESP_IDF_VERSION_MAJOR == 4 + + +# if CONFIG_IDF_TARGET_ESP32S3 // ESP32-S3 + # define HAS_HALL_EFFECT_SENSOR 0 + # define HAS_TOUCH_GPIO 1 +# elif CONFIG_IDF_TARGET_ESP32S2 // ESP32-S2 + # define HAS_HALL_EFFECT_SENSOR 0 + # define HAS_TOUCH_GPIO 1 +# elif CONFIG_IDF_TARGET_ESP32C6 // ESP32-C6 + # define HAS_HALL_EFFECT_SENSOR 0 + # define HAS_TOUCH_GPIO 0 +# elif CONFIG_IDF_TARGET_ESP32C3 // ESP32-C3 + # define HAS_HALL_EFFECT_SENSOR 0 + # define HAS_TOUCH_GPIO 0 +# elif CONFIG_IDF_TARGET_ESP32C2 // ESP32-C2 + # define HAS_HALL_EFFECT_SENSOR 0 + # define HAS_TOUCH_GPIO 0 +# elif CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4 + # if ESP_IDF_VERSION_MAJOR < 5 + # define HAS_HALL_EFFECT_SENSOR 1 + # else // if ESP_IDF_VERSION_MAJOR < 5 + +// Support for Hall Effect sensor was removed in ESP_IDF 5.x + # define HAS_HALL_EFFECT_SENSOR 0 + # endif // if ESP_IDF_VERSION_MAJOR < 5 + # define HAS_TOUCH_GPIO 1 +# else // if CONFIG_IDF_TARGET_ESP32S3 + # error Target CONFIG_IDF_TARGET is not supported +# endif // if CONFIG_IDF_TARGET_ESP32S3 + + +# ifndef HAS_TOUCH_GPIO +# define HAS_TOUCH_GPIO 0 +# endif // ifndef HAS_TOUCH_GPIO + + +# if ESP_IDF_VERSION_MAJOR >= 5 + +# include +# include +# include +# include + +// #include + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + +# include "../Helpers/Hardware_ADC_cali.h" + +#if FEATURE_ETHERNET +#include +#endif + +#endif // ifdef ESP32 + + +#if FEATURE_SD +# include +#endif // if FEATURE_SD + + +#include + + +# define GPIO_PLUGIN_ID 1 + +/********************************************************************************************\ + * Initialize specific hardware settings (only global ones, others are set through devices) + \*********************************************************************************************/ +void hardwareInit() +{ + // set GPIO pins state if not set to default + bool hasPullUp, hasPullDown; + + for (int gpio = 0; gpio <= MAX_GPIO; ++gpio) { + const bool serialPinConflict = isSerialConsolePin(gpio); + + if (!serialPinConflict) { + const uint32_t key = createKey(PLUGIN_GPIO, gpio); + #ifdef ESP32 + checkAndClearPWM(key); + #endif // ifdef ESP32 + + if (getGpioPullResistor(gpio, hasPullUp, hasPullDown)) { + PinBootState bootState = Settings.getPinBootState(gpio); + #if FEATURE_ETHERNET +/* + if (Settings.ETH_Pin_power_rst == gpio) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("ETH : Reset ETH module on pin "); + log += Settings.ETH_Pin_power_rst; + addLog(LOG_LEVEL_INFO, log); + } + bootState = PinBootState::Output_low; + } + */ + #endif // if FEATURE_ETHERNET + + #ifdef ESP32 + if (bootState != PinBootState::Default_state) { + gpio_reset_pin(static_cast(gpio)); + } + #endif + + switch (bootState) + { + case PinBootState::Default_state: + // At startup, pins are configured as INPUT + break; + case PinBootState::Output_low: + createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); + GPIO_Write(PLUGIN_GPIO, gpio, LOW, PIN_MODE_OUTPUT); + + // setPinState(1, gpio, PIN_MODE_OUTPUT, LOW); + break; + case PinBootState::Output_high: + createAndSetPortStatus_Mode_State(key, PIN_MODE_OUTPUT, 0); + GPIO_Write(PLUGIN_GPIO, gpio, HIGH, PIN_MODE_OUTPUT); + + // setPinState(1, gpio, PIN_MODE_OUTPUT, HIGH); + break; + case PinBootState::Input_pullup: + + if (hasPullUp) { + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLUP, 0); + pinMode(gpio, INPUT_PULLUP); + } + break; + case PinBootState::Input_pulldown: + + if (hasPullDown) { + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT_PULLDOWN, 0); + + #ifdef ESP8266 + + if (gpio == 16) { + pinMode(gpio, INPUT_PULLDOWN_16); + } + #endif // ifdef ESP8266 + #ifdef ESP32 + pinMode(gpio, INPUT_PULLDOWN); + #endif // ifdef ESP32 + } + break; + case PinBootState::Input: + createAndSetPortStatus_Mode_State(key, PIN_MODE_INPUT, 0); + pinMode(gpio, INPUT); + break; + } + } + } + } + + if (getGpioPullResistor(Settings.Pin_Reset, hasPullUp, hasPullDown)) { + if (hasPullUp) { + pinMode(Settings.Pin_Reset, INPUT_PULLUP); + } + } + + initI2C(); + + #if FEATURE_PLUGIN_PRIORITY + String dummy; + PluginCall(PLUGIN_PRIORITY_INIT_ALL, nullptr, dummy); + #endif // if FEATURE_PLUGIN_PRIORITY + + bool tryInitSPI = true; +#if FEATURE_ETHERNET + if ((Settings.NetworkMedium == NetworkMedium_t::Ethernet) && + isValid(Settings.ETH_Phy_Type) && + isSPI_EthernetType(Settings.ETH_Phy_Type)) + { +#if !ETH_SPI_SUPPORTS_CUSTOM + tryInitSPI = false; +#endif + } +#endif + + + // SPI Init + bool SPI_initialized = false; + if (tryInitSPI && Settings.isSPI_valid()) + { + SPI.setHwCs(false); + + // MFD: for ESP32 enable the SPI on HSPI as the default is VSPI + #ifdef ESP32 + + const SPI_Options_e SPI_selection = static_cast(Settings.InitSPI); + int8_t spi_gpios[3] = {}; + + if (Settings.getSPI_pins(spi_gpios)) { + if (SPI_selection == SPI_Options_e::Vspi_Fspi) { + SPI.begin(); // Default SPI bus + } else { + SPI.begin(spi_gpios[0], spi_gpios[1], spi_gpios[2]); + } + SPI_initialized = true; + } + #else // ifdef ESP32 + SPI.begin(); + SPI_initialized = true; + #endif // ifdef ESP32 + } + + if (SPI_initialized) + { + addLog(LOG_LEVEL_INFO, F("INIT : SPI Init (without CS)")); + #if FEATURE_SD + + if (Settings.Pin_sd_cs >= 0) + { + if (SD.begin(Settings.Pin_sd_cs)) + { + addLog(LOG_LEVEL_INFO, F("SD : Init OK")); + } + else + { + SD.end(); + addLog(LOG_LEVEL_ERROR, F("SD : Init failed")); + } + } +#endif // if FEATURE_SD + } else { + addLog(LOG_LEVEL_INFO, F("INIT : SPI not enabled")); + } +} + + +void checkResetFactoryPin() { + static uint8_t factoryResetCounter = 0; + + if (Settings.Pin_Reset == -1) { + return; + } + + if (digitalRead(Settings.Pin_Reset) == 0) { // active low reset pin + factoryResetCounter++; // just count every second + } + else + { // reset pin released + if (factoryResetCounter > 9) { + // factory reset and reboot + ResetFactory(); + } + + if (factoryResetCounter > 3) { + // normal reboot + reboot(IntendedRebootReason_e::ResetFactoryPinActive); + } + factoryResetCounter = 0; // count was < 3, reset counter + } +} + +#ifdef ESP8266 +int lastADCvalue = 0; + +int espeasy_analogRead(int pin) { + if (!WiFiEventData.wifiConnectInProgress) { + #if FEATURE_ADC_VCC + lastADCvalue = ESP.getVcc(); + #else + lastADCvalue = analogRead(A0); + #endif // if FEATURE_ADC_VCC + } + return lastADCvalue; +} + +#endif // ifdef ESP8266 + +float mapADCtoFloat(float float_value, + float adc1, + float adc2, + float out1, + float out2) +{ + if (!approximatelyEqual(adc1, adc2)) + { + const float normalized = (float_value - adc1) / (adc2 - adc1); + float_value = normalized * (out2 - out1) + out1; + } + return float_value; +} + + +#ifdef ESP32 + +// ESP32 ADC calibration datatypes. + + +// FIXME TD-er: For now keep a local array of the adc calibration +#if ESP_IDF_VERSION_MAJOR < 5 +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTEN_MAX]{}; +#else +Hardware_ADC_cali_t ESP32_ADC_cali[ADC_ATTENDB_MAX]{}; +#endif + + +void initADC() { + for (size_t atten = 0; atten < NR_ELEMENTS(ESP32_ADC_cali); ++atten) { + if (!ESP32_ADC_cali[atten].initialized()) { + // FIXME TD-er: For now fake some pin which is connected to ADC1 + #ifdef ESP32_CLASSIC + const int pin = 36; + #else + const int pin = 1; + #endif + ESP32_ADC_cali[atten].init(pin, static_cast(atten)); + } + } +} + +float applyADCFactoryCalibration(float raw_value, adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].applyFactoryCalibration(raw_value); + } + return raw_value; +} + +bool hasADC_factory_calibration() { + return ESP32_ADC_cali[0].useFactoryCalibration(); +} + +const __FlashStringHelper* getADC_factory_calibration_type() +{ + return ESP32_ADC_cali[0].getADC_factory_calibration_type(); +} + +float getADC_factory_calibrated_min(adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].getMinOut(); + } + return 0.0f; +} + +float getADC_factory_calibrated_max(adc_atten_t attenuation) +{ + if (attenuation < NR_ELEMENTS(ESP32_ADC_cali)) { + return ESP32_ADC_cali[attenuation].getMaxOut(); + } + return MAX_ADC_VALUE; +} + +int getADC_num_for_gpio(int pin) { + int ch; + + return getADC_num_for_gpio(pin, ch); +} + +int getADC_num_for_gpio(int pin, int& channel) +{ + int adc, t; + + if (getADC_gpio_info(pin, adc, channel, t)) { + return adc; + } + return -1; +} + +int espeasy_analogRead(int pin, bool readAsTouch) { + int value = 0; + int adc, ch, t; + + if (getADC_gpio_info(pin, adc, ch, t)) { + bool canread = false; + + switch (adc) { + case 0: + # if HAS_HALL_EFFECT_SENSOR + value = hallRead(); + # endif // if HAS_HALL_EFFECT_SENSOR + break; + case 1: + canread = true; + break; + case 2: +#if ESP_IDF_VERSION_MAJOR < 5 + if (WiFi.getMode() == WIFI_OFF) { + // See: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#configuration-and-reading-adc + // ADC2 is shared with WiFi, so don't read ADC2 when WiFi is on. + canread = true; + } +#else + canread = true; +#endif + break; + } + + if (canread) { + if (readAsTouch && (t >= 0)) { + # if HAS_TOUCH_GPIO + value = touchRead(pin); + # endif // if HAS_TOUCH_GPIO + } else { + value = analogRead(pin); + } + } + } + return value; +} + + +int getCPU_MaxFreqMHz() +{ +#if CONFIG_IDF_TARGET_ESP32 + return static_cast(efuse_hal_get_rated_freq_mhz()); +#elif CONFIG_IDF_TARGET_ESP32C2 + return 120; +#elif CONFIG_IDF_TARGET_ESP32C3 + return 160; +#elif CONFIG_IDF_TARGET_ESP32C6 + return 160; +#elif CONFIG_IDF_TARGET_ESP32H2 + //IDF-6570 + return 96; +#elif CONFIG_IDF_TARGET_ESP32P4 + return 400; +#elif CONFIG_IDF_TARGET_ESP32S2 + return 240; +#elif CONFIG_IDF_TARGET_ESP32S3 + return 240; + +# else + # error Target CONFIG_IDF_TARGET is not supported + return 160; +# endif +} + +int getCPU_MinFreqMHz() +{ + // TODO TD-er: May differ on some ESPs and also some allow less but only without WiFi + return 80; +} + + +#endif // ifdef ESP32 + + + +/*********************************************************************************************\ +* High entropy hardware random generator +* Thanks to DigitalAlchemist +\*********************************************************************************************/ + +#if ESP_IDF_VERSION_MAJOR >= 5 +#include +#endif + +uint32_t HwRandom() { +#if ESP_IDF_VERSION_MAJOR >= 5 + // See for more info on the HW RNG: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/random.html + return esp_random(); +#else + +// Based on code from https://raw.githubusercontent.com/espressif/esp-idf/master/components/esp32/hw_random.c +// https://github.com/arendst/Tasmota/blob/1e6b78a957be538cf494f0e2dc49060d1cb0fe8b/tasmota/support_esp.ino#L805 +#if ESP8266 + + // https://web.archive.org/web/20160922031242/http://esp8266-re.foogod.com/wiki/Random_Number_Generator + # define _RAND_ADDR 0x3FF20E44UL +#endif // ESP8266 +#ifdef ESP32 + # define _RAND_ADDR 0x3FF75144UL +#endif // ESP32 + static uint32_t last_ccount = 0; + uint32_t ccount; + uint32_t result = 0; + + do { + ccount = ESP.getCycleCount(); + result ^= *(volatile uint32_t *)_RAND_ADDR; // -V566 + } while (ccount - last_ccount < 64); + last_ccount = ccount; + return result ^ *(volatile uint32_t *)_RAND_ADDR; // -V566 +#undef _RAND_ADDR +#endif +} + +long HwRandom(long howbig) { + if(howbig == 0) { + return 0; + } + return HwRandom() % howbig; +} + +long HwRandom(long howsmall, long howbig) { + if(howsmall >= howbig) { + return howsmall; + } + long diff = howbig - howsmall; + return HwRandom(diff) + howsmall; +} + +#ifdef ESP8266 +void readBootCause() { + lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; + const rst_info *resetInfo = ESP.getResetInfoPtr(); + + if (resetInfo != nullptr) { + switch (resetInfo->reason) { + // normal startup by power on + case REASON_DEFAULT_RST: lastBootCause = BOOT_CAUSE_COLD_BOOT; break; + + // hardware watch dog reset + case REASON_WDT_RST: lastBootCause = BOOT_CAUSE_EXT_WD; break; + + // exception reset, GPIO status won’t change + case REASON_EXCEPTION_RST: lastBootCause = BOOT_CAUSE_EXCEPTION; break; + + // software watch dog reset, GPIO status won’t change + case REASON_SOFT_WDT_RST: lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; + + // software restart ,system_restart , GPIO status won’t change + case REASON_SOFT_RESTART: lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; + + // wake up from deep-sleep + case REASON_DEEP_SLEEP_AWAKE: lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; + + // external system reset + case REASON_EXT_SYS_RST: lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; + default: + break; + } + } +} + +#endif // ifdef ESP8266 + +#ifdef ESP32 +void readBootCause() { + lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; + + #ifdef ESP32S2 + + 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<20, efuse reset digital core*/ + } + + + +#elif defined(ESP32S3) + 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch 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 POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ + } + + +#elif defined(ESP32C2) + 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; /**<3, Deep Sleep reset digital core*/ + case TG0WDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<7, Timer Group0 Watch dog reset digital core*/ + case RTCWDT_SYS_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<9, RTC Watch dog Reset digital core*/ + case INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + 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 SUPER_WDT_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, super watchdog reset digital core and rtc module*/ + case GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<19, glitch reset digital core and rtc module*/ + case EFUSE_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<20, efuse reset digital core*/ + case JTAG_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<24, jtag reset CPU*/ + } + + +#elif defined(ESP32C3) + 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 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + 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 GLITCH_RTC_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<19, glitch 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 POWER_GLITCH_RESET : lastBootCause = BOOT_CAUSE_POWER_UNSTABLE; break; /**<23, power glitch reset digital core and rtc module*/ + } + +#elif defined(ESP32C6) + 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(ESP32_CLASSIC) + 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_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<3, Software reset digital core*/ + case OWDT_RESET : lastBootCause = BOOT_CAUSE_SW_WATCHDOG; break; /**<4, Legacy watch dog reset digital core*/ + case DEEPSLEEP_RESET : lastBootCause = BOOT_CAUSE_DEEP_SLEEP; break; /**<3, Deep Sleep reset digital core*/ + case SDIO_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<6, Reset by SLC module, reset digital core*/ + 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 INTRUSION_RESET : lastBootCause = BOOT_CAUSE_SOFT_RESTART; break; /**<10, Instrusion tested to reset CPU*/ + case TGWDT_CPU_RESET : lastBootCause = BOOT_CAUSE_EXT_WD; break; /**<11, Time Group reset CPU*/ + case 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 EXT_CPU_RESET : lastBootCause = BOOT_CAUSE_MANUAL_REBOOT; break; /**<14, for APP CPU, reseted by PRO 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*/ + } + + # else + + static_assert(false, "Implement processor architecture"); + + #endif +} + +#endif // ifdef ESP32 + + +/********************************************************************************************\ + Hardware specific configurations + \*********************************************************************************************/ +const __FlashStringHelper* getDeviceModelBrandString(DeviceModel model) { + switch (model) { + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: + case DeviceModel::DeviceModel_Sonoff_POW: + 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 + case DeviceModel::DeviceModel_Olimex_ESP32_PoE: + case DeviceModel::DeviceModel_Olimex_ESP32_EVB: + case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: + #ifdef ESP32_CLASSIC + return F("Olimex"); + #endif // ifdef ESP32_CLASSIC + case DeviceModel::DeviceModel_wESP32: + #ifdef ESP32_CLASSIC + return F("wESP32"); + #endif // ifdef ESP32_CLASSIC + case DeviceModel::DeviceModel_WT32_ETH01: + #ifdef ESP32_CLASSIC + return F("WT32-ETH01"); + #endif // ifdef ESP32_CLASSIC +#endif + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: break; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F(""); +} + +const __FlashStringHelper* getDeviceModelTypeString(DeviceModel model) +{ + switch (model) { +#if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) + case DeviceModel::DeviceModel_Sonoff_Basic: return F(" Basic"); + case DeviceModel::DeviceModel_Sonoff_TH1x: return F(" TH1x"); + case DeviceModel::DeviceModel_Sonoff_S2x: return F(" S2x"); + case DeviceModel::DeviceModel_Sonoff_TouchT1: return F(" TouchT1"); + case DeviceModel::DeviceModel_Sonoff_TouchT2: return F(" TouchT2"); + case DeviceModel::DeviceModel_Sonoff_TouchT3: return F(" TouchT3"); + case DeviceModel::DeviceModel_Sonoff_4ch: return F(" 4ch"); + case DeviceModel::DeviceModel_Sonoff_POW: return F(" POW"); + case DeviceModel::DeviceModel_Sonoff_POWr2: return F(" POW-r2"); + case DeviceModel::DeviceModel_Shelly1: return F("1"); + case DeviceModel::DeviceModel_ShellyPLUG_S: return F(" PLUG S"); +#else // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: + case DeviceModel::DeviceModel_Sonoff_POW: + case DeviceModel::DeviceModel_Sonoff_POWr2: + case DeviceModel::DeviceModel_Shelly1: + case DeviceModel::DeviceModel_ShellyPLUG_S: + return F("default"); +#endif // if defined(ESP8266) && !defined(LIMIT_BUILD_SIZE) +#if CONFIG_ETH_USE_ESP32_EMAC + 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 + + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: return F("default"); + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return F(""); +} + +String getDeviceModelString(DeviceModel model) { + return concat( + getDeviceModelBrandString(model), + getDeviceModelTypeString(model)); +} + +bool modelMatchingFlashSize(DeviceModel model) { +#if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) + const uint32_t size_MB = getFlashRealSizeInBytes() >> 20; +#endif // if defined(ESP8266) || (defined(ESP32_CLASSIC) && FEATURE_ETHERNET) + + // TD-er: This also checks for ESP8266/ESP8285/ESP32_CLASSIC + switch (model) { + case DeviceModel::DeviceModel_Sonoff_Basic: + case DeviceModel::DeviceModel_Sonoff_TH1x: + case DeviceModel::DeviceModel_Sonoff_S2x: + case DeviceModel::DeviceModel_Sonoff_TouchT1: + case DeviceModel::DeviceModel_Sonoff_TouchT2: + case DeviceModel::DeviceModel_Sonoff_TouchT3: + case DeviceModel::DeviceModel_Sonoff_4ch: +#ifdef ESP8266 + return size_MB == 1; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + case DeviceModel::DeviceModel_Sonoff_POW: + case DeviceModel::DeviceModel_Sonoff_POWr2: +#ifdef ESP8266 + return size_MB == 4; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + case DeviceModel::DeviceModel_Shelly1: + case DeviceModel::DeviceModel_ShellyPLUG_S: +#ifdef ESP8266 + return size_MB == 2; +#else // ifdef ESP8266 + return false; +#endif // ifdef ESP8266 + + // These Olimex boards all have Ethernet +#if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_Olimex_ESP32_PoE: + case DeviceModel::DeviceModel_Olimex_ESP32_EVB: + case DeviceModel::DeviceModel_Olimex_ESP32_GATEWAY: + case DeviceModel::DeviceModel_wESP32: + case DeviceModel::DeviceModel_WT32_ETH01: +# if defined(ESP32_CLASSIC) && FEATURE_ETHERNET + return size_MB == 4; +# else // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET + return false; +# endif // if defined(ESP32_CLASSIC) && FEATURE_ETHERNET +#endif // if CONFIG_ETH_USE_ESP32_EMAC + case DeviceModel::DeviceModel_default: + case DeviceModel::DeviceModel_MAX: + return true; + + // Do not use default: as this allows the compiler to detect any missing cases. + } + return true; +} + +void setFactoryDefault(DeviceModel model) { + ResetFactoryDefaultPreference.setDeviceModel(model); +} + +/********************************************************************************************\ + Add pre defined plugins and rules. + \*********************************************************************************************/ +void addSwitchPlugin(taskIndex_t taskIndex, int gpio, const String& name, bool activeLow) { + setTaskDevice_to_TaskIndex(PLUGIN_GPIO, taskIndex); + const int pins[] = { gpio, -1, -1 }; + + setBasicTaskValues( + taskIndex, + 0, // taskdevicetimer + true, // enabled + name, // name + pins); + Settings.TaskDevicePin1PullUp[taskIndex] = true; + + if (activeLow) { + Settings.TaskDevicePluginConfig[taskIndex][2] = 1; // PLUGIN_001_BUTTON_TYPE_PUSH_ACTIVE_LOW; + } + Settings.TaskDevicePluginConfig[taskIndex][3] = 1; // "Send Boot state" checked. +} + +void addPredefinedPlugins(const GpioFactorySettingsStruct& gpio_settings) { + taskIndex_t taskIndex = 0; + + for (int i = 0; i < 4; ++i) { + if (gpio_settings.button[i] >= 0) { + String label = F("Button"); + label += (i + 1); + addSwitchPlugin(taskIndex, gpio_settings.button[i], label, true); + ++taskIndex; + } + + if (gpio_settings.relais[i] >= 0) { + String label = F("Relay"); + label += (i + 1); + addSwitchPlugin(taskIndex, gpio_settings.relais[i], label, false); + ++taskIndex; + } + } +} + +void addButtonRelayRule(uint8_t buttonNumber, int relay_gpio) { + Settings.UseRules = true; + String fileName; + + #if defined(ESP32) + fileName += '/'; + #endif // if defined(ESP32) + fileName += F("rules1.txt"); + String rule = F("on ButtonBNR#state do\n if [RelayBNR#state]=0\n gpio,GNR,1\n else\n gpio,GNR,0\n endif\nendon\n"); + rule.replace(F("BNR"), String(buttonNumber)); + rule.replace(F("GNR"), String(relay_gpio)); + String result = appendLineToFile(fileName, rule); + + if (result.length() > 0) { + addLogMove(LOG_LEVEL_ERROR, result); + } +} + +void addPredefinedRules(const GpioFactorySettingsStruct& gpio_settings) { + for (int i = 0; i < 4; ++i) { + if ((gpio_settings.button[i] >= 0) && (gpio_settings.relais[i] >= 0)) { + addButtonRelayRule((i + 1), gpio_settings.relais[i]); + } + } +} + +// ******************************************************************************** +// change of device: cleanup old device and reset default settings +// ******************************************************************************** +void setTaskDevice_to_TaskIndex(pluginID_t taskdevicenumber, taskIndex_t taskIndex) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + // let the plugin do its cleanup by calling PLUGIN_EXIT with this TaskIndex + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + taskClear(taskIndex, false); // clear settings, but do not save + ClearCustomTaskSettings(taskIndex); + + Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; + + // Settings.getPluginID_for_task(taskIndex) = taskdevicenumber; + + if (validPluginID_fullcheck(taskdevicenumber)) // set default values if a new device has been selected + { + // FIXME TD-er: Must check if this is working (e.g. need to set nr. decimals?) + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + + // NOTE: do not enable task by default. allow user to enter sensible valus first and let him enable it when ready. + PluginCall(PLUGIN_SET_DEFAULTS, &TempEvent, dummy); + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); // the plugin should populate ExtraTaskSettings with its default values. + } else { + // New task is empty task, thus save config now. + taskClear(taskIndex, true); // clear settings, and save + } +} + +// ******************************************************************************** +// Initialize task with some default values applicable for almost all tasks +// ******************************************************************************** +void setBasicTaskValues(taskIndex_t taskIndex, unsigned long taskdevicetimer, + bool enabled, const String& name, const int pins[3]) { + if (!validTaskIndex(taskIndex)) { return; } + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (!validDeviceIndex(DeviceIndex)) { return; } + + LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date + + if (taskdevicetimer > 0) { + Settings.TaskDeviceTimer[taskIndex] = taskdevicetimer; + } else { + if (!Device[DeviceIndex].TimerOptional) { // Set default delay, unless it's optional... + Settings.TaskDeviceTimer[taskIndex] = Settings.Delay; + } + else { + Settings.TaskDeviceTimer[taskIndex] = 0; + } + } + Settings.TaskDeviceEnabled[taskIndex] = enabled; + //Settings.TaskDeviceEnabled[taskIndex].enabled = enabled; + safe_strncpy(ExtraTaskSettings.TaskDeviceName, name.c_str(), sizeof(ExtraTaskSettings.TaskDeviceName)); + + // FIXME TD-er: Check for valid GPIO pin (and -1 for "not set") + Settings.TaskDevicePin1[taskIndex] = pins[0]; + Settings.TaskDevicePin2[taskIndex] = pins[1]; + Settings.TaskDevicePin3[taskIndex] = pins[2]; +} diff --git a/src/src/Helpers/Hardware_ADC.h b/src/src/Helpers/Hardware_ADC.h index e01ae1702..c9eb8e2a4 100644 --- a/src/src/Helpers/Hardware_ADC.h +++ b/src/src/Helpers/Hardware_ADC.h @@ -65,8 +65,8 @@ private: Hardware_ADC_cali_t _adc_cali_handle; # if ESP_IDF_VERSION_MAJOR >= 5 - adc_channel_t _channel; - adc_oneshot_unit_handle_t _adc_handle; + adc_channel_t _channel = ADC_CHANNEL_0; + adc_oneshot_unit_handle_t _adc_handle = nullptr; # endif // if ESP_IDF_VERSION_MAJOR >= 5 #endif // ifdef ESP32 diff --git a/src/src/Helpers/Hardware_ADC_cali.cpp b/src/src/Helpers/Hardware_ADC_cali.cpp index 913e79979..d097e7e88 100644 --- a/src/src/Helpers/Hardware_ADC_cali.cpp +++ b/src/src/Helpers/Hardware_ADC_cali.cpp @@ -1,211 +1,230 @@ -#include "../Helpers/Hardware_ADC_cali.h" - -#ifdef ESP32 - -//# include "../Helpers/ESPEasy_math.h" -# include "../Helpers/Hardware.h" - - -Hardware_ADC_cali_t::~Hardware_ADC_cali_t() -{ -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_useFactoryCalibration) { -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); - -# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - adc_cali_delete_scheme_line_fitting(_adc_cali_handle); -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - } - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 -} - -bool Hardware_ADC_cali_t::init(int pin, - adc_atten_t attenuation) -{ -# if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - _useHighResInterpolation = false; -# elif ESP_IDF_VERSION_MAJOR >= 5 - _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_12; -# else // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_11; -# endif // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_adc_cali_handle != nullptr) { -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); - -# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - adc_cali_delete_scheme_line_fitting(_adc_cali_handle); - -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - } - - _useFactoryCalibration = Hardware_ADC_cali_t::adc_calibration_init( - pin, - attenuation, - &_adc_cali_handle); - - if (_useFactoryCalibration) { - int tmp{}; - adc_cali_raw_to_voltage(_adc_cali_handle, 0, &tmp); - _min_out = tmp; - adc_cali_raw_to_voltage(_adc_cali_handle, MAX_ADC_VALUE, &tmp); - _max_out = tmp; - } - -# else // if ESP_IDF_VERSION_MAJOR >= 5 - # ifndef DEFAULT_VREF - # define DEFAULT_VREF 1100 - # endif // ifndef DEFAULT_VREF - constexpr adc_bits_width_t adc_bit_width = static_cast(ADC_WIDTH_MAX - 1); - _adc_calibration_type = - esp_adc_cal_characterize((getADC_num_for_gpio(pin) == 1) ? ADC_UNIT_1 : ADC_UNIT_2, - static_cast(attenuation), - adc_bit_width, - DEFAULT_VREF, - &_adc_chars); - _useFactoryCalibration = esp_adc_cal_check_efuse(_adc_calibration_type) == ESP_OK; - - if (_useFactoryCalibration) { - _min_out = esp_adc_cal_raw_to_voltage(0, &_adc_chars); - _max_out = esp_adc_cal_raw_to_voltage(MAX_ADC_VALUE, &_adc_chars); - } -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - - _initialized = true; - - return _useFactoryCalibration; -} - -float Hardware_ADC_cali_t::applyFactoryCalibration(float rawValue) const { - if (!_useFactoryCalibration) { - return rawValue; - } - - if (!_useHighResInterpolation) { - const int raw = rawValue; -# if ESP_IDF_VERSION_MAJOR >= 5 - int res{}; - adc_cali_raw_to_voltage(_adc_cali_handle, raw, &res); - return res; -# else // if ESP_IDF_VERSION_MAJOR >= 5 - return esp_adc_cal_raw_to_voltage(raw, &_adc_chars); -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - } - - // All other attenuations do appear to have a straight calibration curve. - // But applying the factory calibration then reduces resolution. - // So we interpolate using the calibrated extremes - - return mapADCtoFloat( - rawValue, - 0, - MAX_ADC_VALUE, - _min_out, - _max_out); -} - -const __FlashStringHelper * Hardware_ADC_cali_t::getADC_factory_calibration_type() const { -# if ESP_IDF_VERSION_MAJOR >= 5 - - if (_useFactoryCalibration) { - # if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - return F("Calibration Curve Fitting"); - # endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - # if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - return F("Calibration Line Fitting"); - # endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - } - -# else // if ESP_IDF_VERSION_MAJOR >= 5 - - switch (_adc_calibration_type) { - case ESP_ADC_CAL_VAL_EFUSE_VREF: return F("V_ref in eFuse"); - case ESP_ADC_CAL_VAL_EFUSE_TP: return F("Two Point values in eFuse"); - case ESP_ADC_CAL_VAL_DEFAULT_VREF: return F("Default reference voltage"); - case ESP_ADC_CAL_VAL_EFUSE_TP_FIT: return F("Two Point values and fitting curve in eFuse"); - case ESP_ADC_CAL_VAL_NOT_SUPPORTED: - break; - } -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - return F("Unknown"); -} - -# if ESP_IDF_VERSION_MAJOR >= 5 -bool Hardware_ADC_cali_t::adc_calibration_init( - int pin, - adc_atten_t atten, - adc_cali_handle_t *out_handle) -{ - int ch{}; - const int adc = getADC_num_for_gpio(pin, ch); - const adc_channel_t channel = static_cast(ch); - -# if HAS_ADC2 - const adc_unit_t unit = (adc == 1) ? ADC_UNIT_1 : ADC_UNIT_2; -# else // if HAS_ADC2 - const adc_unit_t unit = ADC_UNIT_1; -# endif // if HAS_ADC2 - - adc_cali_handle_t handle = NULL; - esp_err_t ret = ESP_FAIL; - bool calibrated = false; - -# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - - if (!calibrated) { - // calibration scheme version: Curve Fitting - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = unit, - .chan = channel, - .atten = atten, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); - - if (ret == ESP_OK) { - calibrated = true; - } - } -# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED - -# if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - - if (!calibrated) { - // calibration scheme version: Line Fitting - adc_cali_line_fitting_config_t cali_config = { - .unit_id = unit, - .atten = atten, - .bitwidth = ADC_BITWIDTH_DEFAULT, - }; - ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle); - - if (ret == ESP_OK) { - calibrated = true; - } - } -# endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED - - *out_handle = handle; - - /* - if (ret == ESP_OK) { - // Calibration Success - } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { - // eFuse not burnt, skip software calibration - } else { - // Invalid arg or no memory - } - */ - - return calibrated; -} - -# endif // if ESP_IDF_VERSION_MAJOR >= 5 - -#endif // ifdef ESP32 +#include "../Helpers/Hardware_ADC_cali.h" + +#ifdef ESP32 + +// # include "../Helpers/ESPEasy_math.h" +# include "../Helpers/Hardware.h" + + +Hardware_ADC_cali_t::~Hardware_ADC_cali_t() +{ +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_useFactoryCalibration) { +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); + +# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_delete_scheme_line_fitting(_adc_cali_handle); +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + } + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +} + +bool Hardware_ADC_cali_t::init(int pin, + adc_atten_t attenuation) +{ +# if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + _useHighResInterpolation = false; +# elif ESP_IDF_VERSION_MAJOR >= 5 + _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + _useHighResInterpolation = attenuation != adc_atten_t::ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 && ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_adc_cali_handle != nullptr) { +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + adc_cali_delete_scheme_curve_fitting(_adc_cali_handle); + _adc_cali_handle = nullptr; + +# elif ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + adc_cali_delete_scheme_line_fitting(_adc_cali_handle); + _adc_cali_handle = nullptr; + +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + } + + _useFactoryCalibration = Hardware_ADC_cali_t::adc_calibration_init( + pin, + attenuation, + &_adc_cali_handle); + + if (_useFactoryCalibration) { + int tmp{}; + adc_cali_raw_to_voltage(_adc_cali_handle, 0, &tmp); + _min_out = tmp; + adc_cali_raw_to_voltage(_adc_cali_handle, MAX_ADC_VALUE, &tmp); + _max_out = tmp; + } + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + # ifndef DEFAULT_VREF + # define DEFAULT_VREF 1100 + # endif // ifndef DEFAULT_VREF + constexpr adc_bits_width_t adc_bit_width = static_cast(ADC_WIDTH_MAX - 1); + _adc_calibration_type = + esp_adc_cal_characterize((getADC_num_for_gpio(pin) == 1) ? ADC_UNIT_1 : ADC_UNIT_2, + static_cast(attenuation), + adc_bit_width, + DEFAULT_VREF, + &_adc_chars); + _useFactoryCalibration = esp_adc_cal_check_efuse(_adc_calibration_type) == ESP_OK; + + if (_useFactoryCalibration) { + _min_out = esp_adc_cal_raw_to_voltage(0, &_adc_chars); + _max_out = esp_adc_cal_raw_to_voltage(MAX_ADC_VALUE, &_adc_chars); + } +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + + _initialized = true; + + return _useFactoryCalibration; +} + +float Hardware_ADC_cali_t::applyFactoryCalibration(float rawValue) const { + if (!_useFactoryCalibration) { + return rawValue; + } + + if (!_useHighResInterpolation) { +# if ESP_IDF_VERSION_MAJOR >= 5 + int adc_low = (static_cast(rawValue) - 128) & 0xFFFFFF80; + int adc_high = (static_cast(rawValue) + 128) & 0xFFFFFF80; + + if (adc_low < 0) { adc_low = 0; } + + if (adc_high > MAX_ADC_VALUE) { adc_high = MAX_ADC_VALUE; } + + int volt_low{}; + int volt_high{}; + + if ( + (adc_cali_raw_to_voltage(_adc_cali_handle, adc_low, &volt_low) == ESP_OK) && + (adc_cali_raw_to_voltage(_adc_cali_handle, adc_high, &volt_high) == ESP_OK)) { + return mapADCtoFloat( + rawValue, + adc_low, + adc_high, + volt_low, + volt_high); + } +# else // if ESP_IDF_VERSION_MAJOR >= 5 + const int raw = rawValue; + return esp_adc_cal_raw_to_voltage(raw, &_adc_chars); +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + } + + // All other attenuations do appear to have a straight calibration curve. + // But applying the factory calibration then reduces resolution. + // So we interpolate using the calibrated extremes + + return mapADCtoFloat( + rawValue, + 0, + MAX_ADC_VALUE, + _min_out, + _max_out); +} + +const __FlashStringHelper * Hardware_ADC_cali_t::getADC_factory_calibration_type() const { +# if ESP_IDF_VERSION_MAJOR >= 5 + + if (_useFactoryCalibration) { + # if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + return F("Calibration Curve Fitting"); + # endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + # if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + return F("Calibration Line Fitting"); + # endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + } + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + + switch (_adc_calibration_type) { + case ESP_ADC_CAL_VAL_EFUSE_VREF: return F("V_ref in eFuse"); + case ESP_ADC_CAL_VAL_EFUSE_TP: return F("Two Point values in eFuse"); + case ESP_ADC_CAL_VAL_DEFAULT_VREF: return F("Default reference voltage"); + case ESP_ADC_CAL_VAL_EFUSE_TP_FIT: return F("Two Point values and fitting curve in eFuse"); + case ESP_ADC_CAL_VAL_NOT_SUPPORTED: + break; + } +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + return F("Unknown"); +} + +# if ESP_IDF_VERSION_MAJOR >= 5 +bool Hardware_ADC_cali_t::adc_calibration_init( + int pin, + adc_atten_t atten, + adc_cali_handle_t *out_handle) +{ + int ch{}; + const int adc = getADC_num_for_gpio(pin, ch); + const adc_channel_t channel = static_cast(ch); + +# if HAS_ADC2 + const adc_unit_t unit = (adc == 1) ? ADC_UNIT_1 : ADC_UNIT_2; +# else // if HAS_ADC2 + const adc_unit_t unit = ADC_UNIT_1; +# endif // if HAS_ADC2 + + adc_cali_handle_t handle = NULL; + esp_err_t ret = ESP_FAIL; + bool calibrated = false; + +# if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + + if (!calibrated) { + // calibration scheme version: Curve Fitting + adc_cali_curve_fitting_config_t cali_config = { + .unit_id = unit, + .chan = channel, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle); + + if (ret == ESP_OK) { + calibrated = true; + } + } +# endif // if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED + +# if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + + if (!calibrated) { + // calibration scheme version: Line Fitting + adc_cali_line_fitting_config_t cali_config = { + .unit_id = unit, + .atten = atten, + .bitwidth = ADC_BITWIDTH_DEFAULT, + }; + ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle); + + if (ret == ESP_OK) { + calibrated = true; + } + } +# endif // if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED + + *out_handle = handle; + + /* + if (ret == ESP_OK) { + // Calibration Success + } else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) { + // eFuse not burnt, skip software calibration + } else { + // Invalid arg or no memory + } + */ + + return calibrated; +} + +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + +#endif // ifdef ESP32 diff --git a/src/src/Helpers/Hardware_PWM.cpp b/src/src/Helpers/Hardware_PWM.cpp index cdd954e39..e0ebcc522 100644 --- a/src/src/Helpers/Hardware_PWM.cpp +++ b/src/src/Helpers/Hardware_PWM.cpp @@ -3,6 +3,7 @@ #include "../ESPEasyCore/ESPEasyGPIO.h" #include "../Helpers/PortStatus.h" +#include "../Helpers/StringConverter.h" #include "../Globals/GlobalMapPortStatus.h" @@ -158,6 +159,7 @@ int8_t attachLedChannel(int pin, uint32_t frequency, uint8_t resolution) if (frequency == 0) { frequency = 1000; } + ledcDetach(pin); // See: https://github.com/espressif/arduino-esp32/issues/9212 return ledcAttach(pin, frequency, resolution) ? 0 : -1; } @@ -214,6 +216,7 @@ bool set_Gpio_PWM(int gpio, uint32_t dutyCycle, uint32_t fadeDuration_ms, uint32 return false; } portStatusStruct tempStatus; + if (frequency == 0) frequency = 1000; // FIXME TD-er: PWM values cannot be stored very well in the portStatusStruct. key = createKey(PLUGIN_GPIO, gpio); @@ -267,9 +270,20 @@ bool set_Gpio_PWM(int gpio, uint32_t dutyCycle, uint32_t fadeDuration_ms, uint32 start_duty = std::min(start_duty, static_cast((1 << resolution) - 1)); target_duty = std::min(target_duty, static_cast((1 << resolution) - 1)); - ledcAttach(gpio, frequency, resolution); + // ledcDetach(gpio); + + if (!ledcWrite(gpio, start_duty)) { + // Pin not yet attached + if (!ledcAttach(gpio, frequency, resolution)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("PWM : ledcAttach failed gpio:%d freq:%d res:%d"), + gpio, frequency, resolution)); + return false; + } + } if (!ledcFade(gpio, start_duty, target_duty, fadeDuration_ms)) { + addLog(LOG_LEVEL_ERROR, F("PWM : ledcFade failed")); return false; } } diff --git a/src/src/Helpers/Hardware_device_info.cpp b/src/src/Helpers/Hardware_device_info.cpp index 67aaec7ca..47d0b4ec9 100644 --- a/src/src/Helpers/Hardware_device_info.cpp +++ b/src/src/Helpers/Hardware_device_info.cpp @@ -2,6 +2,7 @@ #include "../Helpers/Hardware_defines.h" #include "../Helpers/StringConverter.h" +#include "../Helpers/FS_Helper.h" #ifdef ESP32 # include @@ -248,9 +249,28 @@ String getChipFeaturesString() { if (getChipFeatures().ieee_802_15_4) { features += F("IEEE 802.15.4 / "); } - if (getChipFeatures().embeddedFlash) { features += F("Emb. Flash / "); } + const int32_t flash_cap = getEmbeddedFlashSize(); - if (getChipFeatures().embeddedPSRAM) { features += F("Emb. PSRAM"); } + if (getChipFeatures().embeddedFlash || (flash_cap != 0)) { + if (flash_cap > 0) { + features += strformat(F("%dMB "), flash_cap); + } else if (flash_cap < 0) { + features += strformat(F("(%d) "), flash_cap); + } + features += F("Emb. Flash"); + features += F(" / "); + } + + const int32_t psram_cap = getEmbeddedPSRAMSize(); + + if (getChipFeatures().embeddedPSRAM || (psram_cap != 0)) { + if (psram_cap > 0) { + features += strformat(F("%dMB "), psram_cap); + } else if (psram_cap < 0) { + features += strformat(F("(%d) "), psram_cap); + } + features += F("Emb. PSRAM"); + } features.trim(); if (features.endsWith(F("/"))) { features = features.substring(0, features.length() - 1); } @@ -589,8 +609,18 @@ const __FlashStringHelper* getChipModel() { - Reliable security features ensured by RSA-based secure boot, AES-XTS-based flash encryption, the innovative digital signature and the HMAC peripheral, “World Controller†*/ + +/* + + efuse_reg.h: + EFUSE_RD_MAC_SPI_SYS_0_REG = block1_addr + EFUSE_RD_MAC_SPI_SYS_3_REG = block1_addr + (4 * num_word)) // (num_word = 3) + +*/ + # ifdef CONFIG_IDF_TARGET_ESP32S3 # if (ESP_IDF_VERSION_MAJOR >= 5) + pkg_version = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_PKG_VERSION); switch (pkg_version) { case 0: return F("ESP32-S3"); // QFN56 diff --git a/src/src/Helpers/Hardware_device_info.h b/src/src/Helpers/Hardware_device_info.h index b768cad10..b162e6cfa 100644 --- a/src/src/Helpers/Hardware_device_info.h +++ b/src/src/Helpers/Hardware_device_info.h @@ -52,6 +52,9 @@ struct esp32_chip_features { esp32_chip_features getChipFeatures(); String getChipFeaturesString(); +int32_t getEmbeddedFlashSize(); +int32_t getEmbeddedPSRAMSize(); + // @retval true: octal (8 data lines) // @retval false: quad (4 data lines) bool getFlashChipOPI_wired(); diff --git a/src/src/Helpers/Hardware_device_info_ESP32.cpp b/src/src/Helpers/Hardware_device_info_ESP32.cpp new file mode 100644 index 000000000..0134bf464 --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32.cpp @@ -0,0 +1,16 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32_CLASSIC + +int32_t getEmbeddedFlashSize() +{ + return 0; +} + +int32_t getEmbeddedPSRAMSize() +{ + // FIXME TD-er: Need to implement + return 0; +} + +#endif diff --git a/src/src/Helpers/Hardware_device_info_ESP32C2.cpp b/src/src/Helpers/Hardware_device_info_ESP32C2.cpp new file mode 100644 index 000000000..14963dc57 --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32C2.cpp @@ -0,0 +1,17 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32C2 + +int32_t getEmbeddedFlashSize() +{ + // ESP32-C2 doesn't have eFuse field FLASH_CAP. + // Can't get info about the flash chip. + return 0; +} + +int32_t getEmbeddedPSRAMSize() +{ + // Doesn't have PSRAM + return 0; +} +#endif diff --git a/src/src/Helpers/Hardware_device_info_ESP32C3.cpp b/src/src/Helpers/Hardware_device_info_ESP32C3.cpp new file mode 100644 index 000000000..9e47b5d29 --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32C3.cpp @@ -0,0 +1,44 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32C3 + +// See: https://github.com/espressif/esptool/blob/master/esptool/targets/esp32c3.py + + + # include + # include + # include + # include + + +/** EFUSE_FLASH_CAP : R; bitpos: [29:27]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_3_REG + */ + # define EFUSE_FLASH_CAP 0x00000007U + # define EFUSE_FLASH_CAP_M (EFUSE_FLASH_CAP_V << EFUSE_FLASH_CAP_S) + # define EFUSE_FLASH_CAP_V 0x00000007U + # define EFUSE_FLASH_CAP_S 27 + + +int32_t getEmbeddedFlashSize() +{ + const uint32_t flash_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_FLASH_CAP); + + 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); +} + +int32_t getEmbeddedPSRAMSize() +{ + // Doesn't have PSRAM + return 0; +} +#endif diff --git a/src/src/Helpers/Hardware_device_info_ESP32C6.cpp b/src/src/Helpers/Hardware_device_info_ESP32C6.cpp new file mode 100644 index 000000000..484ee2b7b --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32C6.cpp @@ -0,0 +1,33 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32C6 + +# include +# include +# include +# include + +int32_t getEmbeddedFlashSize() +{ + // See: framework-arduinoespressif32\tools\esp32-arduino-libs\esp32c6\include\soc\esp32c6\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); +} + +int32_t getEmbeddedPSRAMSize() +{ + // Doesn't have PSRAM + return 0; +} +#endif diff --git a/src/src/Helpers/Hardware_device_info_ESP32S2.cpp b/src/src/Helpers/Hardware_device_info_ESP32S2.cpp new file mode 100644 index 000000000..ea23ec982 --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32S2.cpp @@ -0,0 +1,63 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32S2 + +// See: https://github.com/espressif/esptool/blob/master/esptool/targets/esp32s2.py + + # include + # include + # include + # include + + +// Flash datalines: https://github.com/tasmota/esp-idf/blob/206ce4b7f875bf5568ba47aba23f4b28e81b0574/components/efuse/esp32s2/esp_efuse_table.csv#L155 + + + +/** EFUSE_FLASH_CAP : R; bitpos: [24:21]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_3_REG + */ + # define EFUSE_FLASH_CAP 0x0000000FU + # define EFUSE_FLASH_CAP_M (EFUSE_FLASH_CAP_V << EFUSE_FLASH_CAP_S) + # define EFUSE_FLASH_CAP_V 0x0000000FU + # define EFUSE_FLASH_CAP_S 21 + + +int32_t getEmbeddedFlashSize() +{ + const uint32_t flash_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_FLASH_CAP); + + switch (flash_cap) { + case 0: return 0; + case 1: return 2; + case 2: return 4; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(flash_cap); +} + +/** EFUSE_PSRAM_CAP : R; bitpos: [31:28]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_3_REG + */ + # define EFUSE_PSRAM_CAP 0x0000000FU + # define EFUSE_PSRAM_CAP_M (EFUSE_PSRAM_CAP_V << EFUSE_PSRAM_CAP_S) + # define EFUSE_PSRAM_CAP_V 0x0000000FU + # define EFUSE_PSRAM_CAP_S 28 + +int32_t getEmbeddedPSRAMSize() +{ + const uint32_t psram_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_PSRAM_CAP); + + switch (psram_cap) { + case 0: return 0; + case 1: return 2; + case 2: return 4; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(psram_cap); +} + + +#endif diff --git a/src/src/Helpers/Hardware_device_info_ESP32S3.cpp b/src/src/Helpers/Hardware_device_info_ESP32S3.cpp new file mode 100644 index 000000000..298e8540f --- /dev/null +++ b/src/src/Helpers/Hardware_device_info_ESP32S3.cpp @@ -0,0 +1,99 @@ +#include "../Helpers/Hardware_device_info.h" + +#ifdef ESP32S3 + +// See: +// - https://github.com/espressif/esptool/blob/master/esptool/targets/esp32s3.py +// - https://github.com/tasmota/esp-idf/blob/206ce4b7f875bf5568ba47aba23f4b28e81b0574/components/efuse/esp32s3/esp_efuse_table.csv#L203-L208 + # include + # include + # include + # include + +// Flash data lines: https://github.com/tasmota/esp-idf/blob/206ce4b7f875bf5568ba47aba23f4b28e81b0574/components/efuse/esp32s3/esp_efuse_table.csv#L175 + +/** EFUSE_FLASH_CAP : R; bitpos: [29:27]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_3_REG + */ + # define EFUSE_FLASH_CAP 0x00000007U + # define EFUSE_FLASH_CAP_M (EFUSE_FLASH_CAP_V << EFUSE_FLASH_CAP_S) + # define EFUSE_FLASH_CAP_V 0x00000007U + # define EFUSE_FLASH_CAP_S 27 + + +/** EFUSE_FLASH_VENDOR : R; bitpos: [2:0]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_4_REG + */ + # define EFUSE_FLASH_VENDOR 0x00000007U + # define EFUSE_FLASH_VENDOR_M (EFUSE_FLASH_VENDOR_V << EFUSE_FLASH_VENDOR_S) + # define EFUSE_FLASH_VENDOR_V 0x00000007U + # define EFUSE_FLASH_VENDOR_S 0 + + +/* + switch (flash_vendor) + { + case 1: features += F("(XMC)"); break; + case 2: features += F("(GD)"); break; + case 3: features += F("(FM)"); break; + case 4: features += F("(TT)"); break; + case 5: features += F("(BY)"); break; + } + */ + + +int32_t getEmbeddedFlashSize() +{ + const uint32_t flash_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_FLASH_CAP); + + switch (flash_cap) { + case 0: return 0; + case 1: return 8; + case 2: return 4; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(flash_cap); +} + +/** EFUSE_PSRAM_CAP : R; bitpos: [4:3]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_4_REG + */ + # define EFUSE_PSRAM_CAP 0x00000003U + # define EFUSE_PSRAM_CAP_M (EFUSE_PSRAM_CAP_V << EFUSE_PSRAM_CAP_S) + # define EFUSE_PSRAM_CAP_V 0x00000003U + # define EFUSE_PSRAM_CAP_S 3 + +/** EFUSE_PSRAM_VENDOR : R; bitpos: [8:7]; default: 0; + * register: EFUSE_RD_MAC_SPI_SYS_4_REG + */ + # define EFUSE_PSRAM_VENDOR 0x00000003U + # define EFUSE_PSRAM_VENDOR_M (EFUSE_PSRAM_VENDOR_V << EFUSE_PSRAM_VENDOR_S) + # define EFUSE_PSRAM_VENDOR_V 0x00000003U + # define EFUSE_PSRAM_VENDOR_S 7 + + +/* + switch (psram_vendor) + { + case 1: features += F("(AP_3v3)"); break; + case 2: features += F("(AP_1v8)"); break; + } +*/ + + +int32_t getEmbeddedPSRAMSize() +{ + const uint32_t psram_cap = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_4_REG, EFUSE_PSRAM_CAP); + + switch (psram_cap) { + case 0: return 0; + case 1: return 8; + case 2: return 2; + } + + // Unknown value, thus mark as negative value + return -1 * static_cast(psram_cap); +} + +#endif // ifdef ESP32S3 diff --git a/src/src/Helpers/Hardware_temperature_sensor.cpp b/src/src/Helpers/Hardware_temperature_sensor.cpp index 4cd04f086..f46e08c02 100644 --- a/src/src/Helpers/Hardware_temperature_sensor.cpp +++ b/src/src/Helpers/Hardware_temperature_sensor.cpp @@ -1,219 +1,222 @@ -#include "../Helpers/Hardware_temperature_sensor.h" - - -#if FEATURE_INTERNAL_TEMPERATURE - -# include "../Helpers/StringConverter.h" - -/** - * Code based on: - * https://github.com/esphome/esphome/blob/518ecb4cc4489c8a76b899bfda7576b05d84c226/esphome/components/internal_temperature/internal_temperature.cpp#L40 - */ - -# ifdef ESP32 -# if defined(ESP32_CLASSIC) - -// there is no official API available on the original ESP32 -extern "C" { -uint8_t temprature_sens_read(); -} -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) -# if ESP_IDF_VERSION_MAJOR < 5 -# include - -// Work-around for bug in ESP-IDF < 5.0 -# if defined(ESP32S3) || defined(ESP32C3) -# include -# elif defined(ESP32S2) -# include -# endif // if defined(ESP32S3) || defined(ESP32C3) -# else // if ESP_IDF_VERSION_MAJOR < 5 -# include -# endif // if ESP_IDF_VERSION_MAJOR < 5 -# endif // ESP32_CLASSIC -# endif // ESP32 - - -# ifdef ESP32 -# if defined(ESP32_CLASSIC) - - -esp_err_t do_read_internal_temperature(float& celsius) { - esp_err_t result = ESP_FAIL; - uint8_t raw = 128u; - int8_t retries = 2; - - while ((128u == raw) && (0 != retries)) { - delay(0); - raw = temprature_sens_read(); // Each reading takes about 112 microseconds - --retries; - } -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, concat(F("ESP32: Raw temperature value: "), raw)); -# endif // ifndef BUILD_NO_DEBUG - - if (raw != 128) { - result = ESP_OK; - - // Raw value is in Fahrenheit - celsius = (raw - 32) / 1.8f; - } - return result; -} - -# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) - -esp_err_t do_read_internal_temperature(float& celsius) { - esp_err_t result = ESP_FAIL; - - celsius = 0.0f; // Make sure it is initialized and within the default range. - -# if ESP_IDF_VERSION_MAJOR < 5 - - temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); - - temp_sensor_set_config(tsens); - temp_sensor_start(); - - // Work-around for bug in ESP-IDF < 5.0 - // Seems to be fixed in ESP_IDF5.1 - // temp_sensor_get_config always returns ESP_OK - // Thus dac_offset can be just about anything - // dac_offset is used as index in an array without bounds checking - { -# if defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) - static float s_deltaT = (esp_efuse_rtc_calib_get_ver() == 1) ? - (esp_efuse_rtc_calib_get_cal_temp(1) / 10.0f) : - 0.0f; -# elif defined(ESP32S2) - static uint32_t version = esp_efuse_rtc_table_read_calib_version(); - static float s_deltaT = (version == 1 || version == 2) ? - (esp_efuse_rtc_table_get_parsed_efuse_value(RTCCALIB_IDX_TMPSENSOR, false) / 10.0f) : - 0.0f; -# endif // if defined(ESP32S3) || defined(ESP32C3) - - - /* - if (isnan(s_deltaT)) { //suggests that the value is not initialized - uint32_t version = esp_efuse_rtc_calib_get_ver(); - if (version == 1) { - // fetch calibration value for temp sensor from eFuse - s_deltaT = esp_efuse_rtc_calib_get_cal_temp(version); - } else { - // no value to fetch, use 0. - s_deltaT = 0; - } - } - */ -# ifndef TSENS_ADC_FACTOR -# define TSENS_ADC_FACTOR (0.4386) -# endif // ifndef TSENS_ADC_FACTOR -# ifndef TSENS_DAC_FACTOR -# define TSENS_DAC_FACTOR (27.88) -# endif // ifndef TSENS_DAC_FACTOR -# ifndef TSENS_SYS_OFFSET -# define TSENS_SYS_OFFSET (20.52) -# endif // ifndef TSENS_SYS_OFFSET - uint32_t tsens_raw{}; - temp_sensor_read_raw(&tsens_raw); - celsius = (TSENS_ADC_FACTOR * tsens_raw) - s_deltaT - TSENS_SYS_OFFSET; - result = ESP_OK; - } - -# else // if ESP_IDF_VERSION_MAJOR < 5 - - // result = temp_sensor_read_celsius(&celsius); - - - static temperature_sensor_handle_t temp_sensor = nullptr; - - // Use range which seems to have the smallest error - // See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/temp_sensor.html - static int range_min = -10; - static int range_max = 80; - - bool must_reinstall = false; - - if (temp_sensor == nullptr) { - temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(range_min, range_max); - result = temperature_sensor_install(&temp_sensor_config, &temp_sensor); - } else { - result = ESP_OK; - } - - if (ESP_OK == result) { - result = temperature_sensor_enable(temp_sensor); - - if (result == ESP_ERR_INVALID_STATE) { - // Sensor reports to be not enabled - must_reinstall = true; - } - } - - if (ESP_OK == result) { - result = temperature_sensor_get_celsius(temp_sensor, &celsius); - - // FIXME TD-er: What to do when result == ESP_FAIL (can be indication of out-of-range) - if (celsius < (range_min + 10)) { - range_min -= 10; - - if (range_min > celsius) { - range_min = celsius - 10; - } - must_reinstall = true; - } - - if (celsius > (range_max - 10)) { - range_max += 10; - - if (range_max < celsius) { - range_max = celsius + 10; - } - must_reinstall = true; - } - } - - temperature_sensor_disable(temp_sensor); - - - if (must_reinstall) { - temperature_sensor_uninstall(temp_sensor); - temp_sensor = nullptr; - } - - -# endif // if ESP_IDF_VERSION_MAJOR < 5 - - return result; -} - -# endif // if defined(ESP32_CLASSIC) -# endif // ifdef ESP32 - - -bool getInternalTemperature(float& temperatureCelsius) { - static float temperature_filtered = NAN; // Improbable value - float celsius{}; - esp_err_t result = do_read_internal_temperature(celsius); - - if (ESP_OK == result) { - if (isnanf(temperature_filtered)) { - temperature_filtered = celsius; - } else { - constexpr float IIR_FACTOR = 5.0f; - constexpr float IIR_DIVIDER = IIR_FACTOR + 1.0f; - temperature_filtered = ((IIR_FACTOR * temperature_filtered) + celsius) / IIR_DIVIDER; - } - } - temperatureCelsius = temperature_filtered; - return ESP_OK == result; -} - -float getInternalTemperature() { - float temperatureCelsius{}; - - getInternalTemperature(temperatureCelsius); - return temperatureCelsius; -} - -#endif // if FEATURE_INTERNAL_TEMPERATURE +#include "../Helpers/Hardware_temperature_sensor.h" + + +#if FEATURE_INTERNAL_TEMPERATURE + +# include "../Helpers/StringConverter.h" + +/** + * Code based on: + * https://github.com/esphome/esphome/blob/518ecb4cc4489c8a76b899bfda7576b05d84c226/esphome/components/internal_temperature/internal_temperature.cpp#L40 + */ + +# ifdef ESP32 +# if defined(ESP32_CLASSIC) + +// there is no official API available on the original ESP32 +extern "C" { +uint8_t temprature_sens_read(); +} +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) +# if ESP_IDF_VERSION_MAJOR < 5 +# include + +// Work-around for bug in ESP-IDF < 5.0 +# if defined(ESP32S3) || defined(ESP32C3) +# include +# elif defined(ESP32S2) +# include +# endif // if defined(ESP32S3) || defined(ESP32C3) +# else // if ESP_IDF_VERSION_MAJOR < 5 +# include +# endif // if ESP_IDF_VERSION_MAJOR < 5 +# endif // ESP32_CLASSIC +# endif // ESP32 + + +# ifdef ESP32 +# if defined(ESP32_CLASSIC) + + +esp_err_t do_read_internal_temperature(float& celsius) { + esp_err_t result = ESP_FAIL; + uint8_t raw = 128u; + int8_t retries = 2; + + while ((128u == raw) && (0 != retries)) { + delay(0); + raw = temprature_sens_read(); // Each reading takes about 112 microseconds + --retries; + } +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("ESP32: Raw temperature value: "), raw)); +# endif // ifndef BUILD_NO_DEBUG + + if (raw != 128) { + result = ESP_OK; + + // Raw value is in Fahrenheit + celsius = (raw - 32) / 1.8f; + } + return result; +} + +# elif defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) || defined(ESP32S2) || defined(ESP32S3) + +esp_err_t do_read_internal_temperature(float& celsius) { + esp_err_t result = ESP_FAIL; + + celsius = 0.0f; // Make sure it is initialized and within the default range. + +# if ESP_IDF_VERSION_MAJOR < 5 + + temp_sensor_config_t tsens = TSENS_CONFIG_DEFAULT(); + + temp_sensor_set_config(tsens); + temp_sensor_start(); + + // Work-around for bug in ESP-IDF < 5.0 + // Seems to be fixed in ESP_IDF5.1 + // temp_sensor_get_config always returns ESP_OK + // Thus dac_offset can be just about anything + // dac_offset is used as index in an array without bounds checking + { +# if defined(ESP32S3) || defined(ESP32C2) || defined(ESP32C3) || defined(ESP32C6) + static float s_deltaT = (esp_efuse_rtc_calib_get_ver() == 1) ? + (esp_efuse_rtc_calib_get_cal_temp(1) / 10.0f) : + 0.0f; +# elif defined(ESP32S2) + static uint32_t version = esp_efuse_rtc_table_read_calib_version(); + static float s_deltaT = (version == 1 || version == 2) ? + (esp_efuse_rtc_table_get_parsed_efuse_value(RTCCALIB_IDX_TMPSENSOR, false) / 10.0f) : + 0.0f; +# endif // if defined(ESP32S3) || defined(ESP32C3) + + + /* + if (isnan(s_deltaT)) { //suggests that the value is not initialized + uint32_t version = esp_efuse_rtc_calib_get_ver(); + if (version == 1) { + // fetch calibration value for temp sensor from eFuse + s_deltaT = esp_efuse_rtc_calib_get_cal_temp(version); + } else { + // no value to fetch, use 0. + s_deltaT = 0; + } + } + */ +# ifndef TSENS_ADC_FACTOR +# define TSENS_ADC_FACTOR (0.4386) +# endif // ifndef TSENS_ADC_FACTOR +# ifndef TSENS_DAC_FACTOR +# define TSENS_DAC_FACTOR (27.88) +# endif // ifndef TSENS_DAC_FACTOR +# ifndef TSENS_SYS_OFFSET +# define TSENS_SYS_OFFSET (20.52) +# endif // ifndef TSENS_SYS_OFFSET + uint32_t tsens_raw{}; + temp_sensor_read_raw(&tsens_raw); + celsius = (TSENS_ADC_FACTOR * tsens_raw) - s_deltaT - TSENS_SYS_OFFSET; + result = ESP_OK; + } + +# else // if ESP_IDF_VERSION_MAJOR < 5 + + // result = temp_sensor_read_celsius(&celsius); + + + static temperature_sensor_handle_t temp_sensor = nullptr; + + // Use range which seems to have the smallest error + // See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32c3/api-reference/peripherals/temp_sensor.html + static int range_min = -10; + static int range_max = 80; + + bool must_reinstall = false; + + if (temp_sensor == nullptr) { + temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(range_min, range_max); + result = temperature_sensor_install(&temp_sensor_config, &temp_sensor); + } else { + result = ESP_OK; + } + + if (ESP_OK == result) { + result = temperature_sensor_enable(temp_sensor); + + if (result == ESP_ERR_INVALID_STATE) { + // Sensor reports to be not enabled + must_reinstall = true; + } + } + + if (ESP_OK == result) { + result = temperature_sensor_get_celsius(temp_sensor, &celsius); + if (result == ESP_FAIL) { + must_reinstall = true; + } + + // FIXME TD-er: What to do when result == ESP_FAIL (can be indication of out-of-range) + if (celsius < (range_min + 10)) { + range_min -= 10; + + if (range_min > celsius) { + range_min = celsius - 10; + } + must_reinstall = true; + } + + if (celsius > (range_max - 10)) { + range_max += 10; + + if (range_max < celsius) { + range_max = celsius + 10; + } + must_reinstall = true; + } + } + + temperature_sensor_disable(temp_sensor); + + + if (must_reinstall) { + temperature_sensor_uninstall(temp_sensor); + temp_sensor = nullptr; + } + + +# endif // if ESP_IDF_VERSION_MAJOR < 5 + + return result; +} + +# endif // if defined(ESP32_CLASSIC) +# endif // ifdef ESP32 + + +bool getInternalTemperature(float& temperatureCelsius) { + static float temperature_filtered = NAN; // Improbable value + float celsius{}; + esp_err_t result = do_read_internal_temperature(celsius); + + if (ESP_OK == result) { + if (isnanf(temperature_filtered)) { + temperature_filtered = celsius; + } else { + constexpr float IIR_FACTOR = 5.0f; + constexpr float IIR_DIVIDER = IIR_FACTOR + 1.0f; + temperature_filtered = ((IIR_FACTOR * temperature_filtered) + celsius) / IIR_DIVIDER; + } + } + temperatureCelsius = temperature_filtered; + return ESP_OK == result; +} + +float getInternalTemperature() { + float temperatureCelsius{}; + + getInternalTemperature(temperatureCelsius); + return temperatureCelsius; +} + +#endif // if FEATURE_INTERNAL_TEMPERATURE diff --git a/src/src/Helpers/Improv_Helper.cpp b/src/src/Helpers/Improv_Helper.cpp index 66b7eeb64..c646fa087 100644 --- a/src/src/Helpers/Improv_Helper.cpp +++ b/src/src/Helpers/Improv_Helper.cpp @@ -17,13 +17,14 @@ void OnImprovError(ImprovTypes::Error error) String log = F("IMPROV : "); switch (error) { + case ImprovTypes::Error::ERROR_NONE: return; case ImprovTypes::Error::ERROR_INVALID_RPC: log += F("Invalid RPC"); break; case ImprovTypes::Error::ERROR_UNKNOWN_RPC: log += F("Unkown RPC"); break; case ImprovTypes::Error::ERROR_UNABLE_TO_CONNECT: log += F("Unable to connect"); break; case ImprovTypes::Error::ERROR_NOT_AUTHORIZED: log += F("Not Authorized"); break; + case ImprovTypes::Error::ERROR_INVALID_CHECKSUM: log += F("Invalid Checksum"); break; + case ImprovTypes::Error::ERROR_EMPTY_SSID: log += F("Empty SSID"); break; case ImprovTypes::Error::ERROR_UNKNOWN: log += F("Unknown"); break; - default: - return; } addLogMove(LOG_LEVEL_ERROR, log); } @@ -34,16 +35,28 @@ void OnImprovConnected(const char *ssid, const char *password) safe_strncpy( SecuritySettings.WifiSSID, ssid, - sizeof(SecuritySettings.WifiSSID2)); + sizeof(SecuritySettings.WifiSSID)); safe_strncpy( SecuritySettings.WifiKey, password, sizeof(SecuritySettings.WifiKey)); - SaveSettings(); + SaveSecuritySettings(); } bool OnImprovESPEasyConnectWiFi(const char *ssid, const char *password) { +// addLog(LOG_LEVEL_INFO, strformat(F("IMPROV WiFi connect: SSID: %s, Pass: %s"), ssid, password)); +/* + safe_strncpy( + SecuritySettings.WifiSSID, + ssid, + sizeof(SecuritySettings.WifiSSID)); + safe_strncpy( + SecuritySettings.WifiKey, + password, + sizeof(SecuritySettings.WifiKey)); + */ + return false; } @@ -56,7 +69,7 @@ void Improv_Helper_t::init() _improv.onImprovConnected(OnImprovConnected); // FIXME TD-er: Implement callback to use ESPEasy functions to connect to WiFi - // _improv.setCustomTryConnectToWiFi(OnImprovESPEasyConnectWiFi); +// _improv.setCustomTryConnectToWiFi(OnImprovESPEasyConnectWiFi); String firmwareName = get_binary_filename(); const String buildString = getSystemBuildString(); @@ -111,7 +124,29 @@ bool Improv_Helper_t::handle(uint8_t b, Stream *serialForWrite) _tmpbuffer.push_back(b); - switch (_improv.handleSerial(b, serialForWrite)) { + const ImprovTypes::ParseState state = _improv.handleSerial(b, serialForWrite); + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG) && + state != ImprovTypes::ParseState::VALID_INCOMPLETE) + { + String log = F("IMPROVDEBUG: "); + for (auto it = _tmpbuffer.begin(); it != _tmpbuffer.end(); ++it) { + if (isAlphaNumeric(*it)) + log += static_cast(*it); + else { + log += strformat(F("_%d_"), *it); + } + } + if (state == ImprovTypes::ParseState::INVALID) { + log += F(" (invalid)"); + } + addLog(LOG_LEVEL_DEBUG, log); + } +#endif + + switch (state) { case ImprovTypes::ParseState::VALID_INCOMPLETE: _mustDumpBuffer = false; return true; diff --git a/src/src/Helpers/Memory.cpp b/src/src/Helpers/Memory.cpp index 412230893..b4749f11c 100644 --- a/src/src/Helpers/Memory.cpp +++ b/src/src/Helpers/Memory.cpp @@ -1,138 +1,199 @@ -#include "../Helpers/Memory.h" - - -#ifdef ESP8266 -extern "C" { -#include -} -#endif - -#include "../../ESPEasy_common.h" - - -#ifdef ESP32 -#if ESP_IDF_VERSION_MAJOR < 5 -#include -#endif -#endif - -#include "../Helpers/Hardware_device_info.h" - -/*********************************************************************************************\ - Memory management -\*********************************************************************************************/ - - -// For keeping track of 'cont' stack -// See: https://github.com/esp8266/Arduino/issues/2557 -// https://github.com/esp8266/Arduino/issues/5148#issuecomment-424329183 -// https://github.com/letscontrolit/ESPEasy/issues/1824 -#ifdef ESP32 - -// FIXME TD-er: For ESP32 you need to provide the task number, or nullptr to get from the calling task. -uint32_t getCurrentFreeStack() { - return ((uint8_t*)esp_cpu_get_sp()) - pxTaskGetStackStart(nullptr); -} - -uint32_t getFreeStackWatermark() { - return uxTaskGetStackHighWaterMark(nullptr); -} - -#else // ifdef ESP32 - -uint32_t getCurrentFreeStack() { - // https://github.com/esp8266/Arduino/issues/2557 - register uint32_t *sp asm ("a1"); - - return 4 * (sp - g_pcont->stack); -} - -uint32_t getFreeStackWatermark() { - return cont_get_free_stack(g_pcont); -} - -bool allocatedOnStack(const void *address) { - register uint32_t *sp asm ("a1"); - - if (sp < address) { return false; } - return g_pcont->stack < address; -} - -#endif // ESP32 - - -/********************************************************************************************\ - Get free system mem - \*********************************************************************************************/ -unsigned long FreeMem() -{ - #if defined(ESP8266) - return system_get_free_heap_size(); - #endif // if defined(ESP8266) - #if defined(ESP32) - return ESP.getFreeHeap(); - #endif // if defined(ESP32) -} - -#ifdef USE_SECOND_HEAP -unsigned long FreeMem2ndHeap() -{ - HeapSelectIram ephemeral; - return ESP.getFreeHeap(); -} -#endif - - -unsigned long getMaxFreeBlock() -{ - const unsigned long freemem = FreeMem(); - // computing max free block is a rather extensive operation, so only perform when free memory is already low. - if (freemem < 6144) { - #if defined(ESP32) - return ESP.getMaxAllocHeap(); - #endif // if defined(ESP32) - #ifdef CORE_POST_2_5_0 - return ESP.getMaxFreeBlockSize(); - #endif // ifdef CORE_POST_2_5_0 - } - return freemem; -} - -/********************************************************************************************\ - Special alloc functions to allocate in PSRAM if available - \*********************************************************************************************/ - -void *special_malloc(uint32_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return malloc(size); - } - #else - return malloc(size); - #endif -} - -void *special_realloc(void *ptr, size_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return realloc(ptr, size); - } - #else - return realloc(ptr, size); - #endif -} -void *special_calloc(size_t num, size_t size) { - #ifdef ESP32 - if (UsePSRAM()) { - return heap_caps_calloc(num, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - } else { - return calloc(num, size); - } - #else - return calloc(num, size); - #endif -} \ No newline at end of file +#include "../Helpers/Memory.h" + + +#ifdef ESP8266 +extern "C" { +# include +} +#endif // ifdef ESP8266 + +#include "../../ESPEasy_common.h" + + +#ifdef ESP32 +# if ESP_IDF_VERSION_MAJOR < 5 +# include +# endif // if ESP_IDF_VERSION_MAJOR < 5 +#endif // ifdef ESP32 + +#include "../Helpers/Hardware_device_info.h" + +/*********************************************************************************************\ + Memory management +\*********************************************************************************************/ + + +// For keeping track of 'cont' stack +// See: https://github.com/esp8266/Arduino/issues/2557 +// https://github.com/esp8266/Arduino/issues/5148#issuecomment-424329183 +// https://github.com/letscontrolit/ESPEasy/issues/1824 +#ifdef ESP32 + +// FIXME TD-er: For ESP32 you need to provide the task number, or nullptr to get from the calling task. +uint32_t getCurrentFreeStack() { + return ((uint8_t *)esp_cpu_get_sp()) - pxTaskGetStackStart(nullptr); +} + +uint32_t getFreeStackWatermark() { + return uxTaskGetStackHighWaterMark(nullptr); +} + +#else // ifdef ESP32 + +uint32_t getCurrentFreeStack() { + // https://github.com/esp8266/Arduino/issues/2557 + register uint32_t *sp asm ("a1"); + + return 4 * (sp - g_pcont->stack); +} + +uint32_t getFreeStackWatermark() { + return cont_get_free_stack(g_pcont); +} + +bool allocatedOnStack(const void *address) { + register uint32_t *sp asm ("a1"); + + if (sp < address) { return false; } + return g_pcont->stack < address; +} + +#endif // ESP32 + + +/********************************************************************************************\ + Get free system mem + \*********************************************************************************************/ +unsigned long FreeMem() +{ + #if defined(ESP8266) + return system_get_free_heap_size(); + #endif // if defined(ESP8266) + #if defined(ESP32) + return ESP.getFreeHeap(); + #endif // if defined(ESP32) +} + +#ifdef USE_SECOND_HEAP +unsigned long FreeMem2ndHeap() +{ + HeapSelectIram ephemeral; + + return ESP.getFreeHeap(); +} + +#endif // ifdef USE_SECOND_HEAP + + +unsigned long getMaxFreeBlock() +{ + const unsigned long freemem = FreeMem(); + + // computing max free block is a rather extensive operation, so only perform when free memory is already low. + if (freemem < 6144) { + #if defined(ESP32) + return ESP.getMaxAllocHeap(); + #endif // if defined(ESP32) + #ifdef CORE_POST_2_5_0 + return ESP.getMaxFreeBlockSize(); + #endif // ifdef CORE_POST_2_5_0 + } + return freemem; +} + +/********************************************************************************************\ + Special alloc functions to allocate in PSRAM if available + See: https://github.com/espressif/esp-idf/blob/master/components/heap/port/esp32s3/memory_layout.c + \*********************************************************************************************/ +void* special_malloc(uint32_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = malloc(size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = malloc(size); + } + + return res; +} + +void* special_realloc(void *ptr, size_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_realloc(ptr, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = realloc(ptr, size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = realloc(ptr, size); + } + + return res; +} + +void* special_calloc(size_t num, size_t size) { + void *res = nullptr; + +#ifdef ESP32 + + if (UsePSRAM()) { + res = heap_caps_calloc(num, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + } +#else // ifdef ESP32 + { +# ifdef USE_SECOND_HEAP + + // Try allocating on ESP8266 2nd heap + HeapSelectIram ephemeral; +# endif // ifdef USE_SECOND_HEAP + res = calloc(num, size); + } +#endif // ifdef ESP32 + + if (res == nullptr) { +#ifdef USE_SECOND_HEAP + + // Not successful, try allocating on (ESP8266) main heap + HeapSelectDram ephemeral; +#endif // ifdef USE_SECOND_HEAP + res = calloc(num, size); + } + + return res; +} diff --git a/src/src/Helpers/Misc.cpp b/src/src/Helpers/Misc.cpp index 2f2858a94..946c12a23 100644 --- a/src/src/Helpers/Misc.cpp +++ b/src/src/Helpers/Misc.cpp @@ -1,538 +1,547 @@ -#include "../Helpers/Misc.h" - -#include "../../ESPEasy-Globals.h" -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/Statistics.h" -#include "../Helpers/ESPEasy_FactoryDefault.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/PeriodicalActions.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringParser.h" - -#if FEATURE_SD -#include -#endif - - -bool remoteConfig(struct EventStruct *event, const String& string) -{ - // FIXME TD-er: Why have an event here as argument? It is not used. - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("remoteConfig")); - #endif // ifndef BUILD_NO_RAM_TRACKER - bool success = false; - String command = parseString(string, 1); - - if (equals(command, F("config"))) - { - // Command: "config,task,," - if (equals(parseString(string, 2), F("task"))) - { - String configTaskName = parseStringKeepCase(string, 3); - - // FIXME TD-er: This command is not using the tolerance setting - // tolerantParseStringKeepCase(Line, 4); - String configCommand = parseStringToEndKeepCase(string, 4); - - if ((configTaskName.isEmpty()) || (configCommand.isEmpty())) { - return success; - } - taskIndex_t index = findTaskIndexByName(configTaskName); - - if (validTaskIndex(index)) - { - event->setTaskIndex(index); - success = PluginCall(PLUGIN_SET_CONFIG, event, configCommand); - } - } else { - addLog(LOG_LEVEL_ERROR, F("Expected syntax: config,task,,")); - } - } - return success; -} - -/********************************************************************************************\ - delay in milliseconds with background processing - \*********************************************************************************************/ -void delayBackground(unsigned long dsdelay) -{ - unsigned long timer = millis() + dsdelay; - - while (!timeOutReached(timer)) { - backgroundtasks(); - } -} - -/********************************************************************************************\ - Toggle controller enabled state - \*********************************************************************************************/ -bool setControllerEnableStatus(controllerIndex_t controllerIndex, bool enabled) -{ - if (!validControllerIndex(controllerIndex)) { return false; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setControllerEnableStatus")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Only enable controller if it has a protocol configured - if ((Settings.Protocol[controllerIndex] != 0) || !enabled) { - Settings.ControllerEnabled[controllerIndex] = enabled; - return true; - } - return false; -} - -/********************************************************************************************\ - Toggle task enabled state - \*********************************************************************************************/ -bool setTaskEnableStatus(struct EventStruct *event, bool enabled) -{ - if (!validTaskIndex(event->TaskIndex)) { return false; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("setTaskEnableStatus")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Only enable task if it has a Plugin configured - if (validPluginID(Settings.getPluginID_for_task(event->TaskIndex)) || !enabled) { - String 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; - - 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; - } - } - return true; - } - return false; -} - -/********************************************************************************************\ - Clear task settings for given task - \*********************************************************************************************/ -void taskClear(taskIndex_t taskIndex, bool save) -{ - if (!validTaskIndex(taskIndex)) { return; } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("taskClear")); - #endif // ifndef BUILD_NO_RAM_TRACKER - Settings.clearTask(taskIndex); - clearTaskCache(taskIndex); // Invalidate any cached values. - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - - if (save) { - addLog(LOG_LEVEL_INFO, F("taskClear() save settings")); - SaveTaskSettings(taskIndex); - SaveSettings(); - } -} - -/********************************************************************************************\ - check the program memory hash - The const MD5_MD5_MD5_MD5_BoundariesOfTheSegmentsGoHere... needs to remain unchanged as it will be replaced by - - 16 bytes md5 hash, followed by - - 4 * uint32_t start of memory segment 1-4 - - 4 * uint32_t end of memory segment 1-4 - currently there are only two segemts included in the hash. Unused segments have start adress 0. - Execution time 520kb @80Mhz: 236ms - Returns: 0 if hash compare fails, number of checked bytes otherwise. - The reference hash is calculated by a .py file and injected into the binary. - Caution: currently the hash sits in an unchecked segment. If it ever moves to a checked segment, make sure - it is excluded from the calculation ! - \*********************************************************************************************/ -#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) -void dump(uint32_t addr) { // Seems already included in core 2.4 ... - serialPrint(String(addr, HEX)); - serialPrint(": "); - - for (uint32_t a = addr; a < addr + 16; a++) - { - serialPrint(String(pgm_read_byte(a), HEX)); - serialPrint(" "); - } - serialPrintln(); -} - -#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) - -/* - uint32_t progMemMD5check(){ - checkRAM(F("progMemMD5check")); - #define BufSize 10 - uint32_t calcBuffer[BufSize]; - CRCValues.numberOfCRCBytes = 0; - memcpy (calcBuffer,CRCValues.compileTimeMD5,16); // is there still the dummy in memory - ? - the dummy needs to be replaced by the real md5 after linking. - if( memcmp (calcBuffer, "MD5_MD5_MD5_",12)==0){ // do not memcmp with CRCdummy - directly or it will get optimized away. - addLog(LOG_LEVEL_INFO, F("CRC : No program memory checksum found. Check output of crc2.py")); - return 0; - } - MD5Builder md5; - md5.begin(); - for (int l = 0; l<4; l++){ // check max segments, if the - pointer is not 0 - uint32_t *ptrStart = (uint32_t *)&CRCValues.compileTimeMD5[16+l*4]; - uint32_t *ptrEnd = (uint32_t *)&CRCValues.compileTimeMD5[16+4*4+l*4]; - if ((*ptrStart) == 0) break; // segment not used. - for (uint32_t i = *ptrStart; i< (*ptrEnd) ; i=i+sizeof(calcBuffer)){ // "<" includes last byte - for (int buf = 0; buf < BufSize; buf ++){ - calcBuffer[buf] = pgm_read_dword((uint32_t*)i+buf); // read 4 bytes - CRCValues.numberOfCRCBytes+=sizeof(calcBuffer[0]); - } - md5.add(reinterpret_cast(&calcBuffer[0]),(*ptrEnd-i) 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] - I = I / 100; - I = I > 0 ? (I < 1 ? I : 1) : 0; - - // Math! Thanks in part to Kyle Miller. - if (H < 2.09439f) { - r = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - g = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - b = 255 * I / 3 * (1 - S); - } else if (H < 4.188787f) { - H = H - 2.09439f; - g = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - b = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - r = 255 * I / 3 * (1 - S); - } else { - H = H - 4.188787f; - b = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); - r = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); - g = 255 * I / 3 * (1 - S); - } - rgb[0] = r; - rgb[1] = g; - rgb[2] = b; - */ -} - -// uses H 0..360 S 1..100 I/V 1..100 (according to homie convention) -// Source https://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white -void HSV2RGBW(float H, float S, float I, int rgbw[4]) { - H = fmod(H, 360); // cycle H around to 0-360 degrees - constexpr float deg2rad = 3.14159f / 180.0f; - H *= deg2rad; // Convert to radians. - S = S / 100; - S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] - I = I / 100; - I = I > 0 ? (I < 1 ? I : 1) : 0; - - #define RGB_ORDER 0 - #define GBR_ORDER 1 - #define BRG_ORDER 2 - - int order = RGB_ORDER; - - constexpr float ANGLE_120_DEG = 120.0f * deg2rad; - constexpr float ANGLE_240_DEG = 240.0f * deg2rad; - constexpr float ANGLE_60_DEG = 60.0f * deg2rad; - - if (H < ANGLE_120_DEG) { - order = RGB_ORDER; - } else if (H < ANGLE_240_DEG) { - H = H - ANGLE_120_DEG; - order = GBR_ORDER; - } else { - H = H - ANGLE_240_DEG; - order = BRG_ORDER; - } - const float cos_h = cosf(H); - const float cos_1047_h = cosf(ANGLE_60_DEG - H); - - const int r = S * 255 * I / 3 * (1 + cos_h / cos_1047_h); - const int g = S * 255 * I / 3 * (1 + (1 - cos_h / cos_1047_h)); - const int b = 0; - rgbw[3] = 255 * (1 - S) * I; - - if (RGB_ORDER == order) { - rgbw[0] = r; - rgbw[1] = g; - rgbw[2] = b; - } else if (GBR_ORDER == order) { - rgbw[0] = g; - rgbw[1] = b; - rgbw[2] = r; - } else if (BRG_ORDER == order) { - rgbw[0] = b; - rgbw[1] = r; - rgbw[2] = g; - } -} - -// Convert RGB Color to HSV Color -void RGB2HSV(uint8_t r, uint8_t g, uint8_t b, float hsv[3]) { - const float rf = static_cast(r) / 255.0f; - const float gf = static_cast(g) / 255.0f; - const float bf = static_cast(b) / 255.0f; - float maxval = rf; - - if (gf > maxval) { maxval = gf; } - - if (bf > maxval) { maxval = bf; } - float minval = rf; - - if (gf < minval) { minval = gf; } - - if (bf < minval) { minval = bf; } - float h = 0.0f, s, v = maxval; - float f = maxval - minval; - - s = maxval == 0.0f ? 0.0f : f / maxval; - - if (maxval == minval) { - h = 0.0f; // achromatic - } else { - if (maxval == rf) { - h = (gf - bf) / f + (gf < bf ? 6.0f : 0.0f); - } else if (maxval == gf) { - h = (bf - rf) / f + 2.0f; - } else if (maxval == bf) { - h = (rf - gf) / f + 4.0f; - } - h /= 6.0f; - } - - hsv[0] = h * 360.0f; - hsv[1] = s * 255.0f; - hsv[2] = v * 255.0f; -} - - - -float getCPUload() { - return 100.0f - Scheduler.getIdleTimePct(); -} - -int getLoopCountPerSec() { - return loopCounterLast / 30; -} - -int getUptimeMinutes() { - return wdcounter / 2; -} - -/****************************************************************************** - * scan an int array of specified size for a value - *****************************************************************************/ -bool intArrayContains(const int arraySize, const int array[], const int& value) { - for (int i = 0; i < arraySize; i++) { - if (array[i] == value) { return true; } - } - return false; -} - -bool intArrayContains(const int arraySize, const uint8_t array[], const uint8_t& value) { - for (int i = 0; i < arraySize; i++) { - if (array[i] == value) { return true; } - } - return false; -} - -#ifndef BUILD_NO_RAM_TRACKER -void logMemUsageAfter(const __FlashStringHelper *function, int value) { - // Store free memory in an int, as subtracting may sometimes result in negative value. - // The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory. - static int last_freemem = ESP.getFreeHeap(); - const int freemem_end = ESP.getFreeHeap(); - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - if (log.reserve(128)) { - log = F("After "); - log += function; - - if (value >= 0) { - log += value; - } - - while (log.length() < 30) { log += ' '; } - log += F("Free mem after: "); - log += freemem_end; - - while (log.length() < 55) { log += ' '; } - log += F("diff: "); - log += last_freemem - freemem_end; - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - - last_freemem = freemem_end; -} - -#endif // ifndef BUILD_NO_RAM_TRACKER +#include "../Helpers/Misc.h" + +#include "../../ESPEasy-Globals.h" +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/Statistics.h" +#include "../Helpers/ESPEasy_FactoryDefault.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/PeriodicalActions.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringParser.h" + +#if FEATURE_SD +#include +#endif + + +bool remoteConfig(struct EventStruct *event, const String& string) +{ + // FIXME TD-er: Why have an event here as argument? It is not used. + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("remoteConfig")); + #endif // ifndef BUILD_NO_RAM_TRACKER + bool success = false; + String command = parseString(string, 1); + + if (equals(command, F("config"))) + { + // Command: "config,task,," + if (equals(parseString(string, 2), F("task"))) + { + String configTaskName = parseStringKeepCase(string, 3); + + // FIXME TD-er: This command is not using the tolerance setting + // tolerantParseStringKeepCase(Line, 4); + String configCommand = parseStringToEndKeepCase(string, 4); + + if ((configTaskName.isEmpty()) || (configCommand.isEmpty())) { + return success; + } + taskIndex_t index = findTaskIndexByName(configTaskName); + + if (validTaskIndex(index)) + { + event->setTaskIndex(index); + success = PluginCall(PLUGIN_SET_CONFIG, event, configCommand); + } + } else { + addLog(LOG_LEVEL_ERROR, F("Expected syntax: config,task,,")); + } + } + return success; +} + +/********************************************************************************************\ + delay in milliseconds with background processing + \*********************************************************************************************/ +void delayBackground(unsigned long dsdelay) +{ + unsigned long timer = millis() + dsdelay; + + while (!timeOutReached(timer)) { + backgroundtasks(); + } +} + +/********************************************************************************************\ + Toggle controller enabled state + \*********************************************************************************************/ +bool setControllerEnableStatus(controllerIndex_t controllerIndex, bool enabled) +{ + if (!validControllerIndex(controllerIndex)) { return false; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setControllerEnableStatus")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Only enable controller if it has a protocol configured + if ((Settings.Protocol[controllerIndex] != 0) || !enabled) { + Settings.ControllerEnabled[controllerIndex] = enabled; + return true; + } + return false; +} + +/********************************************************************************************\ + Toggle task enabled state + \*********************************************************************************************/ +bool setTaskEnableStatus(struct EventStruct *event, bool enabled) +{ + if (!validTaskIndex(event->TaskIndex)) { return false; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("setTaskEnableStatus")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Only enable task if it has a Plugin configured + if (validPluginID(Settings.getPluginID_for_task(event->TaskIndex)) || !enabled) { + String 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; + + 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; + } + } + return true; + } + return false; +} + +/********************************************************************************************\ + Clear task settings for given task + \*********************************************************************************************/ +void taskClear(taskIndex_t taskIndex, bool save) +{ + if (!validTaskIndex(taskIndex)) { return; } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("taskClear")); + #endif // ifndef BUILD_NO_RAM_TRACKER + if (Settings.TaskDeviceEnabled[taskIndex]) { + struct EventStruct TempEvent(taskIndex); + String dummy; + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + Settings.clearTask(taskIndex); + clearTaskCache(taskIndex); // Invalidate any cached values. + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + + if (save) { + addLog(LOG_LEVEL_INFO, F("taskClear() save settings")); + SaveTaskSettings(taskIndex); + SaveSettings(); + } +} + +/********************************************************************************************\ + check the program memory hash + The const MD5_MD5_MD5_MD5_BoundariesOfTheSegmentsGoHere... needs to remain unchanged as it will be replaced by + - 16 bytes md5 hash, followed by + - 4 * uint32_t start of memory segment 1-4 + - 4 * uint32_t end of memory segment 1-4 + currently there are only two segemts included in the hash. Unused segments have start adress 0. + Execution time 520kb @80Mhz: 236ms + Returns: 0 if hash compare fails, number of checked bytes otherwise. + The reference hash is calculated by a .py file and injected into the binary. + Caution: currently the hash sits in an unchecked segment. If it ever moves to a checked segment, make sure + it is excluded from the calculation ! + \*********************************************************************************************/ +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) +void dump(uint32_t addr) { // Seems already included in core 2.4 ... + serialPrint(String(addr, HEX)); + serialPrint(": "); + + for (uint32_t a = addr; a < addr + 16; a++) + { + serialPrint(String(pgm_read_byte(a), HEX)); + serialPrint(" "); + } + serialPrintln(); +} + +#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) + +/* + uint32_t progMemMD5check(){ + checkRAM(F("progMemMD5check")); + #define BufSize 10 + uint32_t calcBuffer[BufSize]; + CRCValues.numberOfCRCBytes = 0; + memcpy (calcBuffer,CRCValues.compileTimeMD5,16); // is there still the dummy in memory + ? - the dummy needs to be replaced by the real md5 after linking. + if( memcmp (calcBuffer, "MD5_MD5_MD5_",12)==0){ // do not memcmp with CRCdummy + directly or it will get optimized away. + addLog(LOG_LEVEL_INFO, F("CRC : No program memory checksum found. Check output of crc2.py")); + return 0; + } + MD5Builder md5; + md5.begin(); + for (int l = 0; l<4; l++){ // check max segments, if the + pointer is not 0 + uint32_t *ptrStart = (uint32_t *)&CRCValues.compileTimeMD5[16+l*4]; + uint32_t *ptrEnd = (uint32_t *)&CRCValues.compileTimeMD5[16+4*4+l*4]; + if ((*ptrStart) == 0) break; // segment not used. + for (uint32_t i = *ptrStart; i< (*ptrEnd) ; i=i+sizeof(calcBuffer)){ // "<" includes last byte + for (int buf = 0; buf < BufSize; buf ++){ + calcBuffer[buf] = pgm_read_dword((uint32_t*)i+buf); // read 4 bytes + CRCValues.numberOfCRCBytes+=sizeof(calcBuffer[0]); + } + md5.add(reinterpret_cast(&calcBuffer[0]),(*ptrEnd-i) 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] + I = I / 100; + I = I > 0 ? (I < 1 ? I : 1) : 0; + + // Math! Thanks in part to Kyle Miller. + if (H < 2.09439f) { + r = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + g = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + b = 255 * I / 3 * (1 - S); + } else if (H < 4.188787f) { + H = H - 2.09439f; + g = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + b = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + r = 255 * I / 3 * (1 - S); + } else { + H = H - 4.188787f; + b = 255 * I / 3 * (1 + S * cosf(H) / cosf(1.047196667f - H)); + r = 255 * I / 3 * (1 + S * (1 - cosf(H) / cosf(1.047196667f - H))); + g = 255 * I / 3 * (1 - S); + } + rgb[0] = r; + rgb[1] = g; + rgb[2] = b; + */ +} + +// uses H 0..360 S 1..100 I/V 1..100 (according to homie convention) +// Source https://blog.saikoled.com/post/44677718712/how-to-convert-from-hsi-to-rgb-white +void HSV2RGBW(float H, float S, float I, int rgbw[4]) { + H = fmod(H, 360); // cycle H around to 0-360 degrees + constexpr float deg2rad = 3.14159f / 180.0f; + H *= deg2rad; // Convert to radians. + S = S / 100; + S = S > 0 ? (S < 1 ? S : 1) : 0; // clamp S and I to interval [0,1] + I = I / 100; + I = I > 0 ? (I < 1 ? I : 1) : 0; + + #define RGB_ORDER 0 + #define GBR_ORDER 1 + #define BRG_ORDER 2 + + int order = RGB_ORDER; + + constexpr float ANGLE_120_DEG = 120.0f * deg2rad; + constexpr float ANGLE_240_DEG = 240.0f * deg2rad; + constexpr float ANGLE_60_DEG = 60.0f * deg2rad; + + if (H < ANGLE_120_DEG) { + order = RGB_ORDER; + } else if (H < ANGLE_240_DEG) { + H = H - ANGLE_120_DEG; + order = GBR_ORDER; + } else { + H = H - ANGLE_240_DEG; + order = BRG_ORDER; + } + const float cos_h = cosf(H); + const float cos_1047_h = cosf(ANGLE_60_DEG - H); + + const int r = S * 255 * I / 3 * (1 + cos_h / cos_1047_h); + const int g = S * 255 * I / 3 * (1 + (1 - cos_h / cos_1047_h)); + const int b = 0; + rgbw[3] = 255 * (1 - S) * I; + + if (RGB_ORDER == order) { + rgbw[0] = r; + rgbw[1] = g; + rgbw[2] = b; + } else if (GBR_ORDER == order) { + rgbw[0] = g; + rgbw[1] = b; + rgbw[2] = r; + } else if (BRG_ORDER == order) { + rgbw[0] = b; + rgbw[1] = r; + rgbw[2] = g; + } +} + +// Convert RGB Color to HSV Color +void RGB2HSV(uint8_t r, uint8_t g, uint8_t b, float hsv[3]) { + const float rf = static_cast(r) / 255.0f; + const float gf = static_cast(g) / 255.0f; + const float bf = static_cast(b) / 255.0f; + float maxval = rf; + + if (gf > maxval) { maxval = gf; } + + if (bf > maxval) { maxval = bf; } + float minval = rf; + + if (gf < minval) { minval = gf; } + + if (bf < minval) { minval = bf; } + float h = 0.0f, s, v = maxval; + float f = maxval - minval; + + s = maxval == 0.0f ? 0.0f : f / maxval; + + if (maxval == minval) { + h = 0.0f; // achromatic + } else { + if (maxval == rf) { + h = (gf - bf) / f + (gf < bf ? 6.0f : 0.0f); + } else if (maxval == gf) { + h = (bf - rf) / f + 2.0f; + } else if (maxval == bf) { + h = (rf - gf) / f + 4.0f; + } + h /= 6.0f; + } + + hsv[0] = h * 360.0f; + hsv[1] = s * 255.0f; + hsv[2] = v * 255.0f; +} + + + +float getCPUload() { + return 100.0f - Scheduler.getIdleTimePct(); +} + +int getLoopCountPerSec() { + return loopCounterLast / 30; +} + +int getUptimeMinutes() { + return wdcounter / 2; +} + +/****************************************************************************** + * scan an int array of specified size for a value + *****************************************************************************/ +bool intArrayContains(const int arraySize, const int array[], const int& value) { + for (int i = 0; i < arraySize; i++) { + if (array[i] == value) { return true; } + } + return false; +} + +bool intArrayContains(const int arraySize, const uint8_t array[], const uint8_t& value) { + for (int i = 0; i < arraySize; i++) { + if (array[i] == value) { return true; } + } + return false; +} + +#ifndef BUILD_NO_RAM_TRACKER +void logMemUsageAfter(const __FlashStringHelper *function, int value) { + // Store free memory in an int, as subtracting may sometimes result in negative value. + // The recorded used memory is not an exact value, as background (or interrupt) tasks may also allocate or free heap memory. + static int last_freemem = ESP.getFreeHeap(); + const int freemem_end = ESP.getFreeHeap(); + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + if (log.reserve(128)) { + log = F("After "); + log += function; + + if (value >= 0) { + log += value; + } + + while (log.length() < 30) { log += ' '; } + log += F("Free mem after: "); + log += freemem_end; + + while (log.length() < 55) { log += ' '; } + log += F("diff: "); + log += last_freemem - freemem_end; + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + + last_freemem = freemem_end; +} + +#endif // ifndef BUILD_NO_RAM_TRACKER diff --git a/src/src/Helpers/Misc.h b/src/src/Helpers/Misc.h index 691cc1fca..0f94ffad4 100644 --- a/src/src/Helpers/Misc.h +++ b/src/src/Helpers/Misc.h @@ -16,8 +16,8 @@ // Simple bitwise get/set functions -#define setNBitToUL(N, B, V, M) N=(((N) & ~(M << (B))) | (static_cast((V) & M) << (B))) -#define getNBitFromUL(number, bitnr, mask) ((number >> bitnr) & mask) +#define setNBitToUL(N, B, V, M) N=(((N) & ~((M) << (B))) | (static_cast((V) & (M)) << (B))) +#define getNBitFromUL(number, bitnr, mask) (((number) >> (bitnr)) & (mask)) #define set8BitToUL(N, B, V) setNBitToUL(N, B, V, 0xFFUL) #define set4BitToUL(N, B, V) setNBitToUL(N, B, V, 0x0FUL) diff --git a/src/src/Helpers/Modbus_RTU.cpp b/src/src/Helpers/Modbus_RTU.cpp index 96d63d7a7..7b3ac05ec 100644 --- a/src/src/Helpers/Modbus_RTU.cpp +++ b/src/src/Helpers/Modbus_RTU.cpp @@ -253,7 +253,7 @@ String ModbusRTU_struct::parse_modbus_MEI_response(unsigned int& object_value_in if (_recv_buf_used < 8) { // Too small. addLog(LOG_LEVEL_INFO, - String(F("MEI response too small: ")) + _recv_buf_used); + concat(F("MEI response too small: "), _recv_buf_used)); next_object_id = 0xFF; more_follows = false; return result; @@ -391,7 +391,7 @@ void ModbusRTU_struct::logModbusException(uint8_t value) { log += F("Modbus No Data"); break; default: - log += String(F("Unknown Exception code: ")) + value; + log += concat(F("Unknown Exception code: "), value); break; } log += F(" - sent: "); @@ -541,13 +541,10 @@ uint32_t ModbusRTU_struct::read_32b_HoldingRegister(short address) { } float ModbusRTU_struct::read_float_HoldingRegister(short address) { - union { - uint32_t ival; - float fval; - } conversion; - - conversion.ival = read_32b_HoldingRegister(address); - return conversion.fval; + const uint32_t ival = read_32b_HoldingRegister(address); + float fval{}; + memcpy(&fval, &ival, sizeof(ival)); + return fval; // uint32_t ival = read_32b_HoldingRegister(address); // float fval = *reinterpret_cast(&ival); diff --git a/src/src/Helpers/Networking.cpp b/src/src/Helpers/Networking.cpp index 756ddc32b..426b977a8 100644 --- a/src/src/Helpers/Networking.cpp +++ b/src/src/Helpers/Networking.cpp @@ -1,1914 +1,2034 @@ -#include "../Helpers/Networking.h" - -#include "../Commands/ExecuteCommand.h" -#include "../CustomBuild/CompiletimeDefines.h" -#include "../DataStructs/NodeStruct.h" -#include "../DataStructs/TimingStats.h" -#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 "../ESPEasyCore/Serial.h" -#include "../Globals/ESPEasyEthEvent.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#include "../Globals/ESPEasy_Scheduler.h" - -#ifdef USES_ESPEASY_NOW -#include "../Globals/ESPEasy_now_handler.h" -#endif - -#include "../Globals/EventQueue.h" -#include "../Globals/NetworkState.h" -#include "../Globals/Nodes.h" -#include "../Globals/ResetFactoryDefaultPref.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Hardware.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Network.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" - -#include "../../ESPEasy-Globals.h" - -#include -#include -#include // for getDigestAuth - -#include - -#include - -// Generic Networking routines - -// Syslog -// UDP system messaging -// 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) - -// #endif - -#include - -#ifdef ESP8266 -#include -#include -#include -#include -#endif - -#ifdef SUPPORT_ARP -# include - -# ifdef ESP32 -# include -# include - -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); -} - -# endif // ifdef ESP32 - -#endif // ifdef SUPPORT_ARP - -#if FEATURE_DOWNLOAD -# ifdef ESP8266 -# include -# endif // ifdef ESP8266 -# ifdef ESP32 -# include -# include -# endif // ifdef ESP32 -#endif // if FEATURE_DOWNLOAD - -#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 - -/*********************************************************************************************\ - Send event using UDP message -\*********************************************************************************************/ -void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) -{ - if (!NetworkConnected(10)) { - return; - } - - if (destUnit != 0) - { - sendUDP(destUnit, (const uint8_t *)data, dataLength); - delay(10); - } else { - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { - if (it->first != Settings.Unit) { - sendUDP(it->first, (const uint8_t *)data, dataLength); - delay(10); - } - } - } - delay(50); -} - -/*********************************************************************************************\ - Send UDP message to specific unit (unit 255=broadcast) -\*********************************************************************************************/ -void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) -{ - if (!NetworkConnected(10)) { - return; - } - - IPAddress remoteNodeIP = getIPAddressForUnit(unit); - - if (remoteNodeIP[0] == 0) { - return; - } - -# 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() - )); - } -# endif // ifndef BUILD_NO_DEBUG - - statusLED(true); - FeedSW_watchdog(); - portUDP.beginPacket(remoteNodeIP, Settings.UDPPort); - portUDP.write(data, size); - portUDP.endPacket(); - FeedSW_watchdog(); - delay(0); -} - -/*********************************************************************************************\ - Update UDP port (ESPEasy propiertary protocol) -\*********************************************************************************************/ -void updateUDPport() -{ - static uint16_t lastUsedUDPPort = 0; - - if (Settings.UDPPort == lastUsedUDPPort) { - return; - } - - if (lastUsedUDPPort != 0) { - portUDP.stop(); - lastUsedUDPPort = 0; - } - - if (!NetworkConnected()) { - return; - } - - if (Settings.UDPPort != 0) { - if (portUDP.begin(Settings.UDPPort) == 0) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("UDP : Cannot bind to ESPEasy p2p UDP port "), Settings.UDPPort)); - } - } else { - lastUsedUDPPort = Settings.UDPPort; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Start listening on port "), Settings.UDPPort)); - } - } - } -} - -/*********************************************************************************************\ - Check UDP messages (ESPEasy propiertary protocol) -\*********************************************************************************************/ -boolean runningUPDCheck = false; -void checkUDP() -{ - if (Settings.UDPPort == 0) { - return; - } - - if (runningUPDCheck) { - return; - } - - runningUPDCheck = true; - - // UDP events - int packetSize = portUDP.parsePacket(); - - if (packetSize > 0 /*&& portUDP.remotePort() == Settings.UDPPort*/) - { - statusLED(true); - - IPAddress remoteIP = portUDP.remoteIP(); - - if (portUDP.remotePort() == 123) - { - // unexpected NTP reply, drop for now... - runningUPDCheck = false; - return; - } - - // UDP_PACKETSIZE_MAX should be as small as possible but still enough to hold all - // data for PLUGIN_UDP_IN or CPLUGIN_UDP_IN calls - // This node may also receive other UDP packets which may be quite large - // and then crash due to memory allocation failures - if ((packetSize >= 2) && (packetSize < UDP_PACKETSIZE_MAX)) { - // Allocate buffer to process packet. - std::vector packetBuffer; - packetBuffer.resize(packetSize + 1); - - if (packetBuffer.size() >= static_cast(packetSize)) { - memset(&packetBuffer[0], 0, packetSize + 1); - int len = portUDP.read(&packetBuffer[0], packetSize); - - if (len >= 2) { - if (static_cast(packetBuffer[0]) != 255) - { - packetBuffer[len] = 0; - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat(F("UDP : %s Command: %s"), - formatIP(remoteIP).c_str(), - wrapWithQuotesIfContainsParameterSeparatorChar(String(&packetBuffer[0])).c_str() - )); - } - #endif - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_SYSTEM, &packetBuffer[0]); - } - else - { - // binary data! - switch (packetBuffer[1]) - { - case 1: // sysinfo message - { - if (len < 13) { - 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) { - copy_length = 56; - } - - if (copy_length > (len - 2)) { - copy_length = (len - 2); - } - NodeStruct received; - memcpy(&received, &packetBuffer[2], copy_length); - - if (received.validate(remoteIP)) { - Nodes.addNode(received); // Create a new element when not present - -# 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()).c_str(), - received.unit)); - } - -#endif // ifndef BUILD_NO_DEBUG - } - break; - } - - default: - { - struct EventStruct TempEvent; - TempEvent.Data = reinterpret_cast(&packetBuffer[0]); - 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); - CPluginCall(CPlugin::Function::CPLUGIN_UDP_IN, &TempEvent); - break; - } - } - } - } - } - } - } - - // Flush any remaining content of the packet. - while (portUDP.available()) { - // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) - portUDP.read(); - } - runningUPDCheck = false; -} - -/*********************************************************************************************\ - Get formatted IP address for unit - formatcodes: 0 = default toString(), 1 = empty string when invalid, 2 = 0 when invalid -\*********************************************************************************************/ -String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode) { - IPAddress unitIPAddress = getIPAddressForUnit(unit); - - if (unitIPAddress[0] == 0) { // Invalid? - switch (formatCode) { - case 1: // Return empty string - { - return EMPTY_STRING; - } - case 2: // Return "0" - { - return String('0'); - } - } - } - return formatIP(unitIPAddress); -} - -/*********************************************************************************************\ - Get IP address for unit -\*********************************************************************************************/ -IPAddress getIPAddressForUnit(uint8_t unit) { - if (unit == 255) { - const IPAddress ip(255, 255, 255, 255); - return ip; - } - auto it = Nodes.find(unit); - - if (it == Nodes.end() || it->second.ip[0] == 0) { - IPAddress ip; - return ip; - } -#if FEATURE_USE_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 - return it->second.IP(); -} - - -/*********************************************************************************************\ - Refresh aging for remote units, drop if too old... -\*********************************************************************************************/ -void refreshNodeList() -{ - unsigned long max_age; - const unsigned long max_age_allowed = 10 * 60 * 1000; // 10 minutes - - Nodes.refreshNodeList(max_age_allowed, max_age); - - #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()); - } - #endif - #endif - - if (max_age > (0.75 * max_age_allowed)) { - Scheduler.sendGratuitousARP_now(); - } - sendSysInfoUDP(1); - #ifdef USES_ESPEASY_NOW - if (Nodes.recentlyBecameDistanceZero()) { - // Send to all channels - ESPEasy_now_handler.sendDiscoveryAnnounce(-1); - } else { - ESPEasy_now_handler.sendDiscoveryAnnounce(); - } - ESPEasy_now_handler.sendNTPquery(); - ESPEasy_now_handler.sendTraceRoute(); - #endif // ifdef USES_ESPEASY_NOW -} - -/*********************************************************************************************\ - Broadcast system info to other nodes. (to update node lists) -\*********************************************************************************************/ -void sendSysInfoUDP(uint8_t repeats) -{ - if ((Settings.UDPPort == 0) || !NetworkConnected(10)) { - return; - } - - // 1 byte 'binary token 255' - // 1 byte id '1' - // NodeStruct object (packed data struct) - - // send my info to the world... -# ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, F("UDP : Send Sysinfo message")); -# endif // ifndef BUILD_NO_DEBUG - - const NodeStruct *thisNode = Nodes.getThisNode(); - - if (thisNode == nullptr) { - // Should not happen - return; - } - - // Prepare UDP packet to send - constexpr size_t data_size = sizeof(NodeStruct) + 2; - uint8_t data[data_size] = {0}; - data[0] = 255; - data[1] = 1; - memcpy(&data[2], thisNode, sizeof(NodeStruct)); - - for (uint8_t counter = 0; counter < repeats; counter++) - { - statusLED(true); - - IPAddress broadcastIP(255, 255, 255, 255); - FeedSW_watchdog(); - portUDP.beginPacket(broadcastIP, Settings.UDPPort); - portUDP.write(data, data_size); - portUDP.endPacket(); - - if (counter < (repeats - 1)) { - // FIXME TD-er: Must use scheduler to send out messages, not using delay - delay(100); - } - } -} - -#endif // FEATURE_ESPEASY_P2P - -#if defined(ESP8266) - -# if FEATURE_SSDP - -/********************************************************************************************\ - Respond to HTTP XML requests for SSDP information - \*********************************************************************************************/ -void SSDP_schema() { - if (!NetworkConnected(10)) { - return; - } - - const IPAddress ip = NetworkLocalIP(); - const uint32_t chipId = ESP.getChipId(); - char uuid[64]; - - sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), - (uint16_t)((chipId >> 16) & 0xff), - (uint16_t)((chipId >> 8) & 0xff), - (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://")); - - web_server.client().print(formatIP(ip)); - web_server.client().print(F(":80/" - "" - "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" - "")); - 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:")); - web_server.client().print(String(uuid)); - web_server.client().print(F("" - "\r\n" - "\r\n")); -} - -/********************************************************************************************\ - Global SSDP stuff - \*********************************************************************************************/ - -UdpContext *_server; - -IPAddress _respondToAddr; -uint16_t _respondToPort; - -bool _pending; -unsigned short _delay; -unsigned long _process_time; -unsigned long _notify_time; - -# define SSDP_INTERVAL 1200 -# define SSDP_PORT 1900 -# define SSDP_METHOD_SIZE 10 -# define SSDP_URI_SIZE 2 -# define SSDP_BUFFER_SIZE 64 -# define SSDP_MULTICAST_TTL 2 - -static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); - - -/********************************************************************************************\ - Launch SSDP listener and send initial notify - \*********************************************************************************************/ -bool SSDP_begin() { - _pending = false; - - if (_server != nullptr) { - _server->unref(); - - // FIXME TD-er: Shouldn't this also call delete _server ? - - _server = nullptr; - } - - _server = new (std::nothrow) UdpContext; - - if (_server == nullptr) { - return false; - } - _server->ref(); - - ip_addr_t ifaddr; - - ifaddr.addr = NetworkLocalIP(); - ip_addr_t multicast_addr; - - multicast_addr.addr = (uint32_t)SSDP_MULTICAST_ADDR; - - if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK) { - return false; - } - -# ifdef CORE_POST_2_5_0 - - // Core 2.5.0 changed the signature of some UdpContext function. - if (!_server->listen(IP_ADDR_ANY, SSDP_PORT)) { - return false; - } - - _server->setMulticastInterface(&ifaddr); - _server->setMulticastTTL(SSDP_MULTICAST_TTL); - _server->onRx(&SSDP_update); - - if (!_server->connect(&multicast_addr, SSDP_PORT)) { - return false; - } -# else // ifdef CORE_POST_2_5_0 - - if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { - return false; - } - - _server->setMulticastInterface(ifaddr); - _server->setMulticastTTL(SSDP_MULTICAST_TTL); - _server->onRx(&SSDP_update); - - if (!_server->connect(multicast_addr, SSDP_PORT)) { - return false; - } -# endif // ifdef CORE_POST_2_5_0 - - SSDP_update(); - - return true; -} - -/********************************************************************************************\ - Send SSDP messages (notify & responses) - \*********************************************************************************************/ -void SSDP_send(uint8_t method) { - uint32_t ip = NetworkLocalIP(); - - // FIXME TD-er: Why create String objects of these flashstrings? - String _ssdp_response_template = F( - "HTTP/1.1 200 OK\r\n" - "EXT:\r\n" - "ST: upnp:rootdevice\r\n"); - - String _ssdp_notify_template = F( - "NOTIFY * HTTP/1.1\r\n" - "HOST: 239.255.255.250:1900\r\n" - "NT: upnp:rootdevice\r\n" - "NTS: ssdp:alive\r\n"); - - String _ssdp_packet_template = F( - "%s" // _ssdp_response_template / _ssdp_notify_template - "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL - "SERVER: Arduino/1.0 UPNP/1.1 ESPEasy/%u\r\n" // _modelNumber - "USN: uuid:%s\r\n" // _uuid - "LOCATION: http://%u.%u.%u.%u:80/ssdp.xml\r\n" // NetworkLocalIP(), - "\r\n"); - { - char uuid[64] = { 0 }; - uint32_t chipId = ESP.getChipId(); - sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), - (uint16_t)((chipId >> 16) & 0xff), - (uint16_t)((chipId >> 8) & 0xff), - (uint16_t)chipId & 0xff); - - char *buffer = new (std::nothrow) char[1460](); - - if (buffer == nullptr) { return; } - int len = snprintf(buffer, 1460, - _ssdp_packet_template.c_str(), - (method == 0) ? _ssdp_response_template.c_str() : _ssdp_notify_template.c_str(), - SSDP_INTERVAL, - Settings.Build, - uuid, - IPADDR2STR(&ip) - ); - - _server->append(buffer, len); - delete[] buffer; - } - - ip_addr_t remoteAddr; - uint16_t remotePort; - - if (method == 0) { - remoteAddr.addr = _respondToAddr; - remotePort = _respondToPort; - } else { - remoteAddr.addr = SSDP_MULTICAST_ADDR; - remotePort = SSDP_PORT; - } - _server->send(&remoteAddr, remotePort); - statusLED(true); -} - -/********************************************************************************************\ - SSDP message processing - \*********************************************************************************************/ -void SSDP_update() { - if (!_pending && _server->next()) { - ssdp_method_t method = NONE; - - _respondToAddr = _server->getRemoteAddress(); - _respondToPort = _server->getRemotePort(); - - typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states; - states state = METHOD; - - typedef enum { START, MAN, ST, MX } headers; - headers header = START; - - uint8_t cursor = 0; - uint8_t cr = 0; - - char buffer[SSDP_BUFFER_SIZE] = { 0 }; - - while (_server->getSize() > 0) { - char c = _server->read(); - - (c == '\r' || c == '\n') ? cr++ : cr = 0; - - switch (state) { - case METHOD: - - if (c == ' ') { - if (strcmp_P(buffer, PSTR("M-SEARCH")) == 0) { method = SEARCH; } - else if (strcmp_P(buffer, PSTR("NOTIFY")) == 0) { method = NOTIFY; } - - if (method == NONE) { state = ABORT; } - else { state = URI; } - cursor = 0; - } else if (cursor < SSDP_METHOD_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case URI: - - if (c == ' ') { - if (strcmp(buffer, "*")) { state = ABORT; } - else { state = PROTO; } - cursor = 0; - } else if (cursor < SSDP_URI_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case PROTO: - - if (cr == 2) { - state = KEY; - cursor = 0; - } - break; - case KEY: - - if (cr == 4) { - _pending = true; - _process_time = millis(); - } - else if (c == ' ') { - cursor = 0; - state = VALUE; - } - else if ((c != '\r') && (c != '\n') && (c != ':') && (cursor < SSDP_BUFFER_SIZE - 1)) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - break; - case VALUE: - - if (cr == 2) { - switch (header) { - case START: - break; - case MAN: - break; - case ST: - - if (strcmp_P(buffer, PSTR("ssdp:all"))) { - state = ABORT; - } - - // if the search type matches our type, we should respond instead of ABORT - if (strcmp_P(buffer, PSTR("urn:schemas-upnp-org:device:BinaryLight:1")) == 0) { - _pending = true; - _process_time = millis(); - state = KEY; - } - break; - case MX: - _delay = HwRandom(0, atoi(buffer)) * 1000L; - break; - } - - if (state != ABORT) { - state = KEY; - header = START; - cursor = 0; - } - } else if ((c != '\r') && (c != '\n')) { - if (header == START) { - if (strncmp(buffer, "MA", 2) == 0) { header = MAN; } - else if (strcmp(buffer, "ST") == 0) { header = ST; } - else if (strcmp(buffer, "MX") == 0) { header = MX; } - } - - if (cursor < SSDP_BUFFER_SIZE - 1) { - buffer[cursor++] = c; - buffer[cursor] = '\0'; - } - } - break; - case ABORT: - _pending = false; _delay = 0; - break; - } - } - } - - if (_pending && timeOutReached(_process_time + _delay)) { - _pending = false; _delay = 0; - SSDP_send(NONE); - } else if ((_notify_time == 0) || timeOutReached(_notify_time + (SSDP_INTERVAL * 1000L))) { - _notify_time = millis(); - SSDP_send(NOTIFY); - } - - if (_pending) { - while (_server->next()) { - _server->flush(); - } - } -} - -# endif // if FEATURE_SSDP -#endif // if defined(ESP8266) - - -// ******************************************************************************** -// Return subnet range of WiFi. -// ******************************************************************************** -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]; - } - } - return true; -} - -// ******************************************************************************** -// Functions to test and handle network/client connectivity. -// ******************************************************************************** - -#ifdef CORE_POST_2_5_0 -# include -#endif // ifdef CORE_POST_2_5_0 - - -bool hasIPaddr() { - if (useStaticIP()) { return true; } - -#ifdef CORE_POST_2_5_0 - bool configured = false; - - for (auto addr : addrList) { - if ((configured = (!addr.isLocal() && (addr.ifnumber() == STATION_IF)))) { - /* - ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", - addr.ifname().c_str(), - addr.ifhostname(), - addr.toString().c_str()); - */ - break; - } - } - return configured; -#else // ifdef CORE_POST_2_5_0 - return WiFi.isConnected(); -#endif // ifdef CORE_POST_2_5_0 -} - -bool useStaticIP() { - #if FEATURE_ETHERNET - if (active_network_medium == NetworkMedium_t::Ethernet) { - return ethUseStaticIP(); - } - #endif - return WiFiUseStaticIP(); -} - -// Check connection. Maximum timeout 500 msec. -bool NetworkConnected(uint32_t timeout_ms) { - -#ifdef USES_ESPEASY_NOW - if (isESPEasy_now_only()) { - return false; - } -#endif - - if (timeout_ms > 500) { - timeout_ms = 500; - } - - uint32_t timer = millis() + timeout_ms; - uint32_t min_delay = timeout_ms / 20; - - if (min_delay < 10) { - delay(0); // Allow at least once time for backgroundtasks - min_delay = 10; - } - - // Apparently something needs network, perform check to see if it is ready now. - while (!NetworkConnected()) { - if (timeOutReached(timer)) { - return false; - } - delay(min_delay); // Allow the backgroundtasks to continue procesing. - } - return true; -} - -bool hostReachable(const IPAddress& ip) { - if (!NetworkConnected()) { return false; } - - return true; // Disabled ping as requested here: - // https://github.com/letscontrolit/ESPEasy/issues/1494#issuecomment-397872538 - - /* - // Only do 1 ping at a time to return early - uint8_t retry = 3; - while (retry > 0) { - #if defined(ESP8266) - if (Ping.ping(ip, 1)) return true; - #endif - #if defined(ESP32) - if (ping_start(ip, 4, 0, 0, 5)) return true; - #endif - delay(50); - --retry; - } - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("Host unreachable: "); - log += formatIP(ip); - addLog(LOG_LEVEL_ERROR, log); - } - if (ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { - // Work-around to fix connected but not able to communicate. - addLog(LOG_LEVEL_ERROR, F("WiFi : Detected strange behavior, reconnect wifi.")); - WifiDisconnect(); - } - logConnectionStatus(); - return false; - */ -} - -#if FEATURE_HTTP_CLIENT -bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint32_t timeout_ms) { - IPAddress ip; - - if (resolveHostByName(hostname, ip, timeout_ms)) { - return connectClient(client, ip, port, timeout_ms); - } - return false; -} - -bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t timeout_ms) -{ - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } - - // In case of domain name resolution error result can be negative. - // https://github.com/esp8266/Arduino/blob/18f643c7e2d6a0da9d26ff2b14c94e6536ab78c1/libraries/Ethernet/src/Dns.cpp#L44 - // Thus must match the result with 1. - bool connected = (client.connect(ip, port) == 1); - - delay(0); - - if (!connected) { - 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 (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) - return connected; -} -#endif // FEATURE_HTTP_CLIENT - -void scrubDNS() { - #if FEATURE_ETHERNET - if (active_network_medium == 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); - } -} - -bool valid_DNS_address(const IPAddress& dns) { - 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); -} - -bool setDNS(int index, const IPAddress& dns) { - 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 (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); - } - return true; - } - #endif - return false; -} - -bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeout_ms) { - START_TIMER; - - if (!NetworkConnected()) { - return false; - } - - FeedSW_watchdog(); - - // FIXME TD-er: Must try to restore DNS server entries. - scrubDNS(); - -#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - bool resolvedIP = WiFi.hostByName(aHostname, aResult) == 1; -#else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - bool resolvedIP = WiFi.hostByName(aHostname, aResult, timeout_ms) == 1; -#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) - delay(0); - FeedSW_watchdog(); - - if (!resolvedIP) { - Scheduler.sendGratuitousARP_now(); - } - STOP_TIMER(HOST_BY_NAME_STATS); - return resolvedIP; -} - -bool hostReachable(const String& hostname) { - IPAddress remote_addr; - - if (resolveHostByName(hostname.c_str(), remote_addr)) { - return hostReachable(remote_addr); - } - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); - } - return false; -} - -// Create a random port for the UDP connection. -// Return true when successful. -bool beginWiFiUDP_randomPort(WiFiUDP& udp) { - if (!NetworkConnected()) { - return false; - } - unsigned int attempts = 3; - - while (attempts > 0) { - --attempts; - long port = HwRandom(1025, 65535); - - if (udp.begin(port) != 0) { - return true; - } - } - return false; -} - -void sendGratuitousARP() { - if (!NetworkConnected()) { - return; - } -#ifdef SUPPORT_ARP - - // See https://github.com/letscontrolit/ESPEasy/issues/2374 - START_TIMER; - netif *n = netif_list; - - while (n) { - if ((n->hwaddr_len == ETH_HWADDR_LEN) && - (n->flags & NETIF_FLAG_ETHARP) && - ((n->flags & NETIF_FLAG_LINK_UP) && (n->flags & NETIF_FLAG_UP))) { - # ifdef ESP32 - etharp_gratuitous_r(n); - # else // ifdef ESP32 - etharp_gratuitous(n); - # endif // ifdef ESP32 - } - n = n->next; - } - STOP_TIMER(GRAT_ARP_STATS); -#endif // ifdef SUPPORT_ARP -} - -bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { - port = 80; // Some default - int index_colon = hostPortString.indexOf(':'); - - if (index_colon >= 0) { - int32_t port_tmp; - - if (!validIntFromString(hostPortString.substring(index_colon + 1), port_tmp)) { - return false; - } - - if ((port_tmp < 0) || (port_tmp > 65535)) { return false; } - port = port_tmp; - host = hostPortString.substring(0, index_colon); - } else { - // No port nr defined. - host = hostPortString; - } - return true; -} - -bool splitUserPass_HostPortString(const String& hostPortString, String& user, String& pass, String& host, uint16_t& port) -{ - const int pos_at = hostPortString.indexOf('@'); - - if (pos_at != -1) { - user = hostPortString.substring(0, pos_at); - const int pos_colon = user.indexOf(':'); - - if (pos_colon != -1) { - pass = user.substring(pos_colon + 1); - user = user.substring(0, pos_colon); - } - return splitHostPortString(hostPortString.substring(pos_at + 1), host, port); - } - return splitHostPortString(hostPortString, host, port); -} - -// Split a full URL like "http://hostname:port/path/file.htm" -// Return value is everything after the hostname:port section (including /) -String splitURL(const String& fullURL, String& user, String& pass, String& host, uint16_t& port, String& file) { - int starthost = fullURL.indexOf(F("://")); - - if (starthost == -1) { - starthost = 0; - } else { - starthost += 3; - } - const int endhost = fullURL.indexOf('/', starthost); - splitUserPass_HostPortString(fullURL.substring(starthost, endhost), user, pass, host, port); - - if (endhost == -1) { - return EMPTY_STRING; - } - - int startfile = fullURL.lastIndexOf('/'); - - if (startfile >= 0) { - file = fullURL.substring(startfile); - } - return fullURL.substring(endhost); -} - -String get_user_agent_string() { - static unsigned int agent_size = 20; - String userAgent; - - userAgent.reserve(agent_size); - userAgent += F("ESP Easy/"); - userAgent += get_build_nr(); - userAgent += '/'; - userAgent += get_build_date(); - userAgent += ' '; - userAgent += get_build_time(); - agent_size = userAgent.length(); - return userAgent; -} - -bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { - if (strpos < 0) { - return false; - } - int colonPos = multiHeaders.indexOf(':', strpos); - - if (colonPos < 0) { - return false; - } - name = multiHeaders.substring(strpos, colonPos); - int valueEndPos = multiHeaders.indexOf('\n', colonPos + 1); - - if (valueEndPos < 0) { - value = multiHeaders.substring(colonPos + 1); - strpos = -1; - } else { - value = multiHeaders.substring(colonPos + 1, valueEndPos); - strpos = valueEndPos + 1; - } - value.replace('\r', ' '); - value.trim(); - return true; -} - -String extractParam(const String& authReq, const String& param, const char delimit) { - int _begin = authReq.indexOf(param); - - if (_begin == -1) { return EMPTY_STRING; } - return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); -} - -#if FEATURE_HTTP_CLIENT -String getCNonce(const int len) { - static const char alphanum[] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; - String s; - - for (int i = 0; i < len; ++i) { - s += alphanum[rand() % (sizeof(alphanum) - 1)]; - } - - return s; -} - -String getDigestAuth(const String& authReq, - const String& username, - const String& password, - const String& method, - const String& uri, - unsigned int counter) { - // extracting required parameters for RFC 2069 simpler Digest - const String realm = extractParam(authReq, F("realm=\""), '"'); - const String nonce = extractParam(authReq, F("nonce=\""), '"'); - const String cNonce = getCNonce(8); - - char nc[9]; - - snprintf(nc, sizeof(nc), "%08x", counter); - - // parameters for the RFC 2617 newer Digest - MD5Builder md5; - - md5.begin(); - md5.add(username + ':' + realm + ':' + password); // md5 of the user:realm:user - md5.calculate(); - const String h1 = md5.toString(); - - md5.begin(); - md5.add(method + ':' + uri); - md5.calculate(); - const String h2 = md5.toString(); - - md5.begin(); - md5.add(h1 + ':' + nonce + ':' + String(nc) + ':' + cNonce + F(":auth:") + h2); - md5.calculate(); - const String response = md5.toString(); - - const String authorization = - String(F("Digest username=\"")) + username + - F("\", realm=\"") + realm + - F("\", nonce=\"") + nonce + - F("\", uri=\"") + uri + - F("\", algorithm=\"MD5\", qop=auth, nc=") + String(nc) + - F(", cnonce=\"") + cNonce + - F("\", response=\"") + response + - '"'; - - // ESPEASY_SERIAL_CONSOLE_PORT.println(authorization); - - return authorization; -} - -#ifndef BUILD_NO_DEBUG -void log_http_result(const HTTPClient& http, - const String & logIdentifier, - const String & host, - const String & HttpMethod, - int httpCode, - const String & response) -{ - uint8_t loglevel = LOG_LEVEL_ERROR; - bool success = false; - - // HTTP codes: - // 1xx Informational response - // 2xx Success - if ((httpCode >= 100) && (httpCode < 300)) { - loglevel = LOG_LEVEL_INFO; - success = true; - } - - if (loglevelActiveFor(loglevel)) { - String log = strformat(F("HTTP : %s %s %s"), - logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); - - if (!success) { - log += F("failed "); - } - log += concat(F("HTTP code: "), httpCode); - - if (!success) { - log += ' '; - log += http.errorToString(httpCode); - } - - if (response.length() > 0) { - log += concat(F(" Received reply: "), response.substring(0, 100)); // Returned string may be huge, so only log the first part. - } - addLogMove(loglevel, log); - } -} -#endif - -int http_authenticate(const String& logIdentifier, - WiFiClient & client, - HTTPClient & http, - uint16_t timeout, - const String& user, - const String& pass, - const String& host, - uint16_t port, - const String& uri, - const String& HttpMethod, - const String& header, - const String& postStr, - bool must_check_reply) -{ - if (!uri.startsWith(F("/"))) { - return http_authenticate( - logIdentifier, - client, - http, - timeout, - user, - pass, - host, - port, - concat(F("/"), uri), - HttpMethod, - header, - postStr, - must_check_reply); - } - 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.setAuthorizationType(""); // Default type is "Basic" -#endif - } - http.setTimeout(timeout); - http.setUserAgent(get_user_agent_string()); - - if (Settings.SendToHTTP_follow_redirects()) { - http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); - http.setRedirectLimit(2); - } - - #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 - - // 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")); - - // Add client IP - http.addHeader(F("X-Forwarded-For"), formatIP(NetworkLocalIP())); - - delay(0); - scrubDNS(); -#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) - http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) - - const char *keys[] = { "WWW-Authenticate" }; - http.collectHeaders(keys, 1); - - { - int headerpos = 0; - String name, value; - - while (splitHeaders(headerpos, header, name, value)) { - // 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); -// } - } - } - - // start connection and send HTTP header (and body) - if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { - httpCode = http.sendRequest(HttpMethod.c_str()); - } else { - httpCode = http.sendRequest(HttpMethod.c_str(), postStr); - } - - // Check to see if we need to try digest auth - if ((httpCode == 401) && must_check_reply) { - const String authReq = http.header(String(F("WWW-Authenticate")).c_str()); - - if (authReq.indexOf(F("Digest")) != -1) { - // Use Digest authorization - 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 - const String authorization = getDigestAuth(authReq, user, pass, F("GET"), uri, 1); - - http.end(); -#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) - http.begin(client, host, port, uri); -#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) - - http.addHeader(F("Authorization"), authorization); - - // start connection and send HTTP header (and body) - if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { - httpCode = http.sendRequest(HttpMethod.c_str()); - } else { - httpCode = http.sendRequest(HttpMethod.c_str(), postStr); - } - } - } - - if (!must_check_reply) { - // There are services which do not send an ack. - // So if the return code matches a read timeout, we change it into HTTP code 200 - if (httpCode == HTTPC_ERROR_READ_TIMEOUT) { - httpCode = 200; - } - } - - if (Settings.UseRules) { - // Generate event with the HTTP return code - // e.g. http#hostname=401 - String event = F("http#"); - event += host; - event += '='; - event += httpCode; - eventQueue.addMove(std::move(event)); - } -#ifndef BUILD_NO_DEBUG - log_http_result(http, logIdentifier, host + ':' + port, HttpMethod, httpCode, EMPTY_STRING); -#endif - return httpCode; -} - -String send_via_http(const String& logIdentifier, - uint16_t timeout, - const String& user, - const String& pass, - const String& host, - uint16_t port, - const String& uri, - const String& HttpMethod, - const String& header, - const String& postStr, - int & httpCode, - bool must_check_reply) { - WiFiClient client; - HTTPClient http; - http.setReuse(false); - - httpCode = http_authenticate( - logIdentifier, - client, - http, - timeout, - user, - pass, - host, - port, - uri, - HttpMethod, - header, - postStr, - must_check_reply); - - String response; - - if ((httpCode > 0) && must_check_reply) { - response = http.getString(); -#ifndef BUILD_NO_DEBUG - if (!response.isEmpty()) { - log_http_result(http, logIdentifier, host, HttpMethod, httpCode, response); - } -#endif - } - 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 - // future connections to the same host until there has been a connection to another host inbetween. - client.stop(); - return response; -} -#endif // FEATURE_HTTP_CLIENT - -#if FEATURE_DOWNLOAD - -// FIXME TD-er: Must set the timeout somewhere -# ifndef DOWNLOAD_FILE_TIMEOUT - # define DOWNLOAD_FILE_TIMEOUT 2000 -# endif // ifndef DOWNLOAD_FILE_TIMEOUT - -// Download a file from a given URL and save to a local file named "file_save" -// If the URL ends with a /, the file part will be assumed the same as file_save. -// If file_save is empty, the file part from the URL will be used as local file name. -// Return true when successful. -bool downloadFile(const String& url, String file_save) { - String error; - - return downloadFile(url, file_save, EMPTY_STRING, EMPTY_STRING, error); -} - -// User and Pass may be updated if they occur in the hostname part. -// Thus have to be copied instead of const reference. -bool start_downloadFile(WiFiClient & client, - HTTPClient & http, - const String& url, - String & file_save, - String user, - String pass, - String & error) { - String host, file; - uint16_t port; - String uri = splitURL(url, user, pass, host, port, file); - - if (file_save.isEmpty()) { - file_save = file; - } else if ((file.isEmpty()) && uri.endsWith("/")) { - // file = file_save; - uri += file_save; - } -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - 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 - - if (file_save.isEmpty()) { - error = F("Empty filename"); - addLog(LOG_LEVEL_ERROR, error); - return false; - } - - const int httpCode = http_authenticate( - F("DownloadFile"), - client, - http, - DOWNLOAD_FILE_TIMEOUT, - user, - pass, - host, - port, - uri, - F("GET"), - EMPTY_STRING, // header - EMPTY_STRING, // postStr - true // must_check_reply - ); - - if (httpCode != HTTP_CODE_OK) { - error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); - - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - return true; -} - -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)) { - return false; - } - - if (fileExists(file_save)) { - error = concat(F("File exists: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - - long len = http.getSize(); - fs::File f = tryOpenFile(file_save, "w"); - - if (f) { - const size_t downloadBuffSize = 256; - uint8_t buff[downloadBuffSize]; - size_t bytesWritten = 0; - unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - // get tcp stream - WiFiClient *stream = &client; - - // read all data from server - while (http.connected() && (len > 0 || len == -1)) { - // read up to downloadBuffSize at a time. - size_t bytes_to_read = downloadBuffSize; - - if ((len > 0) && (len < static_cast(bytes_to_read))) { - bytes_to_read = len; - } - const size_t c = stream->readBytes(buff, bytes_to_read); - - if (c > 0) { - 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); - addLog(LOG_LEVEL_ERROR, error); - http.end(); - client.stop(); - return false; - } - bytesWritten += c; - - if (len > 0) { len -= c; } - } - - if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - delay(0); - http.end(); - client.stop(); - return false; - } - delay(0); - } - f.close(); - http.end(); - client.stop(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); - } - return true; - } - http.end(); - client.stop(); - error = concat(F("Failed to open file for writing: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - return false; -} - -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; - } -# endif // if FEATURE_CUSTOM_PROVISIONING - - const String fullUrl = joinUrlFilename(baseurl, filename); - - return downloadFirmware(fullUrl, filename, user, pass, error); -} - -bool downloadFirmware(const String& url, String& file_save, String& user, String& pass, String& error) -{ - WiFiClient client; - HTTPClient http; - - if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { - return false; - } - - int len = http.getSize(); - - if (Update.begin(len, U_FLASH, Settings.Pin_status_led, Settings.Pin_status_led_Inversed ? LOW : HIGH)) { - const size_t downloadBuffSize = 256; - uint8_t buff[downloadBuffSize]; - size_t bytesWritten = 0; - unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; - - // get tcp stream - WiFiClient *stream = &client; - - while (http.connected() && (len > 0 || len == -1)) { - // read up to downloadBuffSize at a time. - size_t bytes_to_read = downloadBuffSize; - - if ((len > 0) && (len < static_cast(bytes_to_read))) { - bytes_to_read = len; - } - const size_t c = stream->readBytes(buff, bytes_to_read); - - if (c > 0) { - 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); - addLog(LOG_LEVEL_ERROR, error); - Update.end(); - http.end(); - client.stop(); - return false; - } - bytesWritten += c; - - if (len > 0) { len -= c; } - } - - if (timeOutReached(timeout)) { - error = concat(F("Timeout: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - delay(0); - Update.end(); - http.end(); - client.stop(); - return false; - } - - if (!UseRTOSMultitasking) { - // On ESP32 the schedule is executed on the 2nd core. - Scheduler.handle_schedule(); - } - backgroundtasks(); - } - http.end(); - client.stop(); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); - } - - if (Update.end()) { - if (Settings.UseRules) { - eventQueue.addMove(concat(F("ProvisionFirmware#success="), file_save)); - } - } - return true; - } - http.end(); - client.stop(); - Update.end(); - error = concat(F("Failed update firmware: "), file_save); - addLog(LOG_LEVEL_ERROR, error); - - if (Settings.UseRules) { - String event = F("ProvisionFirmware#failed="); - event += file_save; - eventQueue.addMove(std::move(event)); - } - return false; -} - -String joinUrlFilename(const String& url, String& filename) -{ - String fullUrl; - - fullUrl.reserve(url.length() + filename.length() + 1); // May need to add an extra slash - fullUrl = url; - fullUrl = parseTemplate(fullUrl, true); // URL encode - - // URLEncode may also encode the '/' into "%2f" - // FIXME TD-er: Can this really occur? - fullUrl.replace(F("%2f"), F("/")); - - while (filename.startsWith(F("/"))) { - filename = filename.substring(1); - } - - if (!fullUrl.endsWith(F("/"))) { - fullUrl += F("/"); - } - fullUrl += filename; - return fullUrl; -} - -#endif // if FEATURE_DOWNLOAD - +#include "../Helpers/Networking.h" + +#include "../Commands/ExecuteCommand.h" +#include "../CustomBuild/CompiletimeDefines.h" +#include "../DataStructs/NodeStruct.h" +#include "../DataStructs/TimingStats.h" +#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 "../ESPEasyCore/Serial.h" +#include "../Globals/ESPEasyEthEvent.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#include "../Globals/ESPEasy_Scheduler.h" + +#ifdef USES_ESPEASY_NOW +#include "../Globals/ESPEasy_now_handler.h" +#endif + +#include "../Globals/EventQueue.h" +#include "../Globals/NetworkState.h" +#include "../Globals/Nodes.h" +#include "../Globals/ResetFactoryDefaultPref.h" +#include "../Globals/Settings.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Hardware.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" + +#include "../../ESPEasy-Globals.h" + +#include +#include +#include // for getDigestAuth + +#include + +#include + +// Generic Networking routines + +// Syslog +// UDP system messaging +// 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) + +// #endif + +#include + +#ifdef ESP8266 +#include +#include +#include +#include +#endif + +#ifdef SUPPORT_ARP +# include + +# ifdef ESP32 +# include +# include + +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); +} + +# endif // ifdef ESP32 + +#endif // ifdef SUPPORT_ARP + +#if FEATURE_DOWNLOAD +# ifdef ESP8266 +# include +# endif // ifdef ESP8266 +# ifdef ESP32 +# include +# include +# endif // ifdef ESP32 +#endif // if FEATURE_DOWNLOAD + +#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 + +/*********************************************************************************************\ + Send event using UDP message +\*********************************************************************************************/ +void SendUDPCommand(uint8_t destUnit, const char *data, uint8_t dataLength) +{ + if (!NetworkConnected(10)) { + return; + } + + if (destUnit != 0) + { + sendUDP(destUnit, (const uint8_t *)data, dataLength); + delay(10); + } else { + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) { + if (it->first != Settings.Unit) { + sendUDP(it->first, (const uint8_t *)data, dataLength); + delay(10); + } + } + } + delay(50); +} + +/*********************************************************************************************\ + Send UDP message to specific unit (unit 255=broadcast) +\*********************************************************************************************/ +void sendUDP(uint8_t unit, const uint8_t *data, uint8_t size) +{ + if (!NetworkConnected(10)) { + return; + } + + IPAddress remoteNodeIP = getIPAddressForUnit(unit); + + if (remoteNodeIP[0] == 0) { + return; + } + +# 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() + )); + } +# endif // ifndef BUILD_NO_DEBUG + + statusLED(true); + FeedSW_watchdog(); + portUDP.beginPacket(remoteNodeIP, Settings.UDPPort); + portUDP.write(data, size); + portUDP.endPacket(); + FeedSW_watchdog(); + delay(0); +} + +/*********************************************************************************************\ + Update UDP port (ESPEasy propiertary protocol) +\*********************************************************************************************/ +void updateUDPport(bool force) +{ + static uint16_t lastUsedUDPPort = 0; + + if (!force && Settings.UDPPort == lastUsedUDPPort) { + return; + } + + if (lastUsedUDPPort != 0) { + portUDP.stop(); + lastUsedUDPPort = 0; + } + + if (!NetworkConnected()) { + return; + } + + if (Settings.UDPPort != 0) { + if (portUDP.begin(Settings.UDPPort) == 0) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("UDP : Cannot bind to ESPEasy p2p UDP port "), Settings.UDPPort)); + } + } else { + lastUsedUDPPort = Settings.UDPPort; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("UDP : Start listening on port "), Settings.UDPPort)); + } + } + } +} + +/*********************************************************************************************\ + Check UDP messages (ESPEasy propiertary protocol) +\*********************************************************************************************/ +boolean runningUPDCheck = false; +void checkUDP() +{ + if (!NetworkConnected()) + return; + if (Settings.UDPPort == 0) { + return; + } + + if (runningUPDCheck) { + return; + } + START_TIMER + + runningUPDCheck = true; + + // UDP events + int packetSize = portUDP.parsePacket(); + + if (packetSize > 0 /*&& portUDP.remotePort() == Settings.UDPPort*/) + { + statusLED(true); + + IPAddress remoteIP = portUDP.remoteIP(); + + if (portUDP.remotePort() == 123) + { + // unexpected NTP reply, drop for now... + while (portUDP.available()) { + // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) + portUDP.read(); + } + + runningUPDCheck = false; + return; + } + + // UDP_PACKETSIZE_MAX should be as small as possible but still enough to hold all + // data for PLUGIN_UDP_IN or CPLUGIN_UDP_IN calls + // This node may also receive other UDP packets which may be quite large + // 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 + // in case it is some plain text string + std::vector packetBuffer; + packetBuffer.resize(packetSize + 1); + + if (packetBuffer.size() >= static_cast(packetSize)) { + memset(&packetBuffer[0], 0, packetSize + 1); + int len = portUDP.read(&packetBuffer[0], packetSize); + + if (len >= 2) { + if (static_cast(packetBuffer[0]) != 255) + { + packetBuffer[len] = 0; + # 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() + )); + } + #endif + ExecuteCommand_all({EventValueSource::Enum::VALUE_SOURCE_SYSTEM, &packetBuffer[0]}, true); + } + else + { + // binary data! + switch (packetBuffer[1]) + { + case 1: // sysinfo message + { + if (len < 13) { + 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) { + copy_length = 56; + } + + if (copy_length > (len - 2)) { + copy_length = (len - 2); + } + NodeStruct received; + memcpy(&received, &packetBuffer[2], copy_length); + + if (received.validate(remoteIP)) { + Nodes.addNode(received); // Create a new element when not present + +# 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)); + } + +#endif // ifndef BUILD_NO_DEBUG + } + break; + } + + default: + { + struct EventStruct TempEvent; + TempEvent.Data = reinterpret_cast(&packetBuffer[0]); + 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); + CPluginCall(CPlugin::Function::CPLUGIN_UDP_IN, &TempEvent); + break; + } + } + } + } + } + } + } + + // Flush any remaining content of the packet. + while (portUDP.available()) { + // Do not call portUDP.flush() as that's meant to sending the packet (on ESP8266) + portUDP.read(); + } + runningUPDCheck = false; + STOP_TIMER(CHECK_UDP); +} + +/*********************************************************************************************\ + Get formatted IP address for unit + formatcodes: 0 = default toString(), 1 = empty string when invalid, 2 = 0 when invalid +\*********************************************************************************************/ +String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode) { + IPAddress unitIPAddress = getIPAddressForUnit(unit); + + if (unitIPAddress[0] == 0) { // Invalid? + switch (formatCode) { + case 1: // Return empty string + { + return EMPTY_STRING; + } + case 2: // Return "0" + { + return String('0'); + } + } + } + return formatIP(unitIPAddress); +} + +/*********************************************************************************************\ + Get IP address for unit +\*********************************************************************************************/ +IPAddress getIPAddressForUnit(uint8_t unit) { + if (unit == 255) { + const IPAddress ip(255, 255, 255, 255); + return ip; + } + auto it = Nodes.find(unit); + + 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 + return it->second.IP(); +} + + +String getNameForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end() || it->second.getNodeName().isEmpty()) { + return EMPTY_STRING; + } + return it->second.getNodeName(); +} + +long getAgeForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return -1000; // milliseconds, negative == unknown + } + return static_cast(it->second.getAge()); +} + +uint16_t getBuildnrForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end() || it->second.build == 0) { + return 0; + } + return it->second.build; +} + +float getLoadForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return 0.0f; + } + return it->second.getLoad(); +} + +uint8_t getTypeForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return 0; + } + return it->second.nodeType; +} + +const __FlashStringHelper* getTypeStringForUnit(uint8_t unit) { + auto it = Nodes.find(unit); + + if (it == Nodes.end()) { + return F(""); + } + return it->second.getNodeTypeDisplayString(); +} + +/*********************************************************************************************\ + Refresh aging for remote units, drop if too old... +\*********************************************************************************************/ +void refreshNodeList() +{ + unsigned long max_age; + const unsigned long max_age_allowed = 10 * 60 * 1000; // 10 minutes + + Nodes.refreshNodeList(max_age_allowed, max_age); + + #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()); + } + #endif + #endif + + if (max_age > (0.75 * max_age_allowed)) { + Scheduler.sendGratuitousARP_now(); + } + sendSysInfoUDP(1); + #ifdef USES_ESPEASY_NOW + if (Nodes.recentlyBecameDistanceZero()) { + // Send to all channels + ESPEasy_now_handler.sendDiscoveryAnnounce(-1); + } else { + ESPEasy_now_handler.sendDiscoveryAnnounce(); + } + ESPEasy_now_handler.sendNTPquery(); + ESPEasy_now_handler.sendTraceRoute(); + #endif // ifdef USES_ESPEASY_NOW +} + +/*********************************************************************************************\ + Broadcast system info to other nodes. (to update node lists) +\*********************************************************************************************/ +void sendSysInfoUDP(uint8_t repeats) +{ + if ((Settings.UDPPort == 0) || !NetworkConnected(10)) { + return; + } + + // 1 byte 'binary token 255' + // 1 byte id '1' + // NodeStruct object (packed data struct) + + // send my info to the world... +# ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, F("UDP : Send Sysinfo message")); +# endif // ifndef BUILD_NO_DEBUG + + const NodeStruct *thisNode = Nodes.getThisNode(); + + if (thisNode == nullptr) { + // Should not happen + return; + } + + // Prepare UDP packet to send + constexpr size_t data_size = sizeof(NodeStruct) + 2; + uint8_t data[data_size] = {0}; + data[0] = 255; + data[1] = 1; + memcpy(&data[2], thisNode, sizeof(NodeStruct)); + + for (uint8_t counter = 0; counter < repeats; counter++) + { + statusLED(true); + + IPAddress broadcastIP(255, 255, 255, 255); + FeedSW_watchdog(); + portUDP.beginPacket(broadcastIP, Settings.UDPPort); + portUDP.write(data, data_size); + portUDP.endPacket(); + + if (counter < (repeats - 1)) { + // FIXME TD-er: Must use scheduler to send out messages, not using delay + delay(100); + } + } +} + +#endif // FEATURE_ESPEASY_P2P + +#if defined(ESP8266) + +# if FEATURE_SSDP + +/********************************************************************************************\ + Respond to HTTP XML requests for SSDP information + \*********************************************************************************************/ +void SSDP_schema() { + if (!NetworkConnected(10)) { + return; + } + + const IPAddress ip = NetworkLocalIP(); + const uint32_t chipId = ESP.getChipId(); + char uuid[64]; + + sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), + (uint16_t)((chipId >> 16) & 0xff), + (uint16_t)((chipId >> 8) & 0xff), + (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://")); + + web_server.client().print(formatIP(ip)); + web_server.client().print(F(":80/" + "" + "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" + "")); + 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:")); + web_server.client().print(String(uuid)); + web_server.client().print(F("" + "\r\n" + "\r\n")); +} + +/********************************************************************************************\ + Global SSDP stuff + \*********************************************************************************************/ + +UdpContext *_server; + +IPAddress _respondToAddr; +uint16_t _respondToPort; + +bool _pending; +unsigned short _delay; +unsigned long _process_time; +unsigned long _notify_time; + +# define SSDP_INTERVAL 1200 +# define SSDP_PORT 1900 +# define SSDP_METHOD_SIZE 10 +# define SSDP_URI_SIZE 2 +# define SSDP_BUFFER_SIZE 64 +# define SSDP_MULTICAST_TTL 2 + +static const IPAddress SSDP_MULTICAST_ADDR(239, 255, 255, 250); + + +/********************************************************************************************\ + Launch SSDP listener and send initial notify + \*********************************************************************************************/ +bool SSDP_begin() { + _pending = false; + + if (_server != nullptr) { + _server->unref(); + + // FIXME TD-er: Shouldn't this also call delete _server ? + + _server = nullptr; + } + + _server = new (std::nothrow) UdpContext; + + if (_server == nullptr) { + return false; + } + _server->ref(); + + ip_addr_t ifaddr; + + ifaddr.addr = NetworkLocalIP(); + ip_addr_t multicast_addr; + + multicast_addr.addr = (uint32_t)SSDP_MULTICAST_ADDR; + + if (igmp_joingroup(&ifaddr, &multicast_addr) != ERR_OK) { + return false; + } + +# ifdef CORE_POST_2_5_0 + + // Core 2.5.0 changed the signature of some UdpContext function. + if (!_server->listen(IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(&ifaddr); + _server->setMulticastTTL(SSDP_MULTICAST_TTL); + _server->onRx(&SSDP_update); + + if (!_server->connect(&multicast_addr, SSDP_PORT)) { + return false; + } +# else // ifdef CORE_POST_2_5_0 + + if (!_server->listen(*IP_ADDR_ANY, SSDP_PORT)) { + return false; + } + + _server->setMulticastInterface(ifaddr); + _server->setMulticastTTL(SSDP_MULTICAST_TTL); + _server->onRx(&SSDP_update); + + if (!_server->connect(multicast_addr, SSDP_PORT)) { + return false; + } +# endif // ifdef CORE_POST_2_5_0 + + SSDP_update(); + + return true; +} + +/********************************************************************************************\ + Send SSDP messages (notify & responses) + \*********************************************************************************************/ +void SSDP_send(uint8_t method) { + uint32_t ip = NetworkLocalIP(); + + // FIXME TD-er: Why create String objects of these flashstrings? + String _ssdp_response_template = F( + "HTTP/1.1 200 OK\r\n" + "EXT:\r\n" + "ST: upnp:rootdevice\r\n"); + + String _ssdp_notify_template = F( + "NOTIFY * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "NT: upnp:rootdevice\r\n" + "NTS: ssdp:alive\r\n"); + + String _ssdp_packet_template = F( + "%s" // _ssdp_response_template / _ssdp_notify_template + "CACHE-CONTROL: max-age=%u\r\n" // SSDP_INTERVAL + "SERVER: Arduino/1.0 UPNP/1.1 ESPEasy/%u\r\n" // _modelNumber + "USN: uuid:%s\r\n" // _uuid + "LOCATION: http://%u.%u.%u.%u:80/ssdp.xml\r\n" // NetworkLocalIP(), + "\r\n"); + { + char uuid[64] = { 0 }; + uint32_t chipId = ESP.getChipId(); + sprintf_P(uuid, PSTR("38323636-4558-4dda-9188-cda0e6%02x%02x%02x"), + (uint16_t)((chipId >> 16) & 0xff), + (uint16_t)((chipId >> 8) & 0xff), + (uint16_t)chipId & 0xff); + + char *buffer = new (std::nothrow) char[1460](); + + if (buffer == nullptr) { return; } + int len = snprintf(buffer, 1460, + _ssdp_packet_template.c_str(), + (method == 0) ? _ssdp_response_template.c_str() : _ssdp_notify_template.c_str(), + SSDP_INTERVAL, + Settings.Build, + uuid, + IPADDR2STR(&ip) + ); + + _server->append(buffer, len); + delete[] buffer; + } + + ip_addr_t remoteAddr; + uint16_t remotePort; + + if (method == 0) { + remoteAddr.addr = _respondToAddr; + remotePort = _respondToPort; + } else { + remoteAddr.addr = SSDP_MULTICAST_ADDR; + remotePort = SSDP_PORT; + } + _server->send(&remoteAddr, remotePort); + statusLED(true); +} + +/********************************************************************************************\ + SSDP message processing + \*********************************************************************************************/ +void SSDP_update() { + if (!_pending && _server->next()) { + ssdp_method_t method = NONE; + + _respondToAddr = _server->getRemoteAddress(); + _respondToPort = _server->getRemotePort(); + + typedef enum { METHOD, URI, PROTO, KEY, VALUE, ABORT } states; + states state = METHOD; + + typedef enum { START, MAN, ST, MX } headers; + headers header = START; + + uint8_t cursor = 0; + uint8_t cr = 0; + + char buffer[SSDP_BUFFER_SIZE] = { 0 }; + + while (_server->getSize() > 0) { + char c = _server->read(); + + (c == '\r' || c == '\n') ? cr++ : cr = 0; + + switch (state) { + case METHOD: + + if (c == ' ') { + if (strcmp_P(buffer, PSTR("M-SEARCH")) == 0) { method = SEARCH; } + else if (strcmp_P(buffer, PSTR("NOTIFY")) == 0) { method = NOTIFY; } + + if (method == NONE) { state = ABORT; } + else { state = URI; } + cursor = 0; + } else if (cursor < SSDP_METHOD_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case URI: + + if (c == ' ') { + if (strcmp(buffer, "*")) { state = ABORT; } + else { state = PROTO; } + cursor = 0; + } else if (cursor < SSDP_URI_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case PROTO: + + if (cr == 2) { + state = KEY; + cursor = 0; + } + break; + case KEY: + + if (cr == 4) { + _pending = true; + _process_time = millis(); + } + else if (c == ' ') { + cursor = 0; + state = VALUE; + } + else if ((c != '\r') && (c != '\n') && (c != ':') && (cursor < SSDP_BUFFER_SIZE - 1)) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + break; + case VALUE: + + if (cr == 2) { + switch (header) { + case START: + break; + case MAN: + break; + case ST: + + if (strcmp_P(buffer, PSTR("ssdp:all"))) { + state = ABORT; + } + + // if the search type matches our type, we should respond instead of ABORT + if (strcmp_P(buffer, PSTR("urn:schemas-upnp-org:device:BinaryLight:1")) == 0) { + _pending = true; + _process_time = millis(); + state = KEY; + } + break; + case MX: + _delay = HwRandom(0, atoi(buffer)) * 1000L; + break; + } + + if (state != ABORT) { + state = KEY; + header = START; + cursor = 0; + } + } else if ((c != '\r') && (c != '\n')) { + if (header == START) { + if (strncmp(buffer, "MA", 2) == 0) { header = MAN; } + else if (strcmp(buffer, "ST") == 0) { header = ST; } + else if (strcmp(buffer, "MX") == 0) { header = MX; } + } + + if (cursor < SSDP_BUFFER_SIZE - 1) { + buffer[cursor++] = c; + buffer[cursor] = '\0'; + } + } + break; + case ABORT: + _pending = false; _delay = 0; + break; + } + } + } + + if (_pending && timeOutReached(_process_time + _delay)) { + _pending = false; _delay = 0; + SSDP_send(NONE); + } else if ((_notify_time == 0) || timeOutReached(_notify_time + (SSDP_INTERVAL * 1000L))) { + _notify_time = millis(); + SSDP_send(NOTIFY); + } + + if (_pending) { + while (_server->next()) { + _server->flush(); + } + } +} + +# endif // if FEATURE_SSDP +#endif // if defined(ESP8266) + + +// ******************************************************************************** +// Return subnet range of WiFi. +// ******************************************************************************** +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]; + } + } + return true; +} + +// ******************************************************************************** +// Functions to test and handle network/client connectivity. +// ******************************************************************************** + +#ifdef CORE_POST_2_5_0 +# include +#endif // ifdef CORE_POST_2_5_0 + + +bool hasIPaddr() { + if (useStaticIP()) { return true; } + +#ifdef CORE_POST_2_5_0 + bool configured = false; + + for (auto addr : addrList) { + if ((configured = (!addr.isLocal() && (addr.ifnumber() == STATION_IF)))) { + /* + ESPEASY_SERIAL_CONSOLE_PORT.printf("STA: IF='%s' hostname='%s' addr= %s\n", + addr.ifname().c_str(), + addr.ifhostname(), + addr.toString().c_str()); + */ + break; + } + } + return configured; +#else // ifdef CORE_POST_2_5_0 + return WiFi.isConnected(); +#endif // ifdef CORE_POST_2_5_0 +} + +bool useStaticIP() { + #if FEATURE_ETHERNET + if (active_network_medium == NetworkMedium_t::Ethernet) { + return ethUseStaticIP(); + } + #endif + return WiFiUseStaticIP(); +} + +// Check connection. Maximum timeout 500 msec. +bool NetworkConnected(uint32_t timeout_ms) { + +#ifdef USES_ESPEASY_NOW + if (isESPEasy_now_only()) { + return false; + } +#endif + + if (timeout_ms > 500) { + timeout_ms = 500; + } + + uint32_t timer = millis() + timeout_ms; + uint32_t min_delay = timeout_ms / 20; + + if (min_delay < 10) { + delay(0); // Allow at least once time for backgroundtasks + min_delay = 10; + } + + // Apparently something needs network, perform check to see if it is ready now. + while (!NetworkConnected()) { + if (timeOutReached(timer)) { + return false; + } + delay(min_delay); // Allow the backgroundtasks to continue procesing. + } + return true; +} + +bool hostReachable(const IPAddress& ip) { + if (!NetworkConnected()) { return false; } + + return true; // Disabled ping as requested here: + // https://github.com/letscontrolit/ESPEasy/issues/1494#issuecomment-397872538 + + /* + // Only do 1 ping at a time to return early + uint8_t retry = 3; + while (retry > 0) { + #if defined(ESP8266) + if (Ping.ping(ip, 1)) return true; + #endif + #if defined(ESP32) + if (ping_start(ip, 4, 0, 0, 5)) return true; + #endif + delay(50); + --retry; + } + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("Host unreachable: "); + log += formatIP(ip); + addLog(LOG_LEVEL_ERROR, log); + } + if (ip[1] == 0 && ip[2] == 0 && ip[3] == 0) { + // Work-around to fix connected but not able to communicate. + addLog(LOG_LEVEL_ERROR, F("WiFi : Detected strange behavior, reconnect wifi.")); + WifiDisconnect(); + } + logConnectionStatus(); + return false; + */ +} + +#if FEATURE_HTTP_CLIENT +bool connectClient(WiFiClient& client, const char *hostname, uint16_t port, uint32_t timeout_ms) { + IPAddress ip; + + if (resolveHostByName(hostname, ip, timeout_ms)) { + return connectClient(client, ip, port, timeout_ms); + } + return false; +} + +bool connectClient(WiFiClient& client, IPAddress ip, uint16_t port, uint32_t timeout_ms) +{ + START_TIMER; + + if (!NetworkConnected()) { + client.stop(); + return false; + } + + // In case of domain name resolution error result can be negative. + // https://github.com/esp8266/Arduino/blob/18f643c7e2d6a0da9d26ff2b14c94e6536ab78c1/libraries/Ethernet/src/Dns.cpp#L44 + // Thus must match the result with 1. + bool connected = (client.connect(ip, port) == 1); + + delay(0); + + if (!connected) { + 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 (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) + return connected; +} +#endif // FEATURE_HTTP_CLIENT + +void scrubDNS() { + #if FEATURE_ETHERNET + if (active_network_medium == 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); + } +} + +bool valid_DNS_address(const IPAddress& dns) { + 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); +} + +bool setDNS(int index, const IPAddress& dns) { + 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 (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("IP : Set DNS: "), formatIP(dns))); + } + return true; + } + #endif + return false; +} + +bool resolveHostByName(const char *aHostname, IPAddress& aResult, uint32_t timeout_ms) { + START_TIMER; + + if (!NetworkConnected()) { + return false; + } + + FeedSW_watchdog(); + + // FIXME TD-er: Must try to restore DNS server entries. + scrubDNS(); + +#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + bool resolvedIP = WiFi.hostByName(aHostname, aResult) == 1; +#else // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + bool resolvedIP = WiFi.hostByName(aHostname, aResult, timeout_ms) == 1; +#endif // if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ESP32) + delay(0); + FeedSW_watchdog(); + + if (!resolvedIP) { + Scheduler.sendGratuitousARP_now(); + } + STOP_TIMER(HOST_BY_NAME_STATS); + return resolvedIP; +} + +bool hostReachable(const String& hostname) { + IPAddress remote_addr; + + if (resolveHostByName(hostname.c_str(), remote_addr)) { + return hostReachable(remote_addr); + } + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, concat(F("Hostname cannot be resolved: "), hostname)); + } + return false; +} + +// Create a random port for the UDP connection. +// Return true when successful. +bool beginWiFiUDP_randomPort(WiFiUDP& udp) { + if (!NetworkConnected()) { + return false; + } + unsigned int attempts = 3; + + while (attempts > 0) { + --attempts; + long port = HwRandom(1025, 65535); + + if (udp.begin(port) != 0) { + return true; + } + } + return false; +} + +void sendGratuitousARP() { + if (!NetworkConnected()) { + return; + } +#ifdef SUPPORT_ARP + + // See https://github.com/letscontrolit/ESPEasy/issues/2374 + START_TIMER; + netif *n = netif_list; + + while (n) { + if ((n->hwaddr_len == ETH_HWADDR_LEN) && + (n->flags & NETIF_FLAG_ETHARP) && + ((n->flags & NETIF_FLAG_LINK_UP) && (n->flags & NETIF_FLAG_UP))) { + # ifdef ESP32 + etharp_gratuitous_r(n); + # else // ifdef ESP32 + etharp_gratuitous(n); + # endif // ifdef ESP32 + } + n = n->next; + } + STOP_TIMER(GRAT_ARP_STATS); +#endif // ifdef SUPPORT_ARP +} + +bool splitHostPortString(const String& hostPortString, String& host, uint16_t& port) { + port = 80; // Some default + int index_colon = hostPortString.indexOf(':'); + + if (index_colon >= 0) { + int32_t port_tmp; + + if (!validIntFromString(hostPortString.substring(index_colon + 1), port_tmp)) { + return false; + } + + if ((port_tmp < 0) || (port_tmp > 65535)) { return false; } + port = port_tmp; + host = hostPortString.substring(0, index_colon); + } else { + // No port nr defined. + host = hostPortString; + } + return true; +} + +bool splitUserPass_HostPortString(const String& hostPortString, String& user, String& pass, String& host, uint16_t& port) +{ + const int pos_at = hostPortString.indexOf('@'); + + if (pos_at != -1) { + user = hostPortString.substring(0, pos_at); + const int pos_colon = user.indexOf(':'); + + if (pos_colon != -1) { + pass = user.substring(pos_colon + 1); + user = user.substring(0, pos_colon); + } + return splitHostPortString(hostPortString.substring(pos_at + 1), host, port); + } + return splitHostPortString(hostPortString, host, port); +} + +// Split a full URL like "http://hostname:port/path/file.htm" +// Return value is everything after the hostname:port section (including /) +String splitURL(const String& fullURL, String& user, String& pass, String& host, uint16_t& port, String& file) { + int starthost = fullURL.indexOf(F("://")); + + if (starthost == -1) { + starthost = 0; + } else { + starthost += 3; + } + const int endhost = fullURL.indexOf('/', starthost); + splitUserPass_HostPortString(fullURL.substring(starthost, endhost), user, pass, host, port); + + if (endhost == -1) { + return EMPTY_STRING; + } + + int startfile = fullURL.lastIndexOf('/'); + + if (startfile >= 0) { + file = fullURL.substring(startfile); + } + return fullURL.substring(endhost); +} + +String get_user_agent_string() { + static unsigned int agent_size = 20; + String userAgent; + + userAgent.reserve(agent_size); + userAgent += F("ESP Easy/"); + userAgent += get_build_nr(); + userAgent += '/'; + userAgent += get_build_date(); + userAgent += ' '; + userAgent += get_build_time(); + agent_size = userAgent.length(); + return userAgent; +} + +bool splitHeaders(int& strpos, const String& multiHeaders, String& name, String& value) { + if (strpos < 0) { + return false; + } + int colonPos = multiHeaders.indexOf(':', strpos); + + if (colonPos < 0) { + return false; + } + name = multiHeaders.substring(strpos, colonPos); + int valueEndPos = multiHeaders.indexOf('\n', colonPos + 1); + + if (valueEndPos < 0) { + value = multiHeaders.substring(colonPos + 1); + strpos = -1; + } else { + value = multiHeaders.substring(colonPos + 1, valueEndPos); + strpos = valueEndPos + 1; + } + value.replace('\r', ' '); + value.trim(); + return true; +} + +String extractParam(const String& authReq, const String& param, const char delimit) { + int _begin = authReq.indexOf(param); + + if (_begin == -1) { return EMPTY_STRING; } + return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length())); +} + +#if FEATURE_HTTP_CLIENT +String getCNonce(const int len) { + static const char alphanum[] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + String s; + + for (int i = 0; i < len; ++i) { + // FIXME TD-er: Is this "-1" correct? The mod operator makes sure we never reach the sizeof index + s += alphanum[rand() % (sizeof(alphanum) - 1)]; + } + + return s; +} + +String getDigestAuth(const String& authReq, + const String& username, + const String& password, + const String& method, + const String& uri, + unsigned int counter) { + // extracting required parameters for RFC 2069 simpler Digest + const String realm = extractParam(authReq, F("realm=\""), '"'); + const String nonce = extractParam(authReq, F("nonce=\""), '"'); + const String cNonce = getCNonce(8); + + char nc[9]; + + snprintf(nc, sizeof(nc), "%08x", counter); + + // parameters for the RFC 2617 newer Digest + MD5Builder md5; + + md5.begin(); + md5.add(username + ':' + realm + ':' + password); // md5 of the user:realm:user + md5.calculate(); + const String h1 = md5.toString(); + + md5.begin(); + md5.add(method + ':' + uri); + md5.calculate(); + const String h2 = md5.toString(); + + md5.begin(); + md5.add(h1 + ':' + nonce + ':' + String(nc) + ':' + cNonce + F(":auth:") + h2); + md5.calculate(); + + // return authorization + return strformat( + F("Digest username=\"%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(), + uri.c_str(), + nc, + cNonce.c_str(), + md5.toString().c_str()); // response +} + +#ifndef BUILD_NO_DEBUG +void log_http_result(const HTTPClient& http, + const String & logIdentifier, + const String & host, + const String & HttpMethod, + int httpCode, + const String & response) +{ + uint8_t loglevel = LOG_LEVEL_ERROR; + bool success = false; + + // HTTP codes: + // 1xx Informational response + // 2xx Success + if ((httpCode >= 100) && (httpCode < 300)) { + loglevel = LOG_LEVEL_INFO; + success = true; + } + + if (loglevelActiveFor(loglevel)) { + String log = strformat(F("HTTP : %s %s %s"), + logIdentifier.c_str(), host.c_str(), HttpMethod.c_str()); + + if (!success) { + log += F("failed "); + } + log += concat(F("HTTP code: "), httpCode); + + if (!success) { + log += ' '; + log += http.errorToString(httpCode); + } + + if (response.length() > 0) { + log += concat(F(" Received reply: "), response.substring(0, 100)); // Returned string may be huge, so only log the first part. + } + addLogMove(loglevel, log); + } +} +#endif + +int http_authenticate(const String& logIdentifier, + WiFiClient & client, + HTTPClient & http, + uint16_t timeout, + const String& user, + const String& pass, + const String& host, + uint16_t port, + const String& uri, + const String& HttpMethod, + const String& header, + const String& postStr, + bool must_check_reply) +{ + if (!uri.startsWith(F("/"))) { + return http_authenticate( + logIdentifier, + client, + http, + timeout, + user, + pass, + host, + port, + concat(F("/"), uri), + HttpMethod, + header, + postStr, + must_check_reply); + } + 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.setAuthorizationType(""); // Default type is "Basic" +#endif + } + http.setTimeout(timeout); + http.setUserAgent(get_user_agent_string()); + + if (Settings.SendToHTTP_follow_redirects()) { + http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); + http.setRedirectLimit(2); + } + + #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 + + // 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")); + + // Add client IP + http.addHeader(F("X-Forwarded-For"), formatIP(NetworkLocalIP())); + + delay(0); + scrubDNS(); +#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) + http.begin(client, host, port, uri); +#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) + + const char *keys[] = { "WWW-Authenticate" }; + http.collectHeaders(keys, 1); + + { + int headerpos = 0; + String name, value; + + while (splitHeaders(headerpos, header, name, value)) { + // 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); +// } + } + } + + // start connection and send HTTP header (and body) + if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { + httpCode = http.sendRequest(HttpMethod.c_str()); + } else { + httpCode = http.sendRequest(HttpMethod.c_str(), postStr); + } + + // Check to see if we need to try digest auth + if ((httpCode == 401) && must_check_reply) { + const String authReq = http.header(String(F("WWW-Authenticate")).c_str()); + + if (authReq.indexOf(F("Digest")) != -1) { + // Use Digest authorization + 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 + const String authorization = getDigestAuth(authReq, user, pass, F("GET"), uri, 1); + + http.end(); +#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) + http.begin(client, host, port, uri); +#endif // if defined(CORE_POST_2_6_0) || defined(ESP32) + + http.addHeader(F("Authorization"), authorization); + + // start connection and send HTTP header (and body) + if (equals(HttpMethod, F("HEAD")) || equals(HttpMethod, F("GET"))) { + httpCode = http.sendRequest(HttpMethod.c_str()); + } else { + httpCode = http.sendRequest(HttpMethod.c_str(), postStr); + } + } + } + + if (!must_check_reply) { + // There are services which do not send an ack. + // So if the return code matches a read timeout, we change it into HTTP code 200 + if (httpCode == HTTPC_ERROR_READ_TIMEOUT) { + httpCode = 200; + } + } + + if (Settings.UseRules) { + // Generate event with the HTTP return code + // e.g. http#hostname=401 + eventQueue.addMove(strformat(F("http#%s=%d"), host.c_str(), httpCode)); + + #if FEATURE_THINGSPEAK_EVENT + // Generate event with the response of a + // thingspeak request (https://de.mathworks.com/help/thingspeak/readlastfieldentry.html & + // https://de.mathworks.com/help/thingspeak/readdata.html) + // e.g. command for a specific field: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/fields/5/last.csv" + // command for all fields: "sendToHTTP,api.thingspeak.com,80,/channels/1637928/feeds/last.csv" + // where first eventvalue is the channel number and the second to the nineth event values + // are the field values + // Example of the event: "EVENT: ThingspeakReply=1637928,5,24.2,12,900,..." + // ^ ^ â””------┬------┘ + // channel number ┘ | â”” received values + // field number (only available for a "single-value-event") + // In rules you can grep the reply by "On ThingspeakReply Do ..." + //----------------------------------------------------------------------------------------------------------------------------- + // 2024-02-05 - Added the option to get a single value of a field or all values of a channel at a certain time (not only the last entry) + // Examples: + // Single channel: "sendtohttp,api.thingspeak.com,80,channels/1637928/fields/1.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of field 1 at (or the last entry before) 23:59:00 of the channel 1637928 + // All channels: "sendtohttp,api.thingspeak.com,80,channels/1637928/feeds.csv?end=2024-01-01%2023:59:00&results=1" + // => gets the value of each field of the channel 1637928 at (or the last entry before) 23:59:00 + //----------------------------------------------------------------------------------------------------------------------------- + + if (httpCode == 200 && equals(host, F("api.thingspeak.com")) && (uri.endsWith(F("/last.csv")) || (uri.indexOf(F("results=1")) >= 0 && uri.indexOf(F(".csv")) >= 0))){ + String result = http.getString(); + result.replace(' ', '_'); // if using a single field with a certain time, the result contains a space and would break the code + const int posTimestamp = result.lastIndexOf(':'); + if (posTimestamp >= 0){ + result = parseStringToEndKeepCase(result.substring(posTimestamp), 3); + if (uri.indexOf(F("fields")) >= 0){ // when there is a single field call add the field number before the value + result = parseStringKeepCase(uri, 4, '/').substring(0, 1) + "," + result; // since the field number is always the fourth part of the url and is always a single digit, we can use this to extact the fieldnumber + } + eventQueue.addMove(strformat( + F("ThingspeakReply=%s,%s"), + parseStringKeepCase(uri, 2, '/').c_str(), + result.c_str())); + } + } + #endif + } + +#ifndef BUILD_NO_DEBUG + log_http_result(http, logIdentifier, host + ':' + port, HttpMethod, httpCode, EMPTY_STRING); +#endif + return httpCode; +} + +String send_via_http(const String& logIdentifier, + uint16_t timeout, + const String& user, + const String& pass, + const String& host, + uint16_t port, + const String& uri, + const String& HttpMethod, + const String& header, + const String& postStr, + int & httpCode, + bool must_check_reply) { + WiFiClient client; + HTTPClient http; + http.setReuse(false); + + httpCode = http_authenticate( + logIdentifier, + client, + http, + timeout, + user, + pass, + host, + port, + uri, + HttpMethod, + header, + postStr, + must_check_reply); + + String response; + + if ((httpCode > 0) && must_check_reply) { + response = http.getString(); +#ifndef BUILD_NO_DEBUG + if (!response.isEmpty()) { + log_http_result(http, logIdentifier, host, HttpMethod, httpCode, response); + } +#endif + } + 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 + // future connections to the same host until there has been a connection to another host inbetween. + client.stop(); + return response; +} +#endif // FEATURE_HTTP_CLIENT + +#if FEATURE_DOWNLOAD + +// FIXME TD-er: Must set the timeout somewhere +# ifndef DOWNLOAD_FILE_TIMEOUT + # define DOWNLOAD_FILE_TIMEOUT 2000 +# endif // ifndef DOWNLOAD_FILE_TIMEOUT + +// Download a file from a given URL and save to a local file named "file_save" +// If the URL ends with a /, the file part will be assumed the same as file_save. +// If file_save is empty, the file part from the URL will be used as local file name. +// Return true when successful. +bool downloadFile(const String& url, String file_save) { + String error; + + return downloadFile(url, file_save, EMPTY_STRING, EMPTY_STRING, error); +} + +// User and Pass may be updated if they occur in the hostname part. +// Thus have to be copied instead of const reference. +bool start_downloadFile(WiFiClient & client, + HTTPClient & http, + const String& url, + String & file_save, + String user, + String pass, + String & error) { + String host, file; + uint16_t port; + String uri = splitURL(url, user, pass, host, port, file); + + if (file_save.isEmpty()) { + file_save = file; + } else if ((file.isEmpty()) && uri.endsWith("/")) { + // file = file_save; + uri += file_save; + } +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + 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 + + if (file_save.isEmpty()) { + error = F("Empty filename"); + addLog(LOG_LEVEL_ERROR, error); + return false; + } + + const int httpCode = http_authenticate( + F("DownloadFile"), + client, + http, + DOWNLOAD_FILE_TIMEOUT, + user, + pass, + host, + port, + uri, + F("GET"), + EMPTY_STRING, // header + EMPTY_STRING, // postStr + true // must_check_reply + ); + + if (httpCode != HTTP_CODE_OK) { + error = strformat(F("HTTP code: %d %s"), httpCode, url.c_str()); + + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + return true; +} + +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)) { + return false; + } + + if (fileExists(file_save)) { + error = concat(F("File exists: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + + long len = http.getSize(); + fs::File f = tryOpenFile(file_save, "w"); + + if (f) { + const size_t downloadBuffSize = 256; + uint8_t buff[downloadBuffSize]; + size_t bytesWritten = 0; + unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + // get tcp stream + WiFiClient *stream = &client; + + // read all data from server + while (http.connected() && (len > 0 || len == -1)) { + // read up to downloadBuffSize at a time. + size_t bytes_to_read = downloadBuffSize; + + if ((len > 0) && (len < static_cast(bytes_to_read))) { + bytes_to_read = len; + } + const size_t c = stream->readBytes(buff, bytes_to_read); + + if (c > 0) { + 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); + addLog(LOG_LEVEL_ERROR, error); + http.end(); + client.stop(); + return false; + } + bytesWritten += c; + + if (len > 0) { len -= c; } + } + + if (timeOutReached(timeout)) { + error = concat(F("Timeout: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + delay(0); + http.end(); + client.stop(); + return false; + } + delay(0); + } + f.close(); + http.end(); + client.stop(); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); + } + return true; + } + http.end(); + client.stop(); + error = concat(F("Failed to open file for writing: "), file_save); + addLog(LOG_LEVEL_ERROR, error); + return false; +} + +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; + } +# endif // if FEATURE_CUSTOM_PROVISIONING + + const String fullUrl = joinUrlFilename(baseurl, filename); + + return downloadFirmware(fullUrl, filename, user, pass, error); +} + +bool downloadFirmware(const String& url, String& file_save, String& user, String& pass, String& error) +{ + WiFiClient client; + HTTPClient http; + error.clear(); + + if (!start_downloadFile(client, http, url, file_save, user, pass, error)) { + return false; + } + + int len = http.getSize(); + + if (Update.begin(len, U_FLASH, Settings.Pin_status_led, Settings.Pin_status_led_Inversed ? LOW : HIGH)) { + const size_t downloadBuffSize = 256; + uint8_t buff[downloadBuffSize]; + size_t bytesWritten = 0; + unsigned long timeout = millis() + DOWNLOAD_FILE_TIMEOUT; + + // get tcp stream + WiFiClient *stream = &client; + + while (error.isEmpty() && http.connected() && (len > 0 || len == -1)) { + // read up to downloadBuffSize at a time. + size_t bytes_to_read = downloadBuffSize; + + if ((len > 0) && (len < static_cast(bytes_to_read))) { + bytes_to_read = len; + } + const size_t c = stream->readBytes(buff, bytes_to_read); + + if (c > 0) { + 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); + break; + } + bytesWritten += c; + + if (len > 0) { len -= c; } + } + + if (timeOutReached(timeout)) { + error = concat(F("Timeout: "), file_save); + break; + } + + if (!UseRTOSMultitasking) { + // On ESP32 the schedule is executed on the 2nd core. + Scheduler.handle_schedule(); + } + backgroundtasks(); + } + } + http.end(); + client.stop(); + + if (error.isEmpty() && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("downloadFile: %s Success"), file_save.c_str())); + } + + 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; + } + error += concat(F(" Error: "), err_fstr); +#else + error += concat(F(" Error: "), errorcode); +#endif + } else { + if (Settings.UseRules) { + eventQueue.addMove(concat(F("ProvisionFirmware#success="), file_save)); + } + return true; + } + + backgroundtasks(); + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("Failed update firmware: "), error)); + } + + if (Settings.UseRules) { + eventQueue.addMove(concat(F("ProvisionFirmware#failed="), file_save)); + } + return false; +} + +String joinUrlFilename(const String& url, String& filename) +{ + String fullUrl; + + fullUrl.reserve(url.length() + filename.length() + 1); // May need to add an extra slash + fullUrl = url; + fullUrl = parseTemplate(fullUrl, true); // URL encode + + // URLEncode may also encode the '/' into "%2f" + // FIXME TD-er: Can this really occur? + fullUrl.replace(F("%2f"), F("/")); + + while (filename.startsWith(F("/"))) { + filename = filename.substring(1); + } + + if (!fullUrl.endsWith(F("/"))) { + fullUrl += F("/"); + } + fullUrl += filename; + return fullUrl; +} + +#endif // if FEATURE_DOWNLOAD + diff --git a/src/src/Helpers/Networking.h b/src/src/Helpers/Networking.h index e4f820444..ad8e39154 100644 --- a/src/src/Helpers/Networking.h +++ b/src/src/Helpers/Networking.h @@ -28,7 +28,7 @@ void sendSyslog(uint8_t logLevel, const String& message); /*********************************************************************************************\ Update UDP port (ESPEasy propiertary protocol) \*********************************************************************************************/ -void updateUDPport(); +void updateUDPport(bool force); /*********************************************************************************************\ @@ -53,6 +53,36 @@ String formatUnitToIPAddress(uint8_t unit, uint8_t formatCode); \*********************************************************************************************/ IPAddress getIPAddressForUnit(uint8_t unit); +/*********************************************************************************************\ + Get Name for specific unit +\*********************************************************************************************/ +String getNameForUnit(uint8_t unit); + +/*********************************************************************************************\ + Get Age for specific unit +\*********************************************************************************************/ +long getAgeForUnit(uint8_t unit); + +/*********************************************************************************************\ + Get Build for specific unit +\*********************************************************************************************/ +uint16_t getBuildnrForUnit(uint8_t unit); + +/*********************************************************************************************\ + Get Load for specific unit +\*********************************************************************************************/ +float getLoadForUnit(uint8_t unit); + +/*********************************************************************************************\ + Get nodeType for specific unit +\*********************************************************************************************/ +uint8_t getTypeForUnit(uint8_t unit); + +/*********************************************************************************************\ + Get nodeTypeString for specific unit +\*********************************************************************************************/ +const __FlashStringHelper* getTypeStringForUnit(uint8_t unit); + /*********************************************************************************************\ Send UDP message to specific unit (unit 255=broadcast) \*********************************************************************************************/ diff --git a/src/src/Helpers/Numerical.cpp b/src/src/Helpers/Numerical.cpp index dae80fdcf..633c00063 100644 --- a/src/src/Helpers/Numerical.cpp +++ b/src/src/Helpers/Numerical.cpp @@ -150,6 +150,9 @@ bool validDoubleFromString(const String& tBuf, ESPEASY_RULES_FLOAT_TYPE& result) } bool mustConsiderAsString(NumericalType detectedType) { + return detectedType != NumericalType::FloatingPoint && + detectedType != NumericalType::Integer; +/* switch (detectedType) { case NumericalType::FloatingPoint: case NumericalType::Integer: @@ -160,6 +163,7 @@ bool mustConsiderAsString(NumericalType detectedType) { return true; } return false; +*/ } bool mustConsiderAsJSONString(const String& value) { @@ -167,13 +171,22 @@ bool mustConsiderAsJSONString(const String& value) { // Empty string return true; } + const char c = value[0]; - NumericalType detectedType; - if (isNumerical(value, detectedType)) { - return mustConsiderAsString(detectedType); + if (isDigit(c) || c == '-' || c == '.' || c == '+' || c == ' ') { + NumericalType detectedType; + if (isNumerical(value, detectedType)) { + return mustConsiderAsString(detectedType); + } } - const bool isBool = (Settings.JSONBoolWithoutQuotes() && ((value.equalsIgnoreCase(F("true")) || value.equalsIgnoreCase(F("false"))))); - return !isBool; + if (equals(value, F("true")) || + equals(value, F("false")) || + equals(value, F("null"))) + { + return !Settings.JSONBoolWithoutQuotes(); + } + + return true; } String getNumerical(const String& tBuf, NumericalType requestedType, NumericalType& detectedType) { diff --git a/src/src/Helpers/OversamplingHelper.h b/src/src/Helpers/OversamplingHelper.h index d3e32e279..a872034d1 100644 --- a/src/src/Helpers/OversamplingHelper.h +++ b/src/src/Helpers/OversamplingHelper.h @@ -1,108 +1,109 @@ -#ifndef HELPERS_OVERSAMPLINGHELPER_H -#define HELPERS_OVERSAMPLINGHELPER_H - -#include "../../ESPEasy_common.h" -#include - -template -class OversamplingHelper { -public: - - // Oversampling by taking the average over N samples. - // Will filter out peak values when filterPeaksEnabled is set. - OversamplingHelper() = default; - - // Add new sample - void add(T currentValue) { - _sum += currentValue; - ++_count; - - if (currentValue > _maxval) { - _maxval = currentValue; - } - - if (currentValue < _minval) { - _minval = currentValue; - } - } - - // Get current oversampling value without reset. - // @param value Value will only be updated if there were samples available - bool peek(float& value) const { - if (_count == 0) { return false; } - float sum = _sum; - uint32_t count = _count; - - if ((count >= 3) && _filterPeaks) { - // Remove peak values from the average - sum -= _maxval; - sum -= _minval; - count -= 2; - } - value = sum / count; - return true; - } - - // Get current oversampling value and reset. - // @param value Value will only be updated if there were samples available - bool get(float& value) { - if (peek(value)) { - reset(); - return true; - } - return false; - } - - // Return number of used samples - uint32_t getCount() const { - return _count; - } - - // Clear all oversampling values - void reset() { - _count = 0; - _sum = 0.0f; - _minval = std::numeric_limits::max(); - _maxval = std::numeric_limits::min(); - } - - // Clear all oversampling values and add last average if there were samples available. - void resetKeepLast() { - float value{}; - - if (get(value)) { - add(static_cast(value)); - } - } - - // Clear all oversampling values and add last average if there were samples available. - // Last value will be added with a weight according to the given ratio of the last count. - // @param countRatio New weight will be previous nr of samples / countRatio - void resetKeepLastWeighted(int countRatio) { - float value{}; - const uint32_t count = getCount(); - - if (get(value)) { - add(static_cast(value)); - if (count > countRatio) { - const uint32_t weight = (count + (countRatio / 2)) / countRatio; - _count *= weight; - _sum = value * _count; - } - } - } - - void setFilterPeaks(bool enable) { - _filterPeaks = enable; - } - -private: - - uint32_t _count{}; - float _sum{}; - T _minval = std::numeric_limits::max(); - T _maxval = std::numeric_limits::min(); - bool _filterPeaks = true; -}; - -#endif // ifndef HELPERS_OVERSAMPLINGHELPER_H +#ifndef HELPERS_OVERSAMPLINGHELPER_H +#define HELPERS_OVERSAMPLINGHELPER_H + +#include "../../ESPEasy_common.h" +#include + +template +class OversamplingHelper { +public: + + // Oversampling by taking the average over N samples. + // Will filter out peak values when filterPeaksEnabled is set. + OversamplingHelper() = default; + + // Add new sample + void add(T currentValue) { + _sum += currentValue; + ++_count; + + if (currentValue > _maxval) { + _maxval = currentValue; + } + + if (currentValue < _minval) { + _minval = currentValue; + } + } + + // Get current oversampling value without reset. + // @param value Value will only be updated if there were samples available + bool peek(SUM_VALUE_TYPE& value) const { + if (_count == 0) { return false; } + SUM_VALUE_TYPE sum = _sum; + uint32_t count = _count; + + if ((count >= 3) && _filterPeaks) { + // Remove peak values from the average + sum -= _maxval; + sum -= _minval; + count -= 2; + } + value = sum / count; + return true; + } + + // Get current oversampling value and reset. + // @param value Value will only be updated if there were samples available + bool get(SUM_VALUE_TYPE& value) { + if (peek(value)) { + reset(); + return true; + } + return false; + } + + // Return number of used samples + uint32_t getCount() const { + return _count; + } + + // Clear all oversampling values + void reset() { + _count = 0; + _sum = 0.0f; + _minval = std::numeric_limits::max(); + _maxval = std::numeric_limits::min(); + } + + // Clear all oversampling values and add last average if there were samples available. + SUM_VALUE_TYPE resetKeepLast() { + SUM_VALUE_TYPE value{}; + + if (get(value)) { + add(static_cast(value)); + } + return value; + } + + // Clear all oversampling values and add last average if there were samples available. + // Last value will be added with a weight according to the given ratio of the last count. + // @param countRatio New weight will be previous nr of samples / countRatio + void resetKeepLastWeighted(int countRatio) { + SUM_VALUE_TYPE value{}; + const uint32_t count = getCount(); + + if (get(value)) { + add(static_cast(value)); + if (count > countRatio) { + const uint32_t weight = (count + (countRatio / 2)) / countRatio; + _count *= weight; + _sum = value * _count; + } + } + } + + void setFilterPeaks(bool enable) { + _filterPeaks = enable; + } + +private: + + uint32_t _count{}; + SUM_VALUE_TYPE _sum{}; + T _minval = std::numeric_limits::max(); + T _maxval = std::numeric_limits::min(); + bool _filterPeaks = true; +}; + +#endif // ifndef HELPERS_OVERSAMPLINGHELPER_H diff --git a/src/src/Helpers/PeriodicalActions.cpp b/src/src/Helpers/PeriodicalActions.cpp index 266e7745c..6e1f90b96 100644 --- a/src/src/Helpers/PeriodicalActions.cpp +++ b/src/src/Helpers/PeriodicalActions.cpp @@ -1,499 +1,499 @@ -#include "../Helpers/PeriodicalActions.h" - - -#include "../../ESPEasy-Globals.h" - -#include "../ControllerQueue/DelayQueueElements.h" -#include "../ControllerQueue/MQTT_queue_element.h" -#include "../DataStructs/TimingStats.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" -#include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyGPIO.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.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 "../Helpers/ESPEasyRTC.h" -#include "../Helpers/FS_Helper.h" -#include "../Helpers/Hardware_temperature_sensor.h" -#include "../Helpers/Memory.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringGenerator_System.h" -#include "../Helpers/StringGenerator_WiFi.h" -#include "../Helpers/StringProvider.h" - -#ifdef USES_C015 -#include "../../ESPEasy_fdwdecl.h" -#endif - - - -#define PLUGIN_ID_MQTT_IMPORT 37 - - -/*********************************************************************************************\ - * Tasks that run 50 times per second -\*********************************************************************************************/ - -void run50TimesPerSecond() { - String dummy; - { - START_TIMER; - PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy); - STOP_TIMER(PLUGIN_CALL_50PS); - } - { - START_TIMER; - CPluginCall(CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND, 0, dummy); - STOP_TIMER(CPLUGIN_CALL_50PS); - } - processNextEvent(); -} - -/*********************************************************************************************\ - * Tasks that run 10 times per second -\*********************************************************************************************/ -void run10TimesPerSecond() { - String dummy; - //@giig19767g: WARNING: Monitor10xSec must run before PLUGIN_TEN_PER_SECOND - { - START_TIMER; - GPIO_Monitor10xSec(); - STOP_TIMER(PLUGIN_CALL_10PSU); - } - { - START_TIMER; - PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); - STOP_TIMER(PLUGIN_CALL_10PS); - } - { - START_TIMER; -// PluginCall(PLUGIN_UNCONDITIONAL_POLL, 0, dummyString); - PluginCall(PLUGIN_MONITOR, 0, dummy); - STOP_TIMER(PLUGIN_CALL_10PSU); - } - { - START_TIMER; - CPluginCall(CPlugin::Function::CPLUGIN_TEN_PER_SECOND, 0, dummy); - STOP_TIMER(CPLUGIN_CALL_10PS); - } - - #ifdef USES_C015 - if (NetworkConnected()) - Blynk_Run_c015(); - #endif - #ifndef USE_RTOS_MULTITASKING - web_server.handleClient(); - #endif -} - - -/*********************************************************************************************\ - * Tasks each second -\*********************************************************************************************/ -void runOncePerSecond() -{ - START_TIMER; - updateLogLevelCache(); - dailyResetCounter++; - if (dailyResetCounter > 86400) // 1 day elapsed... //86400 - { - RTC.flashDayCounter=0; - saveToRTC(); - dailyResetCounter=0; - addLog(LOG_LEVEL_INFO, F("SYS : Reset 24h counters")); - } - - if (Settings.ConnectionFailuresThreshold) - if (WiFiEventData.connectionFailures > Settings.ConnectionFailuresThreshold) - delayedReboot(60, IntendedRebootReason_e::DelayedReboot); - - if (cmd_within_mainloop != 0) - { - switch (cmd_within_mainloop) - { - case CMD_WIFI_DISCONNECT: - { - WifiDisconnect(); - break; - } - case CMD_REBOOT: - { - reboot(IntendedRebootReason_e::CommandReboot); - break; - } - } - cmd_within_mainloop = 0; - } - // clock events - if (node_time.reportNewMinute()) { - String dummy; - PluginCall(PLUGIN_CLOCK_IN, 0, dummy); - if (Settings.UseRules) - { - // FIXME TD-er: What to do when the system time is not (yet) present? - if (node_time.systemTimePresent()) { - // TD-er: Do not add to the eventQueue, but execute right now. - const String event = strformat( - F("Clock#Time=%s,%s"), - node_time.weekday_str().c_str(), - node_time.getTimeString(':', false).c_str()); - rulesProcessing(event); - } - } - } - -// unsigned long start = micros(); - String dummy; - PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); -// unsigned long elapsed = micros() - start; - - - // I2C Watchdog feed - if (Settings.WDI2CAddress != 0) - { - I2C_write8(Settings.WDI2CAddress, 0xA5); - } - - #if FEATURE_MDNS - #ifdef ESP8266 - // Allow MDNS processing - if (NetworkConnected()) { - MDNS.announce(); - } - #endif - #endif // if FEATURE_MDNS - - #if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) - getInternalTemperature(); // Just read the value every second to hopefully get a valid next reading on original ESP32 - #endif // if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) - - checkResetFactoryPin(); - STOP_TIMER(PLUGIN_CALL_1PS); -} - -/*********************************************************************************************\ - * Tasks each 30 seconds -\*********************************************************************************************/ -void runEach30Seconds() -{ - #ifndef BUILD_NO_RAM_TRACKER - 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 FEATURE_DEFINE_SERIAL_CONSOLE_PORT -// addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); -#endif - } - WiFi_AP_Candidates.purge_expired(); - #if FEATURE_ESPEASY_P2P - sendSysInfoUDP(1); - refreshNodeList(); - #endif - - // sending $stats to homie controller - CPluginCall(CPlugin::Function::CPLUGIN_INTERVAL, 0); - - #if defined(ESP8266) - #if FEATURE_SSDP - if (Settings.UseSSDP) - SSDP_update(); - - #endif // if FEATURE_SSDP - #endif -#if FEATURE_ADC_VCC - if (!WiFiEventData.wifiConnectInProgress) { - vcc = ESP.getVcc() / 1000.0f; - } -#endif - - #if FEATURE_REPORTING - ReportStatus(); - #endif // if FEATURE_REPORTING - -} - -#if FEATURE_MQTT - - -void scheduleNextMQTTdelayQueue() { - if (MQTTDelayHandler != nullptr) { - Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, MQTTDelayHandler->getNextScheduleTime()); - } -} - -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 - if (validDeviceIndex(DeviceIndex)) { - for (taskIndex_t task = 0; task < TASKS_MAX; task++) { - if ((Settings.getPluginID_for_task(task) == PLUGIN_MQTT_IMPORT) && - (Settings.TaskDeviceEnabled[task])) { - // 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)); - } - } - } -} - -void processMQTTdelayQueue() { - if (MQTTDelayHandler == nullptr) { - return; - } - runPeriodicalMQTT(); // Update MQTT connected state. - if (!MQTTclient_connected) { - scheduleNextMQTTdelayQueue(); - return; - } - - START_TIMER; - MQTT_queue_element *element(static_cast(MQTTDelayHandler->getNext())); - - if (element == nullptr) { return; } - - bool handled = false; - - if (element->_call_PLUGIN_PROCESS_CONTROLLER_DATA) { - struct EventStruct TempEvent(element->_taskIndex); - String dummy; - - // FIXME TD-er: Do we need anything from the element in the event? -// TempEvent.String1 = element->_topic; -// TempEvent.String2 = element->_payload; - if (PluginCall(PLUGIN_PROCESS_CONTROLLER_DATA, &TempEvent, dummy)) { - handled = true; - MQTTDelayHandler->markProcessed(true); - } else { - MQTTDelayHandler->markProcessed(false); - } - } else - if (!handled) { - if (MQTTclient.publish(element->_topic.c_str(), element->_payload.c_str(), element->_retained)) { - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; - } - MQTTDelayHandler->markProcessed(true); - } else { - MQTTDelayHandler->markProcessed(false); -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("MQTT : process MQTT queue not published, "); - log += MQTTDelayHandler->sendQueue.size(); - log += F(" items left in queue"); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 10); // Make sure the MQTT is being processed as soon as possible. - scheduleNextMQTTdelayQueue(); - STOP_TIMER(MQTT_DELAY_QUEUE); -} - -void updateMQTTclient_connected() { - if (MQTTclient_connected != MQTTclient.connected()) { - MQTTclient_connected = !MQTTclient_connected; - if (!MQTTclient_connected) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String connectionError = F("MQTT : Connection lost, state: "); - connectionError += getMQTT_state(); - 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) { - eventQueue.add(F("MQTT#Connected")); - } else { - eventQueue.add(F("MQTT#Disconnected")); - } - } - } - if (!MQTTclient_connected) { - // As suggested here: https://github.com/letscontrolit/ESPEasy/issues/1356 - if (timermqtt_interval < 30000) { - timermqtt_interval += 5000; - } - } else { - timermqtt_interval = 250; - } - Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); - scheduleNextMQTTdelayQueue(); -} - -void runPeriodicalMQTT() { - // MQTT_KEEPALIVE = 15 seconds. - if (!NetworkConnected(10)) { - updateMQTTclient_connected(); - return; - } - //dont do this in backgroundtasks(), otherwise causes crashes. (https://github.com/letscontrolit/ESPEasy/issues/683) - controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); - if (validControllerIndex(enabledMqttController)) { - if (!MQTTclient.loop()) { - updateMQTTclient_connected(); - if (MQTTCheck(enabledMqttController)) { - updateMQTTclient_connected(); - } - } - } else { - if (MQTTclient.connected()) { - MQTTclient.disconnect(); - updateMQTTclient_connected(); - } - } -} - - -#endif //if FEATURE_MQTT - - - -void logTimerStatistics() { -# ifndef BUILD_NO_DEBUG - const uint8_t loglevel = LOG_LEVEL_DEBUG; -#else - const uint8_t loglevel = LOG_LEVEL_NONE; -#endif - updateLoopStats_30sec(loglevel); -#ifndef BUILD_NO_DEBUG -// logStatistics(loglevel, true); - if (loglevelActiveFor(loglevel)) { - String queueLog = F("Scheduler stats: (called/tasks/max_length/idle%) "); - queueLog += Scheduler.getQueueStats(); - addLogMove(loglevel, queueLog); - } -#endif -} - -void updateLoopStats_30sec(uint8_t loglevel) { - loopCounterLast = loopCounter; - loopCounter = 0; - if (loopCounterLast > loopCounterMax) - loopCounterMax = loopCounterLast; - - Scheduler.updateIdleTimeStats(); - -#ifndef BUILD_NO_DEBUG - if (loglevelActiveFor(loglevel)) { - String log = F("LoopStats: shortestLoop: "); - log += shortestLoop; - log += F(" longestLoop: "); - log += longestLoop; - log += F(" avgLoopDuration: "); - log += loop_usec_duration_total / loopCounter_full; - log += F(" loopCounterMax: "); - log += loopCounterMax; - log += F(" loopCounterLast: "); - log += loopCounterLast; - addLogMove(loglevel, log); - } -#endif - loop_usec_duration_total = 0; - loopCounter_full = 1; -} - - -/********************************************************************************************\ - Clean up all before going to sleep or reboot. - \*********************************************************************************************/ -void flushAndDisconnectAllClients() { - if (anyControllerEnabled()) { -#if FEATURE_MQTT - bool mqttControllerEnabled = validControllerIndex(firstEnabledMQTT_ControllerIndex()); -#endif //if FEATURE_MQTT - unsigned long timer = millis() + 1000; - while (!timeOutReached(timer)) { - // call to all controllers (delay queue) to flush all data. - CPluginCall(CPlugin::Function::CPLUGIN_FLUSH, 0); -#if FEATURE_MQTT - if (mqttControllerEnabled && MQTTclient.connected()) { - MQTTclient.loop(); - } -#endif //if FEATURE_MQTT - } -#if FEATURE_MQTT - if (mqttControllerEnabled && MQTTclient.connected()) { - MQTTclient.disconnect(); - updateMQTTclient_connected(); - } -#endif //if FEATURE_MQTT - saveToRTC(); - delay(100); // Flush anything in the network buffers. - } - process_serialWriteBuffer(); -} - - -void prepareShutdown(IntendedRebootReason_e reason) -{ - WiFiEventData.intent_to_reboot = true; -#if FEATURE_MQTT - runPeriodicalMQTT(); // Flush outstanding MQTT messages -#endif // if FEATURE_MQTT - process_serialWriteBuffer(); - flushAndDisconnectAllClients(); - saveUserVarToRTC(); - setWifiMode(WIFI_OFF); - ESPEASY_FS.end(); - process_serialWriteBuffer(); - delay(100); // give the node time to flush all before reboot or sleep - node_time.now(); - Scheduler.markIntendedReboot(reason); - saveToRTC(); -} - - +#include "../Helpers/PeriodicalActions.h" + + +#include "../../ESPEasy-Globals.h" + +#include "../ControllerQueue/DelayQueueElements.h" +#include "../ControllerQueue/MQTT_queue_element.h" +#include "../DataStructs/TimingStats.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" +#include "../ESPEasyCore/Controller.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.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 "../Helpers/ESPEasyRTC.h" +#include "../Helpers/FS_Helper.h" +#include "../Helpers/Hardware_temperature_sensor.h" +#include "../Helpers/Memory.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringGenerator_System.h" +#include "../Helpers/StringGenerator_WiFi.h" +#include "../Helpers/StringProvider.h" + +#ifdef USES_C015 +#include "../../ESPEasy_fdwdecl.h" +#endif + + + +#define PLUGIN_ID_MQTT_IMPORT 37 + + +/*********************************************************************************************\ + * Tasks that run 50 times per second +\*********************************************************************************************/ + +void run50TimesPerSecond() { + String dummy; + { + START_TIMER; + PluginCall(PLUGIN_FIFTY_PER_SECOND, 0, dummy); + STOP_TIMER(PLUGIN_CALL_50PS); + } + { + START_TIMER; + CPluginCall(CPlugin::Function::CPLUGIN_FIFTY_PER_SECOND, 0, dummy); + STOP_TIMER(CPLUGIN_CALL_50PS); + } + processNextEvent(); +} + +/*********************************************************************************************\ + * Tasks that run 10 times per second +\*********************************************************************************************/ +void run10TimesPerSecond() { + String dummy; + //@giig19767g: WARNING: Monitor10xSec must run before PLUGIN_TEN_PER_SECOND + { + START_TIMER; + GPIO_Monitor10xSec(); + STOP_TIMER(PLUGIN_CALL_10PSU); + } + { + START_TIMER; + PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); + STOP_TIMER(PLUGIN_CALL_10PS); + } + { + START_TIMER; +// PluginCall(PLUGIN_UNCONDITIONAL_POLL, 0, dummyString); + PluginCall(PLUGIN_MONITOR, 0, dummy); + STOP_TIMER(PLUGIN_CALL_10PSU); + } + { + START_TIMER; + CPluginCall(CPlugin::Function::CPLUGIN_TEN_PER_SECOND, 0, dummy); + STOP_TIMER(CPLUGIN_CALL_10PS); + } + + #ifdef USES_C015 + if (NetworkConnected()) + Blynk_Run_c015(); + #endif + #ifndef USE_RTOS_MULTITASKING + web_server.handleClient(); + #endif +} + + +/*********************************************************************************************\ + * Tasks each second +\*********************************************************************************************/ +void runOncePerSecond() +{ + START_TIMER; + updateLogLevelCache(); + dailyResetCounter++; + if (dailyResetCounter > 86400) // 1 day elapsed... //86400 + { + RTC.flashDayCounter=0; + saveToRTC(); + dailyResetCounter=0; + addLog(LOG_LEVEL_INFO, F("SYS : Reset 24h counters")); + } + + if (Settings.ConnectionFailuresThreshold) + if (WiFiEventData.connectionFailures > Settings.ConnectionFailuresThreshold) + delayedReboot(60, IntendedRebootReason_e::DelayedReboot); + + if (cmd_within_mainloop != 0) + { + switch (cmd_within_mainloop) + { + case CMD_WIFI_DISCONNECT: + { + WifiDisconnect(); + break; + } + case CMD_REBOOT: + { + reboot(IntendedRebootReason_e::CommandReboot); + break; + } + } + cmd_within_mainloop = 0; + } + // clock events + if (node_time.reportNewMinute()) { + String dummy; + PluginCall(PLUGIN_CLOCK_IN, 0, dummy); + if (Settings.UseRules) + { + // FIXME TD-er: What to do when the system time is not (yet) present? + if (node_time.systemTimePresent()) { + // TD-er: Do not add to the eventQueue, but execute right now. + const String event = strformat( + F("Clock#Time=%s,%s"), + node_time.weekday_str().c_str(), + node_time.getTimeString(':', false).c_str()); + rulesProcessing(event); + } + } + } + +// unsigned long start = micros(); + String dummy; + PluginCall(PLUGIN_ONCE_A_SECOND, 0, dummy); +// unsigned long elapsed = micros() - start; + + + // I2C Watchdog feed + if (Settings.WDI2CAddress != 0) + { + I2C_write8(Settings.WDI2CAddress, 0xA5); + } + + #if FEATURE_MDNS + #ifdef ESP8266 + // Allow MDNS processing + if (NetworkConnected()) { + MDNS.announce(); + } + #endif + #endif // if FEATURE_MDNS + + #if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) + getInternalTemperature(); // Just read the value every second to hopefully get a valid next reading on original ESP32 + #endif // if FEATURE_INTERNAL_TEMPERATURE && defined(ESP32_CLASSIC) + + checkResetFactoryPin(); + STOP_TIMER(PLUGIN_CALL_1PS); +} + +/*********************************************************************************************\ + * Tasks each 30 seconds +\*********************************************************************************************/ +void runEach30Seconds() +{ + #ifndef BUILD_NO_RAM_TRACKER + 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 FEATURE_DEFINE_SERIAL_CONSOLE_PORT +// addLogMove(LOG_LEVEL_INFO, ESPEASY_SERIAL_CONSOLE_PORT.getLogString()); +#endif + } + WiFi_AP_Candidates.purge_expired(); + #if FEATURE_ESPEASY_P2P + sendSysInfoUDP(1); + refreshNodeList(); + #endif + + // sending $stats to homie controller + CPluginCall(CPlugin::Function::CPLUGIN_INTERVAL, 0); + + #if defined(ESP8266) + #if FEATURE_SSDP + if (Settings.UseSSDP) + SSDP_update(); + + #endif // if FEATURE_SSDP + #endif +#if FEATURE_ADC_VCC + if (!WiFiEventData.wifiConnectInProgress) { + vcc = ESP.getVcc() / 1000.0f; + } +#endif + + #if FEATURE_REPORTING + ReportStatus(); + #endif // if FEATURE_REPORTING + +} + +#if FEATURE_MQTT + + +void scheduleNextMQTTdelayQueue() { + if (MQTTDelayHandler != nullptr) { + Scheduler.scheduleNextDelayQueue(SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE, MQTTDelayHandler->getNextScheduleTime()); + } +} + +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 + if (validDeviceIndex(DeviceIndex)) { + for (taskIndex_t task = 0; task < TASKS_MAX; task++) { + if ((Settings.getPluginID_for_task(task) == PLUGIN_MQTT_IMPORT) && + (Settings.TaskDeviceEnabled[task])) { + // 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)); + } + } + } +} + +void processMQTTdelayQueue() { + if (MQTTDelayHandler == nullptr) { + return; + } + runPeriodicalMQTT(); // Update MQTT connected state. + if (!MQTTclient_connected) { + scheduleNextMQTTdelayQueue(); + return; + } + + START_TIMER; + MQTT_queue_element *element(static_cast(MQTTDelayHandler->getNext())); + + if (element == nullptr) { return; } + + bool handled = false; + + if (element->_call_PLUGIN_PROCESS_CONTROLLER_DATA) { + struct EventStruct TempEvent(element->_taskIndex); + String dummy; + + // FIXME TD-er: Do we need anything from the element in the event? +// TempEvent.String1 = element->_topic; +// TempEvent.String2 = element->_payload; + if (PluginCall(PLUGIN_PROCESS_CONTROLLER_DATA, &TempEvent, dummy)) { + handled = true; + MQTTDelayHandler->markProcessed(true); + } else { + MQTTDelayHandler->markProcessed(false); + } + } else + if (!handled) { + if (MQTTclient.publish(element->_topic.c_str(), element->_payload.c_str(), element->_retained)) { + if (WiFiEventData.connectionFailures > 0) { + --WiFiEventData.connectionFailures; + } + MQTTDelayHandler->markProcessed(true); + } else { + MQTTDelayHandler->markProcessed(false); +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("MQTT : process MQTT queue not published, "); + log += MQTTDelayHandler->sendQueue.size(); + log += F(" items left in queue"); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + } + } + Scheduler.setIntervalTimerOverride(SchedulerIntervalTimer_e::TIMER_MQTT, 10); // Make sure the MQTT is being processed as soon as possible. + scheduleNextMQTTdelayQueue(); + STOP_TIMER(MQTT_DELAY_QUEUE); +} + +void updateMQTTclient_connected() { + if (MQTTclient_connected != MQTTclient.connected()) { + MQTTclient_connected = !MQTTclient_connected; + if (!MQTTclient_connected) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String connectionError = F("MQTT : Connection lost, state: "); + connectionError += getMQTT_state(); + 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) { + eventQueue.add(F("MQTT#Connected")); + } else { + eventQueue.add(F("MQTT#Disconnected")); + } + } + } + if (!MQTTclient_connected) { + // As suggested here: https://github.com/letscontrolit/ESPEasy/issues/1356 + if (timermqtt_interval < 30000) { + timermqtt_interval += 5000; + } + } else { + timermqtt_interval = 250; + } + Scheduler.setIntervalTimer(SchedulerIntervalTimer_e::TIMER_MQTT); + scheduleNextMQTTdelayQueue(); +} + +void runPeriodicalMQTT() { + // MQTT_KEEPALIVE = 15 seconds. + if (!NetworkConnected(10)) { + updateMQTTclient_connected(); + return; + } + //dont do this in backgroundtasks(), otherwise causes crashes. (https://github.com/letscontrolit/ESPEasy/issues/683) + controllerIndex_t enabledMqttController = firstEnabledMQTT_ControllerIndex(); + if (validControllerIndex(enabledMqttController)) { + if (!MQTTclient.loop()) { + updateMQTTclient_connected(); + if (MQTTCheck(enabledMqttController)) { + updateMQTTclient_connected(); + } + } + } else { + if (MQTTclient.connected()) { + MQTTclient.disconnect(); + updateMQTTclient_connected(); + } + } +} + + +#endif //if FEATURE_MQTT + + + +void logTimerStatistics() { +# ifndef BUILD_NO_DEBUG + const uint8_t loglevel = LOG_LEVEL_DEBUG; +#else + const uint8_t loglevel = LOG_LEVEL_NONE; +#endif + updateLoopStats_30sec(loglevel); +#ifndef BUILD_NO_DEBUG +// logStatistics(loglevel, true); + if (loglevelActiveFor(loglevel)) { + String queueLog = F("Scheduler stats: (called/tasks/max_length/idle%) "); + queueLog += Scheduler.getQueueStats(); + addLogMove(loglevel, queueLog); + } +#endif +} + +void updateLoopStats_30sec(uint8_t loglevel) { + loopCounterLast = loopCounter; + loopCounter = 0; + if (loopCounterLast > loopCounterMax) + loopCounterMax = loopCounterLast; + + Scheduler.updateIdleTimeStats(); + +#ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(loglevel)) { + String log = F("LoopStats: shortestLoop: "); + log += shortestLoop; + log += F(" longestLoop: "); + log += longestLoop; + log += F(" avgLoopDuration: "); + log += loop_usec_duration_total / loopCounter_full; + log += F(" loopCounterMax: "); + log += loopCounterMax; + log += F(" loopCounterLast: "); + log += loopCounterLast; + addLogMove(loglevel, log); + } +#endif + loop_usec_duration_total = 0; + loopCounter_full = 1; +} + + +/********************************************************************************************\ + Clean up all before going to sleep or reboot. + \*********************************************************************************************/ +void flushAndDisconnectAllClients() { + if (anyControllerEnabled()) { +#if FEATURE_MQTT + bool mqttControllerEnabled = validControllerIndex(firstEnabledMQTT_ControllerIndex()); +#endif //if FEATURE_MQTT + unsigned long timer = millis() + 1000; + while (!timeOutReached(timer)) { + // call to all controllers (delay queue) to flush all data. + CPluginCall(CPlugin::Function::CPLUGIN_FLUSH, 0); +#if FEATURE_MQTT + if (mqttControllerEnabled && MQTTclient.connected()) { + MQTTclient.loop(); + } +#endif //if FEATURE_MQTT + } +#if FEATURE_MQTT + if (mqttControllerEnabled && MQTTclient.connected()) { + MQTTclient.disconnect(); + updateMQTTclient_connected(); + } +#endif //if FEATURE_MQTT + saveToRTC(); + delay(100); // Flush anything in the network buffers. + } + process_serialWriteBuffer(); +} + + +void prepareShutdown(IntendedRebootReason_e reason) +{ + WiFiEventData.intent_to_reboot = true; +#if FEATURE_MQTT + runPeriodicalMQTT(); // Flush outstanding MQTT messages +#endif // if FEATURE_MQTT + process_serialWriteBuffer(); + flushAndDisconnectAllClients(); + saveUserVarToRTC(); + setWifiMode(WIFI_OFF); + ESPEASY_FS.end(); + process_serialWriteBuffer(); + delay(100); // give the node time to flush all before reboot or sleep + node_time.now_(); + Scheduler.markIntendedReboot(reason); + saveToRTC(); +} + + diff --git a/src/src/Helpers/PeriodicalActions.h b/src/src/Helpers/PeriodicalActions.h index 983ab67e1..01031442b 100644 --- a/src/src/Helpers/PeriodicalActions.h +++ b/src/src/Helpers/PeriodicalActions.h @@ -1,56 +1,56 @@ -#ifndef HELPERS_PERIODICALACTIONS_H -#define HELPERS_PERIODICALACTIONS_H - -#include "../../ESPEasy_common.h" - -#include "../Globals/CPlugins.h" -#include "../Helpers/Scheduler.h" - -/*********************************************************************************************\ - * Tasks that run 50 times per second -\*********************************************************************************************/ - -void run50TimesPerSecond(); - -/*********************************************************************************************\ - * Tasks that run 10 times per second -\*********************************************************************************************/ -void run10TimesPerSecond(); - - -/*********************************************************************************************\ - * Tasks each second -\*********************************************************************************************/ -void runOncePerSecond(); - -/*********************************************************************************************\ - * Tasks each 30 seconds -\*********************************************************************************************/ -void runEach30Seconds(); - -#if FEATURE_MQTT - -void scheduleNextMQTTdelayQueue(); -void schedule_all_MQTTimport_tasks(); - -void processMQTTdelayQueue(); - -void updateMQTTclient_connected(); - -void runPeriodicalMQTT(); - -#endif //if FEATURE_MQTT - - -void logTimerStatistics(); - -void updateLoopStats_30sec(uint8_t loglevel); - -/********************************************************************************************\ - Clean up all before going to sleep or reboot. - \*********************************************************************************************/ -void prepareShutdown(IntendedRebootReason_e reason); - - - +#ifndef HELPERS_PERIODICALACTIONS_H +#define HELPERS_PERIODICALACTIONS_H + +#include "../../ESPEasy_common.h" + +#include "../Globals/CPlugins.h" +#include "../Helpers/Scheduler.h" + +/*********************************************************************************************\ + * Tasks that run 50 times per second +\*********************************************************************************************/ + +void run50TimesPerSecond(); + +/*********************************************************************************************\ + * Tasks that run 10 times per second +\*********************************************************************************************/ +void run10TimesPerSecond(); + + +/*********************************************************************************************\ + * Tasks each second +\*********************************************************************************************/ +void runOncePerSecond(); + +/*********************************************************************************************\ + * Tasks each 30 seconds +\*********************************************************************************************/ +void runEach30Seconds(); + +#if FEATURE_MQTT + +void scheduleNextMQTTdelayQueue(); +void schedule_all_MQTTimport_tasks(); + +void processMQTTdelayQueue(); + +void updateMQTTclient_connected(); + +void runPeriodicalMQTT(); + +#endif //if FEATURE_MQTT + + +void logTimerStatistics(); + +void updateLoopStats_30sec(uint8_t loglevel); + +/********************************************************************************************\ + Clean up all before going to sleep or reboot. + \*********************************************************************************************/ +void prepareShutdown(IntendedRebootReason_e reason); + + + #endif // HELPERS_PERIODICALACTIONS_H \ No newline at end of file diff --git a/src/src/Helpers/Rules_calculate.cpp b/src/src/Helpers/Rules_calculate.cpp index 1a93ecb76..3e63a53ac 100644 --- a/src/src/Helpers/Rules_calculate.cpp +++ b/src/src/Helpers/Rules_calculate.cpp @@ -651,10 +651,13 @@ String RulesCalculate_t::preProces(const String& input) for (size_t i = 0; i < nrOperators; ++i) { const UnaryOperator op = operators[i]; +#if FEATURE_TRIGONOMETRIC_FUNCTIONS_RULES if (op == UnaryOperator::ArcSin && preprocessed.indexOf(F("sin")) == -1) i += 3; else if (op == UnaryOperator::ArcCos && preprocessed.indexOf(F("cos")) == -1) i += 3; else if (op == UnaryOperator::ArcTan && preprocessed.indexOf(F("tan")) == -1) i += 3; - else { + else +#endif + { preProcessReplace(preprocessed, op); } } diff --git a/src/src/Helpers/Scheduler.cpp b/src/src/Helpers/Scheduler.cpp index 76074c939..98ecf4b25 100644 --- a/src/src/Helpers/Scheduler.cpp +++ b/src/src/Helpers/Scheduler.cpp @@ -1,112 +1,112 @@ -#include "../Helpers/Scheduler.h" - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" - -#include "../DataStructs/Scheduler_IntendedRebootTimerID.h" -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasyRules.h" - -#include "../Globals/RTC.h" - -#include "../Helpers/ESPEasyRTC.h" - - -void ESPEasy_Scheduler::markIntendedReboot(IntendedRebootReason_e reason) { - const IntendedRebootTimerID id(reason); - - RTC.lastMixedSchedulerId = id.mixed_id; - saveToRTC(); -} - -/*********************************************************************************************\ -* Generic Timer functions. -\*********************************************************************************************/ -void ESPEasy_Scheduler::setNewTimerAt(SchedulerTimerID id, unsigned long timer) { - START_TIMER; - msecTimerHandler.registerAt(id.mixed_id, timer); - STOP_TIMER(SET_NEW_TIMER); -} - -/*********************************************************************************************\ -* Handle scheduled timers. -\*********************************************************************************************/ -void ESPEasy_Scheduler::handle_schedule() { - START_TIMER - unsigned long timer = 0; - unsigned long mixed_id = 0; - - if (timePassedSince(last_system_event_run) < 500) { - // Make sure system event queue will be looked at every now and then. - mixed_id = msecTimerHandler.getNextId(timer); - } - - if (RTC.lastMixedSchedulerId != mixed_id) { - RTC.lastMixedSchedulerId = mixed_id; - saveToRTC(); - } - - if (mixed_id == 0) { - // No id ready to run right now. - // Events are not that important to run immediately. - // Make sure normal scheduled jobs run at higher priority. - // backgroundtasks(); - process_system_event_queue(); - - // System events may have added one or more rule events, try to process those - processNextEvent(); - last_system_event_run = millis(); - STOP_TIMER(HANDLE_SCHEDULER_IDLE); - return; - } - - const SchedulerTimerID timerID(mixed_id); - - delay(0); // See: https://github.com/letscontrolit/ESPEasy/issues/1818#issuecomment-425351328 - - switch (timerID.getTimerType()) { - case SchedulerTimerType_e::ConstIntervalTimer: - process_interval_timer(timerID, timer); - break; - case SchedulerTimerType_e::PLUGIN_TASKTIMER_IN_e: - process_plugin_task_timer(timerID); - break; - case SchedulerTimerType_e::PLUGIN_DEVICETIMER_IN_e: - process_plugin_timer(timerID); - break; - case SchedulerTimerType_e::RulesTimer: - process_rules_timer(timerID, timer); - break; - case SchedulerTimerType_e::TaskDeviceTimer: - process_task_device_timer(timerID, timer); - break; - case SchedulerTimerType_e::GPIO_timer: - process_gpio_timer(timerID, timer); - break; - - case SchedulerTimerType_e::SystemEventQueue: - case SchedulerTimerType_e::IntendedReboot: - // TD-er: Not really something that needs to be processed here. - // - SystemEventQueue has its own ScheduledEventQueue which isn't time based. - // - IntendedReboot is just used to mark the intended reboot reason in RTC. - break; - } - STOP_TIMER(HANDLE_SCHEDULER_TASK); -} - -String ESPEasy_Scheduler::getQueueStats() { - return msecTimerHandler.getQueueStats(); -} - -void ESPEasy_Scheduler::updateIdleTimeStats() { - msecTimerHandler.updateIdleTimeStats(); -} - -float ESPEasy_Scheduler::getIdleTimePct() const { - return msecTimerHandler.getIdleTimePct(); -} - -void ESPEasy_Scheduler::setEcoMode(bool enabled) { - msecTimerHandler.setEcoMode(enabled); -} +#include "../Helpers/Scheduler.h" + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" + +#include "../DataStructs/Scheduler_IntendedRebootTimerID.h" +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasyRules.h" + +#include "../Globals/RTC.h" + +#include "../Helpers/ESPEasyRTC.h" + + +void ESPEasy_Scheduler::markIntendedReboot(IntendedRebootReason_e reason) { + const IntendedRebootTimerID id(reason); + + RTC.lastMixedSchedulerId = id.mixed_id; + saveToRTC(); +} + +/*********************************************************************************************\ +* Generic Timer functions. +\*********************************************************************************************/ +void ESPEasy_Scheduler::setNewTimerAt(SchedulerTimerID id, unsigned long timer) { + START_TIMER; + msecTimerHandler.registerAt(id.mixed_id, timer); + STOP_TIMER(SET_NEW_TIMER); +} + +/*********************************************************************************************\ +* Handle scheduled timers. +\*********************************************************************************************/ +void ESPEasy_Scheduler::handle_schedule() { + START_TIMER + unsigned long timer = 0; + unsigned long mixed_id = 0; + + if (timePassedSince(last_system_event_run) < 500) { + // Make sure system event queue will be looked at every now and then. + mixed_id = msecTimerHandler.getNextId(timer); + } + + if (RTC.lastMixedSchedulerId != mixed_id) { + RTC.lastMixedSchedulerId = mixed_id; + saveToRTC(); + } + + if (mixed_id == 0) { + // No id ready to run right now. + // Events are not that important to run immediately. + // Make sure normal scheduled jobs run at higher priority. + // backgroundtasks(); + process_system_event_queue(); + + // System events may have added one or more rule events, try to process those + processNextEvent(); + last_system_event_run = millis(); + STOP_TIMER(HANDLE_SCHEDULER_IDLE); + return; + } + + const SchedulerTimerID timerID(mixed_id); + + delay(0); // See: https://github.com/letscontrolit/ESPEasy/issues/1818#issuecomment-425351328 + + switch (timerID.getTimerType()) { + case SchedulerTimerType_e::ConstIntervalTimer: + process_interval_timer(timerID, timer); + break; + case SchedulerTimerType_e::PLUGIN_TASKTIMER_IN_e: + process_plugin_task_timer(timerID); + break; + case SchedulerTimerType_e::PLUGIN_DEVICETIMER_IN_e: + process_plugin_timer(timerID); + break; + case SchedulerTimerType_e::RulesTimer: + process_rules_timer(timerID, timer); + break; + case SchedulerTimerType_e::TaskDeviceTimer: + process_task_device_timer(timerID, timer); + break; + case SchedulerTimerType_e::GPIO_timer: + process_gpio_timer(timerID, timer); + break; + + case SchedulerTimerType_e::SystemEventQueue: + case SchedulerTimerType_e::IntendedReboot: + // TD-er: Not really something that needs to be processed here. + // - SystemEventQueue has its own ScheduledEventQueue which isn't time based. + // - IntendedReboot is just used to mark the intended reboot reason in RTC. + break; + } + STOP_TIMER(HANDLE_SCHEDULER_TASK); +} + +String ESPEasy_Scheduler::getQueueStats() { + return msecTimerHandler.getQueueStats(); +} + +void ESPEasy_Scheduler::updateIdleTimeStats() { + msecTimerHandler.updateIdleTimeStats(); +} + +float ESPEasy_Scheduler::getIdleTimePct() const { + return msecTimerHandler.getIdleTimePct(); +} + +void ESPEasy_Scheduler::setEcoMode(bool enabled) { + msecTimerHandler.setEcoMode(enabled); +} diff --git a/src/src/Helpers/Scheduler_IntervalTimer.cpp b/src/src/Helpers/Scheduler_IntervalTimer.cpp index 600f5a519..345d3decd 100644 --- a/src/src/Helpers/Scheduler_IntervalTimer.cpp +++ b/src/src/Helpers/Scheduler_IntervalTimer.cpp @@ -1,342 +1,342 @@ -#include "../Helpers/Scheduler.h" - - -#include "../../ESPEasy-Globals.h" - -#include "../ControllerQueue/DelayQueueElements.h" - -#include "../DataStructs/Scheduler_ConstIntervalTimerID.h" - -#include "../Globals/Settings.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Networking.h" -#include "../Helpers/PeriodicalActions.h" - - -/*********************************************************************************************\ -* Interval Timer -* These timers set a new scheduled timer, based on the old value. -* This will make their interval as constant as possible. -\*********************************************************************************************/ - -// Interval where it is more important to actually run the scheduled job, instead of keeping the time drift to a minimum. -// For example running the PLUGIN_FIFTY_PER_SECOND calls probably need to run as fast as possible as they need to fetch data before a buffer -// overflow happens. -// For those it is more important to actually run it than keeping pace. -void ESPEasy_Scheduler::setNextTimeInterval(unsigned long& timer, const unsigned long step) { - timer += step; - const long passed = timePassedSince(timer); - - if (passed < 0) { - // Event has not yet happened, which is fine. - return; - } - - if (static_cast(passed) > step) { - // No need to keep running behind, start again. - timer = millis() + step; - return; - } - - // Try to get in sync again. - timer = millis() + (step - passed); -} - -// More strict interval where no time drift is more important than missing a scheduled interval. -// For example timing for repeating longPulse where 2 scheduled intervals need to be at constant 'distance' from each other. -void ESPEasy_Scheduler::setNextStrictTimeInterval(unsigned long & timer, - const unsigned long step) { - timer += step; - const long passed = timePassedSince(timer); - - if (passed <= 0) { - // Event has not yet happened, which is fine. - return; - } - - // Try to get in sync again. - const unsigned long stepsMissed = static_cast(passed) / step; - - timer += (stepsMissed + 1) * step; -} - -void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer) { - setIntervalTimer(intervalTimer, millis()); -} - -void ESPEasy_Scheduler::setIntervalTimerAt(SchedulerIntervalTimer_e intervalTimer, unsigned long newtimer) { - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, newtimer); -} - -void ESPEasy_Scheduler::setIntervalTimerOverride(SchedulerIntervalTimer_e intervalTimer, unsigned long msecFromNow) { - unsigned long timer = millis(); - - setNextTimeInterval(timer, msecFromNow); - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, timer); -} - -void ESPEasy_Scheduler::scheduleNextDelayQueue(SchedulerIntervalTimer_e intervalTimer, unsigned long nextTime) { - if (nextTime != 0) { - // Schedule for next process run. - setIntervalTimerAt(intervalTimer, nextTime); - } -} - -void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer, unsigned long lasttimer) { - // Set the initial timers for the regular runs - unsigned long interval = 0; - - switch (intervalTimer) { - case SchedulerIntervalTimer_e::TIMER_20MSEC: interval = 20; break; - case SchedulerIntervalTimer_e::TIMER_100MSEC: interval = 100; break; - case SchedulerIntervalTimer_e::TIMER_1SEC: interval = 1000; break; - case SchedulerIntervalTimer_e::TIMER_30SEC: - case SchedulerIntervalTimer_e::TIMER_STATISTICS: interval = 30000; break; - case SchedulerIntervalTimer_e::TIMER_MQTT: interval = timermqtt_interval; break; - case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: interval = timer_gratuitous_arp_interval; break; - - // Fall-through for all DelayQueue, which are just the fall-back timers. - // The timers for all delay queues will be set according to their own settings as long as there is something to process. - case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: - // When extending this, search for EXTEND_CONTROLLER_IDS - // in the code to find all places that need to be updated too. - interval = 1000; break; - } - unsigned long timer = lasttimer; - - setNextTimeInterval(timer, interval); - const ConstIntervalTimerID timerID(intervalTimer); - - setNewTimerAt(timerID, timer); -} - -void ESPEasy_Scheduler::sendGratuitousARP_now() { - sendGratuitousARP(); - - if (Settings.gratuitousARP()) { - timer_gratuitous_arp_interval = 100; - setIntervalTimer(SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP); - } -} - -void ESPEasy_Scheduler::process_interval_timer(SchedulerTimerID timerID, unsigned long lasttimer) { - // Set the interval timer now, it may be altered by the commands below. - // This is the default next-run-time. - - const ConstIntervalTimerID *tmp = reinterpret_cast(&timerID); - const SchedulerIntervalTimer_e intervalTimer = tmp->getIntervalTimer(); - - setIntervalTimer(intervalTimer, lasttimer); - - switch (intervalTimer) { - case SchedulerIntervalTimer_e::TIMER_20MSEC: run50TimesPerSecond(); break; - case SchedulerIntervalTimer_e::TIMER_100MSEC: - - if (!UseRTOSMultitasking) { - run10TimesPerSecond(); - } - break; - case SchedulerIntervalTimer_e::TIMER_1SEC: runOncePerSecond(); break; - case SchedulerIntervalTimer_e::TIMER_30SEC: runEach30Seconds(); break; - case SchedulerIntervalTimer_e::TIMER_MQTT: -#if FEATURE_MQTT - runPeriodicalMQTT(); -#endif // if FEATURE_MQTT - break; - case SchedulerIntervalTimer_e::TIMER_STATISTICS: logTimerStatistics(); break; - case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: - - // Slowly increase the interval timer. - timer_gratuitous_arp_interval = 2 * timer_gratuitous_arp_interval; - - if (timer_gratuitous_arp_interval > TIMER_GRATUITOUS_ARP_MAX) { - timer_gratuitous_arp_interval = TIMER_GRATUITOUS_ARP_MAX; - } - - if (Settings.gratuitousARP()) { - sendGratuitousARP(); - } - break; - case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: - case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: -#if FEATURE_MQTT - processMQTTdelayQueue(); -#endif // if FEATURE_MQTT - break; - case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: - #ifdef USES_C001 - process_c001_delay_queue(); - #endif // ifdef USES_C001 - break; - case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: - #ifdef USES_C003 - process_c003_delay_queue(); - #endif // ifdef USES_C003 - break; - case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: - #ifdef USES_C004 - process_c004_delay_queue(); - #endif // ifdef USES_C004 - break; - case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: - #ifdef USES_C007 - process_c007_delay_queue(); - #endif // ifdef USES_C007 - break; - case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: - #ifdef USES_C008 - process_c008_delay_queue(); - #endif // ifdef USES_C008 - break; - case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: - #ifdef USES_C009 - process_c009_delay_queue(); - #endif // ifdef USES_C009 - break; - case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: - #ifdef USES_C010 - process_c010_delay_queue(); - #endif // ifdef USES_C010 - break; - case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: - #ifdef USES_C011 - process_c011_delay_queue(); - #endif // ifdef USES_C011 - break; - case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: - #ifdef USES_C012 - process_c012_delay_queue(); - #endif // ifdef USES_C012 - break; - - case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: - /* - #ifdef USES_C013 - process_c013_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: - /* - #ifdef USES_C014 - process_c014_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: - #ifdef USES_C015 - process_c015_delay_queue(); - #endif // ifdef USES_C015 - break; - case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: - #ifdef USES_C016 - process_c016_delay_queue(); - #endif // ifdef USES_C016 - break; - - case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: - #ifdef USES_C017 - process_c017_delay_queue(); - #endif // ifdef USES_C017 - break; - - case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: - #ifdef USES_C018 - process_c018_delay_queue(); - #endif // ifdef USES_C018 - break; - - case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: - /* - #ifdef USES_C019 - process_c019_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: - /* - #ifdef USES_C020 - process_c020_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: - /* - #ifdef USES_C021 - process_c021_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: - /* - #ifdef USES_C022 - process_c022_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: - /* - #ifdef USES_C023 - process_c023_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: - /* - #ifdef USES_C024 - process_c024_delay_queue(); - #endif - */ - break; - - case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: - /* - #ifdef USES_C025 - process_c025_delay_queue(); - #endif - */ - break; - - // When extending this, search for EXTEND_CONTROLLER_IDS - // in the code to find all places that need to be updated too. - } -} +#include "../Helpers/Scheduler.h" + + +#include "../../ESPEasy-Globals.h" + +#include "../ControllerQueue/DelayQueueElements.h" + +#include "../DataStructs/Scheduler_ConstIntervalTimerID.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Networking.h" +#include "../Helpers/PeriodicalActions.h" + + +/*********************************************************************************************\ +* Interval Timer +* These timers set a new scheduled timer, based on the old value. +* This will make their interval as constant as possible. +\*********************************************************************************************/ + +// Interval where it is more important to actually run the scheduled job, instead of keeping the time drift to a minimum. +// For example running the PLUGIN_FIFTY_PER_SECOND calls probably need to run as fast as possible as they need to fetch data before a buffer +// overflow happens. +// For those it is more important to actually run it than keeping pace. +void ESPEasy_Scheduler::setNextTimeInterval(unsigned long& timer, const unsigned long step) { + timer += step; + const long passed = timePassedSince(timer); + + if (passed < 0) { + // Event has not yet happened, which is fine. + return; + } + + if (static_cast(passed) > step) { + // No need to keep running behind, start again. + timer = millis() + step; + return; + } + + // Try to get in sync again. + timer = millis() + (step - passed); +} + +// More strict interval where no time drift is more important than missing a scheduled interval. +// For example timing for repeating longPulse where 2 scheduled intervals need to be at constant 'distance' from each other. +void ESPEasy_Scheduler::setNextStrictTimeInterval(unsigned long & timer, + const unsigned long step) { + timer += step; + const long passed = timePassedSince(timer); + + if (passed <= 0) { + // Event has not yet happened, which is fine. + return; + } + + // Try to get in sync again. + const unsigned long stepsMissed = static_cast(passed) / step; + + timer += (stepsMissed + 1) * step; +} + +void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer) { + setIntervalTimer(intervalTimer, millis()); +} + +void ESPEasy_Scheduler::setIntervalTimerAt(SchedulerIntervalTimer_e intervalTimer, unsigned long newtimer) { + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, newtimer); +} + +void ESPEasy_Scheduler::setIntervalTimerOverride(SchedulerIntervalTimer_e intervalTimer, unsigned long msecFromNow) { + unsigned long timer = millis(); + + setNextTimeInterval(timer, msecFromNow); + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, timer); +} + +void ESPEasy_Scheduler::scheduleNextDelayQueue(SchedulerIntervalTimer_e intervalTimer, unsigned long nextTime) { + if (nextTime != 0) { + // Schedule for next process run. + setIntervalTimerAt(intervalTimer, nextTime); + } +} + +void ESPEasy_Scheduler::setIntervalTimer(SchedulerIntervalTimer_e intervalTimer, unsigned long lasttimer) { + // Set the initial timers for the regular runs + unsigned long interval = 0; + + switch (intervalTimer) { + case SchedulerIntervalTimer_e::TIMER_20MSEC: interval = 20; break; + case SchedulerIntervalTimer_e::TIMER_100MSEC: interval = 100; break; + case SchedulerIntervalTimer_e::TIMER_1SEC: interval = 1000; break; + case SchedulerIntervalTimer_e::TIMER_30SEC: + case SchedulerIntervalTimer_e::TIMER_STATISTICS: interval = 30000; break; + case SchedulerIntervalTimer_e::TIMER_MQTT: interval = timermqtt_interval; break; + case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: interval = timer_gratuitous_arp_interval; break; + + // Fall-through for all DelayQueue, which are just the fall-back timers. + // The timers for all delay queues will be set according to their own settings as long as there is something to process. + case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: + // When extending this, search for EXTEND_CONTROLLER_IDS + // in the code to find all places that need to be updated too. + interval = 1000; break; + } + unsigned long timer = lasttimer; + + setNextTimeInterval(timer, interval); + const ConstIntervalTimerID timerID(intervalTimer); + + setNewTimerAt(timerID, timer); +} + +void ESPEasy_Scheduler::sendGratuitousARP_now() { + sendGratuitousARP(); + + if (Settings.gratuitousARP()) { + timer_gratuitous_arp_interval = 100; + setIntervalTimer(SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP); + } +} + +void ESPEasy_Scheduler::process_interval_timer(SchedulerTimerID timerID, unsigned long lasttimer) { + // Set the interval timer now, it may be altered by the commands below. + // This is the default next-run-time. + + const ConstIntervalTimerID *tmp = reinterpret_cast(&timerID); + const SchedulerIntervalTimer_e intervalTimer = tmp->getIntervalTimer(); + + setIntervalTimer(intervalTimer, lasttimer); + + switch (intervalTimer) { + case SchedulerIntervalTimer_e::TIMER_20MSEC: run50TimesPerSecond(); break; + case SchedulerIntervalTimer_e::TIMER_100MSEC: + + if (!UseRTOSMultitasking) { + run10TimesPerSecond(); + } + break; + case SchedulerIntervalTimer_e::TIMER_1SEC: runOncePerSecond(); break; + case SchedulerIntervalTimer_e::TIMER_30SEC: runEach30Seconds(); break; + case SchedulerIntervalTimer_e::TIMER_MQTT: +#if FEATURE_MQTT + runPeriodicalMQTT(); +#endif // if FEATURE_MQTT + break; + case SchedulerIntervalTimer_e::TIMER_STATISTICS: logTimerStatistics(); break; + case SchedulerIntervalTimer_e::TIMER_GRATUITOUS_ARP: + + // Slowly increase the interval timer. + timer_gratuitous_arp_interval = 2 * timer_gratuitous_arp_interval; + + if (timer_gratuitous_arp_interval > TIMER_GRATUITOUS_ARP_MAX) { + timer_gratuitous_arp_interval = TIMER_GRATUITOUS_ARP_MAX; + } + + if (Settings.gratuitousARP()) { + sendGratuitousARP(); + } + break; + case SchedulerIntervalTimer_e::TIMER_MQTT_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C002_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C005_DELAY_QUEUE: + case SchedulerIntervalTimer_e::TIMER_C006_DELAY_QUEUE: +#if FEATURE_MQTT + processMQTTdelayQueue(); +#endif // if FEATURE_MQTT + break; + case SchedulerIntervalTimer_e::TIMER_C001_DELAY_QUEUE: + #ifdef USES_C001 + process_c001_delay_queue(); + #endif // ifdef USES_C001 + break; + case SchedulerIntervalTimer_e::TIMER_C003_DELAY_QUEUE: + #ifdef USES_C003 + process_c003_delay_queue(); + #endif // ifdef USES_C003 + break; + case SchedulerIntervalTimer_e::TIMER_C004_DELAY_QUEUE: + #ifdef USES_C004 + process_c004_delay_queue(); + #endif // ifdef USES_C004 + break; + case SchedulerIntervalTimer_e::TIMER_C007_DELAY_QUEUE: + #ifdef USES_C007 + process_c007_delay_queue(); + #endif // ifdef USES_C007 + break; + case SchedulerIntervalTimer_e::TIMER_C008_DELAY_QUEUE: + #ifdef USES_C008 + process_c008_delay_queue(); + #endif // ifdef USES_C008 + break; + case SchedulerIntervalTimer_e::TIMER_C009_DELAY_QUEUE: + #ifdef USES_C009 + process_c009_delay_queue(); + #endif // ifdef USES_C009 + break; + case SchedulerIntervalTimer_e::TIMER_C010_DELAY_QUEUE: + #ifdef USES_C010 + process_c010_delay_queue(); + #endif // ifdef USES_C010 + break; + case SchedulerIntervalTimer_e::TIMER_C011_DELAY_QUEUE: + #ifdef USES_C011 + process_c011_delay_queue(); + #endif // ifdef USES_C011 + break; + case SchedulerIntervalTimer_e::TIMER_C012_DELAY_QUEUE: + #ifdef USES_C012 + process_c012_delay_queue(); + #endif // ifdef USES_C012 + break; + + case SchedulerIntervalTimer_e::TIMER_C013_DELAY_QUEUE: + /* + #ifdef USES_C013 + process_c013_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C014_DELAY_QUEUE: + /* + #ifdef USES_C014 + process_c014_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C015_DELAY_QUEUE: + #ifdef USES_C015 + process_c015_delay_queue(); + #endif // ifdef USES_C015 + break; + case SchedulerIntervalTimer_e::TIMER_C016_DELAY_QUEUE: + #ifdef USES_C016 + process_c016_delay_queue(); + #endif // ifdef USES_C016 + break; + + case SchedulerIntervalTimer_e::TIMER_C017_DELAY_QUEUE: + #ifdef USES_C017 + process_c017_delay_queue(); + #endif // ifdef USES_C017 + break; + + case SchedulerIntervalTimer_e::TIMER_C018_DELAY_QUEUE: + #ifdef USES_C018 + process_c018_delay_queue(); + #endif // ifdef USES_C018 + break; + + case SchedulerIntervalTimer_e::TIMER_C019_DELAY_QUEUE: + /* + #ifdef USES_C019 + process_c019_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C020_DELAY_QUEUE: + /* + #ifdef USES_C020 + process_c020_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C021_DELAY_QUEUE: + /* + #ifdef USES_C021 + process_c021_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C022_DELAY_QUEUE: + /* + #ifdef USES_C022 + process_c022_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C023_DELAY_QUEUE: + /* + #ifdef USES_C023 + process_c023_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C024_DELAY_QUEUE: + /* + #ifdef USES_C024 + process_c024_delay_queue(); + #endif + */ + break; + + case SchedulerIntervalTimer_e::TIMER_C025_DELAY_QUEUE: + /* + #ifdef USES_C025 + process_c025_delay_queue(); + #endif + */ + break; + + // When extending this, search for EXTEND_CONTROLLER_IDS + // in the code to find all places that need to be updated too. + } +} diff --git a/src/src/Helpers/Scheduler_decodeTimer.cpp b/src/src/Helpers/Scheduler_decodeTimer.cpp index 2582ec73b..805b4d271 100644 --- a/src/src/Helpers/Scheduler_decodeTimer.cpp +++ b/src/src/Helpers/Scheduler_decodeTimer.cpp @@ -62,6 +62,6 @@ String ESPEasy_Scheduler::decodeSchedulerId(SchedulerTimerID timerID) { #endif // ifndef BUILD_NO_DEBUG result += F(" timer, id: "); - result += timerID.id; + result += timerID.getId(); return result; } diff --git a/src/src/Helpers/SerialWriteBuffer.cpp b/src/src/Helpers/SerialWriteBuffer.cpp index ca4364f97..750a6c4bb 100644 --- a/src/src/Helpers/SerialWriteBuffer.cpp +++ b/src/src/Helpers/SerialWriteBuffer.cpp @@ -57,11 +57,6 @@ void SerialWriteBuffer_t::clear() _buffer.clear(); } -int SerialWriteBuffer_t::availableForWrite() const -{ - return _buffer.size(); -} - size_t SerialWriteBuffer_t::write(Stream& stream, size_t nrBytesToWrite) { size_t bytesWritten = 0; @@ -77,14 +72,43 @@ size_t SerialWriteBuffer_t::write(Stream& stream, size_t nrBytesToWrite) } while (nrBytesToWrite > 0 && !_buffer.empty()) { - const char c = _buffer.front(); + uint8_t tmpBuffer[16]{}; - if (stream.write((uint8_t)c) == 0) { + 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; } - _buffer.pop_front(); - --nrBytesToWrite; - ++bytesWritten; } } return bytesWritten; diff --git a/src/src/Helpers/SerialWriteBuffer.h b/src/src/Helpers/SerialWriteBuffer.h index f2bd1ed33..7e5932df4 100644 --- a/src/src/Helpers/SerialWriteBuffer.h +++ b/src/src/Helpers/SerialWriteBuffer.h @@ -28,9 +28,6 @@ public: void clear(); - - int availableForWrite() const; - size_t write(Stream& stream, size_t nrBytesToWrite); diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp index 4f10ac07a..a2c1c68eb 100644 --- a/src/src/Helpers/StringConverter.cpp +++ b/src/src/Helpers/StringConverter.cpp @@ -22,6 +22,7 @@ #include "../Helpers/Misc.h" #include "../Helpers/Networking.h" #include "../Helpers/Numerical.h" +#include "../Helpers/StringGenerator_System.h" #include "../Helpers/StringParser.h" #include "../Helpers/SystemVariables.h" #include "../Helpers/_Plugin_SensorTypeHelper.h" @@ -71,7 +72,7 @@ bool equals(const String& str, const __FlashStringHelper * f_str) { } bool equals(const String& str, const char& c) { - return str.equals(String(c)); + return str.length() == 1 && str[0] == c; } void move_special(String& dest, String&& source) { @@ -129,7 +130,7 @@ String strformat(const String& format, ...) { va_list arg; va_start(arg, format); // variable args start after parameter 'format' - char temp[64]; + static char temp[64]; char* buffer = temp; int len = vsnprintf_P(temp, sizeof(temp), format.c_str(), arg); va_end(arg); @@ -159,7 +160,7 @@ String strformat(const __FlashStringHelper * format, ...) { va_list arg; va_start(arg, format); // variable args start after parameter 'format' - char temp[64]; + static char temp[64]; char* buffer = temp; int len = vsnprintf_P(temp, sizeof(temp), (PGM_P)format, arg); va_end(arg); @@ -211,7 +212,7 @@ bool str2ip(const char *string, uint8_t *IP) return false; } -String formatIP(const IPAddress& ip) { +String formatIP(const IPAddress& ip, bool includeZone) { #ifdef ESP8266 #if defined(ARDUINO_ESP8266_RELEASE_2_3_0) IPAddress tmp(ip); @@ -230,8 +231,12 @@ String formatIP(const IPAddress& ip) { } #endif */ +#if FEATURE_USE_IPV6 + return ip.toString(includeZone); +#else return ip.toString(); #endif +#endif } @@ -445,7 +450,7 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh return EMPTY_STRING; } - { + if (Device[DeviceIndex].HasFormatUserVar) { // First try to format using the plugin specific formatting. String result; EventStruct tempEvent; @@ -456,7 +461,8 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh return result; } } - + + // Spent upto 400 usec till here const uint8_t valueCount = getValueCountForTask(event->TaskIndex); const Sensor_VType sensorType = event->getSensorType(); @@ -503,7 +509,7 @@ String doFormatUserVar(struct EventStruct *event, uint8_t rel_index, bool mustCh } String res = UserVar.getAsString(event->TaskIndex, rel_index, sensorType, nrDecimals); STOP_TIMER(FORMAT_USER_VAR); - return res; + return std::move(res); } String formatUserVarNoCheck(taskIndex_t TaskIndex, uint8_t rel_index) { @@ -642,10 +648,34 @@ String to_json_object_value(const __FlashStringHelper * object, 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"), - wrap_String(object, '"').c_str(), + F("\"%s\":%s"), + object.c_str(), to_json_value(value, wrapInQuotes).c_str()); } @@ -654,6 +684,18 @@ String to_json_value(const String& value, bool wrapInQuotes) { // Empty string return F("\"\""); } + if (value.length() > 2) { + // Check for JSON objects or arrays + const char firstchar = value[0]; + const char lastchar = value[value.length() - 1]; + if ((firstchar == '[' && lastchar == ']') || + (firstchar == '{' && lastchar == '}')) + { + return value; + } + } + + if (wrapInQuotes || mustConsiderAsJSONString(value)) { // Is not a numerical value, or BIN/HEX notation, thus wrap with quotes @@ -956,11 +998,14 @@ std::vector parseHexTextData(const String& argument, int index) { j += 2; // Skip characters we need to ignore - int c = -1; - do { - ++j; - c = (j < arg.length()) ? skipChars.indexOf(arg[j]) : -1; - } while (c > -1); + if ((j + 1 < arg.length()) && (skipChars.indexOf(arg[j + 1]) != -1)) { + int c = -1; + + do { + ++j; + c = (j < arg.length()) ? skipChars.indexOf(arg[j]) : -1; + } while (c > -1); + } } } else { for (size_t s = 0; s < arg.length(); s++) { @@ -1055,7 +1100,8 @@ int GetCommandCode(char* destination, size_t destination_size, const char* needl int GetCommandCode(const char* needle, const char* haystack) { // Likely long enough to parse any command - char temp[32]{}; + static char temp[32]{}; + temp[0] = '\0'; return GetCommandCode(temp, sizeof(temp), needle, haystack); } @@ -1312,12 +1358,13 @@ void parseEventVariables(String& s, struct EventStruct *event, bool useURLencode const bool vname_found = s.indexOf(F("%vname")) != -1; if (vname_found) { - for (uint8_t i = 0; i < 4; ++i) { + const uint8_t valueCount = getValueCountForTask(event->TaskIndex); + for (uint8_t i = 0; i < valueCount; ++i) { String vname = F("%vname"); vname += (i + 1); vname += '%'; - SMART_REPL(vname, getTaskValueName(event->TaskIndex, i)); + SMART_REPL(vname, Cache.getTaskDeviceValueName(event->TaskIndex, i)); } } } @@ -1433,6 +1480,15 @@ void parseStandardConversions(String& s, bool useURLencode) { SMART_CONV(F("%c_m2hcm%"), minutesToHourColonMinute(data.arg1)) SMART_CONV(F("%c_s2dhms%"), secondsToDayHourMinuteSecond(data.arg1)) SMART_CONV(F("%c_2hex%"), formatToHex_no_prefix(data.arg1)) + #if FEATURE_ESPEASY_P2P + SMART_CONV(F("%c_uname%"), getNameForUnit(data.arg1)) + SMART_CONV(F("%c_uage%"), String(static_cast(getAgeForUnit(data.arg1) / 1000))) + SMART_CONV(F("%c_ubuild%"), String(getBuildnrForUnit(data.arg1))) + SMART_CONV(F("%c_ubuildstr%"), formatSystemBuildNr(getBuildnrForUnit(data.arg1))) + SMART_CONV(F("%c_uload%"), toString(getLoadForUnit(data.arg1))) + SMART_CONV(F("%c_utype%"), String(getTypeForUnit(data.arg1))) + SMART_CONV(F("%c_utypestr%"), getTypeStringForUnit(data.arg1)) + #endif // if FEATURE_ESPEASY_P2P #undef SMART_CONV // Conversions with 2 parameters @@ -1476,6 +1532,9 @@ bool GetArgv(const char *string, String& argvString, unsigned int argc, char sep bool GetArgvBeginEnd(const char *string, const unsigned int argc, int& pos_begin, int& pos_end, char separator) { pos_begin = -1; pos_end = -1; + if (string == nullptr) { + return false; + } size_t string_len = strlen(string); unsigned int string_pos = 0, argc_pos = 0; bool parenthesis = false; diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h index 6e8ddac64..de1ebfc58 100644 --- a/src/src/Helpers/StringConverter.h +++ b/src/src/Helpers/StringConverter.h @@ -86,7 +86,7 @@ bool str2ip(const String& string, bool str2ip(const char *string, uint8_t *IP); -String formatIP(const IPAddress& ip); +String formatIP(const IPAddress& ip, bool includeZone = false); /********************************************************************************************\ @@ -227,6 +227,14 @@ 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 ef795b183..9cf1eeff3 100644 --- a/src/src/Helpers/StringConverter_Numerical.cpp +++ b/src/src/Helpers/StringConverter_Numerical.cpp @@ -1,186 +1,190 @@ -#include "../Helpers/StringConverter_Numerical.h" - -#include "../Helpers/Numerical.h" - -#include "../Helpers/StringConverter.h" - -/********************************************************************************************\ - Convert a char string to integer - \*********************************************************************************************/ - -// FIXME: change original code so it uses String and String.toInt() -unsigned long str2int(const char *string) -{ - uint32_t temp = 0; - - validUIntFromString(string, temp); - - return static_cast(temp); -} - -/*********************************************************************************************\ - Workaround for removing trailing white space when String() converts a float with 0 decimals -\*********************************************************************************************/ -String toString(const float& value, unsigned int decimalPlaces) -{ - /* - #ifndef LIMIT_BUILD_SIZE - - if (decimalPlaces == 0) { - if ((value > -2e9f) && (value < 2e9f)) { - const int32_t l_value = static_cast(roundf(value)); - return String(l_value); - } - if ((value > -1e18f) && (value < 1e18f)) { - // Work-around to perform a faster conversion - const int64_t ll_value = static_cast(roundf(value)); - return ll2String(ll_value); - } - } - #endif // ifndef LIMIT_BUILD_SIZE - */ -// #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - // This has been fixed in ESP32 code, not (yet) in ESP8266 code - // https://github.com/espressif/arduino-esp32/pull/6138/files - // #ifdef ESP8266 - - char buf[decimalPlaces + 42]; - String sValue; - move_special(sValue, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf))); - -/* -#else - String sValue = String(value, decimalPlaces); -#endif -*/ - sValue.trim(); - return sValue; -} - -String ull2String(uint64_t value, uint8_t base) { - String res; - - if (value == 0) { - res = '0'; - return res; - } - - while (value > 0) { - res += String(static_cast(value % base), base); - value /= base; - } - - int endpos = res.length() - 1; - int beginpos = 0; - - while (endpos > beginpos) { - const char c = res[beginpos]; - res[beginpos] = res[endpos]; - res[endpos] = c; - ++beginpos; - --endpos; - } - - return res; -} - -String ll2String(int64_t value, uint8_t base) { - if (value < 0) { - String res; - res = '-'; - res += ull2String(value * -1ll, base); - return res; - } else { - return ull2String(value, base); - } -} - -String trimTrailingZeros(const String& value) { - String res(value); - int dot_pos = res.lastIndexOf('.'); - - if (dot_pos != -1) { - bool someTrimmed = false; - - for (int i = res.length() - 1; i > dot_pos && res[i] == '0'; --i) { - someTrimmed = true; - res[i] = ' '; - } - - if (someTrimmed) { - res.trim(); - } - - if (res.endsWith(F("."))) { - res[dot_pos] = ' '; - res.trim(); - } - } - return res; - -} - -/** - * Helper: Convert an integer to string, but return an empty string for 0, to save a little space in settings - */ -String toStringNoZero(int64_t value) { - if (value != 0) { - return ll2String(value); - } else { - return EMPTY_STRING; - } -} - -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -String doubleToString(const double& value, unsigned int decimalPlaces, bool trimTrailingZeros_b) { - // This has been fixed in ESP32 code, not (yet) in ESP8266 code - // https://github.com/espressif/arduino-esp32/pull/6138/files - // #ifdef ESP8266 - unsigned int expectedChars = decimalPlaces + 4; // 1 dot, 2 minus signs and terminating zero - - if ((value > 1e32) || (value < -1e32)) { - expectedChars += 308; // Just assume the worst - } else { - expectedChars += 33; - } - char *buf = (char *)malloc(expectedChars); - - if (nullptr == buf) { - return F("nan"); - } - String res; - move_special(res, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf))); - - free(buf); - - // #else - // String res(value, decimalPlaces); - // #endif - res.trim(); - - if (trimTrailingZeros_b) { - return trimTrailingZeros(res); - } - return res; -} -#endif - -String floatToString(const float& value, - unsigned int decimalPlaces, - bool trimTrailingZeros_b) -{ - const String res = toString(value, decimalPlaces); - - if (trimTrailingZeros_b) { - return trimTrailingZeros(res); - } - return res; -} - - -/********************************************************************************************\ - Check if valid float and convert string to float. - \*********************************************************************************************/ -bool string2float(const String& string, float& floatvalue) { - return validFloatFromString(string, floatvalue); -} +#include "../Helpers/StringConverter_Numerical.h" + +#include "../DataStructs/TimingStats.h" + +#include "../Helpers/Numerical.h" + +#include "../Helpers/StringConverter.h" + + +/********************************************************************************************\ + Convert a char string to integer + \*********************************************************************************************/ + +// FIXME: change original code so it uses String and String.toInt() +unsigned long str2int(const char *string) +{ + uint32_t temp = 0; + + validUIntFromString(string, temp); + + return static_cast(temp); +} + +/*********************************************************************************************\ + Workaround for removing trailing white space when String() converts a float with 0 decimals +\*********************************************************************************************/ +String toString(const float& value, unsigned int decimalPlaces) +{ + String sValue; + #ifndef LIMIT_BUILD_SIZE + + if (decimalPlaces == 0 && isValidFloat(value)) { + if ((value > -2e9f) && (value < 2e9f)) { + const int32_t l_value = static_cast(roundf(value)); + sValue = l_value; + } else if ((value > -1e18f) && (value < 1e18f)) { + // Work-around to perform a faster conversion + const int64_t ll_value = static_cast(roundf(value)); + sValue = ll2String(ll_value); + } + if (sValue.length() > 0) { + return sValue; + } + } + #endif // ifndef LIMIT_BUILD_SIZE +// #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + // This has been fixed in ESP32 code, not (yet) in ESP8266 code + // https://github.com/espressif/arduino-esp32/pull/6138/files + // #ifdef ESP8266 + + char buf[decimalPlaces + 42]; + #ifdef USE_SECOND_HEAP + move_special(sValue, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf))); + #else + sValue = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf); + #endif + +/* +#else + String sValue = String(value, decimalPlaces); +#endif +*/ + sValue.trim(); + return sValue; +} + +String ull2String(uint64_t value, uint8_t base) { + String res; + + if (value == 0) { + res = '0'; + return res; + } + + while (value > 0) { + res += String(static_cast(value % base), base); + value /= base; + } + + int endpos = res.length() - 1; + int beginpos = 0; + + while (endpos > beginpos) { + const char c = res[beginpos]; + res[beginpos] = res[endpos]; + res[endpos] = c; + ++beginpos; + --endpos; + } + + return res; +} + +String ll2String(int64_t value, uint8_t base) { + if (value < 0) { + return concat('-', ull2String(value * -1ll, base)); + } else { + return ull2String(value, base); + } +} + +String trimTrailingZeros(const String& value) { + String res(value); + int dot_pos = res.lastIndexOf('.'); + + if (dot_pos != -1) { + bool someTrimmed = false; + + for (int i = res.length() - 1; i > dot_pos && res[i] == '0'; --i) { + someTrimmed = true; + res[i] = ' '; + } + + if (someTrimmed) { + res.trim(); + } + + if (res.endsWith(F("."))) { + res[dot_pos] = ' '; + res.trim(); + } + } + return res; + +} + +/** + * Helper: Convert an integer to string, but return an empty string for 0, to save a little space in settings + */ +String toStringNoZero(int64_t value) { + if (value != 0) { + return ll2String(value); + } else { + return EMPTY_STRING; + } +} + +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +String doubleToString(const double& value, unsigned int decimalPlaces, bool trimTrailingZeros_b) { + // This has been fixed in ESP32 code, not (yet) in ESP8266 code + // https://github.com/espressif/arduino-esp32/pull/6138/files + // #ifdef ESP8266 + unsigned int expectedChars = decimalPlaces + 4; // 1 dot, 2 minus signs and terminating zero + + if ((value > 1e32) || (value < -1e32)) { + expectedChars += 308; // Just assume the worst + } else { + expectedChars += 33; + } + char *buf = (char *)malloc(expectedChars); + + if (nullptr == buf) { + return F("nan"); + } + String res; + move_special(res, String(dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf))); + + free(buf); + + // #else + // String res(value, decimalPlaces); + // #endif + res.trim(); + + if (trimTrailingZeros_b) { + return trimTrailingZeros(res); + } + return res; +} +#endif + +String floatToString(const float& value, + unsigned int decimalPlaces, + bool trimTrailingZeros_b) +{ + String res = toString(value, decimalPlaces); + + if (trimTrailingZeros_b) { + return trimTrailingZeros(res); + } + return res; +} + + +/********************************************************************************************\ + Check if valid float and convert string to float. + \*********************************************************************************************/ +bool string2float(const String& string, float& floatvalue) { + return validFloatFromString(string, floatvalue); +} diff --git a/src/src/Helpers/StringGenerator_GPIO.cpp b/src/src/Helpers/StringGenerator_GPIO.cpp index 6c9763de4..dd5198f81 100644 --- a/src/src/Helpers/StringGenerator_GPIO.cpp +++ b/src/src/Helpers/StringGenerator_GPIO.cpp @@ -287,21 +287,30 @@ const __FlashStringHelper* getConflictingUse(int gpio, PinSelectPurpose purpose) #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"); } - if (Settings.isEthernetPin(gpio)) { - return F("Eth"); - } + if (Settings.ETH_Pin_mdio_irq == gpio) { return F("Eth SPI IRQ"); } - if (includeEthernet && Settings.isEthernetPinOptional(gpio)) { - if (isGpioUsedInETHClockMode(Settings.ETH_Clock_Mode, gpio)) { return F("Eth Clock"); } + if (Settings.ETH_Pin_power_rst == gpio) { return F("Eth SPI RST"); } + } + } else { + if (Settings.isEthernetPin(gpio)) { + return F("Eth"); + } - if (Settings.ETH_Pin_mdc == gpio) { return F("Eth MDC"); } + if (includeEthernet && Settings.isEthernetPinOptional(gpio)) { + if (isGpioUsedInETHClockMode(Settings.ETH_Clock_Mode, gpio)) { return F("Eth Clock"); } - if (Settings.ETH_Pin_mdio == gpio) { return F("Eth MDIO"); } + if (Settings.ETH_Pin_mdc_cs == gpio) { return F("Eth MDC"); } - if (Settings.ETH_Pin_power == gpio) { return F("Eth Pwr"); } + if (Settings.ETH_Pin_mdio_irq == gpio) { return F("Eth MDIO"); } - return F("Eth"); + if (Settings.ETH_Pin_power_rst == gpio) { return F("Eth Pwr"); } + + return F("Eth"); + } } #endif // if FEATURE_ETHERNET diff --git a/src/src/Helpers/StringGenerator_System.cpp b/src/src/Helpers/StringGenerator_System.cpp index b9b1fce8c..df5d9a368 100644 --- a/src/src/Helpers/StringGenerator_System.cpp +++ b/src/src/Helpers/StringGenerator_System.cpp @@ -305,27 +305,28 @@ String formatSystemBuildNr(uint16_t buildNr) { } String getPluginDescriptionString() { - return F( - "" + String result = F("[" #ifdef PLUGIN_BUILD_NORMAL - "[Normal]" + "\"Normal\"" #endif // ifdef PLUGIN_BUILD_NORMAL #ifdef PLUGIN_BUILD_COLLECTION - "[Collection]" + "\"Collection\"" #endif // ifdef PLUGIN_BUILD_COLLECTION #ifdef PLUGIN_BUILD_DEV - "[Development]" + "\"Development\"" #endif // ifdef PLUGIN_BUILD_DEV #ifdef PLUGIN_DESCR - "[" PLUGIN_DESCR "]" + "\"" PLUGIN_DESCR "\"" #endif // ifdef PLUGIN_DESCR #ifdef BUILD_NO_DEBUG - "[No Debug Log]" + "\"No Debug Log\"" #endif #if FEATURE_NON_STANDARD_24_TASKS && defined(ESP8266) - "[24tasks]" + "\"24tasks\"" #endif // if FEATURE_NON_STANDARD_24_TASKS && defined(ESP8266) - ); + "]"); + result.replace("\"\"", "\",\""); + return result; } String getSystemLibraryString() { diff --git a/src/src/Helpers/StringGenerator_Web.cpp b/src/src/Helpers/StringGenerator_Web.cpp new file mode 100644 index 000000000..8d3985b38 --- /dev/null +++ b/src/src/Helpers/StringGenerator_Web.cpp @@ -0,0 +1,37 @@ + +#include "../Helpers/StringGenerator_Web.h" +#include "../WebServer/HTML_wrappers.h" + +/** + * start a datalist definition + * see: datalistAddValue, datalistFinish + */ +void datalistStart(const __FlashStringHelper *id) { + datalistStart(String(id)); +} + +void datalistStart(const String& id) { + addHtml(F("")); +} + +/** + * add a value to a datalist + * see: datalistStart, datalistFinish + */ +void datalistAddValue(const String& value) { + addHtml(F("")); +} + +/** + * finish the datalist definition + * see: datalistStart, datalistAddValue + */ +void datalistFinish() { + addHtml(F("")); +} diff --git a/src/src/Helpers/StringGenerator_Web.h b/src/src/Helpers/StringGenerator_Web.h new file mode 100644 index 000000000..2b226e413 --- /dev/null +++ b/src/src/Helpers/StringGenerator_Web.h @@ -0,0 +1,10 @@ +#ifndef HELPERS_STRINGGENERATOR_WEB_H +#define HELPERS_STRINGGENERATOR_WEB_H + +#include "../../ESPEasy_common.h" + +void datalistStart(const __FlashStringHelper *id); +void datalistStart(const String& id); +void datalistAddValue(const String& value); +void datalistFinish(); +#endif // ifndef HELPERS_STRINGGENERATOR_WEB_H diff --git a/src/src/Helpers/StringParser.cpp b/src/src/Helpers/StringParser.cpp index 55c83b9df..9d7ba42bb 100644 --- a/src/src/Helpers/StringParser.cpp +++ b/src/src/Helpers/StringParser.cpp @@ -1,781 +1,787 @@ -#include "../Helpers/StringParser.h" - -#include "../../_Plugin_Helper.h" - -#include "../Commands/GPIO.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasyRules.h" - -#include "../Globals/Cache.h" -#include "../Globals/Plugins_other.h" -#include "../Globals/RulesCalculate.h" -#include "../Globals/RuntimeData.h" - -#include "../Helpers/_CPlugin_init.h" -#include "../Helpers/ESPEasy_math.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_GPIO.h" - - - -/********************************************************************************************\ - Parse string template - \*********************************************************************************************/ -String parseTemplate(String& tmpString) -{ - return parseTemplate(tmpString, false); -} - -String parseTemplate(String& tmpString, bool useURLencode) -{ - return parseTemplate_padded(tmpString, 0, useURLencode); -} - -String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize) -{ - return parseTemplate_padded(tmpString, minimal_lineSize, false); -} - -String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool useURLencode) -{ - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate_padded")); - #endif // ifndef BUILD_NO_RAM_TRACKER - START_TIMER; - - // Keep current loaded taskSettings to restore at the end. - const taskIndex_t currentTaskIndex = ExtraTaskSettings.TaskIndex; - String newString; - newString.reserve(minimal_lineSize); // Our best guess of the new size. - - if (parseTemplate_CallBack_ptr != nullptr) { - parseTemplate_CallBack_ptr(tmpString, useURLencode); - } - parseSystemVariables(tmpString, useURLencode); - - - int startpos = 0; - int lastStartpos = 0; - int endpos = 0; - { - String deviceName, valueName, format; - - while (findNextDevValNameInString(tmpString, startpos, endpos, deviceName, valueName, format)) { - // First copy all upto the start of the [...#...] part to be replaced. - newString += tmpString.substring(lastStartpos, startpos); - - // deviceName is lower case, so we can compare literal string (no need for equalsIgnoreCase) - const bool devNameEqInt = equals(deviceName, F("int")); - if (devNameEqInt || equals(deviceName, F("var"))) - { - // Address an internal variable either as float or as int - // For example: Let,10,[VAR#9] - uint32_t varNum; - - if (validUIntFromString(valueName, varNum)) { - unsigned char nr_decimals = maxNrDecimals_fpType(getCustomFloatVar(varNum)); - bool trimTrailingZeros = true; - - if (devNameEqInt) { - nr_decimals = 0; - } else if (!format.isEmpty()) - { - // There is some formatting here, so do not throw away decimals - trimTrailingZeros = false; - } - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - String value = doubleToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); - #else - String value = floatToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); - #endif - transformValue( - newString, - minimal_lineSize, - std::move(value), - format, - tmpString); - } - } - else if (equals(deviceName, F("plugin"))) - { - // Handle a plugin request. - // For example: "[Plugin#GPIO#Pinstate#N]" - // The command is stored in valueName & format - String command = strformat(F("%s#%s"), valueName.c_str(), format.c_str()); - command.replace('#', ','); - - if (getGPIOPinStateValues(command)) { - newString += command; - } - /* @giig1967g - if (PluginCall(PLUGIN_REQUEST, 0, command)) - { - // Do not call transformValue here. - // The "format" is not empty so must not call the formatter function. - newString += command; - } - */ - } - else - { - // Address a value from a plugin. - // For example: "[bme#temp]" - // If value name is unknown, run a PLUGIN_GET_CONFIG_VALUE command. - // For example: "[#getLevel]" - taskIndex_t taskIndex = findTaskIndexByName(deviceName, true); // Check for enabled/disabled is done separately - - if (validTaskIndex(taskIndex)) { - bool isHandled = false; - if (Settings.TaskDeviceEnabled[taskIndex]) { - uint8_t valueNr = findDeviceValueIndexByName(valueName, taskIndex); - - if (valueNr != VARS_PER_TASK) { - // here we know the task and value, so find the uservar - // Try to format and transform the values - bool isvalid; - String value = formatUserVar(taskIndex, valueNr, isvalid); - - if (isvalid) { - transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); - isHandled = true; - } - } else { - // try if this is a get config request - struct EventStruct TempEvent(taskIndex); - String tmpName = valueName; - - if (PluginCall(PLUGIN_GET_CONFIG_VALUE, &TempEvent, tmpName)) - { - transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString); - isHandled = true; - } - } - } - if (!isHandled && valueName.startsWith(F("settings."))) { // Task settings values - String value; - if (valueName.endsWith(F(".enabled"))) { // Task state - value = Settings.TaskDeviceEnabled[taskIndex] ? '1' : '0'; - } else if (valueName.endsWith(F(".interval"))) { // Task interval - value = Settings.TaskDeviceTimer[taskIndex]; - } else if (valueName.endsWith(F(".valuecount"))) { // Task value count - value = getValueCountForTask(taskIndex); - } else if ((valueName.indexOf(F(".controller")) == 8) && valueName.length() >= 20) { // Task controller values - String ctrl = valueName.substring(19, 20); - int32_t ctrlNr = 0; - if (validIntFromString(ctrl, ctrlNr) && (ctrlNr >= 1) && (ctrlNr <= CONTROLLER_MAX) && - Settings.ControllerEnabled[ctrlNr - 1]) { // Controller nr. valid and enabled - if (valueName.endsWith(F(".enabled"))) { // Task-controller enabled - value = Settings.TaskDeviceSendData[ctrlNr - 1][taskIndex]; - } else if (valueName.endsWith(F(".idx"))) { // Task-controller idx value - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(ctrlNr - 1); - - if (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[ctrlNr - 1] != 0)) { - value = Settings.TaskDeviceID[ctrlNr - 1][taskIndex]; - } - } - } - } - if (!value.isEmpty()) { - transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); - // isHandled = true; - } - } - } - } - - - // Conversion is done (or impossible) for the found "[...#...]" - // Continue with the next one. - lastStartpos = endpos + 1; - startpos = endpos + 1; - - // This may have taken some time, so call delay() - delay(0); - } - } - - // Copy the rest of the string (or all if no replacements were done) - newString += tmpString.substring(lastStartpos); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate2")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // Restore previous loaded taskSettings - if (validTaskIndex(currentTaskIndex)) - { - LoadTaskSettings(currentTaskIndex); - } - - parseStandardConversions(newString, useURLencode); - - // process other markups as well - parse_string_commands(newString); - - // padding spaces - while (newString.length() < minimal_lineSize) { - newString += ' '; - } - - STOP_TIMER(PARSE_TEMPLATE_PADDED); - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseTemplate3")); - #endif // ifndef BUILD_NO_RAM_TRACKER - return newString; -} - -/********************************************************************************************\ - Transform values - \*********************************************************************************************/ - -bool isTransformString(char c, bool logicVal, String& strValue) -{ - const __FlashStringHelper * value = F(""); - char value_ch = '\0'; - switch (c) { - case 'O': - value = logicVal == 0 ? F("OFF") : F(" ON"); // (equivalent to XOR operator) - break; - case 'C': - value = logicVal == 0 ? F("CLOSE") : F(" OPEN"); - break; - case 'c': - value = logicVal == 0 ? F("CLOSED") : F(" OPEN"); - break; - case 'M': - value = logicVal == 0 ? F("AUTO") : F(" MAN"); - break; - case 'm': - value_ch = logicVal == 0 ? 'A' : 'M'; - break; - case 'H': - value = logicVal == 0 ? F("COLD") : F(" HOT"); - break; - case 'U': - value = logicVal == 0 ? F("DOWN") : F(" UP"); - break; - case 'u': - value_ch = logicVal == 0 ? 'D' : 'U'; - break; - case 'Y': - value = logicVal == 0 ? F(" NO") : F("YES"); - break; - case 'y': - value_ch = logicVal == 0 ? 'N' : 'Y'; - break; - case 'X': - value_ch = logicVal == 0 ? 'O' : 'X'; - break; - case 'I': - value = logicVal == 0 ? F("OUT") : F(" IN"); - break; - case 'L': - value = logicVal == 0 ? F(" LEFT") : F("RIGHT"); - break; - case 'l': - value_ch = logicVal == 0 ? 'L' : 'R'; - break; - case 'Z': // return "0" or "1" - value_ch = logicVal == 0 ? '0' : '1'; - break; - default: - return false; - } - if (value_ch != '\0') { - strValue = value_ch; - } else { - strValue = value; - } - return true; -} - - -// Syntax: [task#value#transformation#justification] -// valueFormat="transformation#justification" -void transformValue( - String & newString, - uint8_t lineSize, - String value, - String & valueFormat, - const String& tmpString) -{ - // FIXME TD-er: This function does append to newString and uses its length to perform right aling. - // Is this the way it is intended to use? - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("transformValue")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // start changes by giig1967g - 2018-04-20 - // Syntax: [task#value#transformation#justification] - // valueFormat="transformation#justification" - if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop - { - String valueJust; - - int hashtagIndex = valueFormat.indexOf('#'); - - if (hashtagIndex >= 0) - { - valueJust = valueFormat.substring(hashtagIndex + 1); // Justification part - valueFormat = valueFormat.substring(0, hashtagIndex); // Transformation part - } - - // valueFormat="transformation" - // valueJust="justification" - if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop - { - int logicVal = 0; - ESPEASY_RULES_FLOAT_TYPE valFloat{}; - - if (validDoubleFromString(value, valFloat)) - { - // to be used for binary values (0 or 1) - logicVal = lround(static_cast(valFloat)) == 0 ? 0 : 1; - } else { - if (value.length() > 0) { - logicVal = 1; - } - } - String tempValueFormat = valueFormat; - { - const int invertedIndex = tempValueFormat.indexOf('!'); - - if (invertedIndex != -1) { - // We must invert the value. - logicVal = (logicVal == 0) ? 1 : 0; - - // Remove the '!' from the string. - tempValueFormat.remove(invertedIndex, 1); - } - } - - const int rightJustifyIndex = tempValueFormat.indexOf('R'); - const bool rightJustify = rightJustifyIndex >= 0 ? 1 : 0; - - if (rightJustify) { - tempValueFormat.remove(rightJustifyIndex, 1); - } - - const int tempValueFormatLength = tempValueFormat.length(); - - // Check Transformation syntax - if (tempValueFormatLength > 0) - { - if (!isTransformString(tempValueFormat[0], logicVal, value)) { - switch (tempValueFormat[0]) - { - case 'V': // value = value without transformations - break; - case 'p': // Password hide using asterisks or custom character: pc - { - char maskChar = '*'; - - if (tempValueFormatLength > 1) - { - maskChar = tempValueFormat[1]; - } - - if (equals(value, '0')) { - value = String(); - } else { - const int valueLength = value.length(); - - for (int i = 0; i < valueLength; i++) { - value[i] = maskChar; - } - } - break; - } - case 'D': // Dx.y min 'x' digits zero filled & 'y' decimal fixed digits - case 'd': // like above but with spaces padding - { - int x = 0; - int y = 0; - - switch (tempValueFormatLength) - { - case 2: // Dx - - if (isDigit(tempValueFormat[1])) - { - x = static_cast(tempValueFormat[1]) - '0'; - } - break; - case 3: // D.y - - if ((tempValueFormat[1] == '.') && isDigit(tempValueFormat[2])) - { - y = static_cast(tempValueFormat[2]) - '0'; - } - break; - case 4: // Dx.y - - if (isDigit(tempValueFormat[1]) && (tempValueFormat[2] == '.') && isDigit(tempValueFormat[3])) - { - x = static_cast(tempValueFormat[1]) - '0'; - y = static_cast(tempValueFormat[3]) - '0'; - } - break; - case 1: // D - default: // any other combination x=0; y=0; - break; - } - bool trimTrailingZeros = false; -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = doubleToString(valFloat, y, trimTrailingZeros); -#else - value = floatToString(valFloat, y, trimTrailingZeros); -#endif - int indexDot = value.indexOf('.'); - - if (indexDot == -1) { - indexDot = value.length(); - } - - for (uint8_t f = 0; f < (x - indexDot); f++) { - value = (tempValueFormat[0] == 'd' ? ' ' : '0') + value; - } - break; - } - case 'F': // FLOOR (round down) - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = static_cast(floor(valFloat)); - #else - value = static_cast(floorf(valFloat)); - #endif - break; - case 'E': // CEILING (round up) - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - value = static_cast(ceil(valFloat)); - #else - value = static_cast(ceilf(valFloat)); - #endif - break; - default: - value = F("ERR"); - break; - } - } - - // Check Justification syntax - const int valueJustLength = valueJust.length(); - - if (valueJustLength > 0) // do the checks only if a Justification is defined to optimize loop - { - value.trim(); // remove right justification spaces for backward compatibility - - switch (valueJust[0]) - { - case 'P': // Prefix Fill with n spaces: Pn - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check Pn where n is between 0 and 9 - { - int filler = valueJust[1] - value.length() - '0'; // char '0' = 48; char '9' = 58 - - for (uint8_t f = 0; f < filler; f++) { - newString += ' '; - } - } - } - break; - case 'S': // Suffix Fill with n spaces: Sn - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check Sn where n is between 0 and 9 - { - int filler = valueJust[1] - value.length() - '0'; // 48 - - for (uint8_t f = 0; f < filler; f++) { - value += ' '; - } - } - } - break; - case 'L': // left part of the string - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 - { - value = value.substring(0, static_cast(valueJust[1]) - '0'); - } - } - break; - case 'R': // Right part of the string - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 - { - value = value.substring(std::max(0, static_cast(value.length()) - (static_cast(valueJust[1]) - '0'))); - } - } - break; - case 'U': // Substring Ux.y where x=firstChar and y=number of characters - - if (valueJustLength > 1) - { - if (isDigit(valueJust[1]) && (valueJust[2] == '.') && isDigit(valueJust[3]) && (valueJust[1] > '0') && (valueJust[3] > '0')) - { - value = value.substring(std::min(static_cast(value.length()), static_cast(valueJust[1]) - '0' - 1), - static_cast(valueJust[1]) - '0' - 1 + static_cast(valueJust[3]) - '0'); - } - else - { - newString += F("ERR"); - } - } - break; - case 'C': // Capitalize First Word-Character value (space/period are checked) - - if (value.length() > 0) { - value.toLowerCase(); - bool nextCapital = true; - - for (uint8_t i = 0; i < value.length(); i++) { - if (nextCapital) { - value[i] = toupper(value[i]); - } - nextCapital = (value[i] == ' ' || value[i] == '.'); // Very simple, capitalize-first-after-space/period - } - } - break; - case 'u': // Uppercase - value.toUpperCase(); - break; - case 'l': // Lowercase - value.toLowerCase(); - break; - default: - newString += F("ERR"); - break; - } - } - } - - if (rightJustify) - { - int filler = lineSize - newString.length() - value.length() - tmpString.length(); - - for (uint8_t f = 0; f < filler; f++) { - newString += ' '; - } - } - { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String logFormatted = F("DEBUG: Formatted String='"); - logFormatted += newString; - logFormatted += value; - logFormatted += '\''; - addLogMove(LOG_LEVEL_DEBUG, logFormatted); - } -#endif // ifndef BUILD_NO_DEBUG - } - } - } - - // end of changes by giig1967g - 2018-04-18 - - newString += value; - { -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - String logParsed = F("DEBUG DEV: Parsed String='"); - logParsed += newString; - logParsed += '\''; - addLogMove(LOG_LEVEL_DEBUG_DEV, logParsed); - } -#endif // ifndef BUILD_NO_DEBUG - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("transformValue2")); - #endif // ifndef BUILD_NO_RAM_TRACKER -} - -// Find the first (enabled) task with given name -// Return INVALID_TASK_INDEX when not found, else return taskIndex -taskIndex_t findTaskIndexByName(String deviceName, bool allowDisabled) -{ - deviceName.toLowerCase(); - // cache this, since LoadTaskSettings does take some time. - auto result = Cache.taskIndexName.find(deviceName); - - if (result != Cache.taskIndexName.end()) { - return result->second; - } - - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) - { - if (Settings.TaskDeviceEnabled[taskIndex] || allowDisabled) { - String taskDeviceName = getTaskDeviceName(taskIndex); - - if (!taskDeviceName.isEmpty()) - { - // Use entered taskDeviceName can have any case, so compare case insensitive. - if (deviceName.equalsIgnoreCase(taskDeviceName)) - { - Cache.taskIndexName[deviceName] = taskIndex; - return taskIndex; - } - } - } - } - return INVALID_TASK_INDEX; -} - -// Find the first device value index of a taskIndex. -// Return VARS_PER_TASK if none found. -uint8_t findDeviceValueIndexByName(const String& valueName, taskIndex_t taskIndex) -{ - const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - if (!validDeviceIndex(deviceIndex)) { return VARS_PER_TASK; } - - #ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - #endif - - - // cache this, since LoadTaskSettings does take some time. - // We need to use a cache search key including the taskIndex, - // to allow several tasks to have the same value names. - String cache_valueName = strformat( - F("%s#%d"), // The '#' cannot exist in a value name, use it in the cache key. - valueName.c_str(), - static_cast(taskIndex)); - cache_valueName.toLowerCase(); // No need to store multiple versions of the same entry with only different case. - - auto result = Cache.taskIndexValueName.find(cache_valueName); - - if (result != Cache.taskIndexValueName.end()) { - return result->second; - } - const uint8_t valCount = getValueCountForTask(taskIndex); - - for (uint8_t valueNr = 0; valueNr < valCount; valueNr++) - { - // Check case insensitive, since the user entered value name can have any case. - if (valueName.equalsIgnoreCase(getTaskValueName(taskIndex, valueNr))) - { - Cache.taskIndexValueName[cache_valueName] = valueNr; - return valueNr; - } - } - return VARS_PER_TASK; -} - -// Find positions of [...#...] in the given string. -// Only update pos values on success. -// Return true when found. -bool findNextValMarkInString(const String& input, int& startpos, int& hashpos, int& endpos) { - int tmpStartpos = input.indexOf('[', startpos); - - if (tmpStartpos == -1) { return false; } - const int tmpHashpos = input.indexOf('#', tmpStartpos); - - if (tmpHashpos == -1) { return false; } - - // We found a hash position, check if there is another '[' inbetween. - for (int i = tmpStartpos; i < tmpHashpos; ++i) { - if (input[i] == '[') { - tmpStartpos = i; - } - } - - const int tmpEndpos = input.indexOf(']', tmpStartpos); - - if (tmpEndpos == -1) { return false; } - - if (tmpHashpos >= tmpEndpos) { - return false; - } - - hashpos = tmpHashpos; - startpos = tmpStartpos; - endpos = tmpEndpos; - return true; -} - -// Find [deviceName#valueName] or [deviceName#valueName#format] -// DeviceName and valueName will be returned in lower case. -// Format may contain case sensitive formatting syntax. -bool findNextDevValNameInString(const String& input, int& startpos, int& endpos, String& deviceName, String& valueName, String& format) { - int hashpos; - - if (!findNextValMarkInString(input, startpos, hashpos, endpos)) { return false; } - - move_special(deviceName, input.substring(startpos + 1, hashpos)); - move_special(valueName , input.substring(hashpos + 1, endpos)); - hashpos = valueName.indexOf('#'); - - if (hashpos != -1) { - // Found an extra '#' in the valueName, will split valueName and format. - move_special(format, valueName.substring(hashpos + 1)); - move_special(valueName, valueName.substring(0, hashpos)); - } else { - format = String(); - } - deviceName.toLowerCase(); - valueName.toLowerCase(); - return true; -} - -/********************************************************************************************\ - Check to see if a given argument is a valid taskIndex (argc = 0 => command) - \*********************************************************************************************/ -taskIndex_t parseCommandArgumentTaskIndex(const String& string, unsigned int argc) -{ - taskIndex_t taskIndex = INVALID_TASK_INDEX; - const int ti = parseCommandArgumentInt(string, argc); - - if (ti > 0) { - // Task Index used as argument in commands start at 1. - taskIndex = static_cast(ti - 1); - } - return taskIndex; -} - -/********************************************************************************************\ - Get int from command argument (argc = 0 => command) - \*********************************************************************************************/ -int parseCommandArgumentInt(const String& string, unsigned int argc, - int errorValue) -{ - int value = 0; - - if (argc > 0) { - // No need to check for the command (argc == 0) - String TmpStr; - - if (GetArgv(string.c_str(), TmpStr, argc + 1)) { - value = CalculateParam(TmpStr, errorValue); - } - } - return value; -} - -/********************************************************************************************\ - Parse a command string to event struct - \*********************************************************************************************/ -void parseCommandString(struct EventStruct *event, const String& string) -{ - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("parseCommandString")); - #endif // ifndef BUILD_NO_RAM_TRACKER - event->Par1 = parseCommandArgumentInt(string, 1); - event->Par2 = parseCommandArgumentInt(string, 2); - event->Par3 = parseCommandArgumentInt(string, 3); - event->Par4 = parseCommandArgumentInt(string, 4); - event->Par5 = parseCommandArgumentInt(string, 5); -} +#include "../Helpers/StringParser.h" + +#include "../../_Plugin_Helper.h" + +#include "../Commands/GPIO.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasyRules.h" + +#include "../Globals/Cache.h" +#include "../Globals/Plugins_other.h" +#include "../Globals/RulesCalculate.h" +#include "../Globals/RuntimeData.h" + +#include "../Helpers/_CPlugin_init.h" +#include "../Helpers/ESPEasy_math.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_GPIO.h" + + + +/********************************************************************************************\ + Parse string template + \*********************************************************************************************/ +String parseTemplate(String& tmpString) +{ + return parseTemplate(tmpString, false); +} + +String parseTemplate(String& tmpString, bool useURLencode) +{ + return parseTemplate_padded(tmpString, 0, useURLencode); +} + +String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize) +{ + return parseTemplate_padded(tmpString, minimal_lineSize, false); +} + +String parseTemplate_padded(String& tmpString, uint8_t minimal_lineSize, bool useURLencode) +{ + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate_padded")); + #endif // ifndef BUILD_NO_RAM_TRACKER + START_TIMER; + + // Keep current loaded taskSettings to restore at the end. + const taskIndex_t currentTaskIndex = ExtraTaskSettings.TaskIndex; + String newString; + newString.reserve(minimal_lineSize); // Our best guess of the new size. + + if (parseTemplate_CallBack_ptr != nullptr) { + parseTemplate_CallBack_ptr(tmpString, useURLencode); + } + parseSystemVariables(tmpString, useURLencode); + + + int startpos = 0; + int lastStartpos = 0; + int endpos = 0; + { + String deviceName, valueName, format; + + while (findNextDevValNameInString(tmpString, startpos, endpos, deviceName, valueName, format)) { + // First copy all upto the start of the [...#...] part to be replaced. + newString += tmpString.substring(lastStartpos, startpos); + + // deviceName is lower case, so we can compare literal string (no need for equalsIgnoreCase) + const bool devNameEqInt = equals(deviceName, F("int")); + if (devNameEqInt || equals(deviceName, F("var"))) + { + // Address an internal variable either as float or as int + // For example: Let,10,[VAR#9] + uint32_t varNum; + + if (validUIntFromString(valueName, varNum)) { + unsigned char nr_decimals = maxNrDecimals_fpType(getCustomFloatVar(varNum)); + bool trimTrailingZeros = true; + + if (devNameEqInt) { + nr_decimals = 0; + } else if (!format.isEmpty()) + { + // There is some formatting here, so do not throw away decimals + trimTrailingZeros = false; + } + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + String value = doubleToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); + #else + String value = floatToString(getCustomFloatVar(varNum), nr_decimals, trimTrailingZeros); + #endif + transformValue( + newString, + minimal_lineSize, + std::move(value), + format, + tmpString); + } + } + else if (equals(deviceName, F("plugin"))) + { + // Handle a plugin request. + // For example: "[Plugin#GPIO#Pinstate#N]" + // The command is stored in valueName & format + String command = strformat(F("%s#%s"), valueName.c_str(), format.c_str()); + command.replace('#', ','); + + if (getGPIOPinStateValues(command)) { + newString += command; + } + /* @giig1967g + if (PluginCall(PLUGIN_REQUEST, 0, command)) + { + // Do not call transformValue here. + // The "format" is not empty so must not call the formatter function. + newString += command; + } + */ + } + else + { + // Address a value from a plugin. + // For example: "[bme#temp]" + // If value name is unknown, run a PLUGIN_GET_CONFIG_VALUE command. + // For example: "[#getLevel]" + taskIndex_t taskIndex = findTaskIndexByName(deviceName, true); // Check for enabled/disabled is done separately + + if (validTaskIndex(taskIndex)) { + bool isHandled = false; + if (Settings.TaskDeviceEnabled[taskIndex]) { + uint8_t valueNr = findDeviceValueIndexByName(valueName, taskIndex); + + if (valueNr != VARS_PER_TASK) { + // here we know the task and value, so find the uservar + // Try to format and transform the values + bool isvalid; + String value = formatUserVar(taskIndex, valueNr, isvalid); + + if (isvalid) { + transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); + isHandled = true; + } + } else { + // try if this is a get config request + struct EventStruct TempEvent(taskIndex); + String tmpName = valueName; + + if (PluginCall(PLUGIN_GET_CONFIG_VALUE, &TempEvent, tmpName)) + { + transformValue(newString, minimal_lineSize, std::move(tmpName), format, tmpString); + isHandled = true; + } + } + } + if (!isHandled && valueName.startsWith(F("settings."))) { // Task settings values + String value; + if (valueName.endsWith(F(".enabled"))) { // Task state + value = Settings.TaskDeviceEnabled[taskIndex] ? '1' : '0'; + } else if (valueName.endsWith(F(".interval"))) { // Task interval + value = Settings.TaskDeviceTimer[taskIndex]; + } else if (valueName.endsWith(F(".valuecount"))) { // Task value count + value = getValueCountForTask(taskIndex); + } else if ((valueName.indexOf(F(".controller")) == 8) && valueName.length() >= 20) { // Task controller values + String ctrl = valueName.substring(19, 20); + int32_t ctrlNr = 0; + if (validIntFromString(ctrl, ctrlNr) && (ctrlNr >= 1) && (ctrlNr <= CONTROLLER_MAX) && + Settings.ControllerEnabled[ctrlNr - 1]) { // Controller nr. valid and enabled + if (valueName.endsWith(F(".enabled"))) { // Task-controller enabled + value = Settings.TaskDeviceSendData[ctrlNr - 1][taskIndex]; + } else if (valueName.endsWith(F(".idx"))) { // Task-controller idx value + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(ctrlNr - 1); + + if (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[ctrlNr - 1] != 0)) { + value = Settings.TaskDeviceID[ctrlNr - 1][taskIndex]; + } + } + } + } + if (!value.isEmpty()) { + transformValue(newString, minimal_lineSize, std::move(value), format, tmpString); + // isHandled = true; + } + } + } + } + + + // Conversion is done (or impossible) for the found "[...#...]" + // Continue with the next one. + lastStartpos = endpos + 1; + startpos = endpos + 1; + + // This may have taken some time, so call delay() + delay(0); + } + } + + // Copy the rest of the string (or all if no replacements were done) + newString += tmpString.substring(lastStartpos); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate2")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // Restore previous loaded taskSettings + if (validTaskIndex(currentTaskIndex)) + { + LoadTaskSettings(currentTaskIndex); + } + + parseStandardConversions(newString, useURLencode); + + // process other markups as well + parse_string_commands(newString); + + // padding spaces + while (newString.length() < minimal_lineSize) { + newString += ' '; + } + + STOP_TIMER(PARSE_TEMPLATE_PADDED); + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseTemplate3")); + #endif // ifndef BUILD_NO_RAM_TRACKER + return newString; +} + +/********************************************************************************************\ + Transform values + \*********************************************************************************************/ + +bool isTransformString(char c, bool logicVal, String& strValue) +{ + const __FlashStringHelper * value = F(""); + char value_ch = '\0'; + switch (c) { + case 'O': + value = logicVal == 0 ? F("OFF") : F(" ON"); // (equivalent to XOR operator) + break; + case 'C': + value = logicVal == 0 ? F("CLOSE") : F(" OPEN"); + break; + case 'c': + value = logicVal == 0 ? F("CLOSED") : F(" OPEN"); + break; + case 'M': + value = logicVal == 0 ? F("AUTO") : F(" MAN"); + break; + case 'm': + value_ch = logicVal == 0 ? 'A' : 'M'; + break; + case 'H': + value = logicVal == 0 ? F("COLD") : F(" HOT"); + break; + case 'U': + value = logicVal == 0 ? F("DOWN") : F(" UP"); + break; + case 'u': + value_ch = logicVal == 0 ? 'D' : 'U'; + break; + case 'Y': + value = logicVal == 0 ? F(" NO") : F("YES"); + break; + case 'y': + value_ch = logicVal == 0 ? 'N' : 'Y'; + break; + case 'X': + value_ch = logicVal == 0 ? 'O' : 'X'; + break; + case 'I': + value = logicVal == 0 ? F("OUT") : F(" IN"); + break; + case 'L': + value = logicVal == 0 ? F(" LEFT") : F("RIGHT"); + break; + case 'l': + value_ch = logicVal == 0 ? 'L' : 'R'; + break; + case 'Z': // return "0" or "1" + value_ch = logicVal == 0 ? '0' : '1'; + break; + default: + return false; + } + if (value_ch != '\0') { + strValue = value_ch; + } else { + strValue = value; + } + return true; +} + + +// Syntax: [task#value#transformation#justification] +// valueFormat="transformation#justification" +void transformValue( + String & newString, + uint8_t lineSize, + String value, + String & valueFormat, + const String& tmpString) +{ + // FIXME TD-er: This function does append to newString and uses its length to perform right aling. + // Is this the way it is intended to use? + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("transformValue")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // start changes by giig1967g - 2018-04-20 + // Syntax: [task#value#transformation#justification] + // valueFormat="transformation#justification" + if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop + { + String valueJust; + + int hashtagIndex = valueFormat.indexOf('#'); + + if (hashtagIndex >= 0) + { + valueJust = valueFormat.substring(hashtagIndex + 1); // Justification part + valueFormat = valueFormat.substring(0, hashtagIndex); // Transformation part + } + + // valueFormat="transformation" + // valueJust="justification" + if (valueFormat.length() > 0) // do the checks only if a Format is defined to optimize loop + { + int logicVal = 0; + ESPEASY_RULES_FLOAT_TYPE valFloat{}; + + if (validDoubleFromString(value, valFloat)) + { + // to be used for binary values (0 or 1) + logicVal = lround(static_cast(valFloat)) == 0 ? 0 : 1; + } else { + if (value.length() > 0) { + logicVal = 1; + } + } + String tempValueFormat = valueFormat; + { + const int invertedIndex = tempValueFormat.indexOf('!'); + + if (invertedIndex != -1) { + // We must invert the value. + logicVal = (logicVal == 0) ? 1 : 0; + + // Remove the '!' from the string. + tempValueFormat.remove(invertedIndex, 1); + } + } + + const int rightJustifyIndex = tempValueFormat.indexOf('R'); + const bool rightJustify = rightJustifyIndex >= 0 ? 1 : 0; + + if (rightJustify) { + tempValueFormat.remove(rightJustifyIndex, 1); + } + + const int tempValueFormatLength = tempValueFormat.length(); + + // Check Transformation syntax + if (tempValueFormatLength > 0) + { + if (!isTransformString(tempValueFormat[0], logicVal, value)) { + switch (tempValueFormat[0]) + { + case 'V': // value = value without transformations + break; + case 'p': // Password hide using asterisks or custom character: pc + { + char maskChar = '*'; + + if (tempValueFormatLength > 1) + { + maskChar = tempValueFormat[1]; + } + + if (equals(value, '0')) { + value = String(); + } else { + const int valueLength = value.length(); + + for (int i = 0; i < valueLength; i++) { + value[i] = maskChar; + } + } + break; + } + case 'D': // Dx.y min 'x' digits zero filled & 'y' decimal fixed digits + case 'd': // like above but with spaces padding + { + int x = 0; + int y = 0; + + switch (tempValueFormatLength) + { + case 2: // Dx + + if (isDigit(tempValueFormat[1])) + { + x = static_cast(tempValueFormat[1]) - '0'; + } + break; + case 3: // D.y + + if ((tempValueFormat[1] == '.') && isDigit(tempValueFormat[2])) + { + y = static_cast(tempValueFormat[2]) - '0'; + } + break; + case 4: // Dx.y + + if (isDigit(tempValueFormat[1]) && (tempValueFormat[2] == '.') && isDigit(tempValueFormat[3])) + { + x = static_cast(tempValueFormat[1]) - '0'; + y = static_cast(tempValueFormat[3]) - '0'; + } + break; + case 1: // D + default: // any other combination x=0; y=0; + break; + } + bool trimTrailingZeros = false; +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = doubleToString(valFloat, y, trimTrailingZeros); +#else + value = floatToString(valFloat, y, trimTrailingZeros); +#endif + int indexDot = value.indexOf('.'); + + if (indexDot == -1) { + indexDot = value.length(); + } + + for (uint8_t f = 0; f < (x - indexDot); f++) { + value = (tempValueFormat[0] == 'd' ? ' ' : '0') + value; + } + break; + } + case 'F': // FLOOR (round down) + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = static_cast(floor(valFloat)); + #else + value = static_cast(floorf(valFloat)); + #endif + break; + case 'E': // CEILING (round up) + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + value = static_cast(ceil(valFloat)); + #else + value = static_cast(ceilf(valFloat)); + #endif + break; + default: + value = F("ERR"); + break; + } + } + + // Check Justification syntax + const int valueJustLength = valueJust.length(); + + if (valueJustLength > 0) // do the checks only if a Justification is defined to optimize loop + { + value.trim(); // remove right justification spaces for backward compatibility + + switch (valueJust[0]) + { + case 'P': // Prefix Fill with n spaces: Pn + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check Pn where n is between 0 and 9 + { + int filler = valueJust[1] - value.length() - '0'; // char '0' = 48; char '9' = 58 + + for (uint8_t f = 0; f < filler; f++) { + newString += ' '; + } + } + } + break; + case 'S': // Suffix Fill with n spaces: Sn + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check Sn where n is between 0 and 9 + { + int filler = valueJust[1] - value.length() - '0'; // 48 + + for (uint8_t f = 0; f < filler; f++) { + value += ' '; + } + } + } + break; + case 'L': // left part of the string + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 + { + value = value.substring(0, static_cast(valueJust[1]) - '0'); + } + } + break; + case 'R': // Right part of the string + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1])) // Check n where n is between 0 and 9 + { + value = value.substring(std::max(0, static_cast(value.length()) - (static_cast(valueJust[1]) - '0'))); + } + } + break; + case 'U': // Substring Ux.y where x=firstChar and y=number of characters + + if (valueJustLength > 1) + { + if (isDigit(valueJust[1]) && (valueJust[2] == '.') && isDigit(valueJust[3]) && (valueJust[1] > '0') && (valueJust[3] > '0')) + { + value = value.substring(std::min(static_cast(value.length()), static_cast(valueJust[1]) - '0' - 1), + static_cast(valueJust[1]) - '0' - 1 + static_cast(valueJust[3]) - '0'); + } + else + { + newString += F("ERR"); + } + } + break; + case 'C': // Capitalize First Word-Character value (space/period are checked) + + if (value.length() > 0) { + value.toLowerCase(); + bool nextCapital = true; + + for (uint8_t i = 0; i < value.length(); i++) { + if (nextCapital) { + value[i] = toupper(value[i]); + } + nextCapital = (value[i] == ' ' || value[i] == '.'); // Very simple, capitalize-first-after-space/period + } + } + break; + case 'u': // Uppercase + value.toUpperCase(); + break; + case 'l': // Lowercase + value.toLowerCase(); + break; + default: + newString += F("ERR"); + break; + } + } + } + + if (rightJustify) + { + int filler = lineSize - newString.length() - value.length() - tmpString.length(); + + for (uint8_t f = 0; f < filler; f++) { + newString += ' '; + } + } + { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String logFormatted = F("DEBUG: Formatted String='"); + logFormatted += newString; + logFormatted += value; + logFormatted += '\''; + addLogMove(LOG_LEVEL_DEBUG, logFormatted); + } +#endif // ifndef BUILD_NO_DEBUG + } + } + } + + // end of changes by giig1967g - 2018-04-18 + + newString += value; + { +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + String logParsed = F("DEBUG DEV: Parsed String='"); + logParsed += newString; + logParsed += '\''; + addLogMove(LOG_LEVEL_DEBUG_DEV, logParsed); + } +#endif // ifndef BUILD_NO_DEBUG + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("transformValue2")); + #endif // ifndef BUILD_NO_RAM_TRACKER +} + +// Find the first (enabled) task with given name +// Return INVALID_TASK_INDEX when not found, else return taskIndex +taskIndex_t findTaskIndexByName(String deviceName, bool allowDisabled) +{ + deviceName.toLowerCase(); + // cache this, since LoadTaskSettings does take some time. + auto result = Cache.taskIndexName.find(deviceName); + + if (result != Cache.taskIndexName.end()) { + return result->second; + } + + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; taskIndex++) + { + if (Settings.TaskDeviceEnabled[taskIndex] || allowDisabled) { + String taskDeviceName = getTaskDeviceName(taskIndex); + + if (!taskDeviceName.isEmpty()) + { + // Use entered taskDeviceName can have any case, so compare case insensitive. + if (deviceName.equalsIgnoreCase(taskDeviceName)) + { + Cache.taskIndexName.emplace( + std::make_pair( + std::move(deviceName), + taskIndex)); + return taskIndex; + } + } + } + } + return INVALID_TASK_INDEX; +} + +// Find the first device value index of a taskIndex. +// Return VARS_PER_TASK if none found. +uint8_t findDeviceValueIndexByName(const String& valueName, taskIndex_t taskIndex) +{ + const deviceIndex_t deviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + if (!validDeviceIndex(deviceIndex)) { return VARS_PER_TASK; } + + #ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + #endif + + + // cache this, since LoadTaskSettings does take some time. + // We need to use a cache search key including the taskIndex, + // to allow several tasks to have the same value names. + String cache_valueName = strformat( + F("%s#%d"), // The '#' cannot exist in a value name, use it in the cache key. + valueName.c_str(), + static_cast(taskIndex)); + cache_valueName.toLowerCase(); // No need to store multiple versions of the same entry with only different case. + + auto result = Cache.taskIndexValueName.find(cache_valueName); + + if (result != Cache.taskIndexValueName.end()) { + return result->second; + } + const uint8_t valCount = getValueCountForTask(taskIndex); + + for (uint8_t valueNr = 0; valueNr < valCount; valueNr++) + { + // Check case insensitive, since the user entered value name can have any case. + if (valueName.equalsIgnoreCase(Cache.getTaskDeviceValueName(taskIndex, valueNr))) + { + Cache.taskIndexValueName.emplace( + std::make_pair( + std::move(cache_valueName), + valueNr)); + return valueNr; + } + } + return VARS_PER_TASK; +} + +// Find positions of [...#...] in the given string. +// Only update pos values on success. +// Return true when found. +bool findNextValMarkInString(const String& input, int& startpos, int& hashpos, int& endpos) { + int tmpStartpos = input.indexOf('[', startpos); + + if (tmpStartpos == -1) { return false; } + const int tmpHashpos = input.indexOf('#', tmpStartpos); + + if (tmpHashpos == -1) { return false; } + + // We found a hash position, check if there is another '[' inbetween. + for (int i = tmpStartpos; i < tmpHashpos; ++i) { + if (input[i] == '[') { + tmpStartpos = i; + } + } + + const int tmpEndpos = input.indexOf(']', tmpStartpos); + + if (tmpEndpos == -1) { return false; } + + if (tmpHashpos >= tmpEndpos) { + return false; + } + + hashpos = tmpHashpos; + startpos = tmpStartpos; + endpos = tmpEndpos; + return true; +} + +// Find [deviceName#valueName] or [deviceName#valueName#format] +// DeviceName and valueName will be returned in lower case. +// Format may contain case sensitive formatting syntax. +bool findNextDevValNameInString(const String& input, int& startpos, int& endpos, String& deviceName, String& valueName, String& format) { + int hashpos; + + if (!findNextValMarkInString(input, startpos, hashpos, endpos)) { return false; } + + move_special(deviceName, input.substring(startpos + 1, hashpos)); + move_special(valueName , input.substring(hashpos + 1, endpos)); + hashpos = valueName.indexOf('#'); + + if (hashpos != -1) { + // Found an extra '#' in the valueName, will split valueName and format. + move_special(format, valueName.substring(hashpos + 1)); + move_special(valueName, valueName.substring(0, hashpos)); + } else { + format = String(); + } + deviceName.toLowerCase(); + valueName.toLowerCase(); + return true; +} + +/********************************************************************************************\ + Check to see if a given argument is a valid taskIndex (argc = 0 => command) + \*********************************************************************************************/ +taskIndex_t parseCommandArgumentTaskIndex(const String& string, unsigned int argc) +{ + taskIndex_t taskIndex = INVALID_TASK_INDEX; + const int ti = parseCommandArgumentInt(string, argc); + + if (ti > 0) { + // Task Index used as argument in commands start at 1. + taskIndex = static_cast(ti - 1); + } + return taskIndex; +} + +/********************************************************************************************\ + Get int from command argument (argc = 0 => command) + \*********************************************************************************************/ +int parseCommandArgumentInt(const String& string, unsigned int argc, + int errorValue) +{ + int value = 0; + + if (argc > 0) { + // No need to check for the command (argc == 0) + String TmpStr; + + if (GetArgv(string.c_str(), TmpStr, argc + 1)) { + value = CalculateParam(TmpStr, errorValue); + } + } + return value; +} + +/********************************************************************************************\ + Parse a command string to event struct + \*********************************************************************************************/ +void parseCommandString(struct EventStruct *event, const String& string) +{ + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("parseCommandString")); + #endif // ifndef BUILD_NO_RAM_TRACKER + event->Par1 = parseCommandArgumentInt(string, 1); + event->Par2 = parseCommandArgumentInt(string, 2); + event->Par3 = parseCommandArgumentInt(string, 3); + event->Par4 = parseCommandArgumentInt(string, 4); + event->Par5 = parseCommandArgumentInt(string, 5); +} diff --git a/src/src/Helpers/StringProvider.cpp b/src/src/Helpers/StringProvider.cpp index 08e910d62..163ddb438 100644 --- a/src/src/Helpers/StringProvider.cpp +++ b/src/src/Helpers/StringProvider.cpp @@ -1,630 +1,826 @@ -#include "../Helpers/StringProvider.h" - -#if FEATURE_ETHERNET -# include -#endif // if FEATURE_ETHERNET - -#include "../../ESPEasy-Globals.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#if FEATURE_ETHERNET -#include "../ESPEasyCore/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" - -#if FEATURE_ETHERNET -#include "../Globals/ESPEasyEthEvent.h" -#endif - -#include "../Globals/NetworkState.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" -#include "../Globals/WiFi_AP_Candidates.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/ESPEasy_Storage.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/Scheduler.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_System.h" -#include "../Helpers/StringGenerator_WiFi.h" - -#include "../WebServer/JSON.h" -#include "../WebServer/AccessControl.h" - -#ifdef ESP32 -#include -#endif - -String getInternalLabel(LabelType::Enum label, char replaceSpace) { - return to_internal_string(getLabel(label), replaceSpace); -} - -const __FlashStringHelper * getLabel(LabelType::Enum label) { - switch (label) - { - case LabelType::UNIT_NR: return F("Unit Number"); - #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 - - 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"); - case LabelType::WAIT_WIFI_CONNECT: return F("Extra Wait WiFi Connect"); - case LabelType::CONNECT_HIDDEN_SSID: return F("Include Hidden SSID"); - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return F("Hidden SSID Slow Connect"); - case LabelType::SDK_WIFI_AUTORECONNECT: return F("Enable SDK WiFi Auto Reconnect"); - - case LabelType::BUILD_DESC: return F("Build"); - case LabelType::GIT_BUILD: return F("Git Build"); - case LabelType::SYSTEM_LIBRARIES: return F("System Libraries"); - 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 (ESP32)"); - #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"); - 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"); -#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: - { - // Fixed 3-digit unitnumber - return 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: - { - String timeSource_str = toString(node_time.timeSource); - if (((node_time.timeSource == timeSource_t::ESPEASY_p2p_UDP) || - (node_time.timeSource == 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; - } - case LabelType::TIME_WANDER: return String(node_time.timeWander, 1); - #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"); - } - } - return String('-'); - } - #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) -#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 -#endif // ifdef ESP32 - - - case LabelType::JSON_BOOL_QUOTES: return jsonBool(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(); - -#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::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()); -#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 -#ifndef BUILD_NO_RAM_TRACKER - case LabelType::ENABLE_RAM_TRACKING: return jsonBool(Settings.EnableRAMTracking()); -#endif -#if FEATURE_AUTO_DARK_MODE - case LabelType::ENABLE_AUTO_DARK_MODE: return toString(Settings.getCssMode()); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - case LabelType::DISABLE_RULES_AUTOCOMPLETE: return jsonBool(Settings.DisableRulesCodeCompletion()); -#endif // if FEATURE_RULES_EASY_COLOR_CODE - - 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()); - case LabelType::IP6_GLOBAL: return formatIP(NetworkGlobalIP6()); -// case LabelType::IP6_ALL_ADDRESSES: - { - 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; - } -#endif - case LabelType::CLIENT_IP: return formatIP(web_server.client().remoteIP()); - #if FEATURE_INTERNAL_TEMPERATURE - case LabelType::INTERNAL_TEMPERATURE: return toString(getInternalTemperature()); - #endif // if FEATURE_INTERNAL_TEMPERATURE - - #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()); - } - #endif // if FEATURE_ETHERNET - return format_msec_duration(WiFiEventData.lastConnectMoment.millisPassedSince()); - - // 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"); - } - #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; - case LabelType::WAIT_WIFI_CONNECT: return jsonBool(Settings.WaitWiFiConnect()); - case LabelType::CONNECT_HIDDEN_SSID: return jsonBool(Settings.IncludeHiddenSSID()); - case LabelType::HIDDEN_SSID_SLOW_CONNECT: return jsonBool(Settings.HiddenSSID_SlowConnectPerBSSID()); - case LabelType::SDK_WIFI_AUTORECONNECT: return jsonBool(Settings.WifiNoneSleep()); - - case LabelType::BUILD_DESC: return getSystemBuildString(); - case LabelType::GIT_BUILD: - { - const String res(F(BUILD_GIT)); - - if (!res.isEmpty()) { return res; } - return get_git_head(); - } - case LabelType::SYSTEM_LIBRARIES: return getSystemLibraryString(); - 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(); - #ifdef CONFIGURATION_CODE - case LabelType::CONFIGURATION_CODE_LBL: return 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_SD - case LabelType::SD_LOG_LEVEL: return 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; -#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: - { - const uint32_t flashChipId = getFlashChipId(); - const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); - return formatToHex(flashDevice, 4); - } - 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 -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - case LabelType::ETH_WIFI_MODE: return toString(active_network_medium); -#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); - - case LabelType::MAX_LABEL: - break; - } - if (retval != INT_MAX) return String(retval); - return F("MissingString"); -} - -#if FEATURE_ETHERNET -String getEthSpeed() { - return strformat(F("%dMbps"), EthLinkSpeed()); -} - -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()); - } - return getValue(LabelType::ETH_STATE); -} - -#endif // if FEATURE_ETHERNET - -String getExtendedValue(LabelType::Enum label) { - switch (label) - { - case LabelType::UPTIME: - { - return minutesToDayHourMinute(getUptimeMinutes()); - } - - default: - break; - } - return EMPTY_STRING; -} +#include "../Helpers/StringProvider.h" + +#if FEATURE_ETHERNET +# include +#endif // if FEATURE_ETHERNET + +#include "../../ESPEasy-Globals.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#if FEATURE_ETHERNET +#include "../ESPEasyCore/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" + +#if FEATURE_ETHERNET +#include "../Globals/ESPEasyEthEvent.h" +#endif + +#include "../Globals/NetworkState.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" +#include "../Globals/WiFi_AP_Candidates.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/ESPEasy_Storage.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/Scheduler.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_System.h" +#include "../Helpers/StringGenerator_WiFi.h" + +#include "../WebServer/JSON.h" +#include "../WebServer/AccessControl.h" + +#ifdef ESP32 +#include +#endif + +String getInternalLabel(LabelType::Enum label, char replaceSpace) { + return to_internal_string(getLabel(label), replaceSpace); +} + +const __FlashStringHelper * getLabel(LabelType::Enum label) { + switch (label) + { + case LabelType::UNIT_NR: return F("Unit Number"); + #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 + + 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"); + 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: + { + // Fixed 3-digit unitnumber + return 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: + { + 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; + } + case LabelType::TIME_WANDER: return String(node_time.timeWander, 3); + #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"); + } + } + return String('-'); + } + #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) +#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 +#endif // ifdef ESP32 + + + case LabelType::JSON_BOOL_QUOTES: return jsonBool(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(); + +#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::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()); +#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 +#ifndef BUILD_NO_RAM_TRACKER + case LabelType::ENABLE_RAM_TRACKING: return jsonBool(Settings.EnableRAMTracking()); +#endif +#if FEATURE_AUTO_DARK_MODE + case LabelType::ENABLE_AUTO_DARK_MODE: return toString(Settings.getCssMode()); +#endif // FEATURE_AUTO_DARK_MODE +#if FEATURE_RULES_EASY_COLOR_CODE + case LabelType::DISABLE_RULES_AUTOCOMPLETE: return jsonBool(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 + + 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: + { + 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; + } +*/ +#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 + + #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()); + } + #endif // if FEATURE_ETHERNET + return format_msec_duration(WiFiEventData.lastConnectMoment.millisPassedSince()); + + // 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"); + } + #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()); +#if FEATURE_USE_IPV6 + case LabelType::ENABLE_IPV6: return jsonBool(Settings.EnableIPv6()); +#endif + + + case LabelType::BUILD_DESC: return getSystemBuildString(); + case LabelType::GIT_BUILD: + { + const String res(F(BUILD_GIT)); + + if (!res.isEmpty()) { return res; } + return get_git_head(); + } + case LabelType::SYSTEM_LIBRARIES: return getSystemLibraryString(); + 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(); + #ifdef CONFIGURATION_CODE + case LabelType::CONFIGURATION_CODE_LBL: return 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_SD + case LabelType::SD_LOG_LEVEL: return 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; +#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: + { + const uint32_t flashChipId = getFlashChipId(); + const uint32_t flashDevice = (flashChipId & 0xFF00) | ((flashChipId >> 16) & 0xFF); + return formatToHex(flashDevice, 4); + } + 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); +#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); + + case LabelType::MAX_LABEL: + break; + } + if (retval != INT_MAX) return String(retval); + return F("MissingString"); +} + +#if FEATURE_ETHERNET +String getEthSpeed() { + return strformat(F("%d [Mbps]"), EthLinkSpeed()); +} + +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()); + } + return getValue(LabelType::ETH_STATE); +} + +#endif // if FEATURE_ETHERNET + +String getExtendedValue(LabelType::Enum label) { + switch (label) + { + case LabelType::UPTIME: + { + return minutesToDayHourMinute(getUptimeMinutes()); + } + + default: + break; + } + return EMPTY_STRING; +} + +String getFormNote(LabelType::Enum label) +{ + // Keep flash string till the end of the function, to reduce build size + // Otherwise lots of calls to String() constructor are included. + const __FlashStringHelper *flash_str = F(""); + + 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 + 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 + 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 + case LabelType::ENABLE_IPV6: + flash_str = F("Toggling IPv6 requires reboot"); + break; +#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 + case LabelType::DISABLE_RULES_AUTOCOMPLETE: + flash_str = F("Also disables Rules syntax highlighting!"); + break; +#endif // if FEATURE_RULES_EASY_COLOR_CODE + + case LabelType::FORCE_WIFI_NOSLEEP: + flash_str = F("Change WiFi sleep settings requires reboot to activate"); + break; + + case LabelType::CPU_ECO_MODE: + 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"); + 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 + +#if FEATURE_SET_WIFI_TX_PWR + case LabelType::WIFI_TX_MAX_PWR: + case LabelType::WIFI_SENS_MARGIN: + { + float maxTXpwr; + float sensitivity = GetRSSIthreshold(maxTXpwr); + if (LabelType::WIFI_TX_MAX_PWR == label) { + return strformat( + F("Current max: %.2f dBm"), maxTXpwr); + } + return strformat( + F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), + sensitivity); + } +#endif // if FEATURE_SET_WIFI_TX_PWR + + default: + return EMPTY_STRING; + } + + return flash_str; +} + + +String getFormUnit(LabelType::Enum label) +{ + const __FlashStringHelper *flash_str = F(""); + + 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) + + 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 diff --git a/src/src/Helpers/StringProvider.h b/src/src/Helpers/StringProvider.h index 9571edd8f..0b8e6b4b6 100644 --- a/src/src/Helpers/StringProvider.h +++ b/src/src/Helpers/StringProvider.h @@ -1,247 +1,265 @@ -#ifndef STRING_PROVIDER_TYPES_H -#define STRING_PROVIDER_TYPES_H - -#include "../../ESPEasy_common.h" - -struct LabelType { - enum Enum : uint8_t { - UNIT_NR, - #if FEATURE_ZEROFILLED_UNITNUMBER - UNIT_NR_0, - #endif // FEATURE_ZEROFILLED_UNITNUMBER - UNIT_NAME, - HOST_NAME, - - LOCAL_TIME, - TIME_SOURCE, - TIME_WANDER, - #if FEATURE_EXT_RTC - EXT_RTC_UTC_TIME, - #endif - UPTIME, - LOAD_PCT, // 15.10 - LOOP_COUNT, // 400 - CPU_ECO_MODE, // true -#if FEATURE_SET_WIFI_TX_PWR - WIFI_TX_MAX_PWR, // Unit: 0.25 dBm, 0 = use default (do not set) - WIFI_CUR_TX_PWR, // Unit dBm of current WiFi TX power. - WIFI_SENS_MARGIN, // Margin in dB on top of sensitivity - WIFI_SEND_AT_MAX_TX_PWR, -#endif - WIFI_NR_EXTRA_SCANS, - WIFI_USE_LAST_CONN_FROM_RTC, - - FREE_MEM, // 9876 - FREE_STACK, // 3456 -#ifdef USE_SECOND_HEAP - FREE_HEAP_IRAM, -#endif -#if defined(CORE_POST_2_5_0) || defined(ESP32) - #ifndef LIMIT_BUILD_SIZE - 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 - HEAP_FRAGMENTATION, // 12 - #endif -#endif // if defined(CORE_POST_2_5_0) - -#ifdef ESP32 - HEAP_SIZE, - HEAP_MIN_FREE, - #ifdef BOARD_HAS_PSRAM - PSRAM_SIZE, - PSRAM_FREE, - PSRAM_MIN_FREE, - PSRAM_MAX_FREE_BLOCK, - #endif // BOARD_HAS_PSRAM -#endif // ifdef ESP32 - - JSON_BOOL_QUOTES, -#if FEATURE_TIMING_STATS - ENABLE_TIMING_STATISTICS, -#endif - ENABLE_RULES_CACHING, - ENABLE_SERIAL_PORT_CONSOLE, - CONSOLE_SERIAL_PORT, -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - CONSOLE_FALLBACK_TO_SERIAL0, - CONSOLE_FALLBACK_PORT, -#endif -// ENABLE_RULES_EVENT_REORDER, // TD-er: Disabled for now - TASKVALUESET_ALL_PLUGINS, - ALLOW_OTA_UNLIMITED, -#if FEATURE_CLEAR_I2C_STUCK - ENABLE_CLEAR_HUNG_I2C_BUS, -#endif - #if FEATURE_I2C_DEVICE_CHECK - ENABLE_I2C_DEVICE_CHECK, - #endif // if FEATURE_I2C_DEVICE_CHECK -#ifndef BUILD_NO_RAM_TRACKER - ENABLE_RAM_TRACKING, -#endif -#if FEATURE_AUTO_DARK_MODE - ENABLE_AUTO_DARK_MODE, -#endif -#if FEATURE_RULES_EASY_COLOR_CODE - DISABLE_RULES_AUTOCOMPLETE, -#endif // if FEATURE_RULES_EASY_COLOR_CODE - - BOOT_TYPE, // Cold boot - BOOT_COUNT, // 0 - RESET_REASON, // Software/System restart - DEEP_SLEEP_ALTERNATIVE_CALL, - LAST_TASK_BEFORE_REBOOT, // Last scheduled task. - SW_WD_COUNT, - - 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 - GATEWAY, // 192.168.1.1 -#if FEATURE_USE_IPV6 - IP6_LOCAL, - IP6_GLOBAL, -// IP6_ALL_ADDRESSES, -// IP6_ADDRESS_CDIR, -// IP6_GATEWAY, -#endif - CLIENT_IP, // 192.168.1.67 - #if FEATURE_MDNS - M_DNS, // breadboard.local - #endif // if FEATURE_MDNS - DNS, // 192.168.1.1 / (IP unset) - DNS_1, - DNS_2, - ALLOWED_IP_RANGE, // 192.168.1.0 - 192.168.1.255 - STA_MAC, // EC:FA:BC:0E:AE:5B - AP_MAC, // EE:FA:BC:0E:AE:5B - SSID, // mynetwork - BSSID, - CHANNEL, // 1 - ENCRYPTION_TYPE_STA, // WPA2 - CONNECTED, // 1h16m - CONNECTED_MSEC, // 1h16m - LAST_DISCONNECT_REASON, // 200 - LAST_DISC_REASON_STR, // Beacon timeout - NUMBER_RECONNECTS, // 5 - WIFI_STORED_SSID1, - WIFI_STORED_SSID2, - - FORCE_WIFI_BG, - RESTART_WIFI_LOST_CONN, - FORCE_WIFI_NOSLEEP, - PERIODICAL_GRAT_ARP, - CONNECTION_FAIL_THRESH, - WAIT_WIFI_CONNECT, - HIDDEN_SSID_SLOW_CONNECT, - CONNECT_HIDDEN_SSID, - SDK_WIFI_AUTORECONNECT, - - BUILD_DESC, - GIT_BUILD, - SYSTEM_LIBRARIES, - PLUGIN_COUNT, - PLUGIN_DESCRIPTION, - BUILD_TIME, - BINARY_FILENAME, - BUILD_PLATFORM, - GIT_HEAD, - #ifdef CONFIGURATION_CODE - CONFIGURATION_CODE_LBL, - #endif // ifdef CONFIGURATION_CODE - - - I2C_BUS_STATE, - I2C_BUS_CLEARED_COUNT, - - SYSLOG_LOG_LEVEL, - SERIAL_LOG_LEVEL, - WEB_LOG_LEVEL, -#if FEATURE_SD - SD_LOG_LEVEL, -#endif // if FEATURE_SD - - ESP_CHIP_ID, - ESP_CHIP_FREQ, -#ifdef ESP32 - ESP_CHIP_XTAL_FREQ, - ESP_CHIP_APB_FREQ, -#endif - ESP_CHIP_MODEL, - ESP_CHIP_REVISION, - ESP_CHIP_CORES, - BOARD_NAME, - - FLASH_CHIP_ID, - FLASH_CHIP_VENDOR, - FLASH_CHIP_MODEL, - FLASH_CHIP_REAL_SIZE, - FLASH_CHIP_SPEED, - FLASH_IDE_SIZE, - FLASH_IDE_SPEED, - FLASH_IDE_MODE, - FLASH_WRITE_COUNT, - SKETCH_SIZE, - SKETCH_FREE, - FS_SIZE, - FS_FREE, - MAX_OTA_SKETCH_SIZE, - OTA_2STEP, - OTA_POSSIBLE, - #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, - ETH_MAC, - ETH_DUPLEX, - ETH_SPEED, - ETH_STATE, - ETH_SPEED_STATE, - ETH_CONNECTED, -#endif // if FEATURE_ETHERNET -# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) - ETH_WIFI_MODE, -#endif - SUNRISE, - SUNSET, - ISNTP, - UPTIME_MS, - TIMEZONE_OFFSET, - LATITUDE, - LONGITUDE, - SUNRISE_S, - SUNSET_S, - SUNRISE_M, - SUNSET_M, - - - MAX_LABEL // Keep as last - }; -}; - - -#if FEATURE_ETHERNET -String getEthSpeed(); - -String getEthLinkSpeedState(); -#endif // if FEATURE_ETHERNET - -String getInternalLabel(LabelType::Enum label, - char replaceSpace = '_'); -const __FlashStringHelper * getLabel(LabelType::Enum label); -String getValue(LabelType::Enum label); -String getExtendedValue(LabelType::Enum label); - - -#endif // STRING_PROVIDER_TYPES_H +#ifndef STRING_PROVIDER_TYPES_H +#define STRING_PROVIDER_TYPES_H + +#include "../../ESPEasy_common.h" + +struct LabelType { + enum Enum : uint8_t { + UNIT_NR, + #if FEATURE_ZEROFILLED_UNITNUMBER + UNIT_NR_0, + #endif // FEATURE_ZEROFILLED_UNITNUMBER + UNIT_NAME, + HOST_NAME, + + LOCAL_TIME, + TIME_SOURCE, + TIME_WANDER, + #if FEATURE_EXT_RTC + EXT_RTC_UTC_TIME, + #endif + UPTIME, + LOAD_PCT, // 15.10 + LOOP_COUNT, // 400 + CPU_ECO_MODE, // true +#if FEATURE_SET_WIFI_TX_PWR + WIFI_TX_MAX_PWR, // Unit: 0.25 dBm, 0 = use default (do not set) + WIFI_CUR_TX_PWR, // Unit dBm of current WiFi TX power. + WIFI_SENS_MARGIN, // Margin in dB on top of sensitivity + WIFI_SEND_AT_MAX_TX_PWR, +#endif + WIFI_NR_EXTRA_SCANS, + WIFI_USE_LAST_CONN_FROM_RTC, + + FREE_MEM, // 9876 + FREE_STACK, // 3456 +#ifdef USE_SECOND_HEAP + FREE_HEAP_IRAM, +#endif +#if defined(CORE_POST_2_5_0) || defined(ESP32) + #ifndef LIMIT_BUILD_SIZE + 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 + HEAP_FRAGMENTATION, // 12 + #endif +#endif // if defined(CORE_POST_2_5_0) + +#ifdef ESP32 + HEAP_SIZE, + HEAP_MIN_FREE, + #ifdef BOARD_HAS_PSRAM + PSRAM_SIZE, + PSRAM_FREE, + PSRAM_MIN_FREE, + PSRAM_MAX_FREE_BLOCK, + #endif // BOARD_HAS_PSRAM +#endif // ifdef ESP32 + + JSON_BOOL_QUOTES, +#if FEATURE_TIMING_STATS + ENABLE_TIMING_STATISTICS, +#endif + ENABLE_RULES_CACHING, + ENABLE_SERIAL_PORT_CONSOLE, + CONSOLE_SERIAL_PORT, +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + CONSOLE_FALLBACK_TO_SERIAL0, + CONSOLE_FALLBACK_PORT, +#endif +// ENABLE_RULES_EVENT_REORDER, // TD-er: Disabled for now + TASKVALUESET_ALL_PLUGINS, + ALLOW_OTA_UNLIMITED, +#if FEATURE_CLEAR_I2C_STUCK + ENABLE_CLEAR_HUNG_I2C_BUS, +#endif + #if FEATURE_I2C_DEVICE_CHECK + ENABLE_I2C_DEVICE_CHECK, + #endif // if FEATURE_I2C_DEVICE_CHECK +#ifndef BUILD_NO_RAM_TRACKER + ENABLE_RAM_TRACKING, +#endif +#if FEATURE_AUTO_DARK_MODE + ENABLE_AUTO_DARK_MODE, +#endif +#if FEATURE_RULES_EASY_COLOR_CODE + DISABLE_RULES_AUTOCOMPLETE, +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + DISABLE_SAVE_CONFIG_AS_TAR, +#endif // if FEATURE_TARSTREAM_SUPPORT + + BOOT_TYPE, // Cold boot + BOOT_COUNT, // 0 + RESET_REASON, // Software/System restart + DEEP_SLEEP_ALTERNATIVE_CALL, + LAST_TASK_BEFORE_REBOOT, // Last scheduled task. + SW_WD_COUNT, + + 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 + GATEWAY, // 192.168.1.1 +#if FEATURE_USE_IPV6 + IP6_LOCAL, + IP6_GLOBAL, +// IP6_ALL_ADDRESSES, +// IP6_ADDRESS_CDIR, +// IP6_GATEWAY, +#endif + CLIENT_IP, // 192.168.1.67 + #if FEATURE_MDNS + M_DNS, // breadboard.local + #endif // if FEATURE_MDNS + DNS, // 192.168.1.1 / (IP unset) + DNS_1, + DNS_2, + ALLOWED_IP_RANGE, // 192.168.1.0 - 192.168.1.255 + STA_MAC, // EC:FA:BC:0E:AE:5B + AP_MAC, // EE:FA:BC:0E:AE:5B + SSID, // mynetwork + BSSID, + CHANNEL, // 1 + ENCRYPTION_TYPE_STA, // WPA2 + CONNECTED, // 1h16m + CONNECTED_MSEC, // 1h16m + LAST_DISCONNECT_REASON, // 200 + LAST_DISC_REASON_STR, // Beacon timeout + NUMBER_RECONNECTS, // 5 + WIFI_STORED_SSID1, + WIFI_STORED_SSID2, + + FORCE_WIFI_BG, + RESTART_WIFI_LOST_CONN, + FORCE_WIFI_NOSLEEP, + PERIODICAL_GRAT_ARP, + CONNECTION_FAIL_THRESH, +#ifndef ESP32 + WAIT_WIFI_CONNECT, +#endif + HIDDEN_SSID_SLOW_CONNECT, + CONNECT_HIDDEN_SSID, +#ifdef ESP32 + WIFI_PASSIVE_SCAN, +#endif + SDK_WIFI_AUTORECONNECT, +#if FEATURE_USE_IPV6 + ENABLE_IPV6, +#endif + + BUILD_DESC, + GIT_BUILD, + SYSTEM_LIBRARIES, + PLUGIN_COUNT, + PLUGIN_DESCRIPTION, + BUILD_TIME, + BINARY_FILENAME, + BUILD_PLATFORM, + GIT_HEAD, + #ifdef CONFIGURATION_CODE + CONFIGURATION_CODE_LBL, + #endif // ifdef CONFIGURATION_CODE + + + I2C_BUS_STATE, + I2C_BUS_CLEARED_COUNT, + + SYSLOG_LOG_LEVEL, + SERIAL_LOG_LEVEL, + WEB_LOG_LEVEL, +#if FEATURE_SD + SD_LOG_LEVEL, +#endif // if FEATURE_SD + + ESP_CHIP_ID, + ESP_CHIP_FREQ, +#ifdef ESP32 + ESP_CHIP_XTAL_FREQ, + ESP_CHIP_APB_FREQ, +#endif + ESP_CHIP_MODEL, + ESP_CHIP_REVISION, + ESP_CHIP_CORES, + BOARD_NAME, + + FLASH_CHIP_ID, + FLASH_CHIP_VENDOR, + FLASH_CHIP_MODEL, + FLASH_CHIP_REAL_SIZE, + FLASH_CHIP_SPEED, + FLASH_IDE_SIZE, + FLASH_IDE_SPEED, + FLASH_IDE_MODE, + FLASH_WRITE_COUNT, + SKETCH_SIZE, + SKETCH_FREE, + FS_SIZE, + FS_FREE, + MAX_OTA_SKETCH_SIZE, + OTA_2STEP, + OTA_POSSIBLE, + #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, + ETH_STATE, + ETH_SPEED_STATE, + ETH_CONNECTED, + ETH_CHIP, +#endif // if FEATURE_ETHERNET +# if FEATURE_ETHERNET || defined(USES_ESPEASY_NOW) + ETH_WIFI_MODE, +#endif + SUNRISE, + SUNSET, + ISNTP, + UPTIME_MS, + TIMEZONE_OFFSET, + LATITUDE, + LONGITUDE, + SUNRISE_S, + SUNSET_S, + SUNRISE_M, + SUNSET_M, + + + MAX_LABEL // Keep as last + }; +}; + + +#if FEATURE_ETHERNET +String getEthSpeed(); + +String getEthLinkSpeedState(); +#endif // if FEATURE_ETHERNET + +String getInternalLabel(LabelType::Enum label, + char replaceSpace = '_'); +const __FlashStringHelper * getLabel(LabelType::Enum label); +String getValue(LabelType::Enum label); +String getExtendedValue(LabelType::Enum label); + +String getFormNote(LabelType::Enum label); +String getFormUnit(LabelType::Enum label); + + +#endif // STRING_PROVIDER_TYPES_H diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp index a8eaec3e7..51bd62d24 100644 --- a/src/src/Helpers/SystemVariables.cpp +++ b/src/src/Helpers/SystemVariables.cpp @@ -1,600 +1,607 @@ -#include "../Helpers/SystemVariables.h" - - -#include "../../ESPEasy_common.h" - -#include "../../ESPEasy-Globals.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../Globals/CRCValues.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/ESPEasyWiFiEvent.h" -#if FEATURE_MQTT -# include "../Globals/MQTT.h" -#endif // if FEATURE_MQTT -#include "../Globals/NetworkState.h" -#include "../Globals/RulesCalculate.h" -#include "../Globals/RuntimeData.h" -#include "../Globals/Settings.h" -#include "../Globals/Statistics.h" - -#include "../Helpers/Convert.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" - - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # include -#endif // if defined(ESP32) - - -String getReplacementString(const String& format, const String& s) { - int startpos = s.indexOf(format); - int endpos = s.indexOf('%', startpos + 1); - if (endpos == -1) { - addLog(LOG_LEVEL_ERROR, concat(F("SunTime syntax error: "), format)); - return format; - } - String R = s.substring(startpos, endpos + 1); - - -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("ReplacementString SunTime: "); - log += R; - log += F(" offset: "); - log += ESPEasy_time::getSecOffset(R); - addLogMove(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - return R; -} - -void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { - const String R(getReplacementString(format, s)); - - repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); -} - -void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { - const String R(getReplacementString(format, s)); - - repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); -} - -String timeReplacement_leadZero(int value) -{ - char valueString[5] = { 0 }; - - sprintf_P(valueString, PSTR("%02d"), value); - return valueString; -} - -// FIXME TD-er: Try to match these with StringProvider::getValue -LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { - LabelType::Enum label = LabelType::MAX_LABEL; - - switch (enumval) - { - case SystemVariables::IP: label = LabelType::IP_ADDRESS; break; - case SystemVariables::SUBNET: label = LabelType::IP_SUBNET; break; - case SystemVariables::DNS: label = LabelType::DNS; break; - case SystemVariables::DNS_1: label = LabelType::DNS_1; break; - case SystemVariables::DNS_2: label = LabelType::DNS_2; break; - case SystemVariables::GATEWAY: label = LabelType::GATEWAY; break; - case SystemVariables::CLIENTIP: label = LabelType::CLIENT_IP; break; - #if FEATURE_INTERNAL_TEMPERATURE - case SystemVariables::INTERNAL_TEMPERATURE: label = LabelType::INTERNAL_TEMPERATURE; break; - #endif // if FEATURE_INTERNAL_TEMPERATURE - - #if FEATURE_ETHERNET - - case SystemVariables::ETHWIFIMODE: label = LabelType::ETH_WIFI_MODE; break; // 0=WIFI, 1=ETH - case SystemVariables::ETHCONNECTED: label = LabelType::ETH_CONNECTED; break; // 0=disconnected, 1=connected - case SystemVariables::ETHDUPLEX: label = LabelType::ETH_DUPLEX; break; - case SystemVariables::ETHSPEED: label = LabelType::ETH_SPEED; break; - case SystemVariables::ETHSTATE: label = LabelType::ETH_STATE; break; - case SystemVariables::ETHSPEEDSTATE: label = LabelType::ETH_SPEED_STATE; break; - #endif // if FEATURE_ETHERNET - case SystemVariables::LCLTIME: label = LabelType::LOCAL_TIME; break; - 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; - case SystemVariables::SUNSET_S: label = LabelType::SUNSET_S; break; - case SystemVariables::SUNRISE_M: label = LabelType::SUNRISE_M; break; - case SystemVariables::SUNSET_M: label = LabelType::SUNSET_M; break; - case SystemVariables::SYSBUILD_DESCR: label = LabelType::BUILD_DESC; break; - case SystemVariables::SYSBUILD_FILENAME: label = LabelType::BINARY_FILENAME; break; - case SystemVariables::SYSBUILD_GIT: label = LabelType::GIT_BUILD; break; - case SystemVariables::SYSSTACK: label = LabelType::FREE_STACK; break; - case SystemVariables::UNIT_sysvar: label = LabelType::UNIT_NR; break; - #if FEATURE_ZEROFILLED_UNITNUMBER - case SystemVariables::UNIT_0_sysvar: label = LabelType::UNIT_NR_0; break; - #endif // FEATURE_ZEROFILLED_UNITNUMBER - case SystemVariables::FLASH_FREQ: label = LabelType::FLASH_CHIP_SPEED; break; - case SystemVariables::FLASH_SIZE: label = LabelType::FLASH_CHIP_REAL_SIZE; break; - case SystemVariables::FLASH_CHIP_VENDOR: label = LabelType::FLASH_CHIP_VENDOR; break; - case SystemVariables::FLASH_CHIP_MODEL: label = LabelType::FLASH_CHIP_MODEL; break; - case SystemVariables::FS_SIZE: label = LabelType::FS_SIZE; break; - case SystemVariables::FS_FREE: label = LabelType::FS_FREE; break; - - case SystemVariables::ESP_CHIP_ID: label = LabelType::ESP_CHIP_ID; break; - case SystemVariables::ESP_CHIP_FREQ: label = LabelType::ESP_CHIP_FREQ; break; - case SystemVariables::ESP_CHIP_MODEL: label = LabelType::ESP_CHIP_MODEL; break; - case SystemVariables::ESP_CHIP_REVISION: label = LabelType::ESP_CHIP_REVISION; break; - case SystemVariables::ESP_CHIP_CORES: label = LabelType::ESP_CHIP_CORES; break; - case SystemVariables::BOARD_NAME: label = LabelType::BOARD_NAME; break; - - default: - // No matching LabelType yet. - break; - } - return label; -} - -String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { - const LabelType::Enum label = SystemVariables2LabelType(enumval); - - if (LabelType::MAX_LABEL != label) { - return getValue(label); - } - constexpr int INT_NOT_SET = std::numeric_limits::min(); - - int intvalue = INT_NOT_SET; - - 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 CR: return String('\r'); - case IP4: intvalue = static_cast(NetworkLocalIP()[3]); break; // 4th IP octet - case ISMQTT: intvalue = - #if FEATURE_MQTT - MQTTclient_connected ? 1 : - #endif // if FEATURE_MQTT - 0; break; - - case ISMQTTIMP: intvalue = - #ifdef USES_P037 - P037_MQTTImport_connected ? 1 : - #endif // ifdef USES_P037 - 0; break; - - case ISNTP: intvalue = statusNTPInitialized ? 1 : 0; break; - case ISWIFI: intvalue = 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. - case SPACE: return String(' '); - case SSID: return (WiFiEventData.WiFiDisconnected()) ? 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; - case SYSDAY_0: return timeReplacement_leadZero(node_time.day()); - case SYSHEAP: intvalue = ESP.getFreeHeap(); break; - case SYSHOUR: intvalue = node_time.hour(); break; - case SYSHOUR_0: return timeReplacement_leadZero(node_time.hour()); - case SYSLOAD: return String(getCPUload(), 2); - case SYSMIN: intvalue = node_time.minute(); break; - case SYSMIN_0: return timeReplacement_leadZero(node_time.minute()); - case SYSMONTH: intvalue = node_time.month(); break; - case SYSMONTH_S: return node_time.month_str(); - case SYSNAME: return Settings.getHostname(); - case SYSSEC: intvalue = node_time.second(); break; - case SYSSEC_0: return timeReplacement_leadZero(node_time.second()); - case SYSSEC_D: intvalue = ((node_time.hour() * 60) + node_time.minute()) * 60 + node_time.second(); break; - case SYSTIME: return node_time.getTimeString(':'); - case SYSTIME_AM: return node_time.getTimeString_ampm(':'); - case SYSTIME_AM_0: return node_time.getTimeString_ampm(':', true, '0'); - case SYSTIME_AM_SP: return node_time.getTimeString_ampm(':', true, ' '); - case SYSTM_HM: return node_time.getTimeString(':', false); - case SYSTM_HM_0: return node_time.getTimeString(':', false, '0'); - case SYSTM_HM_SP: return node_time.getTimeString(':', false, ' '); - case SYSTM_HM_AM: return node_time.getTimeString_ampm(':', false); - case SYSTM_HM_AM_0: return node_time.getTimeString_ampm(':', false, '0'); - case SYSTM_HM_AM_SP: return node_time.getTimeString_ampm(':', false, ' '); - case SYSTZOFFSET: return node_time.getTimeZoneOffsetString(); - case SYSWEEKDAY: intvalue = node_time.weekday(); break; - case SYSWEEKDAY_S: return node_time.weekday_str(); - case SYSYEAR_0: - case SYSYEAR: intvalue = node_time.year(); break; - case SYSYEARS: return timeReplacement_leadZero(node_time.year() % 100); - case SYS_MONTH_0: return timeReplacement_leadZero(node_time.month()); - case S_CR: return F("\\r"); - case S_LF: return F("\\n"); - case UNIXDAY: intvalue = node_time.getUnixTime() / 86400; break; - case UNIXDAY_SEC: intvalue = node_time.getUnixTime() % 86400; break; - case UNIXTIME: return String(node_time.getUnixTime()); - case UPTIME: intvalue = getUptimeMinutes(); break; - case UPTIME_MS: return ull2String(getMicros64() / 1000); - #if FEATURE_ADC_VCC - case VCC: return String(vcc); - #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; - - default: - // Already handled above. - return EMPTY_STRING; - } - - if (intvalue != INT_NOT_SET) { - return String(intvalue); - } - - return EMPTY_STRING; -} - -/* -#define SMART_REPL_T(T, S) \ - while (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } -*/ - -#define SMART_REPL_T(T, S) \ - const String T_str(T); int __pos__ = s.indexOf(T_str); \ - while (__pos__ != -1) { (S((T_str), s, useURLencode)); __pos__ = s.indexOf(T_str, __pos__ + 1);} - -// Parse %vN% to replace ESPEasy variables -bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) -{ - const String key_prefix = F("%v"); - int v_index = s.indexOf(key_prefix, start_pos); - - bool somethingReplaced = false; - - while ((v_index != -1)) { - // Exclude "%valname% or %value%" - // FIXME TD-er: Must find a more elegant way to fix this - if (!isalpha(s.charAt(v_index + 2))) { - // Check for: - // - Calculations indicated with leading '=' - // - nested indirections like %v%v1%% - if ((s.charAt(v_index + 2) == '=') || - (s.charAt(v_index + 2) == '%' && s.charAt(v_index + 3) == 'v')) { - // FIXME TD-er: This may lead to stack overflow if we do an awful lot of nested user variables - if (parse_pct_v_num_pct(s, useURLencode, v_index + 2)) { - somethingReplaced = true; - } - } - - uint32_t i{}; - // variable index may contain a calculation - // Calculations are enforced by a leading '=' - // like: %v=1+%v2%% - const int pos_closing_pct = s.indexOf('%', v_index + 1); - const String arg = s.substring(v_index + 2, pos_closing_pct); - i = CalculateParam(arg, -1); - //addLog(LOG_LEVEL_INFO, strformat(F("calc parse: %s => %u"), arg.c_str(), i)); - if (i >= 0) { - // Need to replace the entire arg and not just the 'i' - const String key = strformat(F("%%v%s%%"), arg.c_str()); - - if (s.indexOf(key) != -1) { - const bool trimTrailingZeros = true; - #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - const String value = doubleToString(getCustomFloatVar(i), 6, trimTrailingZeros); - #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - const String value = floatToString(getCustomFloatVar(i), 6, trimTrailingZeros); - #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - if (repl(key, value, s, useURLencode)) { - somethingReplaced = true; - } - } - } - } - v_index = s.indexOf(key_prefix, v_index + 1); // Find next occurance - //addLog(LOG_LEVEL_INFO, strformat(F("parse: %s"), s.c_str())); - } - return somethingReplaced; -} - -void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) -{ - START_TIMER - - if (s.indexOf('%') == -1) { - STOP_TIMER(PARSE_SYSVAR_NOCHANGE); - return; - } - - bool somethingReplaced = false; - - // Parse ESPEasy user variables first as they might be combined - // as arument or index for other variables - parse_pct_v_num_pct(s, useURLencode, 0); - - do { - int last_percent_pos = -1; - somethingReplaced = false; - SystemVariables::Enum enumval = static_cast(0); - do { - enumval = SystemVariables::nextReplacementEnum(s, enumval, last_percent_pos); - - switch (enumval) - { - case SUNRISE: { - SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); - somethingReplaced = true; - break; - } - case SUNSET: { - SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); - somethingReplaced = true; - break; - } - case VARIABLE: - { - // Should not be present anymore, but just in case... - if (parse_pct_v_num_pct(s, useURLencode, 0)) - somethingReplaced = true; - - break; - } - case UNKNOWN: - - // Do not replace - break; - default: - { - const String sysvar_str(SystemVariables::toString(enumval)); - if (s.indexOf(sysvar_str) != -1) { - if (repl( - sysvar_str, - getSystemVariable(enumval), - s, - useURLencode)) - somethingReplaced = true; - } - break; - } - } - } - while (enumval != SystemVariables::Enum::UNKNOWN); - } - while (somethingReplaced); - - STOP_TIMER(PARSE_SYSVAR); -} - -#undef SMART_REPL_T - - -SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, SystemVariables::Enum last_tested, int& last_percent_pos) -{ - SystemVariables::Enum nextTested; - int percent_pos = last_percent_pos; - - do { - // Find first position in string which might be a good candidate to look for a system variable. - // Look for "%N" where 'N' is the first letter of a variable name we support. - percent_pos = str.indexOf('%', percent_pos + 1); - - if (percent_pos == -1) { - return Enum::UNKNOWN; - } - - nextTested = SystemVariables::startIndex_beginWith(str[percent_pos + 1]); - } while (Enum::UNKNOWN == nextTested); - - if (last_percent_pos < percent_pos) { - last_percent_pos = percent_pos; - last_tested = nextTested; - } - - if (last_tested > nextTested) { - // Iterate over the possible system variables - nextTested = static_cast(last_tested + 1); - const char firstChar_nextTested = static_cast(pgm_read_byte(SystemVariables::toFlashString(nextTested))); - const char firstChar_expected = str[percent_pos + 1]; - - if (firstChar_nextTested != firstChar_expected) { - nextTested = Enum::UNKNOWN; - } - } - - if (nextTested >= Enum::UNKNOWN) { - // We have tested all possible system variables - // Skip unsupported ones or maybe it is just a single percentage symbol in a string. - percent_pos = str.indexOf('%', percent_pos + 1); - - if (percent_pos == -1) { - return Enum::UNKNOWN; - } - last_percent_pos = percent_pos; - return SystemVariables::startIndex_beginWith(str[percent_pos + 1]); - } - - const __FlashStringHelper *fstr_sysvar = SystemVariables::toFlashString(nextTested); - String str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); - bool str_prefix_exists = str.indexOf(str_prefix) != -1; - - for (int i = nextTested; i < Enum::UNKNOWN; ++i) { - SystemVariables::Enum enumval = static_cast(i); - fstr_sysvar = SystemVariables::toFlashString(enumval); - const String new_str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); - - if ((str_prefix == new_str_prefix) && !str_prefix_exists) { - // Just continue - } else { - str_prefix = new_str_prefix; - str_prefix_exists = str.indexOf(str_prefix) != -1; - - if (str_prefix_exists) { - if (str.indexOf(SystemVariables::toString(enumval)) != -1) { - return enumval; - } - } - } - } - - return Enum::UNKNOWN; -} - -String SystemVariables::toString(Enum enumval) -{ - if ((enumval == Enum::SUNRISE) || (enumval == Enum::SUNSET) || enumval == Enum::VARIABLE) { - // These need variables, so only prepend a %, not wrap. - return String('%') + SystemVariables::toFlashString(enumval); - } - - return wrap_String(SystemVariables::toFlashString(enumval), '%'); -} - -SystemVariables::Enum SystemVariables::startIndex_beginWith(char beginchar) -{ - switch (tolower(beginchar)) - { - case 'b': return Enum::BOARD_NAME; - case 'c': return Enum::CLIENTIP; - case 'd': return Enum::DNS; -#if FEATURE_ETHERNET - case 'e': return Enum::ETHCONNECTED; -#endif // if FEATURE_ETHERNET - case 'f': return Enum::FLASH_CHIP_MODEL; - case 'g': return Enum::GATEWAY; -#if FEATURE_INTERNAL_TEMPERATURE - case 'i': return Enum::INTERNAL_TEMPERATURE; -#else // if FEATURE_INTERNAL_TEMPERATURE - case 'i': return Enum::IP4; -#endif // if FEATURE_INTERNAL_TEMPERATURE - case 'l': return Enum::LCLTIME; - case 'm': return Enum::SUNRISE_M; - case 'n': return Enum::S_LF; - case 'r': return Enum::S_CR; - case 's': return Enum::SPACE; - case 'u': return Enum::UNIT_sysvar; - case 'v': return Enum::VARIABLE; - case 'w': return Enum::WI_CH; - } - - return Enum::UNKNOWN; -} - -const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum enumval) -{ - switch (enumval) { - case Enum::BOARD_NAME: return F("board_name"); - case Enum::BOOT_CAUSE: return F("bootcause"); - case Enum::BSSID: return F("bssid"); - case Enum::CLIENTIP: return F("clientip"); - case Enum::CR: return F("CR"); - case Enum::ESP_CHIP_CORES: return F("cpu_cores"); - case Enum::ESP_CHIP_FREQ: return F("cpu_freq"); - case Enum::ESP_CHIP_ID: return F("cpu_id"); - case Enum::ESP_CHIP_MODEL: return F("cpu_model"); - case Enum::ESP_CHIP_REVISION: return F("cpu_rev"); - case Enum::DNS: return F("dns"); - case Enum::DNS_1: return F("dns1"); - case Enum::DNS_2: return F("dns2"); -#if FEATURE_ETHERNET - case Enum::ETHCONNECTED: return F("ethconnected"); - case Enum::ETHDUPLEX: return F("ethduplex"); - case Enum::ETHSPEED: return F("ethspeed"); - case Enum::ETHSPEEDSTATE: return F("ethspeedstate"); - case Enum::ETHSTATE: return F("ethstate"); - case Enum::ETHWIFIMODE: return F("ethwifimode"); -#endif // if FEATURE_ETHERNET - - case Enum::FLASH_CHIP_MODEL: return F("flash_chip_model"); - case Enum::FLASH_CHIP_VENDOR: return F("flash_chip_vendor"); - case Enum::FLASH_FREQ: return F("flash_freq"); - case Enum::FLASH_SIZE: return F("flash_size"); - case Enum::FS_FREE: return F("fs_free"); - case Enum::FS_SIZE: return F("fs_size"); - case Enum::GATEWAY: return F("gateway"); -#if FEATURE_INTERNAL_TEMPERATURE - case Enum::INTERNAL_TEMPERATURE: return F("inttemp"); -#endif // if FEATURE_INTERNAL_TEMPERATURE - - case Enum::IP4: return F("ip4"); - case Enum::IP: return F("ip"); - case Enum::ISMQTT: return F("ismqtt"); - case Enum::ISMQTTIMP: return F("ismqttimp"); - case Enum::ISNTP: return F("isntp"); - 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"); - 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"); - case Enum::S_CR: return F("R"); - case Enum::RSSI: return F("rssi"); - case Enum::SPACE: return F("SP"); - case Enum::SSID: return F("ssid"); - case Enum::SUBNET: return F("subnet"); - case Enum::SUNRISE: return F("sunrise"); - case Enum::SUNRISE_S: return F("s_sunrise"); - case Enum::SUNSET: return F("sunset"); - case Enum::SUNSET_S: return F("s_sunset"); - case Enum::SYSBUILD_DATE: return F("sysbuild_date"); - case Enum::SYSBUILD_DESCR: return F("sysbuild_desc"); - case Enum::SYSBUILD_FILENAME: return F("sysbuild_filename"); - case Enum::SYSBUILD_GIT: return F("sysbuild_git"); - case Enum::SYSBUILD_TIME: return F("sysbuild_time"); - case Enum::SYSDAY: return F("sysday"); - case Enum::SYSDAY_0: return F("sysday_0"); - case Enum::SYSHEAP: return F("sysheap"); - case Enum::SYSHOUR: return F("syshour"); - case Enum::SYSHOUR_0: return F("syshour_0"); - case Enum::SYSLOAD: return F("sysload"); - case Enum::SYSMIN: return F("sysmin"); - case Enum::SYSMIN_0: return F("sysmin_0"); - case Enum::SYSMONTH: return F("sysmonth"); - case Enum::SYSMONTH_S: return F("sysmonth_s"); - case Enum::SYSNAME: return F("sysname"); - case Enum::SYSSEC: return F("syssec"); - case Enum::SYSSEC_0: return F("syssec_0"); - case Enum::SYSSEC_D: return F("syssec_d"); - case Enum::SYSSTACK: return F("sysstack"); - case Enum::SYSTIME: return F("systime"); - case Enum::SYSTIME_AM: return F("systime_am"); - case Enum::SYSTIME_AM_0: return F("systime_am_0"); - case Enum::SYSTIME_AM_SP: return F("systime_am_sp"); - case Enum::SYSTM_HM: return F("systm_hm"); - case Enum::SYSTM_HM_0: return F("systm_hm_0"); - case Enum::SYSTM_HM_AM: return F("systm_hm_am"); - case Enum::SYSTM_HM_AM_0: return F("systm_hm_am_0"); - case Enum::SYSTM_HM_AM_SP: return F("systm_hm_am_sp"); - case Enum::SYSTM_HM_SP: return F("systm_hm_sp"); - case Enum::SYSTZOFFSET: return F("systzoffset"); - case Enum::SYSWEEKDAY: return F("sysweekday"); - case Enum::SYSWEEKDAY_S: return F("sysweekday_s"); - case Enum::SYSYEAR: return F("sysyear"); - case Enum::SYSYEARS: return F("sysyears"); - case Enum::SYSYEAR_0: return F("sysyear_0"); - case Enum::SYS_MONTH_0: return F("sysmonth_0"); - case Enum::UNIT_sysvar: return F("unit"); -#if FEATURE_ZEROFILLED_UNITNUMBER - case Enum::UNIT_0_sysvar: return F("unit_0"); -#endif // FEATURE_ZEROFILLED_UNITNUMBER - case Enum::UNIXDAY: return F("unixday"); - case Enum::UNIXDAY_SEC: return F("unixday_sec"); - case Enum::UNIXTIME: return F("unixtime"); - case Enum::UPTIME: return F("uptime"); - case Enum::UPTIME_MS: return F("uptime_ms"); - case Enum::VARIABLE: return F("v"); - case Enum::VCC: return F("vcc"); - case Enum::WI_CH: return F("wi_ch"); - - case Enum::UNKNOWN: break; - } - return F("Unknown"); -} +#include "../Helpers/SystemVariables.h" + + +#include "../../ESPEasy_common.h" + +#include "../../ESPEasy-Globals.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../Globals/CRCValues.h" +#include "../Globals/ESPEasy_time.h" +#include "../Globals/ESPEasyWiFiEvent.h" +#if FEATURE_MQTT +# include "../Globals/MQTT.h" +#endif // if FEATURE_MQTT +#include "../Globals/NetworkState.h" +#include "../Globals/RulesCalculate.h" +#include "../Globals/RuntimeData.h" +#include "../Globals/Settings.h" +#include "../Globals/Statistics.h" + +#include "../Helpers/Convert.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" + + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # include +#endif // if defined(ESP32) + + +String getReplacementString(const String& format, const String& s) { + int startpos = s.indexOf(format); + int endpos = s.indexOf('%', startpos + 1); + if (endpos == -1) { + addLog(LOG_LEVEL_ERROR, concat(F("SunTime syntax error: "), format)); + return format; + } + String R = s.substring(startpos, endpos + 1); + + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("ReplacementString SunTime: "); + log += R; + log += F(" offset: "); + log += ESPEasy_time::getSecOffset(R); + addLogMove(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + return R; +} + +void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { + const String R(getReplacementString(format, s)); + + repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + +void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { + const String R(getReplacementString(format, s)); + + repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + +String timeReplacement_leadZero(int value) +{ + char valueString[5] = { 0 }; + + sprintf_P(valueString, PSTR("%02d"), value); + return valueString; +} + +// FIXME TD-er: Try to match these with StringProvider::getValue +LabelType::Enum SystemVariables2LabelType(SystemVariables::Enum enumval) { + LabelType::Enum label = LabelType::MAX_LABEL; + + switch (enumval) + { + case SystemVariables::IP: label = LabelType::IP_ADDRESS; break; +#if FEATURE_USE_IPV6 + case SystemVariables::IP6_LOCAL: label = LabelType::IP6_LOCAL; break; +#endif + case SystemVariables::SUBNET: label = LabelType::IP_SUBNET; break; + case SystemVariables::DNS: label = LabelType::DNS; break; + case SystemVariables::DNS_1: label = LabelType::DNS_1; break; + case SystemVariables::DNS_2: label = LabelType::DNS_2; break; + case SystemVariables::GATEWAY: label = LabelType::GATEWAY; break; + case SystemVariables::CLIENTIP: label = LabelType::CLIENT_IP; break; + #if FEATURE_INTERNAL_TEMPERATURE + case SystemVariables::INTERNAL_TEMPERATURE: label = LabelType::INTERNAL_TEMPERATURE; break; + #endif // if FEATURE_INTERNAL_TEMPERATURE + + #if FEATURE_ETHERNET + + case SystemVariables::ETHWIFIMODE: label = LabelType::ETH_WIFI_MODE; break; // 0=WIFI, 1=ETH + case SystemVariables::ETHCONNECTED: label = LabelType::ETH_CONNECTED; break; // 0=disconnected, 1=connected + case SystemVariables::ETHDUPLEX: label = LabelType::ETH_DUPLEX; break; + case SystemVariables::ETHSPEED: label = LabelType::ETH_SPEED; break; + case SystemVariables::ETHSTATE: label = LabelType::ETH_STATE; break; + case SystemVariables::ETHSPEEDSTATE: label = LabelType::ETH_SPEED_STATE; break; + #endif // if FEATURE_ETHERNET + case SystemVariables::LCLTIME: label = LabelType::LOCAL_TIME; break; + 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; + case SystemVariables::SUNSET_S: label = LabelType::SUNSET_S; break; + case SystemVariables::SUNRISE_M: label = LabelType::SUNRISE_M; break; + case SystemVariables::SUNSET_M: label = LabelType::SUNSET_M; break; + case SystemVariables::SYSBUILD_DESCR: label = LabelType::BUILD_DESC; break; + case SystemVariables::SYSBUILD_FILENAME: label = LabelType::BINARY_FILENAME; break; + case SystemVariables::SYSBUILD_GIT: label = LabelType::GIT_BUILD; break; + case SystemVariables::SYSSTACK: label = LabelType::FREE_STACK; break; + case SystemVariables::UNIT_sysvar: label = LabelType::UNIT_NR; break; + #if FEATURE_ZEROFILLED_UNITNUMBER + case SystemVariables::UNIT_0_sysvar: label = LabelType::UNIT_NR_0; break; + #endif // FEATURE_ZEROFILLED_UNITNUMBER + case SystemVariables::FLASH_FREQ: label = LabelType::FLASH_CHIP_SPEED; break; + case SystemVariables::FLASH_SIZE: label = LabelType::FLASH_CHIP_REAL_SIZE; break; + case SystemVariables::FLASH_CHIP_VENDOR: label = LabelType::FLASH_CHIP_VENDOR; break; + case SystemVariables::FLASH_CHIP_MODEL: label = LabelType::FLASH_CHIP_MODEL; break; + case SystemVariables::FS_SIZE: label = LabelType::FS_SIZE; break; + case SystemVariables::FS_FREE: label = LabelType::FS_FREE; break; + + case SystemVariables::ESP_CHIP_ID: label = LabelType::ESP_CHIP_ID; break; + case SystemVariables::ESP_CHIP_FREQ: label = LabelType::ESP_CHIP_FREQ; break; + case SystemVariables::ESP_CHIP_MODEL: label = LabelType::ESP_CHIP_MODEL; break; + case SystemVariables::ESP_CHIP_REVISION: label = LabelType::ESP_CHIP_REVISION; break; + case SystemVariables::ESP_CHIP_CORES: label = LabelType::ESP_CHIP_CORES; break; + case SystemVariables::BOARD_NAME: label = LabelType::BOARD_NAME; break; + + default: + // No matching LabelType yet. + break; + } + return label; +} + +String SystemVariables::getSystemVariable(SystemVariables::Enum enumval) { + const LabelType::Enum label = SystemVariables2LabelType(enumval); + + if (LabelType::MAX_LABEL != label) { + return getValue(label); + } + constexpr int INT_NOT_SET = std::numeric_limits::min(); + + int intvalue = INT_NOT_SET; + + 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 CR: return String('\r'); + case IP4: intvalue = static_cast(NetworkLocalIP()[3]); break; // 4th IP octet + case ISMQTT: intvalue = + #if FEATURE_MQTT + MQTTclient_connected ? 1 : + #endif // if FEATURE_MQTT + 0; break; + + case ISMQTTIMP: intvalue = + #ifdef USES_P037 + P037_MQTTImport_connected ? 1 : + #endif // ifdef USES_P037 + 0; break; + + case ISNTP: intvalue = statusNTPInitialized ? 1 : 0; break; + case ISWIFI: intvalue = 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. + case SPACE: return String(' '); + case SSID: return (WiFiEventData.WiFiDisconnected()) ? 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; + case SYSDAY_0: return timeReplacement_leadZero(node_time.day()); + case SYSHEAP: intvalue = ESP.getFreeHeap(); break; + case SYSHOUR: intvalue = node_time.hour(); break; + case SYSHOUR_0: return timeReplacement_leadZero(node_time.hour()); + case SYSLOAD: return String(getCPUload(), 2); + case SYSMIN: intvalue = node_time.minute(); break; + case SYSMIN_0: return timeReplacement_leadZero(node_time.minute()); + case SYSMONTH: intvalue = node_time.month(); break; + case SYSMONTH_S: return node_time.month_str(); + case SYSNAME: return Settings.getHostname(); + case SYSSEC: intvalue = node_time.second(); break; + case SYSSEC_0: return timeReplacement_leadZero(node_time.second()); + case SYSSEC_D: intvalue = ((node_time.hour() * 60) + node_time.minute()) * 60 + node_time.second(); break; + case SYSTIME: return node_time.getTimeString(':'); + case SYSTIME_AM: return node_time.getTimeString_ampm(':'); + case SYSTIME_AM_0: return node_time.getTimeString_ampm(':', true, '0'); + case SYSTIME_AM_SP: return node_time.getTimeString_ampm(':', true, ' '); + case SYSTM_HM: return node_time.getTimeString(':', false); + case SYSTM_HM_0: return node_time.getTimeString(':', false, '0'); + case SYSTM_HM_SP: return node_time.getTimeString(':', false, ' '); + case SYSTM_HM_AM: return node_time.getTimeString_ampm(':', false); + case SYSTM_HM_AM_0: return node_time.getTimeString_ampm(':', false, '0'); + case SYSTM_HM_AM_SP: return node_time.getTimeString_ampm(':', false, ' '); + case SYSTZOFFSET: return node_time.getTimeZoneOffsetString(); + case SYSWEEKDAY: intvalue = node_time.weekday(); break; + case SYSWEEKDAY_S: return node_time.weekday_str(); + case SYSYEAR_0: + case SYSYEAR: intvalue = node_time.year(); break; + case SYSYEARS: return timeReplacement_leadZero(node_time.year() % 100); + case SYS_MONTH_0: return timeReplacement_leadZero(node_time.month()); + case S_CR: return F("\\r"); + case S_LF: return F("\\n"); + case UNIXDAY: intvalue = node_time.getUnixTime() / 86400; break; + case UNIXDAY_SEC: intvalue = node_time.getUnixTime() % 86400; break; + case UNIXTIME: return String(node_time.getUnixTime()); + case UPTIME: intvalue = getUptimeMinutes(); break; + case UPTIME_MS: return ull2String(getMicros64() / 1000); + #if FEATURE_ADC_VCC + case VCC: return String(vcc); + #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; + + default: + // Already handled above. + return EMPTY_STRING; + } + + if (intvalue != INT_NOT_SET) { + return String(intvalue); + } + + return EMPTY_STRING; +} + +/* +#define SMART_REPL_T(T, S) \ + while (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } +*/ + +#define SMART_REPL_T(T, S) \ + const String T_str(T); int __pos__ = s.indexOf(T_str); \ + while (__pos__ != -1) { (S((T_str), s, useURLencode)); __pos__ = s.indexOf(T_str, __pos__ + 1);} + +// Parse %vN% to replace ESPEasy variables +bool parse_pct_v_num_pct(String& s, boolean useURLencode, int start_pos) +{ + const String key_prefix = F("%v"); + int v_index = s.indexOf(key_prefix, start_pos); + + bool somethingReplaced = false; + + while ((v_index != -1)) { + // Exclude "%valname% or %value%" + // FIXME TD-er: Must find a more elegant way to fix this + if (!isalpha(s.charAt(v_index + 2))) { + // Check for: + // - Calculations indicated with leading '=' + // - nested indirections like %v%v1%% + if ((s.charAt(v_index + 2) == '=') || + (s.charAt(v_index + 2) == '%' && s.charAt(v_index + 3) == 'v')) { + // FIXME TD-er: This may lead to stack overflow if we do an awful lot of nested user variables + if (parse_pct_v_num_pct(s, useURLencode, v_index + 2)) { + somethingReplaced = true; + } + } + + uint32_t i{}; + // variable index may contain a calculation + // Calculations are enforced by a leading '=' + // like: %v=1+%v2%% + const int pos_closing_pct = s.indexOf('%', v_index + 1); + const String arg = s.substring(v_index + 2, pos_closing_pct); + i = CalculateParam(arg, -1); + //addLog(LOG_LEVEL_INFO, strformat(F("calc parse: %s => %u"), arg.c_str(), i)); + if (i >= 0) { + // Need to replace the entire arg and not just the 'i' + const String key = strformat(F("%%v%s%%"), arg.c_str()); + + if (s.indexOf(key) != -1) { + const bool trimTrailingZeros = true; + #if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + const String value = doubleToString(getCustomFloatVar(i), 6, trimTrailingZeros); + #else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + const String value = floatToString(getCustomFloatVar(i), 6, trimTrailingZeros); + #endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + if (repl(key, value, s, useURLencode)) { + somethingReplaced = true; + } + } + } + } + v_index = s.indexOf(key_prefix, v_index + 1); // Find next occurance + //addLog(LOG_LEVEL_INFO, strformat(F("parse: %s"), s.c_str())); + } + return somethingReplaced; +} + +void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) +{ + START_TIMER + + if (s.indexOf('%') == -1) { + STOP_TIMER(PARSE_SYSVAR_NOCHANGE); + return; + } + + bool somethingReplaced = false; + + // Parse ESPEasy user variables first as they might be combined + // as arument or index for other variables + parse_pct_v_num_pct(s, useURLencode, 0); + + do { + int last_percent_pos = -1; + somethingReplaced = false; + SystemVariables::Enum enumval = static_cast(0); + do { + enumval = SystemVariables::nextReplacementEnum(s, enumval, last_percent_pos); + + switch (enumval) + { + case SUNRISE: { + SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); + somethingReplaced = true; + break; + } + case SUNSET: { + SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); + somethingReplaced = true; + break; + } + case VARIABLE: + { + // Should not be present anymore, but just in case... + if (parse_pct_v_num_pct(s, useURLencode, 0)) + somethingReplaced = true; + + break; + } + case UNKNOWN: + + // Do not replace + break; + default: + { + const String sysvar_str(SystemVariables::toString(enumval)); + if (s.indexOf(sysvar_str) != -1) { + if (repl( + sysvar_str, + getSystemVariable(enumval), + s, + useURLencode)) + somethingReplaced = true; + } + break; + } + } + } + while (enumval != SystemVariables::Enum::UNKNOWN); + } + while (somethingReplaced); + + STOP_TIMER(PARSE_SYSVAR); +} + +#undef SMART_REPL_T + + +SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, SystemVariables::Enum last_tested, int& last_percent_pos) +{ + SystemVariables::Enum nextTested; + int percent_pos = last_percent_pos; + + do { + // Find first position in string which might be a good candidate to look for a system variable. + // Look for "%N" where 'N' is the first letter of a variable name we support. + percent_pos = str.indexOf('%', percent_pos + 1); + + if (percent_pos == -1) { + return Enum::UNKNOWN; + } + + nextTested = SystemVariables::startIndex_beginWith(str[percent_pos + 1]); + } while (Enum::UNKNOWN == nextTested); + + if (last_percent_pos < percent_pos) { + last_percent_pos = percent_pos; + last_tested = nextTested; + } + + if (last_tested > nextTested) { + // Iterate over the possible system variables + nextTested = static_cast(last_tested + 1); + const char firstChar_nextTested = static_cast(pgm_read_byte(SystemVariables::toFlashString(nextTested))); + const char firstChar_expected = str[percent_pos + 1]; + + if (firstChar_nextTested != firstChar_expected) { + nextTested = Enum::UNKNOWN; + } + } + + if (nextTested >= Enum::UNKNOWN) { + // We have tested all possible system variables + // Skip unsupported ones or maybe it is just a single percentage symbol in a string. + percent_pos = str.indexOf('%', percent_pos + 1); + + if (percent_pos == -1) { + return Enum::UNKNOWN; + } + last_percent_pos = percent_pos; + return SystemVariables::startIndex_beginWith(str[percent_pos + 1]); + } + + const __FlashStringHelper *fstr_sysvar = SystemVariables::toFlashString(nextTested); + String str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); + bool str_prefix_exists = str.indexOf(str_prefix) != -1; + + for (int i = nextTested; i < Enum::UNKNOWN; ++i) { + SystemVariables::Enum enumval = static_cast(i); + fstr_sysvar = SystemVariables::toFlashString(enumval); + const String new_str_prefix = strformat(F("%%%c"), static_cast(pgm_read_byte(fstr_sysvar))); + + if ((str_prefix == new_str_prefix) && !str_prefix_exists) { + // Just continue + } else { + str_prefix = new_str_prefix; + str_prefix_exists = str.indexOf(str_prefix) != -1; + + if (str_prefix_exists) { + if (str.indexOf(SystemVariables::toString(enumval)) != -1) { + return enumval; + } + } + } + } + + return Enum::UNKNOWN; +} + +String SystemVariables::toString(Enum enumval) +{ + if ((enumval == Enum::SUNRISE) || (enumval == Enum::SUNSET) || enumval == Enum::VARIABLE) { + // These need variables, so only prepend a %, not wrap. + return String('%') + SystemVariables::toFlashString(enumval); + } + + return wrap_String(SystemVariables::toFlashString(enumval), '%'); +} + +SystemVariables::Enum SystemVariables::startIndex_beginWith(char beginchar) +{ + switch (tolower(beginchar)) + { + case 'b': return Enum::BOARD_NAME; + case 'c': return Enum::CLIENTIP; + case 'd': return Enum::DNS; +#if FEATURE_ETHERNET + case 'e': return Enum::ETHCONNECTED; +#endif // if FEATURE_ETHERNET + case 'f': return Enum::FLASH_CHIP_MODEL; + case 'g': return Enum::GATEWAY; +#if FEATURE_INTERNAL_TEMPERATURE + case 'i': return Enum::INTERNAL_TEMPERATURE; +#else // if FEATURE_INTERNAL_TEMPERATURE + case 'i': return Enum::IP4; +#endif // if FEATURE_INTERNAL_TEMPERATURE + case 'l': return Enum::LCLTIME; + case 'm': return Enum::SUNRISE_M; + case 'n': return Enum::S_LF; + case 'r': return Enum::S_CR; + case 's': return Enum::SPACE; + case 'u': return Enum::UNIT_sysvar; + // case 'v': return Enum::VARIABLE; // Can not be the first 'v' variable, as the name is only 1 character long + case 'v': return Enum::VCC; + case 'w': return Enum::WI_CH; + } + + return Enum::UNKNOWN; +} + +const __FlashStringHelper * SystemVariables::toFlashString(SystemVariables::Enum enumval) +{ + switch (enumval) { + case Enum::BOARD_NAME: return F("board_name"); + case Enum::BOOT_CAUSE: return F("bootcause"); + case Enum::BSSID: return F("bssid"); + case Enum::CLIENTIP: return F("clientip"); + case Enum::CR: return F("CR"); + case Enum::ESP_CHIP_CORES: return F("cpu_cores"); + case Enum::ESP_CHIP_FREQ: return F("cpu_freq"); + case Enum::ESP_CHIP_ID: return F("cpu_id"); + case Enum::ESP_CHIP_MODEL: return F("cpu_model"); + case Enum::ESP_CHIP_REVISION: return F("cpu_rev"); + case Enum::DNS: return F("dns"); + case Enum::DNS_1: return F("dns1"); + case Enum::DNS_2: return F("dns2"); +#if FEATURE_ETHERNET + case Enum::ETHCONNECTED: return F("ethconnected"); + case Enum::ETHDUPLEX: return F("ethduplex"); + case Enum::ETHSPEED: return F("ethspeed"); + case Enum::ETHSPEEDSTATE: return F("ethspeedstate"); + case Enum::ETHSTATE: return F("ethstate"); + case Enum::ETHWIFIMODE: return F("ethwifimode"); +#endif // if FEATURE_ETHERNET + + case Enum::FLASH_CHIP_MODEL: return F("flash_chip_model"); + case Enum::FLASH_CHIP_VENDOR: return F("flash_chip_vendor"); + case Enum::FLASH_FREQ: return F("flash_freq"); + case Enum::FLASH_SIZE: return F("flash_size"); + case Enum::FS_FREE: return F("fs_free"); + case Enum::FS_SIZE: return F("fs_size"); + case Enum::GATEWAY: return F("gateway"); +#if FEATURE_INTERNAL_TEMPERATURE + case Enum::INTERNAL_TEMPERATURE: return F("inttemp"); +#endif // if FEATURE_INTERNAL_TEMPERATURE + + case Enum::IP4: return F("ip4"); + case Enum::IP: return F("ip"); +#if FEATURE_USE_IPV6 + case Enum::IP6_LOCAL: return F("ipv6local"); +#endif + case Enum::ISMQTT: return F("ismqtt"); + case Enum::ISMQTTIMP: return F("ismqttimp"); + case Enum::ISNTP: return F("isntp"); + 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"); + 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"); + case Enum::S_CR: return F("R"); + case Enum::RSSI: return F("rssi"); + case Enum::SPACE: return F("SP"); + case Enum::SSID: return F("ssid"); + case Enum::SUBNET: return F("subnet"); + case Enum::SUNRISE: return F("sunrise"); + case Enum::SUNRISE_S: return F("s_sunrise"); + case Enum::SUNSET: return F("sunset"); + case Enum::SUNSET_S: return F("s_sunset"); + case Enum::SYSBUILD_DATE: return F("sysbuild_date"); + case Enum::SYSBUILD_DESCR: return F("sysbuild_desc"); + case Enum::SYSBUILD_FILENAME: return F("sysbuild_filename"); + case Enum::SYSBUILD_GIT: return F("sysbuild_git"); + case Enum::SYSBUILD_TIME: return F("sysbuild_time"); + case Enum::SYSDAY: return F("sysday"); + case Enum::SYSDAY_0: return F("sysday_0"); + case Enum::SYSHEAP: return F("sysheap"); + case Enum::SYSHOUR: return F("syshour"); + case Enum::SYSHOUR_0: return F("syshour_0"); + case Enum::SYSLOAD: return F("sysload"); + case Enum::SYSMIN: return F("sysmin"); + case Enum::SYSMIN_0: return F("sysmin_0"); + case Enum::SYSMONTH: return F("sysmonth"); + case Enum::SYSMONTH_S: return F("sysmonth_s"); + case Enum::SYSNAME: return F("sysname"); + case Enum::SYSSEC: return F("syssec"); + case Enum::SYSSEC_0: return F("syssec_0"); + case Enum::SYSSEC_D: return F("syssec_d"); + case Enum::SYSSTACK: return F("sysstack"); + case Enum::SYSTIME: return F("systime"); + case Enum::SYSTIME_AM: return F("systime_am"); + case Enum::SYSTIME_AM_0: return F("systime_am_0"); + case Enum::SYSTIME_AM_SP: return F("systime_am_sp"); + case Enum::SYSTM_HM: return F("systm_hm"); + case Enum::SYSTM_HM_0: return F("systm_hm_0"); + case Enum::SYSTM_HM_AM: return F("systm_hm_am"); + case Enum::SYSTM_HM_AM_0: return F("systm_hm_am_0"); + case Enum::SYSTM_HM_AM_SP: return F("systm_hm_am_sp"); + case Enum::SYSTM_HM_SP: return F("systm_hm_sp"); + case Enum::SYSTZOFFSET: return F("systzoffset"); + case Enum::SYSWEEKDAY: return F("sysweekday"); + case Enum::SYSWEEKDAY_S: return F("sysweekday_s"); + case Enum::SYSYEAR: return F("sysyear"); + case Enum::SYSYEARS: return F("sysyears"); + case Enum::SYSYEAR_0: return F("sysyear_0"); + case Enum::SYS_MONTH_0: return F("sysmonth_0"); + case Enum::UNIT_sysvar: return F("unit"); +#if FEATURE_ZEROFILLED_UNITNUMBER + case Enum::UNIT_0_sysvar: return F("unit_0"); +#endif // FEATURE_ZEROFILLED_UNITNUMBER + case Enum::UNIXDAY: return F("unixday"); + case Enum::UNIXDAY_SEC: return F("unixday_sec"); + case Enum::UNIXTIME: return F("unixtime"); + case Enum::UPTIME: return F("uptime"); + case Enum::UPTIME_MS: return F("uptime_ms"); + case Enum::VCC: return F("vcc"); + case Enum::VARIABLE: return F("v"); // Can not be the first 'v' variable, as the name is only 1 character long + case Enum::WI_CH: return F("wi_ch"); + + case Enum::UNKNOWN: break; + } + return F("Unknown"); +} diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h index 220928ff5..d9398b250 100644 --- a/src/src/Helpers/SystemVariables.h +++ b/src/src/Helpers/SystemVariables.h @@ -1,140 +1,143 @@ -#ifndef HELPERS_SYSTEMVARIABLES_H -#define HELPERS_SYSTEMVARIABLES_H - -#include "../../ESPEasy_common.h" - -class SystemVariables { -public: - - enum Enum : uint8_t { - // For optmization, keep enums sorted alfabetically by their flash string - BOARD_NAME, - BOOT_CAUSE, - BSSID, - CLIENTIP, - CR, - DNS, - DNS_1, - DNS_2, - ESP_CHIP_CORES, - ESP_CHIP_FREQ, - ESP_CHIP_ID, - ESP_CHIP_MODEL, - ESP_CHIP_REVISION, -#if FEATURE_ETHERNET - ETHCONNECTED, - ETHDUPLEX, - ETHSPEED, - ETHSPEEDSTATE, - ETHSTATE, - ETHWIFIMODE, -#endif // if FEATURE_ETHERNET - - FLASH_CHIP_MODEL, - FLASH_CHIP_VENDOR, - FLASH_FREQ, - FLASH_SIZE, - FS_FREE, - FS_SIZE, - GATEWAY, -#if FEATURE_INTERNAL_TEMPERATURE - INTERNAL_TEMPERATURE, -#endif // if FEATURE_INTERNAL_TEMPERATURE - - IP4, - IP, - ISMQTT, - ISMQTTIMP, - ISNTP, - ISWIFI, - LCLTIME, - LCLTIME_AM, - LF, - SUNRISE_M, - SUNSET_M, - MAC, - MAC_INT, - S_LF, - S_CR, - RSSI, - SPACE, - SSID, - SUBNET, - SUNRISE, - SUNRISE_S, - SUNSET, - SUNSET_S, - SYSBUILD_DATE, - SYSBUILD_DESCR, - SYSBUILD_FILENAME, - SYSBUILD_GIT, - SYSBUILD_TIME, - SYSDAY, - SYSDAY_0, - SYSHEAP, - SYSHOUR, - SYSHOUR_0, - SYSLOAD, - SYSMIN, - SYSMIN_0, - SYSMONTH, - SYSMONTH_S, - SYSNAME, - SYSSEC, - SYSSEC_0, - SYSSEC_D, - SYSSTACK, - SYSTIME, - SYSTIME_AM, - SYSTIME_AM_0, - SYSTIME_AM_SP, - SYSTM_HM, - SYSTM_HM_0, - SYSTM_HM_AM, - SYSTM_HM_AM_0, - SYSTM_HM_AM_SP, - SYSTM_HM_SP, - SYSTZOFFSET, - SYSWEEKDAY, - SYSWEEKDAY_S, - SYSYEAR, - SYSYEARS, - SYSYEAR_0, - SYS_MONTH_0, - UNIT_sysvar, -#if FEATURE_ZEROFILLED_UNITNUMBER - UNIT_0_sysvar, -#endif // FEATURE_ZEROFILLED_UNITNUMBER - UNIXDAY, - UNIXDAY_SEC, - UNIXTIME, - UPTIME, - UPTIME_MS, - VARIABLE, - VCC, - WI_CH, - - - // Keep UNKNOWN as last - UNKNOWN - }; - - // Find the next thing to replace. - // Return UNKNOWN when nothing needs to be replaced. - static SystemVariables::Enum nextReplacementEnum(const String & str, - SystemVariables::Enum last_tested, - int & last_percent_pos); - - static String toString(SystemVariables::Enum enumval); - - static SystemVariables::Enum startIndex_beginWith(char beginchar); - static const __FlashStringHelper* toFlashString(SystemVariables::Enum enumval); - - static String getSystemVariable(SystemVariables::Enum enumval); - - static void parseSystemVariables(String& s, - boolean useURLencode); -}; - - -#endif // HELPERS_SYSTEMVARIABLES_H +#ifndef HELPERS_SYSTEMVARIABLES_H +#define HELPERS_SYSTEMVARIABLES_H + +#include "../../ESPEasy_common.h" + +class SystemVariables { +public: + + enum Enum : uint8_t { + // For optmization, keep enums sorted alfabetically by their flash string + BOARD_NAME, + BOOT_CAUSE, + BSSID, + CLIENTIP, + CR, + DNS, + DNS_1, + DNS_2, + ESP_CHIP_CORES, + ESP_CHIP_FREQ, + ESP_CHIP_ID, + ESP_CHIP_MODEL, + ESP_CHIP_REVISION, +#if FEATURE_ETHERNET + ETHCONNECTED, + ETHDUPLEX, + ETHSPEED, + ETHSPEEDSTATE, + ETHSTATE, + ETHWIFIMODE, +#endif // if FEATURE_ETHERNET + + FLASH_CHIP_MODEL, + FLASH_CHIP_VENDOR, + FLASH_FREQ, + FLASH_SIZE, + FS_FREE, + FS_SIZE, + GATEWAY, +#if FEATURE_INTERNAL_TEMPERATURE + INTERNAL_TEMPERATURE, +#endif // if FEATURE_INTERNAL_TEMPERATURE + + IP4, + IP, +#if FEATURE_USE_IPV6 + IP6_LOCAL, +#endif + ISMQTT, + ISMQTTIMP, + ISNTP, + ISWIFI, + LCLTIME, + LCLTIME_AM, + LF, + SUNRISE_M, + SUNSET_M, + MAC, + MAC_INT, + S_LF, + S_CR, + RSSI, + SPACE, + SSID, + SUBNET, + SUNRISE, + SUNRISE_S, + SUNSET, + SUNSET_S, + SYSBUILD_DATE, + SYSBUILD_DESCR, + SYSBUILD_FILENAME, + SYSBUILD_GIT, + SYSBUILD_TIME, + SYSDAY, + SYSDAY_0, + SYSHEAP, + SYSHOUR, + SYSHOUR_0, + SYSLOAD, + SYSMIN, + SYSMIN_0, + SYSMONTH, + SYSMONTH_S, + SYSNAME, + SYSSEC, + SYSSEC_0, + SYSSEC_D, + SYSSTACK, + SYSTIME, + SYSTIME_AM, + SYSTIME_AM_0, + SYSTIME_AM_SP, + SYSTM_HM, + SYSTM_HM_0, + SYSTM_HM_AM, + SYSTM_HM_AM_0, + SYSTM_HM_AM_SP, + SYSTM_HM_SP, + SYSTZOFFSET, + SYSWEEKDAY, + SYSWEEKDAY_S, + SYSYEAR, + SYSYEARS, + SYSYEAR_0, + SYS_MONTH_0, + UNIT_sysvar, +#if FEATURE_ZEROFILLED_UNITNUMBER + UNIT_0_sysvar, +#endif // FEATURE_ZEROFILLED_UNITNUMBER + UNIXDAY, + UNIXDAY_SEC, + UNIXTIME, + UPTIME, + UPTIME_MS, + VCC, + VARIABLE, // Can not be the first 'v' variable, as the name is only 1 character long + WI_CH, + + + // Keep UNKNOWN as last + UNKNOWN + }; + + // Find the next thing to replace. + // Return UNKNOWN when nothing needs to be replaced. + static SystemVariables::Enum nextReplacementEnum(const String & str, + SystemVariables::Enum last_tested, + int & last_percent_pos); + + static String toString(SystemVariables::Enum enumval); + + static SystemVariables::Enum startIndex_beginWith(char beginchar); + static const __FlashStringHelper* toFlashString(SystemVariables::Enum enumval); + + static String getSystemVariable(SystemVariables::Enum enumval); + + static void parseSystemVariables(String& s, + boolean useURLencode); +}; + + +#endif // HELPERS_SYSTEMVARIABLES_H diff --git a/src/src/Helpers/TarStream.cpp b/src/src/Helpers/TarStream.cpp new file mode 100644 index 000000000..e8f1568d1 --- /dev/null +++ b/src/src/Helpers/TarStream.cpp @@ -0,0 +1,669 @@ +#include "../Helpers/TarStream.h" + +#if FEATURE_TARSTREAM_SUPPORT +# include "../Globals/ESPEasy_time.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringConverter.h" + +/** + * TarStream : Create/receive a .tar file while streaming via http webserver + * Copyright (c) 2023.. Ton Huisman for ESPEasy + * + * Changelog: See TarStream.h + */ + +/** + * TarFileInfo_struct implementation + */ +TarFileInfo_struct::TarFileInfo_struct(const String fname, + size_t fsize) : + fileName(fname), fileSize(fsize) { + tarSize = ((fsize % TAR_BLOCK_SIZE == 0 ? 0 : 1) + (fsize / TAR_BLOCK_SIZE)) * TAR_BLOCK_SIZE; // Multiple of block size + # if TAR_STREAM_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("Added file: %s, size: %d, tarSize: %d"), fileName.c_str(), fileSize, tarSize)); + } + # endif // if TAR_STREAM_DEBUG +} + +/** + * TarStream implementation + */ +TarStream::TarStream() {} + +TarStream::TarStream(const String fileName) + : _fileName(fileName) {} + +TarStream::TarStream(const String fileName, FileDestination_e destination) + : _fileName(fileName), _destination(destination) {} + +TarStream::~TarStream() { + _filesList.clear(); +} + +size_t TarStream::write(uint8_t ch) { + // TODO implement + addLogMove(LOG_LEVEL_ERROR, F("TarStream: write(ch) NOT IMPLEMENTED YET.")); + return 1u; +} + +size_t TarStream::write(const uint8_t *buf, + size_t size) { + size_t bufOffset = 0u; // Offset into the buffer + bool stayInLoop = true; // To allow processing the rest of the data + + # if TAR_STREAM_DEBUG + const bool logInfo = loglevelActiveFor(LOG_LEVEL_INFO); + # endif // if TAR_STREAM_DEBUG + + while (stayInLoop) { + stayInLoop = false; + + switch (_streamState) { + case TarStreamState_e::Initial: // Initial behaves like WritingHeader + case TarStreamState_e::WritingHeader: + { + if (_headerPosition == 0) { + clearHeader(); + } + const size_t toMove = std::min(size - bufOffset, TAR_HEADER_SIZE - _headerPosition); + + memcpy(&_tarData[_headerPosition], &buf[bufOffset], toMove); + _headerPosition += toMove; + bufOffset += toMove; + + if (_headerPosition == TAR_HEADER_SIZE) { + bool allZeros = true; + + for (size_t n = 0; n < TAR_HEADER_SIZE && allZeros; ++n) { + allZeros &= (_tarData[n] == 0u); + } + + if (allZeros) { + _headerPosition = 0; + _streamState = TarStreamState_e::WritingFinal; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial/WritingHeader to WritingFinal")); + # endif // if TAR_STREAM_DEBUG + } else { + const String fname(_tarHeader.name); + const size_t fsize = strtoul(_tarHeader.size, nullptr, 8); // Octal + const bool isValid = validateHeader() && fname.indexOf('/') == -1; // Don't _allow_ subdirectories + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("TarStream: Write Receiving file %s size: %d"), + fname.c_str(), fsize)); + # else // if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, concat(F("Tar : Load file: "), fname)); + # endif // if TAR_STREAM_DEBUG + } + + if (((_tarHeader.typeflag == REGTYPE) || (_tarHeader.typeflag == AREGTYPE)) && + isValid) { // Checked: typeflag, magic & checksum + addFile(fname, fsize); // Add to list + _fileIndex++; + _filesSizes += fsize; + bool validConfig = true; + + bufOffset += TAR_BLOCK_SIZE - TAR_HEADER_SIZE; // Skip remaining bytes to start of next block + + if (matchFileType(fname, FileType::CONFIG_DAT)) { + validConfig = validateUploadConfigDat(&buf[bufOffset]); + } + + if (validConfig) { + _streamState = TarStreamState_e::WritingFile; + + size_t available = UINT32_MAX; + + if (FileDestination_e::SD != _destination) { // Check flash storage only + available = SpiffsFreeSpace(); // Leave(s) at least 2 blocks free + fs::File tmpfile = tryOpenFile(_filesList[_fileIndex].fileName, F("r"), _destination); + + if (tmpfile) { + available += tmpfile.size(); // Existing file will be deleted + tmpfile.close(); + } + } + + if (available > _filesList[_fileIndex].tarSize) { // Use rounded-up size + // delete and create file for write mode + if (fileExists(_filesList[_fileIndex].fileName) && + !tryDeleteFile(_filesList[_fileIndex].fileName, _destination) && + loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("TarStream: Can't delete file: "), _filesList[_fileIndex].fileName)); + } + _currentFile = tryOpenFile(_filesList[_fileIndex].fileName, F("w"), _destination); + + if (!_currentFile && loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("TarStream: Can't create file: "), _filesList[_fileIndex].fileName)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, concat(F("TarStream: Not enough space to save file: "), + _filesList[_fileIndex].fileName)); + } + } + + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial/WritingHeader to WritingFile")); + # endif // if TAR_STREAM_DEBUG + } else { + _streamState = TarStreamState_e::WritingSlack; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, F("TarStream: Received invalid config.dat, ignored.")); + } + _filesList[_fileIndex].fileName = F("(ignored)"); // Won't be recognized + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial/WritingHeader to WritingSlack")); + # endif // if TAR_STREAM_DEBUG + } + _writePosition = 0u; // Start at file-position 0 + } else { + _streamState = TarStreamState_e::Error; + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("TarStream: Unsupported file: %s, type: %c"), + fname.c_str(), _tarHeader.typeflag)); + } + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial/WritingHeader to Error")); + # endif // if TAR_STREAM_DEBUG + } + } + } + + # if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + if (loglevelActiveFor(TAR_LOG_LEVEL_DEBUG)) { + addLog(TAR_LOG_LEVEL_DEBUG, strformat(F("TarStream: DEBUG WritingHeader size: %d, offset: %d, stay: %d"), + size, bufOffset, stayInLoop)); + } + # endif // if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + break; + } + case TarStreamState_e::WritingFile: + { + const size_t toWrite = std::min(size - bufOffset, _filesList[_fileIndex].fileSize - _writePosition); + + // write to file + if (_currentFile) { + _currentFile.write(&buf[bufOffset], toWrite); + } + + _writePosition += toWrite; + bufOffset += toWrite; + # if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + if (loglevelActiveFor(TAR_LOG_LEVEL_DEBUG)) { + addLog(TAR_LOG_LEVEL_DEBUG, + strformat(F("TarStream: DEBUG WritingFile %d bytes of %d to file, pos: %d, size: %d, bufoff: %d, stay: %d"), + toWrite, _filesList[_fileIndex].fileSize, + _writePosition, size, bufOffset, stayInLoop)); + } + # endif // if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + if (_writePosition == _filesList[_fileIndex].fileSize) { // Done with this file? + // close the file + if (_currentFile) { + _currentFile.close(); + } + + if (_writePosition < _filesList[_fileIndex].tarSize) { + _streamState = TarStreamState_e::WritingSlack; + # if TAR_STREAM_DEBUG + + if (logInfo) { + addLog(LOG_LEVEL_INFO, strformat(F("TarStream: Switch from WritingFile to WritingSlack, bytes: %d"), + _writePosition)); + } + # endif // if TAR_STREAM_DEBUG + } else { + _streamState = TarStreamState_e::WritingHeader; + # if TAR_STREAM_DEBUG + + if (logInfo) { + addLog(LOG_LEVEL_INFO, strformat(F("TarStream: Switch from WritingFile to WritingHeader, bytes: %d"), + _writePosition)); + } + # endif // if TAR_STREAM_DEBUG + _headerPosition = 0; + } + } + + break; + } + case TarStreamState_e::WritingSlack: + { + const size_t toSkip = std::min(size - bufOffset, _filesList[_fileIndex].tarSize - _writePosition); + + _writePosition += toSkip; + bufOffset += toSkip; + # if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + if (loglevelActiveFor(TAR_LOG_LEVEL_DEBUG)) { + addLog(TAR_LOG_LEVEL_DEBUG, strformat(F("%sDEBUG WritingSlack %d bytes of %d to file"), + F("TarStream: "), toSkip, _filesList[_fileIndex].tarSize)); + } + # endif // if !defined(BUILD_NO_DEBUG) && TAR_STREAM_DEBUG + + if (_writePosition == _filesList[_fileIndex].tarSize) { + _streamState = TarStreamState_e::WritingHeader; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from WritingSlack to WritingHeader")); + # endif // if TAR_STREAM_DEBUG + _headerPosition = 0; + } + break; + } + case TarStreamState_e::WritingFinal: + { + // FIXME check entire 512 byte block or just ignore? + const size_t toMove = std::min(size - bufOffset, TAR_HEADER_SIZE - _headerPosition); + memcpy(&_tarData[_headerPosition], &buf[bufOffset], toMove); + _headerPosition += toMove; + bufOffset += toMove; + bool allZeros = true; + + if (_headerPosition == TAR_HEADER_SIZE) { + for (size_t n = 0; n < TAR_HEADER_SIZE && allZeros; ++n) { + allZeros &= (_tarData[n] == 0u); + } + _streamState = TarStreamState_e::WritingDone; + # if TAR_STREAM_DEBUG + + if (logInfo) { + addLog(LOG_LEVEL_INFO, strformat(F("%sWritingFinal to WritingDone, allZeros: %d"), + F("TarStream: Switch from "), allZeros)); + } + # endif // if TAR_STREAM_DEBUG + } + break; + } + case TarStreamState_e::WritingDone: + { + const size_t toSkip = size - bufOffset; + bufOffset += toSkip; + # if TAR_STREAM_DEBUG + + if (logInfo) { + addLog(LOG_LEVEL_INFO, strformat(F("TarStream: WritingDone, skipping: %d"), toSkip)); + } + # endif // if TAR_STREAM_DEBUG + break; + } + case TarStreamState_e::ReadingHeader: // Not here + case TarStreamState_e::ReadingFile: + case TarStreamState_e::ReadingSlack: + case TarStreamState_e::ReadingFinal: + break; + case TarStreamState_e::Error: // No real error state + break; + } + + if (bufOffset < size) { // We got leftover bytes + stayInLoop = true; + } + } + delay(0); + _tarSize += size; + return size; +} + +int TarStream::available() { + return _tarRemaining; +} + +void TarStream::clearHeader() { + memset(&_tarHeader, 0, TAR_HEADER_SIZE); +} + +void TarStream::setupHeader() { + constexpr size_t tarHeader_name_size = NR_ELEMENTS(_tarHeader.name); + + clearHeader(); + safe_strncpy(_tarHeader.name, _currentIndex.fileName.c_str(), tarHeader_name_size); + sprintf(_tarHeader.mode, PSTR("%07o"), TUREAD + TUWRITE + TUEXEC + TGREAD + TGWRITE + TGEXEC + TOREAD + TOWRITE + TOEXEC); + sprintf(_tarHeader.uid, PSTR("%07o"), 0); + sprintf(_tarHeader.gid, PSTR("%07o"), 0); + sprintf(_tarHeader.size, PSTR("%011o"), _currentIndex.fileSize); + sprintf(_tarHeader.mtime, PSTR("%011o"), node_time.getUnixTime()); // We don't have file-date/times, use current date/time + _tarHeader.typeflag = REGTYPE; + sprintf(_tarHeader.magic, PSTR("%s"), TMAGIC); + _tarHeader.version[0] = TVERSION[0]; _tarHeader.version[1] = TVERSION[1]; + + const uint32_t chksum = clearAndCalculateHeaderChecksum(); + + sprintf(_tarHeader.chksum, "%06o", chksum); // FIXME Compatible with 7-zip: 6 octal digits + 0x0 + space? +} + +bool TarStream::validateHeader() { + String magic(_tarHeader.magic); + + magic = magic.substring(0, 6); // Only get magic part + magic.trim(); + const size_t expected = strtoul(_tarHeader.chksum, nullptr, 8); // Octal + const uint32_t chksum = clearAndCalculateHeaderChecksum(); + + # if TAR_STREAM_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("TarStream: Validate Header, magic: %s checksum: %06o expected: %06o (octal)"), + magic.c_str(), chksum, expected)); + } + # endif // if TAR_STREAM_DEBUG + return magic.equals(F(TMAGIC)) && chksum == expected; +} + +uint32_t TarStream::clearAndCalculateHeaderChecksum() { + constexpr size_t tarHeader_chksum_size = NR_ELEMENTS(_tarHeader.chksum); + uint32_t chksum = 0u; + + for (size_t c = 0; c < tarHeader_chksum_size; ++c) { // note: chksum content during calculation is spaces + _tarHeader.chksum[c] = ' '; + } + + for (uint16_t hdr = 0; hdr < TAR_HEADER_SIZE; ++hdr) { + chksum += _tarData[hdr]; + } + return chksum; +} + +int TarStream::read() { + int result = EOF; + + switch (_streamState) { + case TarStreamState_e::Initial: + { + _currentIterator = _filesList.begin(); + _currentIndex = *_currentIterator; + _currentFile = tryOpenFile(_currentIndex.fileName, F("r")); + + if (_currentFile) { + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("Tar : Save file: "), _currentIndex.fileName)); + } + # endif // ifndef BUILD_NO_DEBUG + + // Set up header + setupHeader(); + + // Header done + _streamState = TarStreamState_e::ReadingHeader; + _headerPosition = 0; + result = _tarData[_headerPosition]; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial to ReadingHeader")); + # endif // if TAR_STREAM_DEBUG + } else { + _streamState = TarStreamState_e::Error; + + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from Initial to Error")); + # endif // if TAR_STREAM_DEBUG + } + break; + } + case TarStreamState_e::ReadingHeader: + { + _headerPosition++; + + if (_headerPosition < TAR_HEADER_SIZE) { + result = _tarData[_headerPosition]; + } else if (_headerPosition < TAR_BLOCK_SIZE) { + result = 0; + } else { + _tarPosition = 0; + + if (_currentFile) { + if (_tarPosition < _currentFile.size()) { + result = _currentFile.read(); + _streamState = TarStreamState_e::ReadingFile; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingHeader to ReadingFile")); + # endif // if TAR_STREAM_DEBUG + } else { + _streamState = TarStreamState_e::ReadingSlack; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingHeader to ReadingSlack 0")); + # endif // if TAR_STREAM_DEBUG + result = 0; + } + } else { + _streamState = TarStreamState_e::Error; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingHeader to Error")); + # endif // if TAR_STREAM_DEBUG + } + } + + if (!((TarStreamState_e::ReadingSlack == _streamState) && (_currentIndex.fileSize == 0))) { + break; // Special 0-file case + } + } + case TarStreamState_e::ReadingFile: + { + // FIXME tonhuisman: This looks horrible... but we can't return a single byte for a 0-size file :-( + if (!((TarStreamState_e::ReadingSlack == _streamState) && (_currentIndex.fileSize == 0))) { + _tarPosition++; + + if (_tarPosition < _currentFile.size()) { + result = _currentFile.read(); + break; + } else if (_tarPosition < _currentIndex.tarSize) { + _currentFile.close(); + result = 0; + _streamState = TarStreamState_e::ReadingSlack; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingFile to ReadingSlack 1")); + # endif // if TAR_STREAM_DEBUG + break; + } + _currentFile.close(); + _tarPosition--; // revert 1 position + _streamState = TarStreamState_e::ReadingSlack; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingFile to ReadingSlack 2")); + # endif // if TAR_STREAM_DEBUG + } + + // Fall through + } + case TarStreamState_e::ReadingSlack: + { + _tarPosition++; + + if ((_tarPosition < _currentIndex.tarSize) && (_currentIndex.fileSize != 0)) { + result = 0; + } else { + if (_currentIndex.fileSize == 0) { + _tarPosition--; // Revert 1 position for 0 byte file + } + _currentIterator++; + + if (_currentIterator != _filesList.end()) { + _currentIndex = *_currentIterator; + _currentFile = tryOpenFile(_currentIndex.fileName, F("r")); + + if (_currentFile) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("Tar : Save file: "), _currentIndex.fileName)); + } + + // Set up header + setupHeader(); + + // Header done + _streamState = TarStreamState_e::ReadingHeader; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingSlack to ReadingHeader")); + # endif // if TAR_STREAM_DEBUG + } else { + _streamState = TarStreamState_e::Error; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingSlack to Error")); + # endif // if TAR_STREAM_DEBUG + } + } else { + clearHeader(); + _streamState = TarStreamState_e::ReadingFinal; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingSlack to ReadingFinal")); + # endif // if TAR_STREAM_DEBUG + } + _headerPosition = 0; + result = _tarData[_headerPosition]; + } + break; + } + case TarStreamState_e::ReadingFinal: + { + _headerPosition++; + result = 0; + + if (_headerPosition == (TAR_BLOCK_SIZE * 2)) { + result = EOF; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Reached ReadingFinal EOF")); + # endif // if TAR_STREAM_DEBUG + } else if (_headerPosition > (TAR_BLOCK_SIZE * 2)) { + result = EOF; + _streamState = TarStreamState_e::Error; + # if TAR_STREAM_DEBUG + addLog(LOG_LEVEL_INFO, F("TarStream: Switch from ReadingFinal to Error")); + # endif // if TAR_STREAM_DEBUG + } + break; + } + case TarStreamState_e::WritingHeader: // Not here + case TarStreamState_e::WritingFile: + case TarStreamState_e::WritingSlack: + case TarStreamState_e::WritingFinal: + case TarStreamState_e::WritingDone: + break; + case TarStreamState_e::Error: // Endstate, something went wrong + break; + } + + if (_tarRemaining > 0u) { + _tarRemaining--; + } + + return result; +} + +int TarStream::peek() { + int result = 0; + + # if TAR_STREAM_PEEK + + switch (_streamState) { + case TarStreamState_e::Initial: + break; + case TarStreamState_e::ReadingHeader: + { + if (_headerPosition < TAR_HEADER_SIZE - 1) { + result = _tarData[_headerPosition]; + } else if (_headerPosition < TAR_BLOCK_SIZE - 1) { + result = 0; + } else if (_currentFile) { + result = _currentFile.peek(); + } + break; + } + case TarStreamState_e::ReadingFile: + { + if (_tarPosition < _currentIndex.fileSize + 1) { + result = _currentFile.peek(); + } + break; + } + case TarStreamState_e::ReadingFinal: + { + if (_headerPosition == (TAR_BLOCK_SIZE * 2) - 1) { + result = EOF; + } + break; + } + case TarStreamState_e::ReadingSlack: + case TarStreamState_e::WritingHeader: // Ignore + case TarStreamState_e::WritingFile: + case TarStreamState_e::WritingSlack: + case TarStreamState_e::WritingFinal: + case TarStreamState_e::WritingDone: + break; + case TarStreamState_e::Error: + result = EOF; // Endstate, something went wrong + break; + } + # endif // if TAR_STREAM_PEEK + + return result; +} + +void TarStream::flush() { + if (_currentFile) { + _currentFile.flush(); + } + _tarRemaining = 0u; +} + +size_t TarStream::size() { + return _tarSize; +} + +const char * TarStream::name() { + return _fileName.c_str(); +} + +bool TarStream::addFileIfExists(const String& fileName) { + fs::File tryFile = tryOpenFile(fileName, F("r")); + + if (tryFile) { + addFile(tryFile.name(), tryFile.size()); + + tryFile.close(); + + return true; + } + return false; +} + +bool TarStream::addFile(const String& fileName, + size_t fileSize) { + const TarFileInfo_struct tarFileInfo(fileName, fileSize); + + _filesList.push_back(tarFileInfo); + + if (_tarSize == 0) { + _tarSize = TAR_BLOCK_SIZE * 2; // Closing 2 blocks of 0s + } + _tarSize += TAR_BLOCK_SIZE + tarFileInfo.tarSize; // Header block + rounded-up file-size + _filesSizes += tarFileInfo.fileSize; // Actual file-size + _tarRemaining = _tarSize; + + return true; +} + +bool TarStream::isFileIncluded(const String& filename) { + if (!filename.isEmpty()) { + for (auto it = _filesList.begin(); it != _filesList.end(); ++it) { + if (it->fileName.equalsIgnoreCase(filename)) { + return true; + } + } + } + return false; +} + +size_t TarStream::getFileCount() const { + return _filesList.size(); +} + +#endif // if FEATURE_TARSTREAM_SUPPORT diff --git a/src/src/Helpers/TarStream.h b/src/src/Helpers/TarStream.h new file mode 100644 index 000000000..abc0e5003 --- /dev/null +++ b/src/src/Helpers/TarStream.h @@ -0,0 +1,186 @@ +/** + * TarStream : Create/receive a .tar file while streaming via http webserver + * Copyright (c) 2023.. Ton Huisman for ESPEasy + * Code is inspired by this example: https://github.com/esp8266/Arduino/issues/3966#issuecomment-351850298 + * + * Changelog: + * 2024-01-10 tonhuisman: Fix handling of 0-byte files (next files where shifted 1 byte forward for each 0-byte file) + * 2023-08-27 tonhuisman: Add explicit check for / in filename, to avoid subdirectory/file to overwrite file (subdir is ignored by SPIFFS) + * Check if file exists before trying to delete it, avoiding unneeded error log messages + * Add link to the code that inspired this class + * 2023-08-26 tonhuisman: Code improvements and de-duplication + * 2023-08-24 tonhuisman: Implement streaming in a .tar storing all regular files in the chosen storage (Flash or SD), replacing existing + * files, Flash: adding/replacing only if there is at least 2 blocks of storage available + * 2023-08-23 tonhuisman: Implement streaming out a .tar via the read() method + * 2023-08-19 tonhuisman: Initial setup + */ + +#ifndef HELPERS_TAR_STREAM_H +#define HELPERS_TAR_STREAM_H + +#include "../../ESPEasy_common.h" + +#if FEATURE_TARSTREAM_SUPPORT + +# include "../Helpers/ESPEasy_Storage.h" + +# include +# include + +// This are internal features only, to be used when debugging the code +# define TAR_STREAM_DEBUG 0 // Include/exclude some logging +# define TAR_STREAM_PEEK 0 // Include/exclude peek() +# define TAR_LOG_LEVEL_DEBUG LOG_LEVEL_DEBUG // Can use DEBUG or INFO + +/* tar Header Block, from POSIX 1003.1-1990. */ + +// Source: https://www.gnu.org/software/tar/manual/html_node/Standard.html + +/* POSIX header. */ + +struct posix_header /* Using POSIX Tar-definitions for accuracy */ +{ /* byte offset */ + char name[100]; /* 0 */ + char mode[8]; /* 100 */ + char uid[8]; /* 108 */ + char gid[8]; /* 116 */ + char size[12]; /* 124 */ + char mtime[12]; /* 136 */ + char chksum[8]; /* 148 */ + char typeflag; /* 156 */ + char linkname[100]; /* 157 */ + char magic[6]; /* 257 */ + char version[2]; /* 263 */ + char uname[32]; /* 265 */ + char gname[32]; /* 297 */ + char devmajor[8]; /* 329 */ + char devminor[8]; /* 337 */ + char prefix[155]; /* 345 */ + /* 500 */ +}; + +# define TMAGIC "ustar" /* ustar and a null */ +# define TMAGLEN 6 +# define TVERSION "00" /* 00 and no null */ +# define TVERSLEN 2 + +/* Values used in typeflag field. */ +# define REGTYPE '0' /* regular file */ +# define AREGTYPE '\0' /* regular file */ +# define LNKTYPE '1' /* link */ +# define SYMTYPE '2' /* reserved */ +# define CHRTYPE '3' /* character special */ +# define BLKTYPE '4' /* block special */ +# define DIRTYPE '5' /* directory */ +# define FIFOTYPE '6' /* FIFO special */ +# define CONTTYPE '7' /* reserved */ + +# define XHDTYPE 'x' /* Extended header referring to the next file in the archive */ +# define XGLTYPE 'g' /* Global extended header */ + +/* Bits used in the mode field, values in octal. */ +# define TSUID 04000 /* set UID on execution */ +# define TSGID 02000 /* set GID on execution */ +# define TSVTX 01000 /* reserved */ + /* file permissions */ +# define TUREAD 00400 /* read by owner */ +# define TUWRITE 00200 /* write by owner */ +# define TUEXEC 00100 /* execute/search by owner */ +# define TGREAD 00040 /* read by group */ +# define TGWRITE 00020 /* write by group */ +# define TGEXEC 00010 /* execute/search by group */ +# define TOREAD 00004 /* read by other */ +# define TOWRITE 00002 /* write by other */ +# define TOEXEC 00001 /* execute/search by other */ + +# define TAR_HEADER_EXPECTED_SIZE 500 +constexpr size_t TAR_HEADER_SIZE = sizeof(posix_header); +static_assert(TAR_HEADER_SIZE == TAR_HEADER_EXPECTED_SIZE, "TarStream: posix_header invalid size"); +# undef TAR_HEADER_EXPECTED_SIZE + +constexpr size_t TAR_BLOCK_SIZE = 512u; + +struct TarFileInfo_struct { + TarFileInfo_struct() {} + + TarFileInfo_struct(const String fname, + size_t fsize); + + String fileName; + size_t fileSize; // Actual file size in bytes + size_t tarSize; // File size rounded up to the next 512 byte block (tar block-size) +}; + +enum TarStreamState_e : uint8_t { + Initial = 0u, // Initial state + ReadingHeader, // Processing a file header + ReadingFile, // Reading a file + ReadingSlack, // Reading the slack space after the file data is processed + ReadingFinal, // Reading the empty block after the last file + WritingHeader, // Writing the header data + WritingFile, // Writing the file data + WritingSlack, // Writing nothing, receiving the slack space + WritingFinal, // Writing nothing, receiving the final block + WritingDone, // We're done, just skip any incoming bytes + Error, +}; + +class TarStream : public Stream { +public: + + TarStream(); + TarStream(const String fileName); + TarStream(const String fileName, + FileDestination_e destination); + virtual ~TarStream(); + + virtual size_t write(uint8_t ch); + + virtual size_t write(const uint8_t *buf, + size_t size); + virtual int available(); + virtual int read(); + virtual int peek(); + virtual void flush(); + virtual size_t size(); + virtual const char* name(); + + bool addFileIfExists(const String& fileName); // Check file and add to list if exists + bool addFile(const String& fileName, + size_t fileSize); // Add a file to the list and update _tarSize + bool isFileIncluded(const String& filename); // Is this file included? + size_t getFileCount() const; // Actual number of files in the achive when uploading + size_t getFilesSizes() const { // Actual size of all files in bytes + return _filesSizes; + } + +private: + + void clearHeader(); + void setupHeader(); + bool validateHeader(); + uint32_t clearAndCalculateHeaderChecksum(); + + union { + posix_header _tarHeader; // Header + uint8_t _tarData[TAR_HEADER_SIZE]{}; // Access to the tarheader data + }; + std::vector_filesList; // List of files + String _fileName; // Archive name + size_t _tarSize = 0u; // Total size of the .tar file + size_t _tarRemaining = 0u; // Remaining size of the .tar file (read) + size_t _filesSizes = 0u; // Total sizes of all files + size_t _tarPosition = 0u; // Current position in the _tarSize (read) + size_t _writePosition = 0u; // Current file written bytes + size_t _headerPosition = 0u; // Offset into the current header + int _fileIndex = -1; // Current file in _filesList during write actions + std::vector::iterator _currentIterator; + TarFileInfo_struct _currentIndex; // File we're currently reading + FileDestination_e _destination = FileDestination_e::ANY; // Where to write the files + TarStreamState_e _streamState = TarStreamState_e::Initial; // Current stream state + fs::File _currentFile; // The file currently being handled +}; + +#endif // if FEATURE_TARSTREAM_SUPPORT + +#endif // ifndef HELPERS_TAR_STREAM_H diff --git a/src/src/Helpers/WebServer_commandHelper.cpp b/src/src/Helpers/WebServer_commandHelper.cpp index 2afdd63b0..1a9092d22 100644 --- a/src/src/Helpers/WebServer_commandHelper.cpp +++ b/src/src/Helpers/WebServer_commandHelper.cpp @@ -35,7 +35,7 @@ HandledWebCommand_result handle_command_from_web(EventValueSource::Enum source, if (command_e == ESPEasy_cmd_e::NotMatched) { // For sure not an internal command, try plugin or remote config printToWeb = true; - handledCmd = ExecuteCommand_plugin_config(source, webrequest.c_str()); + handledCmd = ExecuteCommand_plugin_config({source, webrequest.c_str()}); sendOK = false; } else { if ((command_e == ESPEasy_cmd_e::event) || (command_e == ESPEasy_cmd_e::asyncevent)) @@ -43,41 +43,55 @@ HandledWebCommand_result handle_command_from_web(EventValueSource::Enum source, eventQueue.addMove(parseStringToEndKeepCase(webrequest, 2)); handledCmd = true; sendOK = true; - } else if (command_e == ESPEasy_cmd_e::taskrun || - command_e == ESPEasy_cmd_e::taskrunat || - command_e == ESPEasy_cmd_e::scheduletaskrun || - command_e == ESPEasy_cmd_e::taskvalueset || - command_e == ESPEasy_cmd_e::taskvaluesetandrun || - command_e == ESPEasy_cmd_e::taskvaluetoggle || - command_e == ESPEasy_cmd_e::let || + } else { + switch (command_e) { + case ESPEasy_cmd_e::taskrun: + case ESPEasy_cmd_e::taskrunat: + case ESPEasy_cmd_e::scheduletaskrun: + case ESPEasy_cmd_e::taskvalueset: + case ESPEasy_cmd_e::taskvaluesetandrun: + case ESPEasy_cmd_e::taskvaluetoggle: + case ESPEasy_cmd_e::let: #ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - command_e == ESPEasy_cmd_e::logportstatus || + case ESPEasy_cmd_e::logportstatus: #endif - command_e == ESPEasy_cmd_e::logentry || + case ESPEasy_cmd_e::logentry: + case ESPEasy_cmd_e::rules: + sendOK = true; + break; #ifndef BUILD_NO_DIAGNOSTIC_COMMANDS - command_e == ESPEasy_cmd_e::jsonportstatus || + case ESPEasy_cmd_e::jsonportstatus: + sendOK = true; + printToWebJSON = true; + break; #endif - command_e == ESPEasy_cmd_e::rules) { - sendOK = true; +#if FEATURE_USE_IPV6 + case ESPEasy_cmd_e::ip6: + sendOK = true; + printToWebJSON = true; + break; +#endif + default: + sendOK = false; + break; + } // handledCmd = true; - } else { - sendOK = false; - } + } if (!handledCmd) { printToWeb = true; - handledCmd = ExecuteCommand_internal(source, webrequest.c_str()); + handledCmd = ExecuteCommand_internal({source, webrequest.c_str()}); } } if (handledCmd) { if (sendOK) { String reply = printWebString.isEmpty() ? F("OK") : printWebString; - removeChar(reply, '\n'); // Don't use newline in JSON. if (printToWebJSON) { + removeChar(reply, '\n'); // Don't use newline in JSON. // Format return string of command to JSON format printWebString = strformat( - F("{\"return\": \"%s\",\"command\": \"%s\"}"), + F("{\"return\": %s,\"command\": %s}"), to_json_value(reply).c_str(), to_json_value(webrequest).c_str()); } else { diff --git a/src/src/Helpers/WiFi_AP_CandidatesList.cpp b/src/src/Helpers/WiFi_AP_CandidatesList.cpp index 6deadfce4..21b047aa4 100644 --- a/src/src/Helpers/WiFi_AP_CandidatesList.cpp +++ b/src/src/Helpers/WiFi_AP_CandidatesList.cpp @@ -1,551 +1,551 @@ -#include "../Helpers/WiFi_AP_CandidatesList.h" - -#ifdef ESP32 -#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" - -#if defined(ESP8266) - # include -#endif // if defined(ESP8266) -#if defined(ESP32) - # 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 - -WiFi_AP_CandidatesList::WiFi_AP_CandidatesList() { - known.clear(); - candidates.clear(); - known_it = known.begin(); -} - -WiFi_AP_CandidatesList::~WiFi_AP_CandidatesList() { - candidates.clear(); - known.clear(); - scanned.clear(); - scanned_new.clear(); -} - -void WiFi_AP_CandidatesList::load_knownCredentials() { - if (!_mustLoadCredentials && !known.empty()) { return; } - _mustLoadCredentials = false; - known.clear(); - candidates.clear(); -// attemptsLeft = 1; - _addedKnownCandidate = false; -// addFromRTC(); - - { - // Add the known SSIDs - String ssid; - uint8_t index = 1; // Index 0 is the "unset" value - - bool done = false; - - 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); - if (SettingsIndexMatchCustomCredentials(index)) { - if (SettingsIndexMatchEmergencyFallback(index)) { - known.back().isEmergencyFallback = true; - } else { - known.back().lowPriority = true; - } - } - ++index; - } else { - if (SettingsIndexMatchCustomCredentials(index)) { - ++index; - } else { - done = true; - } - } - } - } - loadCandidatesFromScanned(); - addFromRTC(); -} - -void WiFi_AP_CandidatesList::clearCache() { - _mustLoadCredentials = true; - known.clear(); - known_it = known.begin(); -} - - -void WiFi_AP_CandidatesList::force_reload() { - clearCache(); - RTC.clearLastWiFi(); // Invalidate the RTC WiFi data. - candidates.clear(); - loadCandidatesFromScanned(); -} - -void WiFi_AP_CandidatesList::begin_sync_scan() { - candidates.clear(); - _addedKnownCandidate = false; -} - -void WiFi_AP_CandidatesList::purge_expired() { - for (auto it = scanned.begin(); it != scanned.end(); ) { - if (it->expired()) { - it = scanned.erase(it); - } else { - ++it; - } - } -} - -#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN -void WiFi_AP_CandidatesList::process_WiFiscan(uint8_t scancount) { - // Append or update found APs from scan. - for (uint8_t i = 0; i < scancount; ++i) { - const WiFi_AP_Candidate tmp(i); - - scanned_new.push_back(tmp); - } - - 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() { - scanned_new.sort(); - scanned_new.unique(); - _mustLoadCredentials = true; - WiFi.scanDelete(); - attemptsLeft = 1; -} - -bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { - load_knownCredentials(); - - if (candidates.empty()) { - if (scanAllowed) { - return false; - } - loadCandidatesFromScanned(); - attemptsLeft = 1; - if (candidates.empty()) { return false; } - } - - currentCandidate = candidates.front(); - bool mustPop = true; - - if (currentCandidate.isHidden) { - // Iterate over the known credentials to try them all - // Hidden SSID stations do not broadcast their SSID, so we must fill it in ourselves. - if (known_it != known.end()) { - currentCandidate.ssid = known_it->ssid; - currentCandidate.index = known_it->index; - ++known_it; - } - - if (known_it != known.end()) { - mustPop = false; - } - } - - if (mustPop) { - if (attemptsLeft == 0) { - if (currentCandidate.isHidden) { - // 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->isHidden; ) { - cand_it = candidates.erase(cand_it); - } - } else { - if (!candidates.empty()) { - candidates.pop_front(); - } - } - - known_it = known.begin(); - attemptsLeft = 1; - } else { - markAttempt(); - } - } - return currentCandidate.usable(); -} - -const WiFi_AP_Candidate& WiFi_AP_CandidatesList::getCurrent() const { - return currentCandidate; -} - -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; } - } - return WiFi_AP_Candidate(); -} - -bool WiFi_AP_CandidatesList::hasCandidateCredentials() { - load_knownCredentials(); - return !known.empty(); -} - -bool WiFi_AP_CandidatesList::hasCandidates() const { - return !candidates.empty(); -} - -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; - } - } - } - if (currentCandidate.usable()) { - // Store in RTC - RTC.lastWiFiChannel = currentCandidate.channel; - currentCandidate.bssid.get(RTC.lastBSSID); - RTC.lastWiFiSettingsIndex = currentCandidate.index; -#ifdef ESP32 - if (Settings.UseLastWiFiFromRTC()) - WiFi_AP_Candidates_NVS::currentConnection_to_NVS(currentCandidate); - else - WiFi_AP_Candidates_NVS::clear_from_NVS(); -#endif - } - - candidates.clear(); - _addedKnownCandidate = false; - addFromRTC(); // Store the current one from RTC as the first candidate for a reconnect. -} - -int8_t WiFi_AP_CandidatesList::scanComplete() const { - 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) { - return found; - } - const int8_t scanCompleteStatus = WiFi.scanComplete(); - if (scanCompleteStatus <= 0) { - return scanCompleteStatus; - } - return 0; -} - -bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) -{ - return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || - WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || - SettingsIndexMatchEmergencyFallback(index)); -} - -bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) -{ - return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); -} - - -void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { - // Make sure list operations are not done on the 2nd heap - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - 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 - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, concat(F("WiFi : Scan result: "), scan->toString())); - } - #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();) { - if (*tmp == *scan) { - tmp = scanned.erase(tmp); - } else { - ++tmp; - } - } - - // We copy instead of move, to make sure it is stored on the 2nd heap. - scanned.push_back(*scan); - scan = scanned_new.erase(scan); - } - scanned.sort(); - scanned.unique(); - } - - if (candidates.size() > 1) { - // 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()) { - ++it; - } else { - it = known.erase(it); - } - } - known.sort(); - known.unique(); - known_it = known.begin(); - - for (auto scan = scanned.begin(); scan != scanned.end();) { - if (scan->expired()) { - scan = scanned.erase(scan); - } else { - if (scan->isHidden) { - if (Settings.IncludeHiddenSSID()) { - if (SecuritySettings.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.lowPriority = kn_it->lowPriority; - tmp.isEmergencyFallback = kn_it->isEmergencyFallback; - - if (tmp.usable()) { - candidates.push_back(tmp); - _addedKnownCandidate = true; - - // Check all knowns as we may have several AP's with the same SSID and different passwords. - } - } - } - } - ++scan; - } - } - # 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 - candidates.sort(); - candidates.unique(); - addFromRTC(); - purge_unusable(); -} - -void WiFi_AP_CandidatesList::addFromRTC() { - 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); - RTC.lastWiFiSettingsIndex = currentCandidate.index; - } else { - return; - } - #else - return; - #endif - } - - if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) - { - return; - } - - String ssid; - - if (!get_SSID(RTC.lastWiFiSettingsIndex, ssid)) { - return; - } - - WiFi_AP_Candidate fromRTC(RTC.lastWiFiSettingsIndex, ssid); - fromRTC.bssid = RTC.lastBSSID; - fromRTC.channel = RTC.lastWiFiChannel; - - if (!fromRTC.usable()) { - return; - } - - if (candidates.size() > 0 && candidates.front().ssid.equals(fromRTC.ssid)) { - // Front candidate was already from RTC. - 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()) { - bestMatch = it; - } - } - } - 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))) { - // Last used was not found or - // Other candidate has significant better RSSI - matchAdded = true; - candidates.push_front(*bestMatch); - } - } else if (lastUsed != candidates.end()) { - 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) { - candidates.front().enc_type = it->enc_type; - return; - } - } - } - - candidates.front().rssi = -1; // Set to best possible RSSI so it is tried first. - - if (!candidates.front().usable() || !candidates.front().allowQuickConnect()) { - candidates.pop_front(); - return; - } - - if (currentCandidate == candidates.front()) { - candidates.front().enc_type = currentCandidate.enc_type; - } -} - -void WiFi_AP_CandidatesList::purge_unusable() { - for (auto it = candidates.begin(); it != candidates.end();) { - if (it->usable()) { - ++it; - } else { - it = candidates.erase(it); - } - } - if (candidates.size() > 1) { - candidates.sort(); - candidates.unique(); - } -} - -bool WiFi_AP_CandidatesList::get_SSID_key(uint8_t index, String& ssid, String& key) { - switch (index) { - case 1: - ssid = SecuritySettings.WifiSSID; - key = SecuritySettings.WifiKey; - break; - case 2: - ssid = SecuritySettings.WifiSSID2; - key = SecuritySettings.WifiKey2; - break; - case WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX: - #if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) - return false; - #else - ssid = F(CUSTOM_DEPLOYMENT_SSID); - key = F(CUSTOM_DEPLOYMENT_KEY); - #endif - break; - case WIFI_CUSTOM_SUPPORT_KEY_INDEX: - #if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) - return false; - #else - ssid = F(CUSTOM_SUPPORT_SSID); - key = F(CUSTOM_SUPPORT_KEY); - #endif - break; - case WIFI_CREDENTIALS_FALLBACK_SSID_INDEX: - { - #if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) - return false; - #else - int allowedUptimeMinutes = 10; - #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME - allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; - #endif - if (getUptimeMinutes() < allowedUptimeMinutes && SecuritySettings.hasWiFiCredentials()) { - ssid = F(CUSTOM_EMERGENCY_FALLBACK_SSID); - key = F(CUSTOM_EMERGENCY_FALLBACK_KEY); - } else { - return false; - } - #endif - break; - } - default: - return false; - } - - // TODO TD-er: Read other credentials from extra file. - - - - // Spaces are allowed in both SSID and pass phrase, so make sure to not trim the ssid and key. - return true; -} - -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)) - return key; - return EMPTY_STRING; +#include "../Helpers/WiFi_AP_CandidatesList.h" + +#ifdef ESP32 +#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" + +#if defined(ESP8266) + # include +#endif // if defined(ESP8266) +#if defined(ESP32) + # 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 + +WiFi_AP_CandidatesList::WiFi_AP_CandidatesList() { + known.clear(); + candidates.clear(); + known_it = known.begin(); +} + +WiFi_AP_CandidatesList::~WiFi_AP_CandidatesList() { + candidates.clear(); + known.clear(); + scanned.clear(); + scanned_new.clear(); +} + +void WiFi_AP_CandidatesList::load_knownCredentials() { + if (!_mustLoadCredentials && !known.empty()) { return; } + _mustLoadCredentials = false; + known.clear(); + candidates.clear(); +// attemptsLeft = 1; + _addedKnownCandidate = false; +// addFromRTC(); + + { + // Add the known SSIDs + String ssid; + uint8_t index = 1; // Index 0 is the "unset" value + + bool done = false; + + 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); + if (SettingsIndexMatchCustomCredentials(index)) { + if (SettingsIndexMatchEmergencyFallback(index)) { + known.back().bits.isEmergencyFallback = true; + } else { + known.back().bits.lowPriority = true; + } + } + ++index; + } else { + if (SettingsIndexMatchCustomCredentials(index)) { + ++index; + } else { + done = true; + } + } + } + } + loadCandidatesFromScanned(); + addFromRTC(); +} + +void WiFi_AP_CandidatesList::clearCache() { + _mustLoadCredentials = true; + known.clear(); + known_it = known.begin(); +} + + +void WiFi_AP_CandidatesList::force_reload() { + clearCache(); + RTC.clearLastWiFi(); // Invalidate the RTC WiFi data. + candidates.clear(); + loadCandidatesFromScanned(); +} + +void WiFi_AP_CandidatesList::begin_sync_scan() { + candidates.clear(); + _addedKnownCandidate = false; +} + +void WiFi_AP_CandidatesList::purge_expired() { + for (auto it = scanned.begin(); it != scanned.end(); ) { + if (it->expired()) { + it = scanned.erase(it); + } else { + ++it; + } + } +} + +#if !FEATURE_ESP8266_DIRECT_WIFI_SCAN +void WiFi_AP_CandidatesList::process_WiFiscan(uint8_t scancount) { + // Append or update found APs from scan. + for (uint8_t i = 0; i < scancount; ++i) { + const WiFi_AP_Candidate tmp(i); + + scanned_new.push_back(tmp); + } + + 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() { + scanned_new.sort(); + scanned_new.unique(); + _mustLoadCredentials = true; + WiFi.scanDelete(); + attemptsLeft = 1; +} + +bool WiFi_AP_CandidatesList::getNext(bool scanAllowed) { + load_knownCredentials(); + + if (candidates.empty()) { + if (scanAllowed) { + return false; + } + loadCandidatesFromScanned(); + attemptsLeft = 1; + if (candidates.empty()) { return false; } + } + + currentCandidate = candidates.front(); + bool mustPop = true; + + if (currentCandidate.bits.isHidden) { + // Iterate over the known credentials to try them all + // Hidden SSID stations do not broadcast their SSID, so we must fill it in ourselves. + if (known_it != known.end()) { + currentCandidate.ssid = known_it->ssid; + currentCandidate.index = known_it->index; + ++known_it; + } + + if (known_it != known.end()) { + mustPop = false; + } + } + + if (mustPop) { + if (attemptsLeft == 0) { + if (currentCandidate.bits.isHidden) { + // 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; ) { + cand_it = candidates.erase(cand_it); + } + } else { + if (!candidates.empty()) { + candidates.pop_front(); + } + } + + known_it = known.begin(); + attemptsLeft = 1; + } else { + markAttempt(); + } + } + return currentCandidate.usable(); +} + +const WiFi_AP_Candidate& WiFi_AP_CandidatesList::getCurrent() const { + return currentCandidate; +} + +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; } + } + return WiFi_AP_Candidate(); +} + +bool WiFi_AP_CandidatesList::hasCandidateCredentials() { + load_knownCredentials(); + return !known.empty(); +} + +bool WiFi_AP_CandidatesList::hasCandidates() const { + return !candidates.empty(); +} + +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; + } + } + } + if (currentCandidate.usable()) { + // Store in RTC + RTC.lastWiFiChannel = currentCandidate.channel; + currentCandidate.bssid.get(RTC.lastBSSID); + RTC.lastWiFiSettingsIndex = currentCandidate.index; +#ifdef ESP32 + if (Settings.UseLastWiFiFromRTC()) + WiFi_AP_Candidates_NVS::currentConnection_to_NVS(currentCandidate); + else + WiFi_AP_Candidates_NVS::clear_from_NVS(); +#endif + } + + candidates.clear(); + _addedKnownCandidate = false; + addFromRTC(); // Store the current one from RTC as the first candidate for a reconnect. +} + +int8_t WiFi_AP_CandidatesList::scanComplete() const { + 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) { + return found; + } + const int8_t scanCompleteStatus = WiFi.scanComplete(); + if (scanCompleteStatus <= 0) { + return scanCompleteStatus; + } + return 0; +} + +bool WiFi_AP_CandidatesList::SettingsIndexMatchCustomCredentials(uint8_t index) +{ + return (WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX == index || + WIFI_CUSTOM_SUPPORT_KEY_INDEX == index || + SettingsIndexMatchEmergencyFallback(index)); +} + +bool WiFi_AP_CandidatesList::SettingsIndexMatchEmergencyFallback(uint8_t index) +{ + return (WIFI_CREDENTIALS_FALLBACK_SSID_INDEX == index); +} + + +void WiFi_AP_CandidatesList::loadCandidatesFromScanned() { + // Make sure list operations are not done on the 2nd heap + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + 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 + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("WiFi : Scan result: "), scan->toString())); + } + #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();) { + if (*tmp == *scan) { + tmp = scanned.erase(tmp); + } else { + ++tmp; + } + } + + // We copy instead of move, to make sure it is stored on the 2nd heap. + scanned.push_back(*scan); + scan = scanned_new.erase(scan); + } + scanned.sort(); + scanned.unique(); + } + + if (candidates.size() > 1) { + // 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()) { + ++it; + } else { + it = known.erase(it); + } + } + known.sort(); + known.unique(); + known_it = known.begin(); + + for (auto scan = scanned.begin(); scan != scanned.end();) { + if (scan->expired()) { + scan = scanned.erase(scan); + } else { + if (scan->bits.isHidden) { + if (Settings.IncludeHiddenSSID()) { + if (SecuritySettings.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; + tmp.bits.isEmergencyFallback = kn_it->bits.isEmergencyFallback; + + if (tmp.usable()) { + candidates.push_back(tmp); + _addedKnownCandidate = true; + + // Check all knowns as we may have several AP's with the same SSID and different passwords. + } + } + } + } + ++scan; + } + } + # 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 + candidates.sort(); + candidates.unique(); + addFromRTC(); + purge_unusable(); +} + +void WiFi_AP_CandidatesList::addFromRTC() { + 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); + RTC.lastWiFiSettingsIndex = currentCandidate.index; + } else { + return; + } + #else + return; + #endif + } + + if (SettingsIndexMatchCustomCredentials(RTC.lastWiFiSettingsIndex)) + { + return; + } + + String ssid; + + if (!get_SSID(RTC.lastWiFiSettingsIndex, ssid)) { + return; + } + + WiFi_AP_Candidate fromRTC(RTC.lastWiFiSettingsIndex, ssid); + fromRTC.bssid = RTC.lastBSSID; + fromRTC.channel = RTC.lastWiFiChannel; + + if (!fromRTC.usable()) { + return; + } + + if (candidates.size() > 0 && candidates.front().ssid.equals(fromRTC.ssid)) { + // Front candidate was already from RTC. + 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()) { + bestMatch = it; + } + } + } + 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))) { + // Last used was not found or + // Other candidate has significant better RSSI + matchAdded = true; + candidates.push_front(*bestMatch); + } + } else if (lastUsed != candidates.end()) { + 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) { + candidates.front().enc_type = it->enc_type; + return; + } + } + } + + candidates.front().rssi = -1; // Set to best possible RSSI so it is tried first. + + if (!candidates.front().usable() || !candidates.front().allowQuickConnect()) { + candidates.pop_front(); + return; + } + + if (currentCandidate == candidates.front()) { + candidates.front().enc_type = currentCandidate.enc_type; + } +} + +void WiFi_AP_CandidatesList::purge_unusable() { + for (auto it = candidates.begin(); it != candidates.end();) { + if (it->usable()) { + ++it; + } else { + it = candidates.erase(it); + } + } + if (candidates.size() > 1) { + candidates.sort(); + candidates.unique(); + } +} + +bool WiFi_AP_CandidatesList::get_SSID_key(uint8_t index, String& ssid, String& key) { + switch (index) { + case 1: + ssid = SecuritySettings.WifiSSID; + key = SecuritySettings.WifiKey; + break; + case 2: + ssid = SecuritySettings.WifiSSID2; + key = SecuritySettings.WifiKey2; + break; + case WIFI_CUSTOM_DEPLOYMENT_KEY_INDEX: + #if !defined(CUSTOM_DEPLOYMENT_SSID) || !defined(CUSTOM_DEPLOYMENT_KEY) + return false; + #else + ssid = F(CUSTOM_DEPLOYMENT_SSID); + key = F(CUSTOM_DEPLOYMENT_KEY); + #endif + break; + case WIFI_CUSTOM_SUPPORT_KEY_INDEX: + #if !defined(CUSTOM_SUPPORT_SSID) || !defined(CUSTOM_SUPPORT_KEY) + return false; + #else + ssid = F(CUSTOM_SUPPORT_SSID); + key = F(CUSTOM_SUPPORT_KEY); + #endif + break; + case WIFI_CREDENTIALS_FALLBACK_SSID_INDEX: + { + #if !defined(CUSTOM_EMERGENCY_FALLBACK_SSID) || !defined(CUSTOM_EMERGENCY_FALLBACK_KEY) + return false; + #else + int allowedUptimeMinutes = 10; + #ifdef CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME + allowedUptimeMinutes = CUSTOM_EMERGENCY_FALLBACK_ALLOW_MINUTES_UPTIME; + #endif + if (getUptimeMinutes() < allowedUptimeMinutes && SecuritySettings.hasWiFiCredentials()) { + ssid = F(CUSTOM_EMERGENCY_FALLBACK_SSID); + key = F(CUSTOM_EMERGENCY_FALLBACK_KEY); + } else { + return false; + } + #endif + break; + } + default: + return false; + } + + // TODO TD-er: Read other credentials from extra file. + + + + // Spaces are allowed in both SSID and pass phrase, so make sure to not trim the ssid and key. + return true; +} + +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)) + return key; + return EMPTY_STRING; } \ No newline at end of file diff --git a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp index 449c9f6c1..3413f6506 100644 --- a/src/src/Helpers/_CPlugin_DomoticzHelper.cpp +++ b/src/src/Helpers/_CPlugin_DomoticzHelper.cpp @@ -217,12 +217,12 @@ String serializeDomoticzJson(struct EventStruct *event) String json; { json += '{'; - json += to_json_object_value(F("idx"), String(event->idx)); + json += to_json_object_value(F("idx"), static_cast(event->idx)); json += ','; - json += to_json_object_value(F("RSSI"), String(mapRSSItoDomoticz())); + json += to_json_object_value(F("RSSI"), mapRSSItoDomoticz()); # if FEATURE_ADC_VCC json += ','; - json += to_json_object_value(F("Battery"), String(mapVccToDomoticz())); + json += to_json_object_value(F("Battery"), mapVccToDomoticz()); # endif // if FEATURE_ADC_VCC const Sensor_VType sensorType = event->getSensorType(); @@ -242,7 +242,7 @@ String serializeDomoticzJson(struct EventStruct *event) } } else { json += ','; - json += to_json_object_value(F("nvalue"), F("0")); + json += to_json_object_value(F("nvalue"), 0); json += ','; json += to_json_object_value(F("svalue"), formatDomoticzSensorType(event), true); } diff --git a/src/src/Helpers/_CPlugin_Helper.cpp b/src/src/Helpers/_CPlugin_Helper.cpp index 85ce842ee..14120d583 100644 --- a/src/src/Helpers/_CPlugin_Helper.cpp +++ b/src/src/Helpers/_CPlugin_Helper.cpp @@ -1,330 +1,331 @@ -#include "../Helpers/_CPlugin_Helper.h" - -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/CompiletimeDefines.h" -#include "../CustomBuild/ESPEasyLimits.h" - -#include "../DataStructs/SecurityStruct.h" -#include "../DataStructs/SettingsStruct.h" - -#include "../DataStructs/ControllerSettingsStruct.h" -#include "../DataStructs/TimingStats.h" - -#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" -#include "../ESPEasyCore/ESPEasy_Log.h" -#include "../ESPEasyCore/ESPEasyEth.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/Settings.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/ESPEasyWiFiEvent.h" - -#include "../Helpers/ESPEasy_time_calc.h" -#include "../Helpers/Misc.h" -#include "../Helpers/Network.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" - -#include -#include - - -bool safeReadStringUntil(Stream & input, - String & str, - char terminator, - unsigned int maxSize, - unsigned int timeout) -{ - int c; - const unsigned long start = millis(); - const unsigned long timer = start + timeout; - unsigned long backgroundtasks_timer = start + 10; - - str = String(); - - do { - // read character - if (input.available()) { - c = input.read(); - - if (c >= 0) { - // found terminator, we're ok - if (c == terminator) { - return true; - } - - // found character, add to string - str += char(c); - - // string at max size? - if (str.length() >= maxSize) { - addLog(LOG_LEVEL_ERROR, F("Not enough bufferspace to read all input data!")); - return false; - } - } - - // We must run the backgroundtasks every now and then. - if (timeOutReached(backgroundtasks_timer)) { - backgroundtasks_timer += 10; - backgroundtasks(); - } else { - delay(0); - } - } else { - delay(0); - } - } while (!timeOutReached(timer)); - - addLog(LOG_LEVEL_ERROR, F("Timeout while reading input data!")); - return false; -} - -#ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper *prefix, int controller_number, ControllerSettingsStruct& ControllerSettings) { - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = prefix; - log += get_formatted_Controller_number(controller_number); - log += F(" connecting to "); - log += ControllerSettings.getHostPortString(); - addLogMove(LOG_LEVEL_DEBUG, log); - } -} - -#endif // ifndef BUILD_NO_DEBUG - -void log_connecting_fail(const __FlashStringHelper *prefix, int controller_number) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = prefix; - log += get_formatted_Controller_number(controller_number); - log += F(" connection failed ("); - log += WiFiEventData.connectionFailures; - log += F("/"); - log += Settings.ConnectionFailuresThreshold; - log += F(")"); - addLogMove(LOG_LEVEL_ERROR, log); - } -} - -bool count_connection_results(bool success, const __FlashStringHelper *prefix, int controller_number, unsigned long connect_start_time) { - WiFiEventData.connectDurations[controller_number] = timePassedSince(connect_start_time); - if (!success) - { - ++WiFiEventData.connectionFailures; - log_connecting_fail(prefix, controller_number); - return false; - } - statusLED(true); - - if (WiFiEventData.connectionFailures > 0) { - --WiFiEventData.connectionFailures; - } - return true; -} - -bool try_connect_host(int controller_number, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings) { - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } - // 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(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - client.setTimeout(timeout); // in msec as it should be! - delay(0); -#ifndef BUILD_NO_DEBUG - log_connecting_to(F("UDP : "), controller_number, ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - - const unsigned long connect_start_time = millis(); - bool success = ControllerSettings.beginPacket(client); - if (!success) { - client.stop(); - } - const bool result = count_connection_results( - success, - F("UDP : "), - controller_number, - connect_start_time); - STOP_TIMER(TRY_CONNECT_HOST_UDP); - return result; -} - -#if FEATURE_HTTP_CLIENT -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) { - return try_connect_host(controller_number, client, ControllerSettings, F("HTTP : ")); -} - -bool try_connect_host(int controller_number, - WiFiClient & client, - ControllerSettingsStruct & ControllerSettings, - const __FlashStringHelper *loglabel) { - START_TIMER; - - if (!NetworkConnected()) { - client.stop(); - return false; - } - - // Use WiFiClient class to create TCP connections - delay(0); - - // 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(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - #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 - -#ifndef BUILD_NO_DEBUG - log_connecting_to(loglabel, controller_number, ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - - const unsigned long connect_start_time = millis(); - - const bool success = ControllerSettings.connectToHost(client); - if (!success) { - client.stop(); - } - const bool result = count_connection_results( - success, - loglabel, - controller_number, - connect_start_time); - STOP_TIMER(TRY_CONNECT_HOST_TCP); - return result; -} - -// Use "client.available() || client.connected()" to read all lines from slow servers. -// See: https://github.com/esp8266/Arduino/pull/5113 -// https://github.com/esp8266/Arduino/pull/1829 -bool client_available(WiFiClient& client) { - delay(0); - return (client.available() != 0) || (client.connected() != 0); -} - -String send_via_http(int controller_number, - const ControllerSettingsStruct& ControllerSettings, - controllerIndex_t controller_idx, - const String & uri, - const String & HttpMethod, - const String & header, - const String & postStr, - int & httpCode) { - - // Ignoring the ACK from the HTTP 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(controller_number, ControllerSettings.ClientTimeout) - : ControllerSettings.ClientTimeout; - - const unsigned long connect_start_time = millis(); - const String result = send_via_http( - get_formatted_Controller_number(controller_number), - timeout, - getControllerUser(controller_idx, ControllerSettings), - getControllerPass(controller_idx, ControllerSettings), - ControllerSettings.getHost(), - ControllerSettings.Port, - uri, - HttpMethod, - header, - postStr, - httpCode, - ControllerSettings.MustCheckReply); - - // FIXME TD-er: Shouldn't this be: success = (httpCode >= 100) && (httpCode < 300) - // or is reachability of the host the important factor here? - const bool success = httpCode > 0; - - count_connection_results( - success, - F("HTTP : "), - controller_number, - connect_start_time); - - return result; -} -#endif // FEATURE_HTTP_CLIENT - - - - - -String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool doParseTemplate) -{ - if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } - - String res; - if (ControllerSettings.useExtendedCredentials()) { - res = ExtendedControllerCredentials.getControllerUser(controller_idx); - } else { - res = String(SecuritySettings.ControllerUser[controller_idx]); - } - res.trim(); - if (doParseTemplate) { - res = parseTemplate(res); - } - return res; -} - -String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) -{ - if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } - - if (ControllerSettings.useExtendedCredentials()) { - return ExtendedControllerCredentials.getControllerPass(controller_idx); - } - String res(SecuritySettings.ControllerPassword[controller_idx]); - res.trim(); - return res; -} - -void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) -{ - if (!validControllerIndex(controller_idx)) { return; } - - if (ControllerSettings.useExtendedCredentials()) { - ExtendedControllerCredentials.setControllerUser(controller_idx, value); - } else { - safe_strncpy(SecuritySettings.ControllerUser[controller_idx], value, sizeof(SecuritySettings.ControllerUser[0])); - } -} - -void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) -{ - if (!validControllerIndex(controller_idx)) { return; } - - if (ControllerSettings.useExtendedCredentials()) { - ExtendedControllerCredentials.setControllerPass(controller_idx, value); - } else { - safe_strncpy(SecuritySettings.ControllerPassword[controller_idx], value, sizeof(SecuritySettings.ControllerPassword[0])); - } -} - -bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) -{ - return !getControllerUser(controller_idx, ControllerSettings, false).isEmpty() && - !getControllerPass(controller_idx, ControllerSettings).isEmpty(); -} +#include "../Helpers/_CPlugin_Helper.h" + +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/CompiletimeDefines.h" +#include "../CustomBuild/ESPEasyLimits.h" + +#include "../DataStructs/SecurityStruct.h" +#include "../DataStructs/SettingsStruct.h" + +#include "../DataStructs/ControllerSettingsStruct.h" +#include "../DataStructs/TimingStats.h" + +#include "../ESPEasyCore/ESPEasy_backgroundtasks.h" +#include "../ESPEasyCore/ESPEasy_Log.h" +#include "../ESPEasyCore/ESPEasyEth.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/Settings.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/ESPEasyWiFiEvent.h" + +#include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/Misc.h" +#include "../Helpers/Network.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + +#include +#include + + +bool safeReadStringUntil(Stream & input, + String & str, + char terminator, + unsigned int maxSize, + unsigned int timeout) +{ + int c; + const unsigned long start = millis(); + const unsigned long timer = start + timeout; + unsigned long backgroundtasks_timer = start + 10; + + str = String(); + + do { + // read character + if (input.available()) { + c = input.read(); + + if (c >= 0) { + // found terminator, we're ok + if (c == terminator) { + return true; + } + + // found character, add to string + str += char(c); + + // string at max size? + if (str.length() >= maxSize) { + addLog(LOG_LEVEL_ERROR, F("Not enough bufferspace to read all input data!")); + return false; + } + } + + // We must run the backgroundtasks every now and then. + if (timeOutReached(backgroundtasks_timer)) { + backgroundtasks_timer += 10; + backgroundtasks(); + } else { + delay(0); + } + } else { + delay(0); + } + } while (!timeOutReached(timer)); + + addLog(LOG_LEVEL_ERROR, strformat(F("Timeout while reading input data! str: `%s`"), str.c_str())); + return false; +} + +#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, + strformat(F("%s%s connecting to %s"), + prefix, + get_formatted_Controller_number(cpluginID).c_str(), + ControllerSettings.getHostPortString().c_str())); + } +} + +#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) { + protocolIndex_t protocolIndex = getProtocolIndex_from_CPluginID(cpluginID); + if (!success) + { + ++WiFiEventData.connectionFailures; + log_connecting_fail(prefix, cpluginID); + STOP_TIMER_CONTROLLER(protocolIndex, CPlugin::Function::CPLUGIN_CONNECT_FAIL); + return false; + } + WiFiEventData.connectDurations[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()) { + client.stop(); + return false; + } + + // 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(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + client.setTimeout(timeout); // in msec as it should be! + delay(0); +#ifndef BUILD_NO_DEBUG + log_connecting_to(F("UDP : "), cpluginID, ControllerSettings); +#endif // ifndef BUILD_NO_DEBUG + + bool success = ControllerSettings.beginPacket(client); + + if (!success) { + client.stop(); + } + const bool result = count_connection_results( + success, + F("UDP : "), + cpluginID, + statisticsTimerStart); + STOP_TIMER(TRY_CONNECT_HOST_UDP); + return result; +} + +#if FEATURE_HTTP_CLIENT +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings) { + return try_connect_host(cpluginID, client, ControllerSettings, F("HTTP : ")); +} + +bool try_connect_host(cpluginID_t cpluginID, + WiFiClient & client, + ControllerSettingsStruct & ControllerSettings, + const __FlashStringHelper *loglabel) { + const uint64_t statisticsTimerStart(getMicros64()); // START_TIMER; + + if (!NetworkConnected()) { + client.stop(); + return false; + } + + // Use WiFiClient class to create TCP connections + delay(0); + + // 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(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + # 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 + +# ifndef BUILD_NO_DEBUG + log_connecting_to(loglabel, cpluginID, ControllerSettings); +# endif // ifndef BUILD_NO_DEBUG + + const bool success = ControllerSettings.connectToHost(client); + + if (!success) { + client.stop(); + } + const bool result = count_connection_results( + success, + loglabel, + cpluginID, + statisticsTimerStart); + STOP_TIMER(TRY_CONNECT_HOST_TCP); + return result; +} + +// Use "client.available() || client.connected()" to read all lines from slow servers. +// See: https://github.com/esp8266/Arduino/pull/5113 +// https://github.com/esp8266/Arduino/pull/1829 +bool client_available(WiFiClient& client) { + delay(0); + return (client.available() != 0) || (client.connected() != 0); +} + +String send_via_http(int cpluginID, + const ControllerSettingsStruct& ControllerSettings, + controllerIndex_t controller_idx, + const String & uri, + const String & HttpMethod, + const String & header, + const String & postStr, + int & httpCode) { + // Ignoring the ACK from the HTTP 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(cpluginID, ControllerSettings.ClientTimeout) + : ControllerSettings.ClientTimeout; + + const uint64_t statisticsTimerStart(getMicros64()); + const String result = send_via_http( + get_formatted_Controller_number(cpluginID), + timeout, + getControllerUser(controller_idx, ControllerSettings), + getControllerPass(controller_idx, ControllerSettings), + ControllerSettings.getHost(), + ControllerSettings.Port, + uri, + HttpMethod, + header, + postStr, + httpCode, + ControllerSettings.MustCheckReply); + + // FIXME TD-er: Shouldn't this be: success = (httpCode >= 100) && (httpCode < 300) + // or is reachability of the host the important factor here? + const bool success = httpCode > 0; + + count_connection_results( + success, + F("HTTP : "), + cpluginID, + statisticsTimerStart); + + return result; +} + +#endif // FEATURE_HTTP_CLIENT + + +String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool doParseTemplate) +{ + if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } + + String res; + + if (ControllerSettings.useExtendedCredentials()) { + res = ExtendedControllerCredentials.getControllerUser(controller_idx); + } else { + res = String(SecuritySettings.ControllerUser[controller_idx]); + } + res.trim(); + + if (doParseTemplate) { + res = parseTemplate(res); + } + return res; +} + +String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) +{ + if (!validControllerIndex(controller_idx)) { return EMPTY_STRING; } + + if (ControllerSettings.useExtendedCredentials()) { + return ExtendedControllerCredentials.getControllerPass(controller_idx); + } + String res(SecuritySettings.ControllerPassword[controller_idx]); + + res.trim(); + return res; +} + +void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) +{ + if (!validControllerIndex(controller_idx)) { return; } + + if (ControllerSettings.useExtendedCredentials()) { + ExtendedControllerCredentials.setControllerUser(controller_idx, value); + } else { + safe_strncpy(SecuritySettings.ControllerUser[controller_idx], value, sizeof(SecuritySettings.ControllerUser[0])); + } +} + +void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value) +{ + if (!validControllerIndex(controller_idx)) { return; } + + if (ControllerSettings.useExtendedCredentials()) { + ExtendedControllerCredentials.setControllerPass(controller_idx, value); + } else { + safe_strncpy(SecuritySettings.ControllerPassword[controller_idx], value, sizeof(SecuritySettings.ControllerPassword[0])); + } +} + +bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings) +{ + return !getControllerUser(controller_idx, ControllerSettings, false).isEmpty() && + !getControllerPass(controller_idx, ControllerSettings).isEmpty(); +} diff --git a/src/src/Helpers/_CPlugin_Helper.h b/src/src/Helpers/_CPlugin_Helper.h index c6ffae680..0b08d4f01 100644 --- a/src/src/Helpers/_CPlugin_Helper.h +++ b/src/src/Helpers/_CPlugin_Helper.h @@ -1,76 +1,76 @@ -#ifndef CPLUGIN_HELPER_H -#define CPLUGIN_HELPER_H - -#include "../../ESPEasy_common.h" -#include "../../_Plugin_Helper.h" - - -#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 "../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/Networking.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/_CPlugin_Helper_webform.h" - - - -/*********************************************************************************************\ -* Helper functions used in a number of controllers -\*********************************************************************************************/ -bool safeReadStringUntil(Stream & input, - String & str, - char terminator, - unsigned int maxSize = 1024, - unsigned int timeout = 1000); - - -#ifndef BUILD_NO_DEBUG -void log_connecting_to(const __FlashStringHelper * prefix, int controller_number, ControllerSettingsStruct& ControllerSettings); -#endif // ifndef BUILD_NO_DEBUG - -void log_connecting_fail(const __FlashStringHelper * prefix, int controller_number); - -bool count_connection_results(bool success, const __FlashStringHelper * prefix, int controller_number, unsigned long connect_start_time); - -#if FEATURE_HTTP_CLIENT -bool try_connect_host(int controller_number, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings); - -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings); - -bool try_connect_host(int controller_number, WiFiClient& client, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper * loglabel); - -// Use "client.available() || client.connected()" to read all lines from slow servers. -// See: https://github.com/esp8266/Arduino/pull/5113 -// https://github.com/esp8266/Arduino/pull/1829 -bool client_available(WiFiClient& client); - - - -String send_via_http(int controller_number, - const ControllerSettingsStruct& ControllerSettings, - controllerIndex_t controller_idx, - const String & uri, - const String & HttpMethod, - const String & header, - const String & postStr, - int & httpCode); -#endif // FEATURE_HTTP_CLIENT - - -String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool parseTemplate = true); -String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); -void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); -void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); - -bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); - - -#endif // CPLUGIN_HELPER_H +#ifndef CPLUGIN_HELPER_H +#define CPLUGIN_HELPER_H + +#include "../../ESPEasy_common.h" +#include "../../_Plugin_Helper.h" + + +#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 "../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/Networking.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/_CPlugin_Helper_webform.h" + + + +/*********************************************************************************************\ +* Helper functions used in a number of controllers +\*********************************************************************************************/ +bool safeReadStringUntil(Stream & input, + String & str, + char terminator, + unsigned int maxSize = 1024, + unsigned int timeout = 1000); + + +#ifndef BUILD_NO_DEBUG +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 +bool try_connect_host(cpluginID_t cpluginID, WiFiUDP& client, ControllerSettingsStruct& ControllerSettings); + +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings); + +bool try_connect_host(cpluginID_t cpluginID, WiFiClient& client, ControllerSettingsStruct& ControllerSettings, const __FlashStringHelper * loglabel); + +// Use "client.available() || client.connected()" to read all lines from slow servers. +// See: https://github.com/esp8266/Arduino/pull/5113 +// https://github.com/esp8266/Arduino/pull/1829 +bool client_available(WiFiClient& client); + + + +String send_via_http(int cpluginID, + const ControllerSettingsStruct& ControllerSettings, + controllerIndex_t controller_idx, + const String & uri, + const String & HttpMethod, + const String & header, + const String & postStr, + int & httpCode); +#endif // FEATURE_HTTP_CLIENT + + +String getControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, bool parseTemplate = true); +String getControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); +void setControllerUser(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); +void setControllerPass(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings, const String& value); + +bool hasControllerCredentialsSet(controllerIndex_t controller_idx, const ControllerSettingsStruct& ControllerSettings); + + +#endif // CPLUGIN_HELPER_H diff --git a/src/src/Helpers/_Internal_GPIO_pulseHelper.cpp b/src/src/Helpers/_Internal_GPIO_pulseHelper.cpp index 45c678584..047e826ff 100644 --- a/src/src/Helpers/_Internal_GPIO_pulseHelper.cpp +++ b/src/src/Helpers/_Internal_GPIO_pulseHelper.cpp @@ -5,6 +5,7 @@ #include "../ESPEasyCore/ESPEasy_Log.h" #include "../Globals/ESPEasy_Scheduler.h" #include "../Helpers/ESPEasy_time_calc.h" +#include "../Helpers/StringConverter.h" #include "../WebServer/Markup_Forms.h" #include "../../ESPEasy_common.h" @@ -77,8 +78,8 @@ bool Internal_GPIO_pulseHelper::init() attachInterruptArg( digitalPinToInterrupt(config.gpio), config.useEdgeMode() ? - reinterpret_cast(ISR_edgeCheck) : - reinterpret_cast(ISR_pulseCheck), + reinterpret_cast(ISR_edgeCheck) : + reinterpret_cast(ISR_pulseCheck), this, intPinMode); return true; @@ -197,7 +198,7 @@ void Internal_GPIO_pulseHelper::doPulseStepProcessing(int pStep) pulseModeData.Step2NOKcounter++; #endif // PULSE_STATISTIC // lets ignore previous pin status. It might have been a spike. Try to detect stable signal - pulseModeData.lastCheckState = pinState; // now trust the new state + pulseModeData.lastCheckState = pinState; // now trust the new state // after debounceTime/2, do step 2 again Scheduler.setPluginTaskTimer(config.debounceTime >> 1, config.taskIndex, GPIO_PULSE_HELPER_PROCESSING_STEP_2); } @@ -247,7 +248,7 @@ void Internal_GPIO_pulseHelper::doPulseStepProcessing(int pStep) #endif // PULSE_STATISTIC // ignore spike from previous step. It is regarded as spike within previous=current signal. Again try to detect stable signal - pulseModeData.lastCheckState = pinState; // now trust the previous=new state + pulseModeData.lastCheckState = pinState; // now trust the previous=new state Scheduler.setPluginTaskTimer(config.debounceTime >> 1, config.taskIndex, GPIO_PULSE_HELPER_PROCESSING_STEP_2); #ifdef PULSE_STATISTIC @@ -261,9 +262,8 @@ void Internal_GPIO_pulseHelper::doPulseStepProcessing(int pStep) default: { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; log.reserve(48); - log = F("_P003:PLUGIN_TASKTIMER_IN: Invalid processingStep: "); log += pStep; - addLogMove(LOG_LEVEL_ERROR, log); + addLog(LOG_LEVEL_ERROR, + concat(F("_P003:PLUGIN_TASKTIMER_IN: Invalid processingStep: "), pStep)); } break; } @@ -334,11 +334,9 @@ void Internal_GPIO_pulseHelper::processStablePulse(int pinState, uint64_t pulseC default: { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - log.reserve(48); - log = F("_P003:PLUGIN_TASKTIMER_IN: Invalid modeType: "); - log += static_cast(config.interruptPinMode); - addLogMove(LOG_LEVEL_ERROR, log); + addLog(LOG_LEVEL_ERROR, + concat(F("_P003:PLUGIN_TASKTIMER_IN: Invalid modeType: "), + static_cast(config.interruptPinMode))); } break; } @@ -379,7 +377,7 @@ void IRAM_ATTR Internal_GPIO_pulseHelper::ISR_edgeCheck(Internal_GPIO_pulseHelpe self->ISRdata.pulseTime = timeSinceLastTrigger; self->ISRdata.currentStableStartTime = currentTime; // reset when counted to determine interval between counted pulses } - ISR_interrupts(); // enable interrupts again. + ISR_interrupts(); // enable interrupts again. } void IRAM_ATTR Internal_GPIO_pulseHelper::ISR_pulseCheck(Internal_GPIO_pulseHelper *self) @@ -399,7 +397,7 @@ void IRAM_ATTR Internal_GPIO_pulseHelper::ISR_pulseCheck(Internal_GPIO_pulseHelp self->ISRdata.initStepsFlags = true; // PLUGIN_FIFTY_PER_SECOND is polling for this flag set self->ISRdata.triggerTimestamp = getMicros64(); } - ISR_interrupts(); // enable interrupts again. + ISR_interrupts(); // enable interrupts again. } #ifdef PULSE_STATISTIC @@ -437,24 +435,19 @@ void Internal_GPIO_pulseHelper::doStatisticLogging(uint8_t logLevel) { if (loglevelActiveFor(logLevel)) { // Statistic to logfile. E.g: ... [123/1|111|100/5|80/3/4|40] [12243|3244] - String log; - - if (log.reserve(125)) { - log = F("Pulse:"); - log += F("Stats (GPIO) [step0|1|2|3|tot(ok/nok/ign)] [lo|hi]= ("); - log += config.gpio; log += F(") ["); - log += ISRdata.Step0counter; log += '|'; - log += pulseModeData.Step1counter; log += '|'; - log += pulseModeData.Step2OKcounter; log += '/'; - log += pulseModeData.Step2NOKcounter; log += '|'; - log += pulseModeData.Step3OKcounter; log += '/'; - log += pulseModeData.Step3NOKcounter; log += '/'; - log += pulseModeData.Step3IGNcounter; log += '|'; - log += ISRdata.pulseTotalCounter; log += F("] ["); - log += pulseModeData.pulseLowTime / 1000L; log += '|'; - log += pulseModeData.pulseHighTime / 1000L; log += ']'; - addLogMove(logLevel, log); - } + addLog(logLevel, + strformat(F("Pulse:Stats (GPIO) [step0|1|2|3|tot(ok/nok/ign)] [lo|hi]= (%d) [%d|%d/%d|%d/%d/%d|%d][%.4f|%.4f]"), + static_cast(config.gpio), + static_cast(ISRdata.Step0counter), + pulseModeData.Step1counter, + pulseModeData.Step2OKcounter, + pulseModeData.Step2NOKcounter, + pulseModeData.Step3OKcounter, + pulseModeData.Step3NOKcounter, + pulseModeData.Step3IGNcounter, + static_cast(ISRdata.pulseTotalCounter), + pulseModeData.pulseLowTime / 1000L, + pulseModeData.pulseHighTime / 1000L)); } } @@ -468,11 +461,10 @@ void Internal_GPIO_pulseHelper::doTimingLogging(uint8_t logLevel) String log; if (log.reserve(120)) { - log = F("Pulse:"); - log += F("OverDueStats (GPIO) [dbTim] {step0OdCnt} [maxOdTimeStep0|1|2|3]= ("); - log += config.gpio; log += F(") ["); - log += config.debounceTime; log += F("] {"); - log += pulseModeData.Step0ODcounter; log += F("} ["); + log = strformat(F("Pulse:OverDueStats (GPIO) [dbTim] {step0OdCnt} [maxOdTimeStep0|1|2|3]= (%d) [%d] {%d} ["), + config.gpio, + config.debounceTime, + pulseModeData.Step0ODcounter); for (int pStep = 0; pStep <= P003_PSTEP_MAX; pStep++) { log += pulseModeData.StepOverdueMax[pStep]; diff --git a/src/src/Helpers/_Plugin_Helper_serial.cpp b/src/src/Helpers/_Plugin_Helper_serial.cpp index 63ae8a240..cd815129a 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.cpp +++ b/src/src/Helpers/_Plugin_Helper_serial.cpp @@ -17,8 +17,8 @@ #include -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType) { - return ESPEasySerialPort_toString(serType); +String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, bool shortName) { + return ESPEasySerialPort_toString(serType, shortName); } void serialHelper_log_GpioDescription(ESPEasySerialPort typeHint, int config_pin1, int config_pin2) { diff --git a/src/src/Helpers/_Plugin_Helper_serial.h b/src/src/Helpers/_Plugin_Helper_serial.h index 9bf008659..1273c4085 100644 --- a/src/src/Helpers/_Plugin_Helper_serial.h +++ b/src/src/Helpers/_Plugin_Helper_serial.h @@ -11,7 +11,8 @@ struct ESPeasySerialType; -String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType); +String serialHelper_getSerialTypeLabel(ESPEasySerialPort serType, + bool shortName = false); void serialHelper_log_GpioDescription(ESPEasySerialPort typeHint, int config_pin1, @@ -40,8 +41,8 @@ ESPEasySerialPort serialHelper_getSerialType(struct EventStruct *event); String serialHelper_getSerialTypeLabel(struct EventStruct *event); #ifndef DISABLE_SC16IS752_Serial -void serialHelper_addI2CuartSelectors(int address, - int channel); +void serialHelper_addI2CuartSelectors(int address, + int channel); #endif // ifndef DISABLE_SC16IS752_Serial void serialHelper_webformLoad(struct EventStruct *event); diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp index c42b7edda..d51c4dd4b 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.cpp +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.cpp @@ -10,7 +10,7 @@ -void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pconfigIndex) +void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, int pconfigIndex) { const uint8_t optionValues[] { static_cast(Sensor_VType::SENSOR_TYPE_SINGLE), @@ -48,7 +48,7 @@ void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pc sensorTypeHelper_webformLoad(event, pconfigIndex, optionCount, optionValues); } -void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pconfigIndex) +void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigIndex) { const uint8_t optionValues[] { static_cast(Sensor_VType::SENSOR_TYPE_SINGLE), @@ -61,29 +61,31 @@ void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pcon sensorTypeHelper_webformLoad(event, pconfigIndex, optionCount, optionValues); } -void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigIndex, int optionCount, const uint8_t options[]) +void sensorTypeHelper_webformLoad(struct EventStruct *event, int pconfigIndex, int optionCount, const uint8_t options[]) { addFormSubHeader(F("Output Configuration")); - if (pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { return; } Sensor_VType choice = static_cast(PCONFIG(pconfigIndex)); const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(event->TaskIndex); if (!validDeviceIndex(DeviceIndex)) { + // FIXME TD-er: Should we even continue here? choice = Sensor_VType::SENSOR_TYPE_NONE; PCONFIG(pconfigIndex) = static_cast(choice); } else if (getValueCountFromSensorType(choice) != getValueCountForTask(event->TaskIndex)) { // Invalid value - checkDeviceVTypeForTask(event); - choice = event->sensorType; - PCONFIG(pconfigIndex) = static_cast(choice); + if (checkDeviceVTypeForTask(event) >= 0) { + choice = event->sensorType; + PCONFIG(pconfigIndex) = static_cast(choice); + } } const __FlashStringHelper *outputTypeLabel = F("Output Data Type"); - if (Device[DeviceIndex].OutputDataType == Output_Data_type_t::Simple) { + if (validDeviceIndex(DeviceIndex) && Device[DeviceIndex].OutputDataType == Output_Data_type_t::Simple) { if (!isSimpleOutputDataType(event->sensorType)) { choice = Device[DeviceIndex].VType; @@ -92,7 +94,7 @@ void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigInde outputTypeLabel = F("Number Output Values"); } addRowLabel(outputTypeLabel); - addSelector_Head(PCONFIG_LABEL(pconfigIndex)); + addSelector_Head(sensorTypeHelper_webformID(pconfigIndex)); for (uint8_t x = 0; x < optionCount; x++) { @@ -114,7 +116,7 @@ void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigInde PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, event, dummy); } -void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valueIndex, const String& defaultValueName) +void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, int pconfigIndex, uint8_t valueIndex, const String& defaultValueName) { const bool isDefault = defaultValueName.equals(ExtraTaskSettings.TaskDeviceValueNames[valueIndex]); if (isDefault) { @@ -124,18 +126,26 @@ void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pcon pconfig_webformSave(event, pconfigIndex); } -void pconfig_webformSave(struct EventStruct *event, uint8_t pconfigIndex) +void pconfig_webformSave(struct EventStruct *event, int pconfigIndex) { - PCONFIG(pconfigIndex) = getFormItemInt(PCONFIG_LABEL(pconfigIndex), PCONFIG(pconfigIndex)); + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + + PCONFIG(pconfigIndex) = getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), PCONFIG(pconfigIndex)); } void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const __FlashStringHelper *options[], const int indices[]) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + addFormSelector( concat(F("Value "), valuenr + 1), - PCONFIG_LABEL(pconfigIndex), + sensorTypeHelper_webformID(pconfigIndex), optionCount, options, indices, @@ -143,14 +153,26 @@ void sensorTypeHelper_loadOutputSelector( } void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const String options[], const int indices[]) { + if (pconfigIndex < 0 || pconfigIndex >= PLUGIN_CONFIGVAR_MAX) { + return; + } + addFormSelector( concat(F("Value "), valuenr + 1), - PCONFIG_LABEL(pconfigIndex), + sensorTypeHelper_webformID(pconfigIndex), optionCount, options, indices, PCONFIG(pconfigIndex)); } + +String sensorTypeHelper_webformID(int pconfigIndex) +{ + if (pconfigIndex >= 0 && pconfigIndex < PLUGIN_CONFIGVAR_MAX) { + return concat(F("pconfigIndex_"), pconfigIndex); + } + return F("error"); +} \ No newline at end of file diff --git a/src/src/Helpers/_Plugin_SensorTypeHelper.h b/src/src/Helpers/_Plugin_SensorTypeHelper.h index 165322c26..262d42fe3 100644 --- a/src/src/Helpers/_Plugin_SensorTypeHelper.h +++ b/src/src/Helpers/_Plugin_SensorTypeHelper.h @@ -5,23 +5,24 @@ #include "../DataStructs/DeviceStruct.h" -void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, uint8_t pconfigIndex); +void sensorTypeHelper_webformLoad_allTypes(struct EventStruct *event, int pconfigIndex); -void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, uint8_t pconfigIndex); +void sensorTypeHelper_webformLoad_simple(struct EventStruct *event, int pconfigIndex); -void sensorTypeHelper_webformLoad(struct EventStruct *event, uint8_t pconfigIndex, int optionCount, const uint8_t options[]); +void sensorTypeHelper_webformLoad(struct EventStruct *event, int pconfigIndex, int optionCount, const uint8_t options[]); -void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uint8_t valueIndex, const String& defaultValueName); +void sensorTypeHelper_saveOutputSelector(struct EventStruct *event, int pconfigIndex, uint8_t valueIndex, const String& defaultValueName); -void pconfig_webformSave(struct EventStruct *event, uint8_t pconfigIndex); +void pconfig_webformSave(struct EventStruct *event, int pconfigIndex); void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const __FlashStringHelper * options[], const int indices[] = nullptr); void sensorTypeHelper_loadOutputSelector( - struct EventStruct *event, uint8_t pconfigIndex, uint8_t valuenr, + struct EventStruct *event, int pconfigIndex, uint8_t valuenr, int optionCount, const String options[], const int indices[] = nullptr); +String sensorTypeHelper_webformID(int pconfigIndex); #endif // HELPER_CPLUGIN_SENSORTYPEHELPER_H \ No newline at end of file diff --git a/src/src/Helpers/_Plugin_init.cpp b/src/src/Helpers/_Plugin_init.cpp index 7ef5bfc14..6669335b3 100644 --- a/src/src/Helpers/_Plugin_init.cpp +++ b/src/src/Helpers/_Plugin_init.cpp @@ -1,2290 +1,2292 @@ -#include "../Helpers/_Plugin_init.h" - -#include "../../ESPEasy_common.h" - -#include "../Globals/Device.h" -#include "../Globals/Settings.h" - -#include "../Helpers/Misc.h" - - -// ******************************************************************************** -// Initialize all plugins that where defined earlier -// and initialize the function call pointer into the plugin array -// ******************************************************************************** - - - -// Vector to match a "DeviceIndex" to a plugin ID. -constexpr /*pluginID_t*/ uint8_t DeviceIndex_to_Plugin_id[] PROGMEM = -{ -#ifdef USES_P001 - 1, -#endif // ifdef USES_P001 - -#ifdef USES_P002 - 2, -#endif // ifdef USES_P002 - -#ifdef USES_P003 - 3, -#endif // ifdef USES_P003 - -#ifdef USES_P004 - 4, -#endif // ifdef USES_P004 - -#ifdef USES_P005 - 5, -#endif // ifdef USES_P005 - -#ifdef USES_P006 - 6, -#endif // ifdef USES_P006 - -#ifdef USES_P007 - 7, -#endif // ifdef USES_P007 - -#ifdef USES_P008 - 8, -#endif // ifdef USES_P008 - -#ifdef USES_P009 - 9, -#endif // ifdef USES_P009 - -#ifdef USES_P010 - 10, -#endif // ifdef USES_P010 - -#ifdef USES_P011 - 11, -#endif // ifdef USES_P011 - -#ifdef USES_P012 - 12, -#endif // ifdef USES_P012 - -#ifdef USES_P013 - 13, -#endif // ifdef USES_P013 - -#ifdef USES_P014 - 14, -#endif // ifdef USES_P014 - -#ifdef USES_P015 - 15, -#endif // ifdef USES_P015 - -#ifdef USES_P016 - 16, -#endif // ifdef USES_P016 - -#ifdef USES_P017 - 17, -#endif // ifdef USES_P017 - -#ifdef USES_P018 - 18, -#endif // ifdef USES_P018 - -#ifdef USES_P019 - 19, -#endif // ifdef USES_P019 - -#ifdef USES_P020 - 20, -#endif // ifdef USES_P020 - -#ifdef USES_P021 - 21, -#endif // ifdef USES_P021 - -#ifdef USES_P022 - 22, -#endif // ifdef USES_P022 - -#ifdef USES_P023 - 23, -#endif // ifdef USES_P023 - -#ifdef USES_P024 - 24, -#endif // ifdef USES_P024 - -#ifdef USES_P025 - 25, -#endif // ifdef USES_P025 - -#ifdef USES_P026 - 26, -#endif // ifdef USES_P026 - -#ifdef USES_P027 - 27, -#endif // ifdef USES_P027 - -#ifdef USES_P028 - 28, -#endif // ifdef USES_P028 - -#ifdef USES_P029 - 29, -#endif // ifdef USES_P029 - -#ifdef USES_P030 - 30, -#endif // ifdef USES_P030 - -#ifdef USES_P031 - 31, -#endif // ifdef USES_P031 - -#ifdef USES_P032 - 32, -#endif // ifdef USES_P032 - -#ifdef USES_P033 - 33, -#endif // ifdef USES_P033 - -#ifdef USES_P034 - 34, -#endif // ifdef USES_P034 - -#ifdef USES_P035 - 35, -#endif // ifdef USES_P035 - -#ifdef USES_P036 - 36, -#endif // ifdef USES_P036 - -#ifdef USES_P037 - 37, -#endif // ifdef USES_P037 - -#ifdef USES_P038 - 38, -#endif // ifdef USES_P038 - -#ifdef USES_P039 - 39, -#endif // ifdef USES_P039 - -#ifdef USES_P040 - 40, -#endif // ifdef USES_P040 - -#ifdef USES_P041 - 41, -#endif // ifdef USES_P041 - -#ifdef USES_P042 - 42, -#endif // ifdef USES_P042 - -#ifdef USES_P043 - 43, -#endif // ifdef USES_P043 - -#ifdef USES_P044 - 44, -#endif // ifdef USES_P044 - -#ifdef USES_P045 - 45, -#endif // ifdef USES_P045 - -#ifdef USES_P046 - 46, -#endif // ifdef USES_P046 - -#ifdef USES_P047 - 47, -#endif // ifdef USES_P047 - -#ifdef USES_P048 - 48, -#endif // ifdef USES_P048 - -#ifdef USES_P049 - 49, -#endif // ifdef USES_P049 - -#ifdef USES_P050 - 50, -#endif // ifdef USES_P050 - -#ifdef USES_P051 - 51, -#endif // ifdef USES_P051 - -#ifdef USES_P052 - 52, -#endif // ifdef USES_P052 - -#ifdef USES_P053 - 53, -#endif // ifdef USES_P053 - -#ifdef USES_P054 - 54, -#endif // ifdef USES_P054 - -#ifdef USES_P055 - 55, -#endif // ifdef USES_P055 - -#ifdef USES_P056 - 56, -#endif // ifdef USES_P056 - -#ifdef USES_P057 - 57, -#endif // ifdef USES_P057 - -#ifdef USES_P058 - 58, -#endif // ifdef USES_P058 - -#ifdef USES_P059 - 59, -#endif // ifdef USES_P059 - -#ifdef USES_P060 - 60, -#endif // ifdef USES_P060 - -#ifdef USES_P061 - 61, -#endif // ifdef USES_P061 - -#ifdef USES_P062 - 62, -#endif // ifdef USES_P062 - -#ifdef USES_P063 - 63, -#endif // ifdef USES_P063 - -#ifdef USES_P064 - 64, -#endif // ifdef USES_P064 - -#ifdef USES_P065 - 65, -#endif // ifdef USES_P065 - -#ifdef USES_P066 - 66, -#endif // ifdef USES_P066 - -#ifdef USES_P067 - 67, -#endif // ifdef USES_P067 - -#ifdef USES_P068 - 68, -#endif // ifdef USES_P068 - -#ifdef USES_P069 - 69, -#endif // ifdef USES_P069 - -#ifdef USES_P070 - 70, -#endif // ifdef USES_P070 - -#ifdef USES_P071 - 71, -#endif // ifdef USES_P071 - -#ifdef USES_P072 - 72, -#endif // ifdef USES_P072 - -#ifdef USES_P073 - 73, -#endif // ifdef USES_P073 - -#ifdef USES_P074 - 74, -#endif // ifdef USES_P074 - -#ifdef USES_P075 - 75, -#endif // ifdef USES_P075 - -#ifdef USES_P076 - 76, -#endif // ifdef USES_P076 - -#ifdef USES_P077 - 77, -#endif // ifdef USES_P077 - -#ifdef USES_P078 - 78, -#endif // ifdef USES_P078 - -#ifdef USES_P079 - 79, -#endif // ifdef USES_P079 - -#ifdef USES_P080 - 80, -#endif // ifdef USES_P080 - -#ifdef USES_P081 - 81, -#endif // ifdef USES_P081 - -#ifdef USES_P082 - 82, -#endif // ifdef USES_P082 - -#ifdef USES_P083 - 83, -#endif // ifdef USES_P083 - -#ifdef USES_P084 - 84, -#endif // ifdef USES_P084 - -#ifdef USES_P085 - 85, -#endif // ifdef USES_P085 - -#ifdef USES_P086 - 86, -#endif // ifdef USES_P086 - -#ifdef USES_P087 - 87, -#endif // ifdef USES_P087 - -#ifdef USES_P088 - 88, -#endif // ifdef USES_P088 - -#ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 - 89, - # endif // ifdef ESP8266 -#endif // ifdef USES_P089 - -#ifdef USES_P090 - 90, -#endif // ifdef USES_P090 - -#ifdef USES_P091 - 91, -#endif // ifdef USES_P091 - -#ifdef USES_P092 - 92, -#endif // ifdef USES_P092 - -#ifdef USES_P093 - 93, -#endif // ifdef USES_P093 - -#ifdef USES_P094 - 94, -#endif // ifdef USES_P094 - -#ifdef USES_P095 - 95, -#endif // ifdef USES_P095 - -#ifdef USES_P096 - 96, -#endif // ifdef USES_P096 - -#ifdef USES_P097 - # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) - - // Touch (ESP32) - 97, - # endif // if defined(ESP32) && !defined(ESP32Cxx) -#endif // ifdef USES_P097 - -#ifdef USES_P098 - 98, -#endif // ifdef USES_P098 - -#ifdef USES_P099 - 99, -#endif // ifdef USES_P099 - -#ifdef USES_P100 - 100, -#endif // ifdef USES_P100 - -#ifdef USES_P101 - 101, -#endif // ifdef USES_P101 - -#ifdef USES_P102 - 102, -#endif // ifdef USES_P102 - -#ifdef USES_P103 - 103, -#endif // ifdef USES_P103 - -#ifdef USES_P104 - 104, -#endif // ifdef USES_P104 - -#ifdef USES_P105 - 105, -#endif // ifdef USES_P105 - -#ifdef USES_P106 - 106, -#endif // ifdef USES_P106 - -#ifdef USES_P107 - 107, -#endif // ifdef USES_P107 - -#ifdef USES_P108 - 108, -#endif // ifdef USES_P108 - -#ifdef USES_P109 - 109, -#endif // ifdef USES_P109 - -#ifdef USES_P110 - 110, -#endif // ifdef USES_P110 - -#ifdef USES_P111 - 111, -#endif // ifdef USES_P111 - -#ifdef USES_P112 - 112, -#endif // ifdef USES_P112 - -#ifdef USES_P113 - 113, -#endif // ifdef USES_P113 - -#ifdef USES_P114 - 114, -#endif // ifdef USES_P114 - -#ifdef USES_P115 - 115, -#endif // ifdef USES_P115 - -#ifdef USES_P116 - 116, -#endif // ifdef USES_P116 - -#ifdef USES_P117 - 117, -#endif // ifdef USES_P117 - -#ifdef USES_P118 - 118, -#endif // ifdef USES_P118 - -#ifdef USES_P119 - 119, -#endif // ifdef USES_P119 - -#ifdef USES_P120 - 120, -#endif // ifdef USES_P120 - -#ifdef USES_P121 - 121, -#endif // ifdef USES_P121 - -#ifdef USES_P122 - 122, -#endif // ifdef USES_P122 - -#ifdef USES_P123 - 123, -#endif // ifdef USES_P123 - -#ifdef USES_P124 - 124, -#endif // ifdef USES_P124 - -#ifdef USES_P125 - 125, -#endif // ifdef USES_P125 - -#ifdef USES_P126 - 126, -#endif // ifdef USES_P126 - -#ifdef USES_P127 - 127, -#endif // ifdef USES_P127 - -#ifdef USES_P128 - 128, -#endif // ifdef USES_P128 - -#ifdef USES_P129 - 129, -#endif // ifdef USES_P129 - -#ifdef USES_P130 - 130, -#endif // ifdef USES_P130 - -#ifdef USES_P131 - 131, -#endif // ifdef USES_P131 - -#ifdef USES_P132 - 132, -#endif // ifdef USES_P132 - -#ifdef USES_P133 - 133, -#endif // ifdef USES_P133 - -#ifdef USES_P134 - 134, -#endif // ifdef USES_P134 - -#ifdef USES_P135 - 135, -#endif // ifdef USES_P135 - -#ifdef USES_P136 - 136, -#endif // ifdef USES_P136 - -#ifdef USES_P137 - 137, -#endif // ifdef USES_P137 - -#ifdef USES_P138 - 138, -#endif // ifdef USES_P138 - -#ifdef USES_P139 - 139, -#endif // ifdef USES_P139 - -#ifdef USES_P140 - 140, -#endif // ifdef USES_P140 - -#ifdef USES_P141 - 141, -#endif // ifdef USES_P141 - -#ifdef USES_P142 - 142, -#endif // ifdef USES_P142 - -#ifdef USES_P143 - 143, -#endif // ifdef USES_P143 - -#ifdef USES_P144 - 144, -#endif // ifdef USES_P144 - -#ifdef USES_P145 - 145, -#endif // ifdef USES_P145 - -#ifdef USES_P146 - 146, -#endif // ifdef USES_P146 - -#ifdef USES_P147 - 147, -#endif // ifdef USES_P147 - -#ifdef USES_P148 - 148, -#endif // ifdef USES_P148 - -#ifdef USES_P149 - 149, -#endif // ifdef USES_P149 - -#ifdef USES_P150 - 150, -#endif // ifdef USES_P150 - -#ifdef USES_P151 - 151, -#endif // ifdef USES_P151 - -#ifdef USES_P152 - 152, -#endif // ifdef USES_P152 - -#ifdef USES_P153 - 153, -#endif // ifdef USES_P153 - -#ifdef USES_P154 - 154, -#endif // ifdef USES_P154 - -#ifdef USES_P155 - 155, -#endif // ifdef USES_P155 - -#ifdef USES_P156 - 156, -#endif // ifdef USES_P156 - -#ifdef USES_P157 - 157, -#endif // ifdef USES_P157 - -#ifdef USES_P158 - 158, -#endif // ifdef USES_P158 - -#ifdef USES_P159 - 159, -#endif // ifdef USES_P159 - -#ifdef USES_P160 - 160, -#endif // ifdef USES_P160 - -#ifdef USES_P161 - 161, -#endif // ifdef USES_P161 - -#ifdef USES_P162 - 162, -#endif // ifdef USES_P162 - -#ifdef USES_P163 - 163, -#endif // ifdef USES_P163 - -#ifdef USES_P164 - 164, -#endif // ifdef USES_P164 - -#ifdef USES_P165 - 165, -#endif // ifdef USES_P165 - -#ifdef USES_P166 - 166, -#endif // ifdef USES_P166 - -#ifdef USES_P167 - 167, -#endif // ifdef USES_P167 - -#ifdef USES_P168 - 168, -#endif // ifdef USES_P168 - -#ifdef USES_P169 - 169, -#endif // ifdef USES_P169 - -#ifdef USES_P170 - 170, -#endif // ifdef USES_P170 - -#ifdef USES_P171 - 171, -#endif // ifdef USES_P171 - -#ifdef USES_P172 - 172, -#endif // ifdef USES_P172 - -#ifdef USES_P173 - 173, -#endif // ifdef USES_P173 - -#ifdef USES_P174 - 174, -#endif // ifdef USES_P174 - -#ifdef USES_P175 - 175, -#endif // ifdef USES_P175 - -#ifdef USES_P176 - 176, -#endif // ifdef USES_P176 - -#ifdef USES_P177 - 177, -#endif // ifdef USES_P177 - -#ifdef USES_P178 - 178, -#endif // ifdef USES_P178 - -#ifdef USES_P179 - 179, -#endif // ifdef USES_P179 - -#ifdef USES_P180 - 180, -#endif // ifdef USES_P180 - -#ifdef USES_P181 - 181, -#endif // ifdef USES_P181 - -#ifdef USES_P182 - 182, -#endif // ifdef USES_P182 - -#ifdef USES_P183 - 183, -#endif // ifdef USES_P183 - -#ifdef USES_P184 - 184, -#endif // ifdef USES_P184 - -#ifdef USES_P185 - 185, -#endif // ifdef USES_P185 - -#ifdef USES_P186 - 186, -#endif // ifdef USES_P186 - -#ifdef USES_P187 - 187, -#endif // ifdef USES_P187 - -#ifdef USES_P188 - 188, -#endif // ifdef USES_P188 - -#ifdef USES_P189 - 189, -#endif // ifdef USES_P189 - -#ifdef USES_P190 - 190, -#endif // ifdef USES_P190 - -#ifdef USES_P191 - 191, -#endif // ifdef USES_P191 - -#ifdef USES_P192 - 192, -#endif // ifdef USES_P192 - -#ifdef USES_P193 - 193, -#endif // ifdef USES_P193 - -#ifdef USES_P194 - 194, -#endif // ifdef USES_P194 - -#ifdef USES_P195 - 195, -#endif // ifdef USES_P195 - -#ifdef USES_P196 - 196, -#endif // ifdef USES_P196 - -#ifdef USES_P197 - 197, -#endif // ifdef USES_P197 - -#ifdef USES_P198 - 198, -#endif // ifdef USES_P198 - -#ifdef USES_P199 - 199, -#endif // ifdef USES_P199 - -#ifdef USES_P200 - 200, -#endif // ifdef USES_P200 - -#ifdef USES_P201 - 201, -#endif // ifdef USES_P201 - -#ifdef USES_P202 - 202, -#endif // ifdef USES_P202 - -#ifdef USES_P203 - 203, -#endif // ifdef USES_P203 - -#ifdef USES_P204 - 204, -#endif // ifdef USES_P204 - -#ifdef USES_P205 - 205, -#endif // ifdef USES_P205 - -#ifdef USES_P206 - 206, -#endif // ifdef USES_P206 - -#ifdef USES_P207 - 207, -#endif // ifdef USES_P207 - -#ifdef USES_P208 - 208, -#endif // ifdef USES_P208 - -#ifdef USES_P209 - 209, -#endif // ifdef USES_P209 - -#ifdef USES_P210 - 210, -#endif // ifdef USES_P210 - -#ifdef USES_P211 - 211, -#endif // ifdef USES_P211 - -#ifdef USES_P212 - 212, -#endif // ifdef USES_P212 - -#ifdef USES_P213 - 213, -#endif // ifdef USES_P213 - -#ifdef USES_P214 - 214, -#endif // ifdef USES_P214 - -#ifdef USES_P215 - 215, -#endif // ifdef USES_P215 - -#ifdef USES_P216 - 216, -#endif // ifdef USES_P216 - -#ifdef USES_P217 - 217, -#endif // ifdef USES_P217 - -#ifdef USES_P218 - 218, -#endif // ifdef USES_P218 - -#ifdef USES_P219 - 219, -#endif // ifdef USES_P219 - -#ifdef USES_P220 - 220, -#endif // ifdef USES_P220 - -#ifdef USES_P221 - 221, -#endif // ifdef USES_P221 - -#ifdef USES_P222 - 222, -#endif // ifdef USES_P222 - -#ifdef USES_P223 - 223, -#endif // ifdef USES_P223 - -#ifdef USES_P224 - 224, -#endif // ifdef USES_P224 - -#ifdef USES_P225 - 225, -#endif // ifdef USES_P225 - -#ifdef USES_P226 - 226, -#endif // ifdef USES_P226 - -#ifdef USES_P227 - 227, -#endif // ifdef USES_P227 - -#ifdef USES_P228 - 228, -#endif // ifdef USES_P228 - -#ifdef USES_P229 - 229, -#endif // ifdef USES_P229 - -#ifdef USES_P230 - 230, -#endif // ifdef USES_P230 - -#ifdef USES_P231 - 231, -#endif // ifdef USES_P231 - -#ifdef USES_P232 - 232, -#endif // ifdef USES_P232 - -#ifdef USES_P233 - 233, -#endif // ifdef USES_P233 - -#ifdef USES_P234 - 234, -#endif // ifdef USES_P234 - -#ifdef USES_P235 - 235, -#endif // ifdef USES_P235 - -#ifdef USES_P236 - 236, -#endif // ifdef USES_P236 - -#ifdef USES_P237 - 237, -#endif // ifdef USES_P237 - -#ifdef USES_P238 - 238, -#endif // ifdef USES_P238 - -#ifdef USES_P239 - 239, -#endif // ifdef USES_P239 - -#ifdef USES_P240 - 240, -#endif // ifdef USES_P240 - -#ifdef USES_P241 - 241, -#endif // ifdef USES_P241 - -#ifdef USES_P242 - 242, -#endif // ifdef USES_P242 - -#ifdef USES_P243 - 243, -#endif // ifdef USES_P243 - -#ifdef USES_P244 - 244, -#endif // ifdef USES_P244 - -#ifdef USES_P245 - 245, -#endif // ifdef USES_P245 - -#ifdef USES_P246 - 246, -#endif // ifdef USES_P246 - -#ifdef USES_P247 - 247, -#endif // ifdef USES_P247 - -#ifdef USES_P248 - 248, -#endif // ifdef USES_P248 - -#ifdef USES_P249 - 249, -#endif // ifdef USES_P249 - -#ifdef USES_P250 - 250, -#endif // ifdef USES_P250 - -#ifdef USES_P251 - 251, -#endif // ifdef USES_P251 - -#ifdef USES_P252 - 252, -#endif // ifdef USES_P252 - -#ifdef USES_P253 - 253, -#endif // ifdef USES_P253 - -#ifdef USES_P254 - 254, -#endif // ifdef USES_P254 - -#ifdef USES_P255 - 255, -#endif // ifdef USES_P255 -}; - -typedef boolean (*Plugin_ptr_t)(uint8_t, - struct EventStruct *, - String&); - -// Array of function pointers to call plugins. -constexpr const Plugin_ptr_t PROGMEM Plugin_ptr[] = -{ -#ifdef USES_P001 - &Plugin_001, -#endif // ifdef USES_P001 - -#ifdef USES_P002 - &Plugin_002, -#endif // ifdef USES_P002 - -#ifdef USES_P003 - &Plugin_003, -#endif // ifdef USES_P003 - -#ifdef USES_P004 - &Plugin_004, -#endif // ifdef USES_P004 - -#ifdef USES_P005 - &Plugin_005, -#endif // ifdef USES_P005 - -#ifdef USES_P006 - &Plugin_006, -#endif // ifdef USES_P006 - -#ifdef USES_P007 - &Plugin_007, -#endif // ifdef USES_P007 - -#ifdef USES_P008 - &Plugin_008, -#endif // ifdef USES_P008 - -#ifdef USES_P009 - &Plugin_009, -#endif // ifdef USES_P009 - -#ifdef USES_P010 - &Plugin_010, -#endif // ifdef USES_P010 - -#ifdef USES_P011 - &Plugin_011, -#endif // ifdef USES_P011 - -#ifdef USES_P012 - &Plugin_012, -#endif // ifdef USES_P012 - -#ifdef USES_P013 - &Plugin_013, -#endif // ifdef USES_P013 - -#ifdef USES_P014 - &Plugin_014, -#endif // ifdef USES_P014 - -#ifdef USES_P015 - &Plugin_015, -#endif // ifdef USES_P015 - -#ifdef USES_P016 - &Plugin_016, -#endif // ifdef USES_P016 - -#ifdef USES_P017 - &Plugin_017, -#endif // ifdef USES_P017 - -#ifdef USES_P018 - &Plugin_018, -#endif // ifdef USES_P018 - -#ifdef USES_P019 - &Plugin_019, -#endif // ifdef USES_P019 - -#ifdef USES_P020 - &Plugin_020, -#endif // ifdef USES_P020 - -#ifdef USES_P021 - &Plugin_021, -#endif // ifdef USES_P021 - -#ifdef USES_P022 - &Plugin_022, -#endif // ifdef USES_P022 - -#ifdef USES_P023 - &Plugin_023, -#endif // ifdef USES_P023 - -#ifdef USES_P024 - &Plugin_024, -#endif // ifdef USES_P024 - -#ifdef USES_P025 - &Plugin_025, -#endif // ifdef USES_P025 - -#ifdef USES_P026 - &Plugin_026, -#endif // ifdef USES_P026 - -#ifdef USES_P027 - &Plugin_027, -#endif // ifdef USES_P027 - -#ifdef USES_P028 - &Plugin_028, -#endif // ifdef USES_P028 - -#ifdef USES_P029 - &Plugin_029, -#endif // ifdef USES_P029 - -#ifdef USES_P030 - &Plugin_030, -#endif // ifdef USES_P030 - -#ifdef USES_P031 - &Plugin_031, -#endif // ifdef USES_P031 - -#ifdef USES_P032 - &Plugin_032, -#endif // ifdef USES_P032 - -#ifdef USES_P033 - &Plugin_033, -#endif // ifdef USES_P033 - -#ifdef USES_P034 - &Plugin_034, -#endif // ifdef USES_P034 - -#ifdef USES_P035 - &Plugin_035, -#endif // ifdef USES_P035 - -#ifdef USES_P036 - &Plugin_036, -#endif // ifdef USES_P036 - -#ifdef USES_P037 - &Plugin_037, -#endif // ifdef USES_P037 - -#ifdef USES_P038 - &Plugin_038, -#endif // ifdef USES_P038 - -#ifdef USES_P039 - &Plugin_039, -#endif // ifdef USES_P039 - -#ifdef USES_P040 - &Plugin_040, -#endif // ifdef USES_P040 - -#ifdef USES_P041 - &Plugin_041, -#endif // ifdef USES_P041 - -#ifdef USES_P042 - &Plugin_042, -#endif // ifdef USES_P042 - -#ifdef USES_P043 - &Plugin_043, -#endif // ifdef USES_P043 - -#ifdef USES_P044 - &Plugin_044, -#endif // ifdef USES_P044 - -#ifdef USES_P045 - &Plugin_045, -#endif // ifdef USES_P045 - -#ifdef USES_P046 - &Plugin_046, -#endif // ifdef USES_P046 - -#ifdef USES_P047 - &Plugin_047, -#endif // ifdef USES_P047 - -#ifdef USES_P048 - &Plugin_048, -#endif // ifdef USES_P048 - -#ifdef USES_P049 - &Plugin_049, -#endif // ifdef USES_P049 - -#ifdef USES_P050 - &Plugin_050, -#endif // ifdef USES_P050 - -#ifdef USES_P051 - &Plugin_051, -#endif // ifdef USES_P051 - -#ifdef USES_P052 - &Plugin_052, -#endif // ifdef USES_P052 - -#ifdef USES_P053 - &Plugin_053, -#endif // ifdef USES_P053 - -#ifdef USES_P054 - &Plugin_054, -#endif // ifdef USES_P054 - -#ifdef USES_P055 - &Plugin_055, -#endif // ifdef USES_P055 - -#ifdef USES_P056 - &Plugin_056, -#endif // ifdef USES_P056 - -#ifdef USES_P057 - &Plugin_057, -#endif // ifdef USES_P057 - -#ifdef USES_P058 - &Plugin_058, -#endif // ifdef USES_P058 - -#ifdef USES_P059 - &Plugin_059, -#endif // ifdef USES_P059 - -#ifdef USES_P060 - &Plugin_060, -#endif // ifdef USES_P060 - -#ifdef USES_P061 - &Plugin_061, -#endif // ifdef USES_P061 - -#ifdef USES_P062 - &Plugin_062, -#endif // ifdef USES_P062 - -#ifdef USES_P063 - &Plugin_063, -#endif // ifdef USES_P063 - -#ifdef USES_P064 - &Plugin_064, -#endif // ifdef USES_P064 - -#ifdef USES_P065 - &Plugin_065, -#endif // ifdef USES_P065 - -#ifdef USES_P066 - &Plugin_066, -#endif // ifdef USES_P066 - -#ifdef USES_P067 - &Plugin_067, -#endif // ifdef USES_P067 - -#ifdef USES_P068 - &Plugin_068, -#endif // ifdef USES_P068 - -#ifdef USES_P069 - &Plugin_069, -#endif // ifdef USES_P069 - -#ifdef USES_P070 - &Plugin_070, -#endif // ifdef USES_P070 - -#ifdef USES_P071 - &Plugin_071, -#endif // ifdef USES_P071 - -#ifdef USES_P072 - &Plugin_072, -#endif // ifdef USES_P072 - -#ifdef USES_P073 - &Plugin_073, -#endif // ifdef USES_P073 - -#ifdef USES_P074 - &Plugin_074, -#endif // ifdef USES_P074 - -#ifdef USES_P075 - &Plugin_075, -#endif // ifdef USES_P075 - -#ifdef USES_P076 - &Plugin_076, -#endif // ifdef USES_P076 - -#ifdef USES_P077 - &Plugin_077, -#endif // ifdef USES_P077 - -#ifdef USES_P078 - &Plugin_078, -#endif // ifdef USES_P078 - -#ifdef USES_P079 - &Plugin_079, -#endif // ifdef USES_P079 - -#ifdef USES_P080 - &Plugin_080, -#endif // ifdef USES_P080 - -#ifdef USES_P081 - &Plugin_081, -#endif // ifdef USES_P081 - -#ifdef USES_P082 - &Plugin_082, -#endif // ifdef USES_P082 - -#ifdef USES_P083 - &Plugin_083, -#endif // ifdef USES_P083 - -#ifdef USES_P084 - &Plugin_084, -#endif // ifdef USES_P084 - -#ifdef USES_P085 - &Plugin_085, -#endif // ifdef USES_P085 - -#ifdef USES_P086 - &Plugin_086, -#endif // ifdef USES_P086 - -#ifdef USES_P087 - &Plugin_087, -#endif // ifdef USES_P087 - -#ifdef USES_P088 - &Plugin_088, -#endif // ifdef USES_P088 - -#ifdef USES_P089 - # ifdef ESP8266 - - // FIXME TD-er: Support Ping plugin for ESP32 - &Plugin_089, - # endif // ifdef ESP8266 -#endif // ifdef USES_P089 - -#ifdef USES_P090 - &Plugin_090, -#endif // ifdef USES_P090 - -#ifdef USES_P091 - &Plugin_091, -#endif // ifdef USES_P091 - -#ifdef USES_P092 - &Plugin_092, -#endif // ifdef USES_P092 - -#ifdef USES_P093 - &Plugin_093, -#endif // ifdef USES_P093 - -#ifdef USES_P094 - &Plugin_094, -#endif // ifdef USES_P094 - -#ifdef USES_P095 - &Plugin_095, -#endif // ifdef USES_P095 - -#ifdef USES_P096 - &Plugin_096, -#endif // ifdef USES_P096 - -#ifdef USES_P097 - # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) - - // Touch (ESP32) - &Plugin_097, - # endif // if defined(ESP32) && !defined(ESP32Cxx) -#endif // ifdef USES_P097 - -#ifdef USES_P098 - &Plugin_098, -#endif // ifdef USES_P098 - -#ifdef USES_P099 - &Plugin_099, -#endif // ifdef USES_P099 - -#ifdef USES_P100 - &Plugin_100, -#endif // ifdef USES_P100 - -#ifdef USES_P101 - &Plugin_101, -#endif // ifdef USES_P101 - -#ifdef USES_P102 - &Plugin_102, -#endif // ifdef USES_P102 - -#ifdef USES_P103 - &Plugin_103, -#endif // ifdef USES_P103 - -#ifdef USES_P104 - &Plugin_104, -#endif // ifdef USES_P104 - -#ifdef USES_P105 - &Plugin_105, -#endif // ifdef USES_P105 - -#ifdef USES_P106 - &Plugin_106, -#endif // ifdef USES_P106 - -#ifdef USES_P107 - &Plugin_107, -#endif // ifdef USES_P107 - -#ifdef USES_P108 - &Plugin_108, -#endif // ifdef USES_P108 - -#ifdef USES_P109 - &Plugin_109, -#endif // ifdef USES_P109 - -#ifdef USES_P110 - &Plugin_110, -#endif // ifdef USES_P110 - -#ifdef USES_P111 - &Plugin_111, -#endif // ifdef USES_P111 - -#ifdef USES_P112 - &Plugin_112, -#endif // ifdef USES_P112 - -#ifdef USES_P113 - &Plugin_113, -#endif // ifdef USES_P113 - -#ifdef USES_P114 - &Plugin_114, -#endif // ifdef USES_P114 - -#ifdef USES_P115 - &Plugin_115, -#endif // ifdef USES_P115 - -#ifdef USES_P116 - &Plugin_116, -#endif // ifdef USES_P116 - -#ifdef USES_P117 - &Plugin_117, -#endif // ifdef USES_P117 - -#ifdef USES_P118 - &Plugin_118, -#endif // ifdef USES_P118 - -#ifdef USES_P119 - &Plugin_119, -#endif // ifdef USES_P119 - -#ifdef USES_P120 - &Plugin_120, -#endif // ifdef USES_P120 - -#ifdef USES_P121 - &Plugin_121, -#endif // ifdef USES_P121 - -#ifdef USES_P122 - &Plugin_122, -#endif // ifdef USES_P122 - -#ifdef USES_P123 - &Plugin_123, -#endif // ifdef USES_P123 - -#ifdef USES_P124 - &Plugin_124, -#endif // ifdef USES_P124 - -#ifdef USES_P125 - &Plugin_125, -#endif // ifdef USES_P125 - -#ifdef USES_P126 - &Plugin_126, -#endif // ifdef USES_P126 - -#ifdef USES_P127 - &Plugin_127, -#endif // ifdef USES_P127 - -#ifdef USES_P128 - &Plugin_128, -#endif // ifdef USES_P128 - -#ifdef USES_P129 - &Plugin_129, -#endif // ifdef USES_P129 - -#ifdef USES_P130 - &Plugin_130, -#endif // ifdef USES_P130 - -#ifdef USES_P131 - &Plugin_131, -#endif // ifdef USES_P131 - -#ifdef USES_P132 - &Plugin_132, -#endif // ifdef USES_P132 - -#ifdef USES_P133 - &Plugin_133, -#endif // ifdef USES_P133 - -#ifdef USES_P134 - &Plugin_134, -#endif // ifdef USES_P134 - -#ifdef USES_P135 - &Plugin_135, -#endif // ifdef USES_P135 - -#ifdef USES_P136 - &Plugin_136, -#endif // ifdef USES_P136 - -#ifdef USES_P137 - &Plugin_137, -#endif // ifdef USES_P137 - -#ifdef USES_P138 - &Plugin_138, -#endif // ifdef USES_P138 - -#ifdef USES_P139 - &Plugin_139, -#endif // ifdef USES_P139 - -#ifdef USES_P140 - &Plugin_140, -#endif // ifdef USES_P140 - -#ifdef USES_P141 - &Plugin_141, -#endif // ifdef USES_P141 - -#ifdef USES_P142 - &Plugin_142, -#endif // ifdef USES_P142 - -#ifdef USES_P143 - &Plugin_143, -#endif // ifdef USES_P143 - -#ifdef USES_P144 - &Plugin_144, -#endif // ifdef USES_P144 - -#ifdef USES_P145 - &Plugin_145, -#endif // ifdef USES_P145 - -#ifdef USES_P146 - &Plugin_146, -#endif // ifdef USES_P146 - -#ifdef USES_P147 - &Plugin_147, -#endif // ifdef USES_P147 - -#ifdef USES_P148 - &Plugin_148, -#endif // ifdef USES_P148 - -#ifdef USES_P149 - &Plugin_149, -#endif // ifdef USES_P149 - -#ifdef USES_P150 - &Plugin_150, -#endif // ifdef USES_P150 - -#ifdef USES_P151 - &Plugin_151, -#endif // ifdef USES_P151 - -#ifdef USES_P152 - &Plugin_152, -#endif // ifdef USES_P152 - -#ifdef USES_P153 - &Plugin_153, -#endif // ifdef USES_P153 - -#ifdef USES_P154 - &Plugin_154, -#endif // ifdef USES_P154 - -#ifdef USES_P155 - &Plugin_155, -#endif // ifdef USES_P155 - -#ifdef USES_P156 - &Plugin_156, -#endif // ifdef USES_P156 - -#ifdef USES_P157 - &Plugin_157, -#endif // ifdef USES_P157 - -#ifdef USES_P158 - &Plugin_158, -#endif // ifdef USES_P158 - -#ifdef USES_P159 - &Plugin_159, -#endif // ifdef USES_P159 - -#ifdef USES_P160 - &Plugin_160, -#endif // ifdef USES_P160 - -#ifdef USES_P161 - &Plugin_161, -#endif // ifdef USES_P161 - -#ifdef USES_P162 - &Plugin_162, -#endif // ifdef USES_P162 - -#ifdef USES_P163 - &Plugin_163, -#endif // ifdef USES_P163 - -#ifdef USES_P164 - &Plugin_164, -#endif // ifdef USES_P164 - -#ifdef USES_P165 - &Plugin_165, -#endif // ifdef USES_P165 - -#ifdef USES_P166 - &Plugin_166, -#endif // ifdef USES_P166 - -#ifdef USES_P167 - &Plugin_167, -#endif // ifdef USES_P167 - -#ifdef USES_P168 - &Plugin_168, -#endif // ifdef USES_P168 - -#ifdef USES_P169 - &Plugin_169, -#endif // ifdef USES_P169 - -#ifdef USES_P170 - &Plugin_170, -#endif // ifdef USES_P170 - -#ifdef USES_P171 - &Plugin_171, -#endif // ifdef USES_P171 - -#ifdef USES_P172 - &Plugin_172, -#endif // ifdef USES_P172 - -#ifdef USES_P173 - &Plugin_173, -#endif // ifdef USES_P173 - -#ifdef USES_P174 - &Plugin_174, -#endif // ifdef USES_P174 - -#ifdef USES_P175 - &Plugin_175, -#endif // ifdef USES_P175 - -#ifdef USES_P176 - &Plugin_176, -#endif // ifdef USES_P176 - -#ifdef USES_P177 - &Plugin_177, -#endif // ifdef USES_P177 - -#ifdef USES_P178 - &Plugin_178, -#endif // ifdef USES_P178 - -#ifdef USES_P179 - &Plugin_179, -#endif // ifdef USES_P179 - -#ifdef USES_P180 - &Plugin_180, -#endif // ifdef USES_P180 - -#ifdef USES_P181 - &Plugin_181, -#endif // ifdef USES_P181 - -#ifdef USES_P182 - &Plugin_182, -#endif // ifdef USES_P182 - -#ifdef USES_P183 - &Plugin_183, -#endif // ifdef USES_P183 - -#ifdef USES_P184 - &Plugin_184, -#endif // ifdef USES_P184 - -#ifdef USES_P185 - &Plugin_185, -#endif // ifdef USES_P185 - -#ifdef USES_P186 - &Plugin_186, -#endif // ifdef USES_P186 - -#ifdef USES_P187 - &Plugin_187, -#endif // ifdef USES_P187 - -#ifdef USES_P188 - &Plugin_188, -#endif // ifdef USES_P188 - -#ifdef USES_P189 - &Plugin_189, -#endif // ifdef USES_P189 - -#ifdef USES_P190 - &Plugin_190, -#endif // ifdef USES_P190 - -#ifdef USES_P191 - &Plugin_191, -#endif // ifdef USES_P191 - -#ifdef USES_P192 - &Plugin_192, -#endif // ifdef USES_P192 - -#ifdef USES_P193 - &Plugin_193, -#endif // ifdef USES_P193 - -#ifdef USES_P194 - &Plugin_194, -#endif // ifdef USES_P194 - -#ifdef USES_P195 - &Plugin_195, -#endif // ifdef USES_P195 - -#ifdef USES_P196 - &Plugin_196, -#endif // ifdef USES_P196 - -#ifdef USES_P197 - &Plugin_197, -#endif // ifdef USES_P197 - -#ifdef USES_P198 - &Plugin_198, -#endif // ifdef USES_P198 - -#ifdef USES_P199 - &Plugin_199, -#endif // ifdef USES_P199 - -#ifdef USES_P200 - &Plugin_200, -#endif // ifdef USES_P200 - -#ifdef USES_P201 - &Plugin_201, -#endif // ifdef USES_P201 - -#ifdef USES_P202 - &Plugin_202, -#endif // ifdef USES_P202 - -#ifdef USES_P203 - &Plugin_203, -#endif // ifdef USES_P203 - -#ifdef USES_P204 - &Plugin_204, -#endif // ifdef USES_P204 - -#ifdef USES_P205 - &Plugin_205, -#endif // ifdef USES_P205 - -#ifdef USES_P206 - &Plugin_206, -#endif // ifdef USES_P206 - -#ifdef USES_P207 - &Plugin_207, -#endif // ifdef USES_P207 - -#ifdef USES_P208 - &Plugin_208, -#endif // ifdef USES_P208 - -#ifdef USES_P209 - &Plugin_209, -#endif // ifdef USES_P209 - -#ifdef USES_P210 - &Plugin_210, -#endif // ifdef USES_P210 - -#ifdef USES_P211 - &Plugin_211, -#endif // ifdef USES_P211 - -#ifdef USES_P212 - &Plugin_212, -#endif // ifdef USES_P212 - -#ifdef USES_P213 - &Plugin_213, -#endif // ifdef USES_P213 - -#ifdef USES_P214 - &Plugin_214, -#endif // ifdef USES_P214 - -#ifdef USES_P215 - &Plugin_215, -#endif // ifdef USES_P215 - -#ifdef USES_P216 - &Plugin_216, -#endif // ifdef USES_P216 - -#ifdef USES_P217 - &Plugin_217, -#endif // ifdef USES_P217 - -#ifdef USES_P218 - &Plugin_218, -#endif // ifdef USES_P218 - -#ifdef USES_P219 - &Plugin_219, -#endif // ifdef USES_P219 - -#ifdef USES_P220 - &Plugin_220, -#endif // ifdef USES_P220 - -#ifdef USES_P221 - &Plugin_221, -#endif // ifdef USES_P221 - -#ifdef USES_P222 - &Plugin_222, -#endif // ifdef USES_P222 - -#ifdef USES_P223 - &Plugin_223, -#endif // ifdef USES_P223 - -#ifdef USES_P224 - &Plugin_224, -#endif // ifdef USES_P224 - -#ifdef USES_P225 - &Plugin_225, -#endif // ifdef USES_P225 - -#ifdef USES_P226 - &Plugin_226, -#endif // ifdef USES_P226 - -#ifdef USES_P227 - &Plugin_227, -#endif // ifdef USES_P227 - -#ifdef USES_P228 - &Plugin_228, -#endif // ifdef USES_P228 - -#ifdef USES_P229 - &Plugin_229, -#endif // ifdef USES_P229 - -#ifdef USES_P230 - &Plugin_230, -#endif // ifdef USES_P230 - -#ifdef USES_P231 - &Plugin_231, -#endif // ifdef USES_P231 - -#ifdef USES_P232 - &Plugin_232, -#endif // ifdef USES_P232 - -#ifdef USES_P233 - &Plugin_233, -#endif // ifdef USES_P233 - -#ifdef USES_P234 - &Plugin_234, -#endif // ifdef USES_P234 - -#ifdef USES_P235 - &Plugin_235, -#endif // ifdef USES_P235 - -#ifdef USES_P236 - &Plugin_236, -#endif // ifdef USES_P236 - -#ifdef USES_P237 - &Plugin_237, -#endif // ifdef USES_P237 - -#ifdef USES_P238 - &Plugin_238, -#endif // ifdef USES_P238 - -#ifdef USES_P239 - &Plugin_239, -#endif // ifdef USES_P239 - -#ifdef USES_P240 - &Plugin_240, -#endif // ifdef USES_P240 - -#ifdef USES_P241 - &Plugin_241, -#endif // ifdef USES_P241 - -#ifdef USES_P242 - &Plugin_242, -#endif // ifdef USES_P242 - -#ifdef USES_P243 - &Plugin_243, -#endif // ifdef USES_P243 - -#ifdef USES_P244 - &Plugin_244, -#endif // ifdef USES_P244 - -#ifdef USES_P245 - &Plugin_245, -#endif // ifdef USES_P245 - -#ifdef USES_P246 - &Plugin_246, -#endif // ifdef USES_P246 - -#ifdef USES_P247 - &Plugin_247, -#endif // ifdef USES_P247 - -#ifdef USES_P248 - &Plugin_248, -#endif // ifdef USES_P248 - -#ifdef USES_P249 - &Plugin_249, -#endif // ifdef USES_P249 - -#ifdef USES_P250 - &Plugin_250, -#endif // ifdef USES_P250 - -#ifdef USES_P251 - &Plugin_251, -#endif // ifdef USES_P251 - -#ifdef USES_P252 - &Plugin_252, -#endif // ifdef USES_P252 - -#ifdef USES_P253 - &Plugin_253, -#endif // ifdef USES_P253 - -#ifdef USES_P254 - &Plugin_254, -#endif // ifdef USES_P254 - -#ifdef USES_P255 - &Plugin_255, -#endif // ifdef USES_P255 -}; - -bool _Plugin_init_setupDone = false; - - -constexpr size_t DeviceIndex_to_Plugin_id_size = NR_ELEMENTS(DeviceIndex_to_Plugin_id); - -// Lowest plugin ID included in the build -constexpr size_t Lowest_Plugin_id = DeviceIndex_to_Plugin_id_size == 0 ? 0 : DeviceIndex_to_Plugin_id[0]; - -// Highest plugin ID included in the build -constexpr size_t Highest_Plugin_id = DeviceIndex_to_Plugin_id_size > 1 ? DeviceIndex_to_Plugin_id[DeviceIndex_to_Plugin_id_size - 1] : 0; - -// Array size including index of highest plugin ID. -constexpr size_t Plugin_id_to_DeviceIndex_size = Highest_Plugin_id + 1 - Lowest_Plugin_id; - -// Array filled during init. -// Valid index: 1 ... Highest_Plugin_id -// Returns index to the DeviceIndex_to_Plugin_id array -// -// Vector size should is lowest pluginID ... highest pluginID -deviceIndex_t Plugin_id_to_DeviceIndex[Plugin_id_to_DeviceIndex_size]{}; - -// Used as lookup for getting an alfabetically sorted deviceIndex -deviceIndex_t DeviceIndex_sorted[DeviceIndex_to_Plugin_id_size]; - - -size_t get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID_t pluginID) -{ - if (pluginID.value >= Lowest_Plugin_id) - { - const size_t arrIndex = static_cast(pluginID.value) - Lowest_Plugin_id; - if (arrIndex < Plugin_id_to_DeviceIndex_size) { - return arrIndex; - } - } - return Plugin_id_to_DeviceIndex_size; -} - - -/* -// TD-er: Test to make constexpr array Plugin_id_to_DeviceIndex -// Have to postpone, since std::integer_sequence is not available in Espressif SDK - -// Constexpr functions to create Plugin_id_to_DeviceIndex at compile time -constexpr uint8_t getDevId_from_PId(unsigned p_id, unsigned d_id) { - return (p_id == 0 || d_id == DeviceIndex_to_Plugin_id_size) ? DEVICE_INDEX_MAX - : (p_id == DeviceIndex_to_Plugin_id[d_id]) ? d_id : getDevId_from_PId(p_id, d_id+1); -} - -constexpr uint8_t getDevId_from_PId(unsigned p_id) { - return getDevId_from_PId(p_id, 0); -} -constexpr auto test = getDevId_from_PId(30); - - -#include -#include -#include - - -template -constexpr auto get_DevId_from_PId_array(std::integer_sequence a) -> std::array { - std::array vals{}; - ((vals[Is] = getDevId_from_PId(Is)), ...); - return vals; -} - -constexpr auto x = get_DevId_from_PId_array(std::make_integer_sequence{}); -*/ - -unsigned getNrBitsDeviceIndex() -{ - // FIXME TD-er: Must somehow make this a constexpr function - constexpr unsigned nrBits = NR_BITS(DeviceIndex_to_Plugin_id_size); - return nrBits; -} - -unsigned getNrBuiltInDeviceIndex() -{ - return DeviceIndex_to_Plugin_id_size; -} - -deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID) -{ - const size_t arrayIndex = get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID); - if (arrayIndex < Plugin_id_to_DeviceIndex_size) - { - return Plugin_id_to_DeviceIndex[arrayIndex]; - } - return INVALID_DEVICE_INDEX; -} - -pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex) -{ - if (validDeviceIndex_init(deviceIndex)) - { - return pluginID_t::toPluginID(pgm_read_byte(DeviceIndex_to_Plugin_id + deviceIndex.value)); - } - return INVALID_PLUGIN_ID; -} - -bool validDeviceIndex_init(deviceIndex_t deviceIndex) -{ - if (_Plugin_init_setupDone) { - return deviceIndex < DeviceIndex_to_Plugin_id_size; - } - return false; -} - -// Array containing "DeviceIndex" alfabetically sorted. -deviceIndex_t getDeviceIndex_sorted(deviceIndex_t deviceIndex) -{ - if (validDeviceIndex_init(deviceIndex)) { - return DeviceIndex_sorted[deviceIndex.value]; - } - return INVALID_DEVICE_INDEX; -} - - -boolean PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string) -{ - if (validDeviceIndex_init(deviceIndex)) - { - Plugin_ptr_t plugin_call = (Plugin_ptr_t)pgm_read_ptr(Plugin_ptr + deviceIndex.value); - return plugin_call(function, event, string); - } - return false; -} - -void PluginSetup() -{ - if (_Plugin_init_setupDone) return; - - _Plugin_init_setupDone = true; - - for (size_t id = 0; id < Plugin_id_to_DeviceIndex_size; ++id) - { - Plugin_id_to_DeviceIndex[id] = INVALID_DEVICE_INDEX; - } - #ifdef ESP8266 - Device = new (std::nothrow) DeviceStruct[DeviceIndex_to_Plugin_id_size]; - #else - Device.resize(DeviceIndex_to_Plugin_id_size); - #endif - - for (deviceIndex_t deviceIndex; deviceIndex < DeviceIndex_to_Plugin_id_size; ++deviceIndex) - { - const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); - - if (validPluginID(pluginID)) { - const size_t arrayIndex = get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID); - if (arrayIndex < Plugin_id_to_DeviceIndex_size) { - // Should never be outside these limits. - Plugin_id_to_DeviceIndex[arrayIndex] = deviceIndex; - struct EventStruct TempEvent; - TempEvent.idx = deviceIndex.value; - String dummy; - PluginCall(deviceIndex, PLUGIN_DEVICE_ADD, &TempEvent, dummy); - } - } - } -#ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("PLUGIN_DEVICE_ADD")); -#endif - - // ******************************************************************************** - // Device Sort routine, actual sorting alfabetically by plugin name. - // Sorting does happen case sensitive. - // Used in device selector dropdown. - // ******************************************************************************** - - // First fill the existing number of the DeviceIndex. - for (deviceIndex_t x; x < DeviceIndex_to_Plugin_id_size; ++x) { - DeviceIndex_sorted[x.value] = x; - } - - struct - { - bool operator()(deviceIndex_t a, deviceIndex_t b) const { - return getPluginNameFromDeviceIndex(a) < - getPluginNameFromDeviceIndex(b); - } - } - customLess; - std::sort(DeviceIndex_sorted, DeviceIndex_sorted + DeviceIndex_to_Plugin_id_size, customLess); -} - -void PluginInit(bool priorityOnly) -{ - - // Set all not supported plugins to disabled. - for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; ++taskIndex) { - if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) { - Settings.TaskDeviceEnabled[taskIndex] = false; - } - } - - - if (!priorityOnly) { - String dummy; - PluginCall(PLUGIN_INIT_ALL, nullptr, dummy); - #ifndef BUILD_NO_RAM_TRACKER - logMemUsageAfter(F("PLUGIN_INIT_ALL")); - #endif - } -} +#include "../Helpers/_Plugin_init.h" + +#include "../../ESPEasy_common.h" + +#include "../Globals/Device.h" +#include "../Globals/Settings.h" + +#include "../Helpers/Misc.h" + + +// ******************************************************************************** +// Initialize all plugins that where defined earlier +// and initialize the function call pointer into the plugin array +// ******************************************************************************** + + + +// Vector to match a "DeviceIndex" to a plugin ID. +constexpr /*pluginID_t*/ uint8_t DeviceIndex_to_Plugin_id[] PROGMEM = +{ +#ifdef USES_P001 + 1, +#endif // ifdef USES_P001 + +#ifdef USES_P002 + 2, +#endif // ifdef USES_P002 + +#ifdef USES_P003 + 3, +#endif // ifdef USES_P003 + +#ifdef USES_P004 + 4, +#endif // ifdef USES_P004 + +#ifdef USES_P005 + 5, +#endif // ifdef USES_P005 + +#ifdef USES_P006 + 6, +#endif // ifdef USES_P006 + +#ifdef USES_P007 + 7, +#endif // ifdef USES_P007 + +#ifdef USES_P008 + 8, +#endif // ifdef USES_P008 + +#ifdef USES_P009 + 9, +#endif // ifdef USES_P009 + +#ifdef USES_P010 + 10, +#endif // ifdef USES_P010 + +#ifdef USES_P011 + 11, +#endif // ifdef USES_P011 + +#ifdef USES_P012 + 12, +#endif // ifdef USES_P012 + +#ifdef USES_P013 + 13, +#endif // ifdef USES_P013 + +#ifdef USES_P014 + 14, +#endif // ifdef USES_P014 + +#ifdef USES_P015 + 15, +#endif // ifdef USES_P015 + +#ifdef USES_P016 + 16, +#endif // ifdef USES_P016 + +#ifdef USES_P017 + 17, +#endif // ifdef USES_P017 + +#ifdef USES_P018 + 18, +#endif // ifdef USES_P018 + +#ifdef USES_P019 + 19, +#endif // ifdef USES_P019 + +#ifdef USES_P020 + 20, +#endif // ifdef USES_P020 + +#ifdef USES_P021 + 21, +#endif // ifdef USES_P021 + +#ifdef USES_P022 + 22, +#endif // ifdef USES_P022 + +#ifdef USES_P023 + 23, +#endif // ifdef USES_P023 + +#ifdef USES_P024 + 24, +#endif // ifdef USES_P024 + +#ifdef USES_P025 + 25, +#endif // ifdef USES_P025 + +#ifdef USES_P026 + 26, +#endif // ifdef USES_P026 + +#ifdef USES_P027 + 27, +#endif // ifdef USES_P027 + +#ifdef USES_P028 + 28, +#endif // ifdef USES_P028 + +#ifdef USES_P029 + 29, +#endif // ifdef USES_P029 + +#ifdef USES_P030 + 30, +#endif // ifdef USES_P030 + +#ifdef USES_P031 + 31, +#endif // ifdef USES_P031 + +#ifdef USES_P032 + 32, +#endif // ifdef USES_P032 + +#ifdef USES_P033 + 33, +#endif // ifdef USES_P033 + +#ifdef USES_P034 + 34, +#endif // ifdef USES_P034 + +#ifdef USES_P035 + 35, +#endif // ifdef USES_P035 + +#ifdef USES_P036 + 36, +#endif // ifdef USES_P036 + +#ifdef USES_P037 + 37, +#endif // ifdef USES_P037 + +#ifdef USES_P038 + 38, +#endif // ifdef USES_P038 + +#ifdef USES_P039 + 39, +#endif // ifdef USES_P039 + +#ifdef USES_P040 + 40, +#endif // ifdef USES_P040 + +#ifdef USES_P041 + 41, +#endif // ifdef USES_P041 + +#ifdef USES_P042 + 42, +#endif // ifdef USES_P042 + +#ifdef USES_P043 + 43, +#endif // ifdef USES_P043 + +#ifdef USES_P044 + 44, +#endif // ifdef USES_P044 + +#ifdef USES_P045 + 45, +#endif // ifdef USES_P045 + +#ifdef USES_P046 + 46, +#endif // ifdef USES_P046 + +#ifdef USES_P047 + 47, +#endif // ifdef USES_P047 + +#ifdef USES_P048 + 48, +#endif // ifdef USES_P048 + +#ifdef USES_P049 + 49, +#endif // ifdef USES_P049 + +#ifdef USES_P050 + 50, +#endif // ifdef USES_P050 + +#ifdef USES_P051 + 51, +#endif // ifdef USES_P051 + +#ifdef USES_P052 + 52, +#endif // ifdef USES_P052 + +#ifdef USES_P053 + 53, +#endif // ifdef USES_P053 + +#ifdef USES_P054 + 54, +#endif // ifdef USES_P054 + +#ifdef USES_P055 + 55, +#endif // ifdef USES_P055 + +#ifdef USES_P056 + 56, +#endif // ifdef USES_P056 + +#ifdef USES_P057 + 57, +#endif // ifdef USES_P057 + +#ifdef USES_P058 + 58, +#endif // ifdef USES_P058 + +#ifdef USES_P059 + 59, +#endif // ifdef USES_P059 + +#ifdef USES_P060 + 60, +#endif // ifdef USES_P060 + +#ifdef USES_P061 + 61, +#endif // ifdef USES_P061 + +#ifdef USES_P062 + 62, +#endif // ifdef USES_P062 + +#ifdef USES_P063 + 63, +#endif // ifdef USES_P063 + +#ifdef USES_P064 + 64, +#endif // ifdef USES_P064 + +#ifdef USES_P065 + 65, +#endif // ifdef USES_P065 + +#ifdef USES_P066 + 66, +#endif // ifdef USES_P066 + +#ifdef USES_P067 + 67, +#endif // ifdef USES_P067 + +#ifdef USES_P068 + 68, +#endif // ifdef USES_P068 + +#ifdef USES_P069 + 69, +#endif // ifdef USES_P069 + +#ifdef USES_P070 + 70, +#endif // ifdef USES_P070 + +#ifdef USES_P071 + 71, +#endif // ifdef USES_P071 + +#ifdef USES_P072 + 72, +#endif // ifdef USES_P072 + +#ifdef USES_P073 + 73, +#endif // ifdef USES_P073 + +#ifdef USES_P074 + 74, +#endif // ifdef USES_P074 + +#ifdef USES_P075 + 75, +#endif // ifdef USES_P075 + +#ifdef USES_P076 + 76, +#endif // ifdef USES_P076 + +#ifdef USES_P077 + 77, +#endif // ifdef USES_P077 + +#ifdef USES_P078 + 78, +#endif // ifdef USES_P078 + +#ifdef USES_P079 + 79, +#endif // ifdef USES_P079 + +#ifdef USES_P080 + 80, +#endif // ifdef USES_P080 + +#ifdef USES_P081 + 81, +#endif // ifdef USES_P081 + +#ifdef USES_P082 + 82, +#endif // ifdef USES_P082 + +#ifdef USES_P083 + 83, +#endif // ifdef USES_P083 + +#ifdef USES_P084 + 84, +#endif // ifdef USES_P084 + +#ifdef USES_P085 + 85, +#endif // ifdef USES_P085 + +#ifdef USES_P086 + 86, +#endif // ifdef USES_P086 + +#ifdef USES_P087 + 87, +#endif // ifdef USES_P087 + +#ifdef USES_P088 + 88, +#endif // ifdef USES_P088 + +#ifdef USES_P089 + # ifdef ESP8266 + + // FIXME TD-er: Support Ping plugin for ESP32 + 89, + # endif // ifdef ESP8266 +#endif // ifdef USES_P089 + +#ifdef USES_P090 + 90, +#endif // ifdef USES_P090 + +#ifdef USES_P091 + 91, +#endif // ifdef USES_P091 + +#ifdef USES_P092 + 92, +#endif // ifdef USES_P092 + +#ifdef USES_P093 + 93, +#endif // ifdef USES_P093 + +#ifdef USES_P094 + 94, +#endif // ifdef USES_P094 + +#ifdef USES_P095 + 95, +#endif // ifdef USES_P095 + +#ifdef USES_P096 + 96, +#endif // ifdef USES_P096 + +#ifdef USES_P097 + # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) + + // Touch (ESP32) + 97, + # endif // if defined(ESP32) && !defined(ESP32Cxx) +#endif // ifdef USES_P097 + +#ifdef USES_P098 + 98, +#endif // ifdef USES_P098 + +#ifdef USES_P099 + 99, +#endif // ifdef USES_P099 + +#ifdef USES_P100 + 100, +#endif // ifdef USES_P100 + +#ifdef USES_P101 + 101, +#endif // ifdef USES_P101 + +#ifdef USES_P102 + 102, +#endif // ifdef USES_P102 + +#ifdef USES_P103 + 103, +#endif // ifdef USES_P103 + +#ifdef USES_P104 + 104, +#endif // ifdef USES_P104 + +#ifdef USES_P105 + 105, +#endif // ifdef USES_P105 + +#ifdef USES_P106 + 106, +#endif // ifdef USES_P106 + +#ifdef USES_P107 + 107, +#endif // ifdef USES_P107 + +#ifdef USES_P108 + 108, +#endif // ifdef USES_P108 + +#ifdef USES_P109 + 109, +#endif // ifdef USES_P109 + +#ifdef USES_P110 + 110, +#endif // ifdef USES_P110 + +#ifdef USES_P111 + 111, +#endif // ifdef USES_P111 + +#ifdef USES_P112 + 112, +#endif // ifdef USES_P112 + +#ifdef USES_P113 + 113, +#endif // ifdef USES_P113 + +#ifdef USES_P114 + 114, +#endif // ifdef USES_P114 + +#ifdef USES_P115 + 115, +#endif // ifdef USES_P115 + +#ifdef USES_P116 + 116, +#endif // ifdef USES_P116 + +#ifdef USES_P117 + 117, +#endif // ifdef USES_P117 + +#ifdef USES_P118 + 118, +#endif // ifdef USES_P118 + +#ifdef USES_P119 + 119, +#endif // ifdef USES_P119 + +#ifdef USES_P120 + 120, +#endif // ifdef USES_P120 + +#ifdef USES_P121 + 121, +#endif // ifdef USES_P121 + +#ifdef USES_P122 + 122, +#endif // ifdef USES_P122 + +#ifdef USES_P123 + 123, +#endif // ifdef USES_P123 + +#ifdef USES_P124 + 124, +#endif // ifdef USES_P124 + +#ifdef USES_P125 + 125, +#endif // ifdef USES_P125 + +#ifdef USES_P126 + 126, +#endif // ifdef USES_P126 + +#ifdef USES_P127 + 127, +#endif // ifdef USES_P127 + +#ifdef USES_P128 + 128, +#endif // ifdef USES_P128 + +#ifdef USES_P129 + 129, +#endif // ifdef USES_P129 + +#ifdef USES_P130 + 130, +#endif // ifdef USES_P130 + +#ifdef USES_P131 + 131, +#endif // ifdef USES_P131 + +#ifdef USES_P132 + 132, +#endif // ifdef USES_P132 + +#ifdef USES_P133 + 133, +#endif // ifdef USES_P133 + +#ifdef USES_P134 + 134, +#endif // ifdef USES_P134 + +#ifdef USES_P135 + 135, +#endif // ifdef USES_P135 + +#ifdef USES_P136 + 136, +#endif // ifdef USES_P136 + +#ifdef USES_P137 + 137, +#endif // ifdef USES_P137 + +#ifdef USES_P138 + 138, +#endif // ifdef USES_P138 + +#ifdef USES_P139 + 139, +#endif // ifdef USES_P139 + +#ifdef USES_P140 + 140, +#endif // ifdef USES_P140 + +#ifdef USES_P141 + 141, +#endif // ifdef USES_P141 + +#ifdef USES_P142 + 142, +#endif // ifdef USES_P142 + +#ifdef USES_P143 + 143, +#endif // ifdef USES_P143 + +#ifdef USES_P144 + 144, +#endif // ifdef USES_P144 + +#ifdef USES_P145 + 145, +#endif // ifdef USES_P145 + +#ifdef USES_P146 + 146, +#endif // ifdef USES_P146 + +#ifdef USES_P147 + 147, +#endif // ifdef USES_P147 + +#ifdef USES_P148 + 148, +#endif // ifdef USES_P148 + +#ifdef USES_P149 + 149, +#endif // ifdef USES_P149 + +#ifdef USES_P150 + 150, +#endif // ifdef USES_P150 + +#ifdef USES_P151 + 151, +#endif // ifdef USES_P151 + +#ifdef USES_P152 + 152, +#endif // ifdef USES_P152 + +#ifdef USES_P153 + 153, +#endif // ifdef USES_P153 + +#ifdef USES_P154 + 154, +#endif // ifdef USES_P154 + +#ifdef USES_P155 + 155, +#endif // ifdef USES_P155 + +#ifdef USES_P156 + 156, +#endif // ifdef USES_P156 + +#ifdef USES_P157 + 157, +#endif // ifdef USES_P157 + +#ifdef USES_P158 + 158, +#endif // ifdef USES_P158 + +#ifdef USES_P159 + 159, +#endif // ifdef USES_P159 + +#ifdef USES_P160 + 160, +#endif // ifdef USES_P160 + +#ifdef USES_P161 + 161, +#endif // ifdef USES_P161 + +#ifdef USES_P162 + 162, +#endif // ifdef USES_P162 + +#ifdef USES_P163 + 163, +#endif // ifdef USES_P163 + +#ifdef USES_P164 + 164, +#endif // ifdef USES_P164 + +#ifdef USES_P165 + 165, +#endif // ifdef USES_P165 + +#ifdef USES_P166 + 166, +#endif // ifdef USES_P166 + +#ifdef USES_P167 + 167, +#endif // ifdef USES_P167 + +#ifdef USES_P168 + 168, +#endif // ifdef USES_P168 + +#ifdef USES_P169 + 169, +#endif // ifdef USES_P169 + +#ifdef USES_P170 + 170, +#endif // ifdef USES_P170 + +#ifdef USES_P171 + 171, +#endif // ifdef USES_P171 + +#ifdef USES_P172 + 172, +#endif // ifdef USES_P172 + +#ifdef USES_P173 + 173, +#endif // ifdef USES_P173 + +#ifdef USES_P174 + 174, +#endif // ifdef USES_P174 + +#ifdef USES_P175 + 175, +#endif // ifdef USES_P175 + +#ifdef USES_P176 + 176, +#endif // ifdef USES_P176 + +#ifdef USES_P177 + 177, +#endif // ifdef USES_P177 + +#ifdef USES_P178 + 178, +#endif // ifdef USES_P178 + +#ifdef USES_P179 + 179, +#endif // ifdef USES_P179 + +#ifdef USES_P180 + 180, +#endif // ifdef USES_P180 + +#ifdef USES_P181 + 181, +#endif // ifdef USES_P181 + +#ifdef USES_P182 + 182, +#endif // ifdef USES_P182 + +#ifdef USES_P183 + 183, +#endif // ifdef USES_P183 + +#ifdef USES_P184 + 184, +#endif // ifdef USES_P184 + +#ifdef USES_P185 + 185, +#endif // ifdef USES_P185 + +#ifdef USES_P186 + 186, +#endif // ifdef USES_P186 + +#ifdef USES_P187 + 187, +#endif // ifdef USES_P187 + +#ifdef USES_P188 + 188, +#endif // ifdef USES_P188 + +#ifdef USES_P189 + 189, +#endif // ifdef USES_P189 + +#ifdef USES_P190 + 190, +#endif // ifdef USES_P190 + +#ifdef USES_P191 + 191, +#endif // ifdef USES_P191 + +#ifdef USES_P192 + 192, +#endif // ifdef USES_P192 + +#ifdef USES_P193 + 193, +#endif // ifdef USES_P193 + +#ifdef USES_P194 + 194, +#endif // ifdef USES_P194 + +#ifdef USES_P195 + 195, +#endif // ifdef USES_P195 + +#ifdef USES_P196 + 196, +#endif // ifdef USES_P196 + +#ifdef USES_P197 + 197, +#endif // ifdef USES_P197 + +#ifdef USES_P198 + 198, +#endif // ifdef USES_P198 + +#ifdef USES_P199 + 199, +#endif // ifdef USES_P199 + +#ifdef USES_P200 + 200, +#endif // ifdef USES_P200 + +#ifdef USES_P201 + 201, +#endif // ifdef USES_P201 + +#ifdef USES_P202 + 202, +#endif // ifdef USES_P202 + +#ifdef USES_P203 + 203, +#endif // ifdef USES_P203 + +#ifdef USES_P204 + 204, +#endif // ifdef USES_P204 + +#ifdef USES_P205 + 205, +#endif // ifdef USES_P205 + +#ifdef USES_P206 + 206, +#endif // ifdef USES_P206 + +#ifdef USES_P207 + 207, +#endif // ifdef USES_P207 + +#ifdef USES_P208 + 208, +#endif // ifdef USES_P208 + +#ifdef USES_P209 + 209, +#endif // ifdef USES_P209 + +#ifdef USES_P210 + 210, +#endif // ifdef USES_P210 + +#ifdef USES_P211 + 211, +#endif // ifdef USES_P211 + +#ifdef USES_P212 + 212, +#endif // ifdef USES_P212 + +#ifdef USES_P213 + 213, +#endif // ifdef USES_P213 + +#ifdef USES_P214 + 214, +#endif // ifdef USES_P214 + +#ifdef USES_P215 + 215, +#endif // ifdef USES_P215 + +#ifdef USES_P216 + 216, +#endif // ifdef USES_P216 + +#ifdef USES_P217 + 217, +#endif // ifdef USES_P217 + +#ifdef USES_P218 + 218, +#endif // ifdef USES_P218 + +#ifdef USES_P219 + 219, +#endif // ifdef USES_P219 + +#ifdef USES_P220 + 220, +#endif // ifdef USES_P220 + +#ifdef USES_P221 + 221, +#endif // ifdef USES_P221 + +#ifdef USES_P222 + 222, +#endif // ifdef USES_P222 + +#ifdef USES_P223 + 223, +#endif // ifdef USES_P223 + +#ifdef USES_P224 + 224, +#endif // ifdef USES_P224 + +#ifdef USES_P225 + 225, +#endif // ifdef USES_P225 + +#ifdef USES_P226 + 226, +#endif // ifdef USES_P226 + +#ifdef USES_P227 + 227, +#endif // ifdef USES_P227 + +#ifdef USES_P228 + 228, +#endif // ifdef USES_P228 + +#ifdef USES_P229 + 229, +#endif // ifdef USES_P229 + +#ifdef USES_P230 + 230, +#endif // ifdef USES_P230 + +#ifdef USES_P231 + 231, +#endif // ifdef USES_P231 + +#ifdef USES_P232 + 232, +#endif // ifdef USES_P232 + +#ifdef USES_P233 + 233, +#endif // ifdef USES_P233 + +#ifdef USES_P234 + 234, +#endif // ifdef USES_P234 + +#ifdef USES_P235 + 235, +#endif // ifdef USES_P235 + +#ifdef USES_P236 + 236, +#endif // ifdef USES_P236 + +#ifdef USES_P237 + 237, +#endif // ifdef USES_P237 + +#ifdef USES_P238 + 238, +#endif // ifdef USES_P238 + +#ifdef USES_P239 + 239, +#endif // ifdef USES_P239 + +#ifdef USES_P240 + 240, +#endif // ifdef USES_P240 + +#ifdef USES_P241 + 241, +#endif // ifdef USES_P241 + +#ifdef USES_P242 + 242, +#endif // ifdef USES_P242 + +#ifdef USES_P243 + 243, +#endif // ifdef USES_P243 + +#ifdef USES_P244 + 244, +#endif // ifdef USES_P244 + +#ifdef USES_P245 + 245, +#endif // ifdef USES_P245 + +#ifdef USES_P246 + 246, +#endif // ifdef USES_P246 + +#ifdef USES_P247 + 247, +#endif // ifdef USES_P247 + +#ifdef USES_P248 + 248, +#endif // ifdef USES_P248 + +#ifdef USES_P249 + 249, +#endif // ifdef USES_P249 + +#ifdef USES_P250 + 250, +#endif // ifdef USES_P250 + +#ifdef USES_P251 + 251, +#endif // ifdef USES_P251 + +#ifdef USES_P252 + 252, +#endif // ifdef USES_P252 + +#ifdef USES_P253 + 253, +#endif // ifdef USES_P253 + +#ifdef USES_P254 + 254, +#endif // ifdef USES_P254 + +#ifdef USES_P255 + 255, +#endif // ifdef USES_P255 +}; + +typedef boolean (*Plugin_ptr_t)(uint8_t, + struct EventStruct *, + String&); + +// Array of function pointers to call plugins. +constexpr const Plugin_ptr_t PROGMEM Plugin_ptr[] = +{ +#ifdef USES_P001 + &Plugin_001, +#endif // ifdef USES_P001 + +#ifdef USES_P002 + &Plugin_002, +#endif // ifdef USES_P002 + +#ifdef USES_P003 + &Plugin_003, +#endif // ifdef USES_P003 + +#ifdef USES_P004 + &Plugin_004, +#endif // ifdef USES_P004 + +#ifdef USES_P005 + &Plugin_005, +#endif // ifdef USES_P005 + +#ifdef USES_P006 + &Plugin_006, +#endif // ifdef USES_P006 + +#ifdef USES_P007 + &Plugin_007, +#endif // ifdef USES_P007 + +#ifdef USES_P008 + &Plugin_008, +#endif // ifdef USES_P008 + +#ifdef USES_P009 + &Plugin_009, +#endif // ifdef USES_P009 + +#ifdef USES_P010 + &Plugin_010, +#endif // ifdef USES_P010 + +#ifdef USES_P011 + &Plugin_011, +#endif // ifdef USES_P011 + +#ifdef USES_P012 + &Plugin_012, +#endif // ifdef USES_P012 + +#ifdef USES_P013 + &Plugin_013, +#endif // ifdef USES_P013 + +#ifdef USES_P014 + &Plugin_014, +#endif // ifdef USES_P014 + +#ifdef USES_P015 + &Plugin_015, +#endif // ifdef USES_P015 + +#ifdef USES_P016 + &Plugin_016, +#endif // ifdef USES_P016 + +#ifdef USES_P017 + &Plugin_017, +#endif // ifdef USES_P017 + +#ifdef USES_P018 + &Plugin_018, +#endif // ifdef USES_P018 + +#ifdef USES_P019 + &Plugin_019, +#endif // ifdef USES_P019 + +#ifdef USES_P020 + &Plugin_020, +#endif // ifdef USES_P020 + +#ifdef USES_P021 + &Plugin_021, +#endif // ifdef USES_P021 + +#ifdef USES_P022 + &Plugin_022, +#endif // ifdef USES_P022 + +#ifdef USES_P023 + &Plugin_023, +#endif // ifdef USES_P023 + +#ifdef USES_P024 + &Plugin_024, +#endif // ifdef USES_P024 + +#ifdef USES_P025 + &Plugin_025, +#endif // ifdef USES_P025 + +#ifdef USES_P026 + &Plugin_026, +#endif // ifdef USES_P026 + +#ifdef USES_P027 + &Plugin_027, +#endif // ifdef USES_P027 + +#ifdef USES_P028 + &Plugin_028, +#endif // ifdef USES_P028 + +#ifdef USES_P029 + &Plugin_029, +#endif // ifdef USES_P029 + +#ifdef USES_P030 + &Plugin_030, +#endif // ifdef USES_P030 + +#ifdef USES_P031 + &Plugin_031, +#endif // ifdef USES_P031 + +#ifdef USES_P032 + &Plugin_032, +#endif // ifdef USES_P032 + +#ifdef USES_P033 + &Plugin_033, +#endif // ifdef USES_P033 + +#ifdef USES_P034 + &Plugin_034, +#endif // ifdef USES_P034 + +#ifdef USES_P035 + &Plugin_035, +#endif // ifdef USES_P035 + +#ifdef USES_P036 + &Plugin_036, +#endif // ifdef USES_P036 + +#ifdef USES_P037 + &Plugin_037, +#endif // ifdef USES_P037 + +#ifdef USES_P038 + &Plugin_038, +#endif // ifdef USES_P038 + +#ifdef USES_P039 + &Plugin_039, +#endif // ifdef USES_P039 + +#ifdef USES_P040 + &Plugin_040, +#endif // ifdef USES_P040 + +#ifdef USES_P041 + &Plugin_041, +#endif // ifdef USES_P041 + +#ifdef USES_P042 + &Plugin_042, +#endif // ifdef USES_P042 + +#ifdef USES_P043 + &Plugin_043, +#endif // ifdef USES_P043 + +#ifdef USES_P044 + &Plugin_044, +#endif // ifdef USES_P044 + +#ifdef USES_P045 + &Plugin_045, +#endif // ifdef USES_P045 + +#ifdef USES_P046 + &Plugin_046, +#endif // ifdef USES_P046 + +#ifdef USES_P047 + &Plugin_047, +#endif // ifdef USES_P047 + +#ifdef USES_P048 + &Plugin_048, +#endif // ifdef USES_P048 + +#ifdef USES_P049 + &Plugin_049, +#endif // ifdef USES_P049 + +#ifdef USES_P050 + &Plugin_050, +#endif // ifdef USES_P050 + +#ifdef USES_P051 + &Plugin_051, +#endif // ifdef USES_P051 + +#ifdef USES_P052 + &Plugin_052, +#endif // ifdef USES_P052 + +#ifdef USES_P053 + &Plugin_053, +#endif // ifdef USES_P053 + +#ifdef USES_P054 + &Plugin_054, +#endif // ifdef USES_P054 + +#ifdef USES_P055 + &Plugin_055, +#endif // ifdef USES_P055 + +#ifdef USES_P056 + &Plugin_056, +#endif // ifdef USES_P056 + +#ifdef USES_P057 + &Plugin_057, +#endif // ifdef USES_P057 + +#ifdef USES_P058 + &Plugin_058, +#endif // ifdef USES_P058 + +#ifdef USES_P059 + &Plugin_059, +#endif // ifdef USES_P059 + +#ifdef USES_P060 + &Plugin_060, +#endif // ifdef USES_P060 + +#ifdef USES_P061 + &Plugin_061, +#endif // ifdef USES_P061 + +#ifdef USES_P062 + &Plugin_062, +#endif // ifdef USES_P062 + +#ifdef USES_P063 + &Plugin_063, +#endif // ifdef USES_P063 + +#ifdef USES_P064 + &Plugin_064, +#endif // ifdef USES_P064 + +#ifdef USES_P065 + &Plugin_065, +#endif // ifdef USES_P065 + +#ifdef USES_P066 + &Plugin_066, +#endif // ifdef USES_P066 + +#ifdef USES_P067 + &Plugin_067, +#endif // ifdef USES_P067 + +#ifdef USES_P068 + &Plugin_068, +#endif // ifdef USES_P068 + +#ifdef USES_P069 + &Plugin_069, +#endif // ifdef USES_P069 + +#ifdef USES_P070 + &Plugin_070, +#endif // ifdef USES_P070 + +#ifdef USES_P071 + &Plugin_071, +#endif // ifdef USES_P071 + +#ifdef USES_P072 + &Plugin_072, +#endif // ifdef USES_P072 + +#ifdef USES_P073 + &Plugin_073, +#endif // ifdef USES_P073 + +#ifdef USES_P074 + &Plugin_074, +#endif // ifdef USES_P074 + +#ifdef USES_P075 + &Plugin_075, +#endif // ifdef USES_P075 + +#ifdef USES_P076 + &Plugin_076, +#endif // ifdef USES_P076 + +#ifdef USES_P077 + &Plugin_077, +#endif // ifdef USES_P077 + +#ifdef USES_P078 + &Plugin_078, +#endif // ifdef USES_P078 + +#ifdef USES_P079 + &Plugin_079, +#endif // ifdef USES_P079 + +#ifdef USES_P080 + &Plugin_080, +#endif // ifdef USES_P080 + +#ifdef USES_P081 + &Plugin_081, +#endif // ifdef USES_P081 + +#ifdef USES_P082 + &Plugin_082, +#endif // ifdef USES_P082 + +#ifdef USES_P083 + &Plugin_083, +#endif // ifdef USES_P083 + +#ifdef USES_P084 + &Plugin_084, +#endif // ifdef USES_P084 + +#ifdef USES_P085 + &Plugin_085, +#endif // ifdef USES_P085 + +#ifdef USES_P086 + &Plugin_086, +#endif // ifdef USES_P086 + +#ifdef USES_P087 + &Plugin_087, +#endif // ifdef USES_P087 + +#ifdef USES_P088 + &Plugin_088, +#endif // ifdef USES_P088 + +#ifdef USES_P089 + # ifdef ESP8266 + + // FIXME TD-er: Support Ping plugin for ESP32 + &Plugin_089, + # endif // ifdef ESP8266 +#endif // ifdef USES_P089 + +#ifdef USES_P090 + &Plugin_090, +#endif // ifdef USES_P090 + +#ifdef USES_P091 + &Plugin_091, +#endif // ifdef USES_P091 + +#ifdef USES_P092 + &Plugin_092, +#endif // ifdef USES_P092 + +#ifdef USES_P093 + &Plugin_093, +#endif // ifdef USES_P093 + +#ifdef USES_P094 + &Plugin_094, +#endif // ifdef USES_P094 + +#ifdef USES_P095 + &Plugin_095, +#endif // ifdef USES_P095 + +#ifdef USES_P096 + &Plugin_096, +#endif // ifdef USES_P096 + +#ifdef USES_P097 + # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) + + // Touch (ESP32) + &Plugin_097, + # endif // if defined(ESP32) && !defined(ESP32Cxx) +#endif // ifdef USES_P097 + +#ifdef USES_P098 + &Plugin_098, +#endif // ifdef USES_P098 + +#ifdef USES_P099 + &Plugin_099, +#endif // ifdef USES_P099 + +#ifdef USES_P100 + &Plugin_100, +#endif // ifdef USES_P100 + +#ifdef USES_P101 + &Plugin_101, +#endif // ifdef USES_P101 + +#ifdef USES_P102 + &Plugin_102, +#endif // ifdef USES_P102 + +#ifdef USES_P103 + &Plugin_103, +#endif // ifdef USES_P103 + +#ifdef USES_P104 + &Plugin_104, +#endif // ifdef USES_P104 + +#ifdef USES_P105 + &Plugin_105, +#endif // ifdef USES_P105 + +#ifdef USES_P106 + &Plugin_106, +#endif // ifdef USES_P106 + +#ifdef USES_P107 + &Plugin_107, +#endif // ifdef USES_P107 + +#ifdef USES_P108 + &Plugin_108, +#endif // ifdef USES_P108 + +#ifdef USES_P109 + &Plugin_109, +#endif // ifdef USES_P109 + +#ifdef USES_P110 + &Plugin_110, +#endif // ifdef USES_P110 + +#ifdef USES_P111 + &Plugin_111, +#endif // ifdef USES_P111 + +#ifdef USES_P112 + &Plugin_112, +#endif // ifdef USES_P112 + +#ifdef USES_P113 + &Plugin_113, +#endif // ifdef USES_P113 + +#ifdef USES_P114 + &Plugin_114, +#endif // ifdef USES_P114 + +#ifdef USES_P115 + &Plugin_115, +#endif // ifdef USES_P115 + +#ifdef USES_P116 + &Plugin_116, +#endif // ifdef USES_P116 + +#ifdef USES_P117 + &Plugin_117, +#endif // ifdef USES_P117 + +#ifdef USES_P118 + &Plugin_118, +#endif // ifdef USES_P118 + +#ifdef USES_P119 + &Plugin_119, +#endif // ifdef USES_P119 + +#ifdef USES_P120 + &Plugin_120, +#endif // ifdef USES_P120 + +#ifdef USES_P121 + &Plugin_121, +#endif // ifdef USES_P121 + +#ifdef USES_P122 + &Plugin_122, +#endif // ifdef USES_P122 + +#ifdef USES_P123 + &Plugin_123, +#endif // ifdef USES_P123 + +#ifdef USES_P124 + &Plugin_124, +#endif // ifdef USES_P124 + +#ifdef USES_P125 + &Plugin_125, +#endif // ifdef USES_P125 + +#ifdef USES_P126 + &Plugin_126, +#endif // ifdef USES_P126 + +#ifdef USES_P127 + &Plugin_127, +#endif // ifdef USES_P127 + +#ifdef USES_P128 + &Plugin_128, +#endif // ifdef USES_P128 + +#ifdef USES_P129 + &Plugin_129, +#endif // ifdef USES_P129 + +#ifdef USES_P130 + &Plugin_130, +#endif // ifdef USES_P130 + +#ifdef USES_P131 + &Plugin_131, +#endif // ifdef USES_P131 + +#ifdef USES_P132 + &Plugin_132, +#endif // ifdef USES_P132 + +#ifdef USES_P133 + &Plugin_133, +#endif // ifdef USES_P133 + +#ifdef USES_P134 + &Plugin_134, +#endif // ifdef USES_P134 + +#ifdef USES_P135 + &Plugin_135, +#endif // ifdef USES_P135 + +#ifdef USES_P136 + &Plugin_136, +#endif // ifdef USES_P136 + +#ifdef USES_P137 + &Plugin_137, +#endif // ifdef USES_P137 + +#ifdef USES_P138 + &Plugin_138, +#endif // ifdef USES_P138 + +#ifdef USES_P139 + &Plugin_139, +#endif // ifdef USES_P139 + +#ifdef USES_P140 + &Plugin_140, +#endif // ifdef USES_P140 + +#ifdef USES_P141 + &Plugin_141, +#endif // ifdef USES_P141 + +#ifdef USES_P142 + &Plugin_142, +#endif // ifdef USES_P142 + +#ifdef USES_P143 + &Plugin_143, +#endif // ifdef USES_P143 + +#ifdef USES_P144 + &Plugin_144, +#endif // ifdef USES_P144 + +#ifdef USES_P145 + &Plugin_145, +#endif // ifdef USES_P145 + +#ifdef USES_P146 + &Plugin_146, +#endif // ifdef USES_P146 + +#ifdef USES_P147 + &Plugin_147, +#endif // ifdef USES_P147 + +#ifdef USES_P148 + &Plugin_148, +#endif // ifdef USES_P148 + +#ifdef USES_P149 + &Plugin_149, +#endif // ifdef USES_P149 + +#ifdef USES_P150 + &Plugin_150, +#endif // ifdef USES_P150 + +#ifdef USES_P151 + &Plugin_151, +#endif // ifdef USES_P151 + +#ifdef USES_P152 + &Plugin_152, +#endif // ifdef USES_P152 + +#ifdef USES_P153 + &Plugin_153, +#endif // ifdef USES_P153 + +#ifdef USES_P154 + &Plugin_154, +#endif // ifdef USES_P154 + +#ifdef USES_P155 + &Plugin_155, +#endif // ifdef USES_P155 + +#ifdef USES_P156 + &Plugin_156, +#endif // ifdef USES_P156 + +#ifdef USES_P157 + &Plugin_157, +#endif // ifdef USES_P157 + +#ifdef USES_P158 + &Plugin_158, +#endif // ifdef USES_P158 + +#ifdef USES_P159 + &Plugin_159, +#endif // ifdef USES_P159 + +#ifdef USES_P160 + &Plugin_160, +#endif // ifdef USES_P160 + +#ifdef USES_P161 + &Plugin_161, +#endif // ifdef USES_P161 + +#ifdef USES_P162 + &Plugin_162, +#endif // ifdef USES_P162 + +#ifdef USES_P163 + &Plugin_163, +#endif // ifdef USES_P163 + +#ifdef USES_P164 + &Plugin_164, +#endif // ifdef USES_P164 + +#ifdef USES_P165 + &Plugin_165, +#endif // ifdef USES_P165 + +#ifdef USES_P166 + &Plugin_166, +#endif // ifdef USES_P166 + +#ifdef USES_P167 + &Plugin_167, +#endif // ifdef USES_P167 + +#ifdef USES_P168 + &Plugin_168, +#endif // ifdef USES_P168 + +#ifdef USES_P169 + &Plugin_169, +#endif // ifdef USES_P169 + +#ifdef USES_P170 + &Plugin_170, +#endif // ifdef USES_P170 + +#ifdef USES_P171 + &Plugin_171, +#endif // ifdef USES_P171 + +#ifdef USES_P172 + &Plugin_172, +#endif // ifdef USES_P172 + +#ifdef USES_P173 + &Plugin_173, +#endif // ifdef USES_P173 + +#ifdef USES_P174 + &Plugin_174, +#endif // ifdef USES_P174 + +#ifdef USES_P175 + &Plugin_175, +#endif // ifdef USES_P175 + +#ifdef USES_P176 + &Plugin_176, +#endif // ifdef USES_P176 + +#ifdef USES_P177 + &Plugin_177, +#endif // ifdef USES_P177 + +#ifdef USES_P178 + &Plugin_178, +#endif // ifdef USES_P178 + +#ifdef USES_P179 + &Plugin_179, +#endif // ifdef USES_P179 + +#ifdef USES_P180 + &Plugin_180, +#endif // ifdef USES_P180 + +#ifdef USES_P181 + &Plugin_181, +#endif // ifdef USES_P181 + +#ifdef USES_P182 + &Plugin_182, +#endif // ifdef USES_P182 + +#ifdef USES_P183 + &Plugin_183, +#endif // ifdef USES_P183 + +#ifdef USES_P184 + &Plugin_184, +#endif // ifdef USES_P184 + +#ifdef USES_P185 + &Plugin_185, +#endif // ifdef USES_P185 + +#ifdef USES_P186 + &Plugin_186, +#endif // ifdef USES_P186 + +#ifdef USES_P187 + &Plugin_187, +#endif // ifdef USES_P187 + +#ifdef USES_P188 + &Plugin_188, +#endif // ifdef USES_P188 + +#ifdef USES_P189 + &Plugin_189, +#endif // ifdef USES_P189 + +#ifdef USES_P190 + &Plugin_190, +#endif // ifdef USES_P190 + +#ifdef USES_P191 + &Plugin_191, +#endif // ifdef USES_P191 + +#ifdef USES_P192 + &Plugin_192, +#endif // ifdef USES_P192 + +#ifdef USES_P193 + &Plugin_193, +#endif // ifdef USES_P193 + +#ifdef USES_P194 + &Plugin_194, +#endif // ifdef USES_P194 + +#ifdef USES_P195 + &Plugin_195, +#endif // ifdef USES_P195 + +#ifdef USES_P196 + &Plugin_196, +#endif // ifdef USES_P196 + +#ifdef USES_P197 + &Plugin_197, +#endif // ifdef USES_P197 + +#ifdef USES_P198 + &Plugin_198, +#endif // ifdef USES_P198 + +#ifdef USES_P199 + &Plugin_199, +#endif // ifdef USES_P199 + +#ifdef USES_P200 + &Plugin_200, +#endif // ifdef USES_P200 + +#ifdef USES_P201 + &Plugin_201, +#endif // ifdef USES_P201 + +#ifdef USES_P202 + &Plugin_202, +#endif // ifdef USES_P202 + +#ifdef USES_P203 + &Plugin_203, +#endif // ifdef USES_P203 + +#ifdef USES_P204 + &Plugin_204, +#endif // ifdef USES_P204 + +#ifdef USES_P205 + &Plugin_205, +#endif // ifdef USES_P205 + +#ifdef USES_P206 + &Plugin_206, +#endif // ifdef USES_P206 + +#ifdef USES_P207 + &Plugin_207, +#endif // ifdef USES_P207 + +#ifdef USES_P208 + &Plugin_208, +#endif // ifdef USES_P208 + +#ifdef USES_P209 + &Plugin_209, +#endif // ifdef USES_P209 + +#ifdef USES_P210 + &Plugin_210, +#endif // ifdef USES_P210 + +#ifdef USES_P211 + &Plugin_211, +#endif // ifdef USES_P211 + +#ifdef USES_P212 + &Plugin_212, +#endif // ifdef USES_P212 + +#ifdef USES_P213 + &Plugin_213, +#endif // ifdef USES_P213 + +#ifdef USES_P214 + &Plugin_214, +#endif // ifdef USES_P214 + +#ifdef USES_P215 + &Plugin_215, +#endif // ifdef USES_P215 + +#ifdef USES_P216 + &Plugin_216, +#endif // ifdef USES_P216 + +#ifdef USES_P217 + &Plugin_217, +#endif // ifdef USES_P217 + +#ifdef USES_P218 + &Plugin_218, +#endif // ifdef USES_P218 + +#ifdef USES_P219 + &Plugin_219, +#endif // ifdef USES_P219 + +#ifdef USES_P220 + &Plugin_220, +#endif // ifdef USES_P220 + +#ifdef USES_P221 + &Plugin_221, +#endif // ifdef USES_P221 + +#ifdef USES_P222 + &Plugin_222, +#endif // ifdef USES_P222 + +#ifdef USES_P223 + &Plugin_223, +#endif // ifdef USES_P223 + +#ifdef USES_P224 + &Plugin_224, +#endif // ifdef USES_P224 + +#ifdef USES_P225 + &Plugin_225, +#endif // ifdef USES_P225 + +#ifdef USES_P226 + &Plugin_226, +#endif // ifdef USES_P226 + +#ifdef USES_P227 + &Plugin_227, +#endif // ifdef USES_P227 + +#ifdef USES_P228 + &Plugin_228, +#endif // ifdef USES_P228 + +#ifdef USES_P229 + &Plugin_229, +#endif // ifdef USES_P229 + +#ifdef USES_P230 + &Plugin_230, +#endif // ifdef USES_P230 + +#ifdef USES_P231 + &Plugin_231, +#endif // ifdef USES_P231 + +#ifdef USES_P232 + &Plugin_232, +#endif // ifdef USES_P232 + +#ifdef USES_P233 + &Plugin_233, +#endif // ifdef USES_P233 + +#ifdef USES_P234 + &Plugin_234, +#endif // ifdef USES_P234 + +#ifdef USES_P235 + &Plugin_235, +#endif // ifdef USES_P235 + +#ifdef USES_P236 + &Plugin_236, +#endif // ifdef USES_P236 + +#ifdef USES_P237 + &Plugin_237, +#endif // ifdef USES_P237 + +#ifdef USES_P238 + &Plugin_238, +#endif // ifdef USES_P238 + +#ifdef USES_P239 + &Plugin_239, +#endif // ifdef USES_P239 + +#ifdef USES_P240 + &Plugin_240, +#endif // ifdef USES_P240 + +#ifdef USES_P241 + &Plugin_241, +#endif // ifdef USES_P241 + +#ifdef USES_P242 + &Plugin_242, +#endif // ifdef USES_P242 + +#ifdef USES_P243 + &Plugin_243, +#endif // ifdef USES_P243 + +#ifdef USES_P244 + &Plugin_244, +#endif // ifdef USES_P244 + +#ifdef USES_P245 + &Plugin_245, +#endif // ifdef USES_P245 + +#ifdef USES_P246 + &Plugin_246, +#endif // ifdef USES_P246 + +#ifdef USES_P247 + &Plugin_247, +#endif // ifdef USES_P247 + +#ifdef USES_P248 + &Plugin_248, +#endif // ifdef USES_P248 + +#ifdef USES_P249 + &Plugin_249, +#endif // ifdef USES_P249 + +#ifdef USES_P250 + &Plugin_250, +#endif // ifdef USES_P250 + +#ifdef USES_P251 + &Plugin_251, +#endif // ifdef USES_P251 + +#ifdef USES_P252 + &Plugin_252, +#endif // ifdef USES_P252 + +#ifdef USES_P253 + &Plugin_253, +#endif // ifdef USES_P253 + +#ifdef USES_P254 + &Plugin_254, +#endif // ifdef USES_P254 + +#ifdef USES_P255 + &Plugin_255, +#endif // ifdef USES_P255 +}; + +bool _Plugin_init_setupDone = false; + + +constexpr size_t DeviceIndex_to_Plugin_id_size = NR_ELEMENTS(DeviceIndex_to_Plugin_id); + +// Lowest plugin ID included in the build +constexpr size_t Lowest_Plugin_id = DeviceIndex_to_Plugin_id_size == 0 ? 0 : DeviceIndex_to_Plugin_id[0]; + +// Highest plugin ID included in the build +constexpr size_t Highest_Plugin_id = DeviceIndex_to_Plugin_id_size > 1 ? DeviceIndex_to_Plugin_id[DeviceIndex_to_Plugin_id_size - 1] : 0; + +// Array size including index of highest plugin ID. +constexpr size_t Plugin_id_to_DeviceIndex_size = Highest_Plugin_id + 1 - Lowest_Plugin_id; + +// Array filled during init. +// Valid index: 1 ... Highest_Plugin_id +// Returns index to the DeviceIndex_to_Plugin_id array +// +// Vector size should is lowest pluginID ... highest pluginID +deviceIndex_t Plugin_id_to_DeviceIndex[Plugin_id_to_DeviceIndex_size]{}; + +// Used as lookup for getting an alfabetically sorted deviceIndex +deviceIndex_t DeviceIndex_sorted[DeviceIndex_to_Plugin_id_size]; + + +size_t get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID_t pluginID) +{ + if (pluginID.value >= Lowest_Plugin_id) + { + const size_t arrIndex = static_cast(pluginID.value) - Lowest_Plugin_id; + if (arrIndex < Plugin_id_to_DeviceIndex_size) { + return arrIndex; + } + } + return Plugin_id_to_DeviceIndex_size; +} + + +/* +// TD-er: Test to make constexpr array Plugin_id_to_DeviceIndex +// Have to postpone, since std::integer_sequence is not available in Espressif SDK + +// Constexpr functions to create Plugin_id_to_DeviceIndex at compile time +constexpr uint8_t getDevId_from_PId(unsigned p_id, unsigned d_id) { + return (p_id == 0 || d_id == DeviceIndex_to_Plugin_id_size) ? DEVICE_INDEX_MAX + : (p_id == DeviceIndex_to_Plugin_id[d_id]) ? d_id : getDevId_from_PId(p_id, d_id+1); +} + +constexpr uint8_t getDevId_from_PId(unsigned p_id) { + return getDevId_from_PId(p_id, 0); +} +constexpr auto test = getDevId_from_PId(30); + + +#include +#include +#include + + +template +constexpr auto get_DevId_from_PId_array(std::integer_sequence a) -> std::array { + std::array vals{}; + ((vals[Is] = getDevId_from_PId(Is)), ...); + return vals; +} + +constexpr auto x = get_DevId_from_PId_array(std::make_integer_sequence{}); +*/ + +unsigned getNrBitsDeviceIndex() +{ + // FIXME TD-er: Must somehow make this a constexpr function + constexpr unsigned nrBits = NR_BITS(DeviceIndex_to_Plugin_id_size); + return nrBits; +} + +unsigned getNrBuiltInDeviceIndex() +{ + return DeviceIndex_to_Plugin_id_size; +} + +deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID) +{ + if (validPluginID(pluginID)) { + const size_t arrayIndex = get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID); + if (arrayIndex < Plugin_id_to_DeviceIndex_size) + { + return Plugin_id_to_DeviceIndex[arrayIndex]; + } + } + return INVALID_DEVICE_INDEX; +} + +pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex) +{ + if (validDeviceIndex_init(deviceIndex)) + { + return pluginID_t::toPluginID(pgm_read_byte(DeviceIndex_to_Plugin_id + deviceIndex.value)); + } + return INVALID_PLUGIN_ID; +} + +bool validDeviceIndex_init(deviceIndex_t deviceIndex) +{ + if (_Plugin_init_setupDone) { + return deviceIndex < DeviceIndex_to_Plugin_id_size; + } + return false; +} + +// Array containing "DeviceIndex" alfabetically sorted. +deviceIndex_t getDeviceIndex_sorted(deviceIndex_t deviceIndex) +{ + if (validDeviceIndex_init(deviceIndex)) { + return DeviceIndex_sorted[deviceIndex.value]; + } + return INVALID_DEVICE_INDEX; +} + + +boolean PluginCall(deviceIndex_t deviceIndex, uint8_t function, struct EventStruct *event, String& string) +{ + if (validDeviceIndex_init(deviceIndex)) + { + Plugin_ptr_t plugin_call = (Plugin_ptr_t)pgm_read_ptr(Plugin_ptr + deviceIndex.value); + return plugin_call(function, event, string); + } + return false; +} + +void PluginSetup() +{ + if (_Plugin_init_setupDone) return; + + _Plugin_init_setupDone = true; + + for (size_t id = 0; id < Plugin_id_to_DeviceIndex_size; ++id) + { + Plugin_id_to_DeviceIndex[id] = INVALID_DEVICE_INDEX; + } + #ifdef ESP8266 + Device = new (std::nothrow) DeviceStruct[DeviceIndex_to_Plugin_id_size]; + #else + Device.resize(DeviceIndex_to_Plugin_id_size); + #endif + + for (deviceIndex_t deviceIndex; deviceIndex < DeviceIndex_to_Plugin_id_size; ++deviceIndex) + { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); + + if (validPluginID(pluginID)) { + const size_t arrayIndex = get_Plugin_id_to_DeviceIndex_arrayIndex(pluginID); + if (arrayIndex < Plugin_id_to_DeviceIndex_size) { + // Should never be outside these limits. + Plugin_id_to_DeviceIndex[arrayIndex] = deviceIndex; + struct EventStruct TempEvent; + TempEvent.idx = deviceIndex.value; + String dummy; + PluginCall(deviceIndex, PLUGIN_DEVICE_ADD, &TempEvent, dummy); + } + } + } +#ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("PLUGIN_DEVICE_ADD")); +#endif + + // ******************************************************************************** + // Device Sort routine, actual sorting alfabetically by plugin name. + // Sorting does happen case sensitive. + // Used in device selector dropdown. + // ******************************************************************************** + + // First fill the existing number of the DeviceIndex. + for (deviceIndex_t x; x < DeviceIndex_to_Plugin_id_size; ++x) { + DeviceIndex_sorted[x.value] = x; + } + + struct + { + bool operator()(deviceIndex_t a, deviceIndex_t b) const { + return getPluginNameFromDeviceIndex(a) < + getPluginNameFromDeviceIndex(b); + } + } + customLess; + std::sort(DeviceIndex_sorted, DeviceIndex_sorted + DeviceIndex_to_Plugin_id_size, customLess); +} + +void PluginInit(bool priorityOnly) +{ + + // Set all not supported plugins to disabled. + for (taskIndex_t taskIndex = 0; taskIndex < TASKS_MAX; ++taskIndex) { + if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) { + Settings.TaskDeviceEnabled[taskIndex] = false; + } + } + + + if (!priorityOnly) { + String dummy; + PluginCall(PLUGIN_INIT_ALL, nullptr, dummy); + #ifndef BUILD_NO_RAM_TRACKER + logMemUsageAfter(F("PLUGIN_INIT_ALL")); + #endif + } +} diff --git a/src/src/Helpers/_Plugin_init.h b/src/src/Helpers/_Plugin_init.h index f1077a88d..c288c557a 100644 --- a/src/src/Helpers/_Plugin_init.h +++ b/src/src/Helpers/_Plugin_init.h @@ -1,1067 +1,1067 @@ -#ifndef HELPERS__PLUGIN_INIT_H -#define HELPERS__PLUGIN_INIT_H - -#include "../../ESPEasy_common.h" - -#include "../DataTypes/DeviceIndex.h" -#include "../DataTypes/PluginID.h" -#include "../DataTypes/ESPEasy_plugin_functions.h" - - -struct EventStruct; - -deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID); -pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex); -bool validDeviceIndex_init(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); - -// Get the sizeof() in number of bits for the number of actually included plugins in the build -unsigned getNrBitsDeviceIndex(); -unsigned getNrBuiltInDeviceIndex(); - -void PluginSetup(); - -void PluginInit(bool priorityOnly = false); - -// Macro to forward declare the Plugin_NNN functions. -// -// Uncrustify must not be used on macros, so turn it off. -// *INDENT-OFF* -#define ADDPLUGIN_H(NNN) boolean Plugin_##NNN(uint8_t function, struct 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_P001 - ADDPLUGIN_H(001) -#endif - -#ifdef USES_P002 - ADDPLUGIN_H(002) -#endif - -#ifdef USES_P003 - ADDPLUGIN_H(003) -#endif - -#ifdef USES_P004 - ADDPLUGIN_H(004) -#endif - -#ifdef USES_P005 - ADDPLUGIN_H(005) -#endif - -#ifdef USES_P006 - ADDPLUGIN_H(006) -#endif - -#ifdef USES_P007 - ADDPLUGIN_H(007) -#endif - -#ifdef USES_P008 - ADDPLUGIN_H(008) -#endif - -#ifdef USES_P009 - ADDPLUGIN_H(009) -#endif - -#ifdef USES_P010 - ADDPLUGIN_H(010) -#endif - -#ifdef USES_P011 - ADDPLUGIN_H(011) -#endif - -#ifdef USES_P012 - ADDPLUGIN_H(012) -#endif - -#ifdef USES_P013 - ADDPLUGIN_H(013) -#endif - -#ifdef USES_P014 - ADDPLUGIN_H(014) -#endif - -#ifdef USES_P015 - ADDPLUGIN_H(015) -#endif - -#ifdef USES_P016 - ADDPLUGIN_H(016) -#endif - -#ifdef USES_P017 - ADDPLUGIN_H(017) -#endif - -#ifdef USES_P018 - ADDPLUGIN_H(018) -#endif - -#ifdef USES_P019 - ADDPLUGIN_H(019) -#endif - -#ifdef USES_P020 - ADDPLUGIN_H(020) -#endif - -#ifdef USES_P021 - ADDPLUGIN_H(021) -#endif - -#ifdef USES_P022 - ADDPLUGIN_H(022) -#endif - -#ifdef USES_P023 - ADDPLUGIN_H(023) -#endif - -#ifdef USES_P024 - ADDPLUGIN_H(024) -#endif - -#ifdef USES_P025 - ADDPLUGIN_H(025) -#endif - -#ifdef USES_P026 - ADDPLUGIN_H(026) -#endif - -#ifdef USES_P027 - ADDPLUGIN_H(027) -#endif - -#ifdef USES_P028 - ADDPLUGIN_H(028) -#endif - -#ifdef USES_P029 - ADDPLUGIN_H(029) -#endif - -#ifdef USES_P030 - ADDPLUGIN_H(030) -#endif - -#ifdef USES_P031 - ADDPLUGIN_H(031) -#endif - -#ifdef USES_P032 - ADDPLUGIN_H(032) -#endif - -#ifdef USES_P033 - ADDPLUGIN_H(033) -#endif - -#ifdef USES_P034 - ADDPLUGIN_H(034) -#endif - -#ifdef USES_P035 - ADDPLUGIN_H(035) -#endif - -#ifdef USES_P036 - ADDPLUGIN_H(036) -#endif - -#ifdef USES_P037 - ADDPLUGIN_H(037) -#endif - -#ifdef USES_P038 - ADDPLUGIN_H(038) -#endif - -#ifdef USES_P039 - ADDPLUGIN_H(039) -#endif - -#ifdef USES_P040 - ADDPLUGIN_H(040) -#endif - -#ifdef USES_P041 - ADDPLUGIN_H(041) -#endif - -#ifdef USES_P042 - ADDPLUGIN_H(042) -#endif - -#ifdef USES_P043 - ADDPLUGIN_H(043) -#endif - -#ifdef USES_P044 - ADDPLUGIN_H(044) -#endif - -#ifdef USES_P045 - ADDPLUGIN_H(045) -#endif - -#ifdef USES_P046 - ADDPLUGIN_H(046) -#endif - -#ifdef USES_P047 - ADDPLUGIN_H(047) -#endif - -#ifdef USES_P048 - ADDPLUGIN_H(048) -#endif - -#ifdef USES_P049 - ADDPLUGIN_H(049) -#endif - -#ifdef USES_P050 - ADDPLUGIN_H(050) -#endif - -#ifdef USES_P051 - ADDPLUGIN_H(051) -#endif - -#ifdef USES_P052 - ADDPLUGIN_H(052) -#endif - -#ifdef USES_P053 - ADDPLUGIN_H(053) -#endif - -#ifdef USES_P054 - ADDPLUGIN_H(054) -#endif - -#ifdef USES_P055 - ADDPLUGIN_H(055) -#endif - -#ifdef USES_P056 - ADDPLUGIN_H(056) -#endif - -#ifdef USES_P057 - ADDPLUGIN_H(057) -#endif - -#ifdef USES_P058 - ADDPLUGIN_H(058) -#endif - -#ifdef USES_P059 - ADDPLUGIN_H(059) -#endif - -#ifdef USES_P060 - ADDPLUGIN_H(060) -#endif - -#ifdef USES_P061 - ADDPLUGIN_H(061) -#endif - -#ifdef USES_P062 - ADDPLUGIN_H(062) -#endif - -#ifdef USES_P063 - ADDPLUGIN_H(063) -#endif - -#ifdef USES_P064 - ADDPLUGIN_H(064) -#endif - -#ifdef USES_P065 - ADDPLUGIN_H(065) -#endif - -#ifdef USES_P066 - ADDPLUGIN_H(066) -#endif - -#ifdef USES_P067 - ADDPLUGIN_H(067) -#endif - -#ifdef USES_P068 - ADDPLUGIN_H(068) -#endif - -#ifdef USES_P069 - ADDPLUGIN_H(069) -#endif - -#ifdef USES_P070 - ADDPLUGIN_H(070) -#endif - -#ifdef USES_P071 - ADDPLUGIN_H(071) -#endif - -#ifdef USES_P072 - ADDPLUGIN_H(072) -#endif - -#ifdef USES_P073 - ADDPLUGIN_H(073) -#endif - -#ifdef USES_P074 - ADDPLUGIN_H(074) -#endif - -#ifdef USES_P075 - ADDPLUGIN_H(075) -#endif - -#ifdef USES_P076 - ADDPLUGIN_H(076) -#endif - -#ifdef USES_P077 - ADDPLUGIN_H(077) -#endif - -#ifdef USES_P078 - ADDPLUGIN_H(078) -#endif - -#ifdef USES_P079 - ADDPLUGIN_H(079) -#endif - -#ifdef USES_P080 - ADDPLUGIN_H(080) -#endif - -#ifdef USES_P081 - ADDPLUGIN_H(081) -#endif - -#ifdef USES_P082 - ADDPLUGIN_H(082) -#endif - -#ifdef USES_P083 - ADDPLUGIN_H(083) -#endif - -#ifdef USES_P084 - ADDPLUGIN_H(084) -#endif - -#ifdef USES_P085 - ADDPLUGIN_H(085) -#endif - -#ifdef USES_P086 - ADDPLUGIN_H(086) -#endif - -#ifdef USES_P087 - ADDPLUGIN_H(087) -#endif - -#ifdef USES_P088 - ADDPLUGIN_H(088) -#endif - -#ifdef USES_P089 - #ifdef ESP8266 - // FIXME TD-er: Support Ping plugin for ESP32 - ADDPLUGIN_H(089) - #endif -#endif - -#ifdef USES_P090 - ADDPLUGIN_H(090) -#endif - -#ifdef USES_P091 - ADDPLUGIN_H(091) -#endif - -#ifdef USES_P092 - ADDPLUGIN_H(092) -#endif - -#ifdef USES_P093 - ADDPLUGIN_H(093) -#endif - -#ifdef USES_P094 - ADDPLUGIN_H(094) -#endif - -#ifdef USES_P095 - ADDPLUGIN_H(095) -#endif - -#ifdef USES_P096 - ADDPLUGIN_H(096) -#endif - -#ifdef USES_P097 - # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) - ADDPLUGIN_H(097) // Touch (ESP32) - #endif -#endif - -#ifdef USES_P098 - ADDPLUGIN_H(098) -#endif - -#ifdef USES_P099 - ADDPLUGIN_H(099) -#endif - -#ifdef USES_P100 - ADDPLUGIN_H(100) -#endif - -#ifdef USES_P101 - ADDPLUGIN_H(101) -#endif - -#ifdef USES_P102 - ADDPLUGIN_H(102) -#endif - -#ifdef USES_P103 - ADDPLUGIN_H(103) -#endif - -#ifdef USES_P104 - ADDPLUGIN_H(104) -#endif - -#ifdef USES_P105 - ADDPLUGIN_H(105) -#endif - -#ifdef USES_P106 - ADDPLUGIN_H(106) -#endif - -#ifdef USES_P107 - ADDPLUGIN_H(107) -#endif - -#ifdef USES_P108 - ADDPLUGIN_H(108) -#endif - -#ifdef USES_P109 - ADDPLUGIN_H(109) -#endif - -#ifdef USES_P110 - ADDPLUGIN_H(110) -#endif - -#ifdef USES_P111 - ADDPLUGIN_H(111) -#endif - -#ifdef USES_P112 - ADDPLUGIN_H(112) -#endif - -#ifdef USES_P113 - ADDPLUGIN_H(113) -#endif - -#ifdef USES_P114 - ADDPLUGIN_H(114) -#endif - -#ifdef USES_P115 - ADDPLUGIN_H(115) -#endif - -#ifdef USES_P116 - ADDPLUGIN_H(116) -#endif - -#ifdef USES_P117 - ADDPLUGIN_H(117) -#endif - -#ifdef USES_P118 - ADDPLUGIN_H(118) -#endif - -#ifdef USES_P119 - ADDPLUGIN_H(119) -#endif - -#ifdef USES_P120 - ADDPLUGIN_H(120) -#endif - -#ifdef USES_P121 - ADDPLUGIN_H(121) -#endif - -#ifdef USES_P122 - ADDPLUGIN_H(122) -#endif - -#ifdef USES_P123 - ADDPLUGIN_H(123) -#endif - -#ifdef USES_P124 - ADDPLUGIN_H(124) -#endif - -#ifdef USES_P125 - ADDPLUGIN_H(125) -#endif - -#ifdef USES_P126 - ADDPLUGIN_H(126) -#endif - -#ifdef USES_P127 - ADDPLUGIN_H(127) -#endif - -#ifdef USES_P128 - ADDPLUGIN_H(128) -#endif - -#ifdef USES_P129 - ADDPLUGIN_H(129) -#endif - -#ifdef USES_P130 - ADDPLUGIN_H(130) -#endif - -#ifdef USES_P131 - ADDPLUGIN_H(131) -#endif - -#ifdef USES_P132 - ADDPLUGIN_H(132) -#endif - -#ifdef USES_P133 - ADDPLUGIN_H(133) -#endif - -#ifdef USES_P134 - ADDPLUGIN_H(134) -#endif - -#ifdef USES_P135 - ADDPLUGIN_H(135) -#endif - -#ifdef USES_P136 - ADDPLUGIN_H(136) -#endif - -#ifdef USES_P137 - ADDPLUGIN_H(137) -#endif - -#ifdef USES_P138 - ADDPLUGIN_H(138) -#endif - -#ifdef USES_P139 - ADDPLUGIN_H(139) -#endif - -#ifdef USES_P140 - ADDPLUGIN_H(140) -#endif - -#ifdef USES_P141 - ADDPLUGIN_H(141) -#endif - -#ifdef USES_P142 - ADDPLUGIN_H(142) -#endif - -#ifdef USES_P143 - ADDPLUGIN_H(143) -#endif - -#ifdef USES_P144 - ADDPLUGIN_H(144) -#endif - -#ifdef USES_P145 - ADDPLUGIN_H(145) -#endif - -#ifdef USES_P146 - ADDPLUGIN_H(146) -#endif - -#ifdef USES_P147 - ADDPLUGIN_H(147) -#endif - -#ifdef USES_P148 - ADDPLUGIN_H(148) -#endif - -#ifdef USES_P149 - ADDPLUGIN_H(149) -#endif - -#ifdef USES_P150 - ADDPLUGIN_H(150) -#endif - -#ifdef USES_P151 - ADDPLUGIN_H(151) -#endif - -#ifdef USES_P152 - ADDPLUGIN_H(152) -#endif - -#ifdef USES_P153 - ADDPLUGIN_H(153) -#endif - -#ifdef USES_P154 - ADDPLUGIN_H(154) -#endif - -#ifdef USES_P155 - ADDPLUGIN_H(155) -#endif - -#ifdef USES_P156 - ADDPLUGIN_H(156) -#endif - -#ifdef USES_P157 - ADDPLUGIN_H(157) -#endif - -#ifdef USES_P158 - ADDPLUGIN_H(158) -#endif - -#ifdef USES_P159 - ADDPLUGIN_H(159) -#endif - -#ifdef USES_P160 - ADDPLUGIN_H(160) -#endif - -#ifdef USES_P161 - ADDPLUGIN_H(161) -#endif - -#ifdef USES_P162 - ADDPLUGIN_H(162) -#endif - -#ifdef USES_P163 - ADDPLUGIN_H(163) -#endif - -#ifdef USES_P164 - ADDPLUGIN_H(164) -#endif - -#ifdef USES_P165 - ADDPLUGIN_H(165) -#endif - -#ifdef USES_P166 - ADDPLUGIN_H(166) -#endif - -#ifdef USES_P167 - ADDPLUGIN_H(167) -#endif - -#ifdef USES_P168 - ADDPLUGIN_H(168) -#endif - -#ifdef USES_P169 - ADDPLUGIN_H(169) -#endif - -#ifdef USES_P170 - ADDPLUGIN_H(170) -#endif - -#ifdef USES_P171 - ADDPLUGIN_H(171) -#endif - -#ifdef USES_P172 - ADDPLUGIN_H(172) -#endif - -#ifdef USES_P173 - ADDPLUGIN_H(173) -#endif - -#ifdef USES_P174 - ADDPLUGIN_H(174) -#endif - -#ifdef USES_P175 - ADDPLUGIN_H(175) -#endif - -#ifdef USES_P176 - ADDPLUGIN_H(176) -#endif - -#ifdef USES_P177 - ADDPLUGIN_H(177) -#endif - -#ifdef USES_P178 - ADDPLUGIN_H(178) -#endif - -#ifdef USES_P179 - ADDPLUGIN_H(179) -#endif - -#ifdef USES_P180 - ADDPLUGIN_H(180) -#endif - -#ifdef USES_P181 - ADDPLUGIN_H(181) -#endif - -#ifdef USES_P182 - ADDPLUGIN_H(182) -#endif - -#ifdef USES_P183 - ADDPLUGIN_H(183) -#endif - -#ifdef USES_P184 - ADDPLUGIN_H(184) -#endif - -#ifdef USES_P185 - ADDPLUGIN_H(185) -#endif - -#ifdef USES_P186 - ADDPLUGIN_H(186) -#endif - -#ifdef USES_P187 - ADDPLUGIN_H(187) -#endif - -#ifdef USES_P188 - ADDPLUGIN_H(188) -#endif - -#ifdef USES_P189 - ADDPLUGIN_H(189) -#endif - -#ifdef USES_P190 - ADDPLUGIN_H(190) -#endif - -#ifdef USES_P191 - ADDPLUGIN_H(191) -#endif - -#ifdef USES_P192 - ADDPLUGIN_H(192) -#endif - -#ifdef USES_P193 - ADDPLUGIN_H(193) -#endif - -#ifdef USES_P194 - ADDPLUGIN_H(194) -#endif - -#ifdef USES_P195 - ADDPLUGIN_H(195) -#endif - -#ifdef USES_P196 - ADDPLUGIN_H(196) -#endif - -#ifdef USES_P197 - ADDPLUGIN_H(197) -#endif - -#ifdef USES_P198 - ADDPLUGIN_H(198) -#endif - -#ifdef USES_P199 - ADDPLUGIN_H(199) -#endif - -#ifdef USES_P200 - ADDPLUGIN_H(200) -#endif - -#ifdef USES_P201 - ADDPLUGIN_H(201) -#endif - -#ifdef USES_P202 - ADDPLUGIN_H(202) -#endif - -#ifdef USES_P203 - ADDPLUGIN_H(203) -#endif - -#ifdef USES_P204 - ADDPLUGIN_H(204) -#endif - -#ifdef USES_P205 - ADDPLUGIN_H(205) -#endif - -#ifdef USES_P206 - ADDPLUGIN_H(206) -#endif - -#ifdef USES_P207 - ADDPLUGIN_H(207) -#endif - -#ifdef USES_P208 - ADDPLUGIN_H(208) -#endif - -#ifdef USES_P209 - ADDPLUGIN_H(209) -#endif - -#ifdef USES_P210 - ADDPLUGIN_H(210) -#endif - -#ifdef USES_P211 - ADDPLUGIN_H(211) -#endif - -#ifdef USES_P212 - ADDPLUGIN_H(212) -#endif - -#ifdef USES_P213 - ADDPLUGIN_H(213) -#endif - -#ifdef USES_P214 - ADDPLUGIN_H(214) -#endif - -#ifdef USES_P215 - ADDPLUGIN_H(215) -#endif - -#ifdef USES_P216 - ADDPLUGIN_H(216) -#endif - -#ifdef USES_P217 - ADDPLUGIN_H(217) -#endif - -#ifdef USES_P218 - ADDPLUGIN_H(218) -#endif - -#ifdef USES_P219 - ADDPLUGIN_H(219) -#endif - -#ifdef USES_P220 - ADDPLUGIN_H(220) -#endif - -#ifdef USES_P221 - ADDPLUGIN_H(221) -#endif - -#ifdef USES_P222 - ADDPLUGIN_H(222) -#endif - -#ifdef USES_P223 - ADDPLUGIN_H(223) -#endif - -#ifdef USES_P224 - ADDPLUGIN_H(224) -#endif - -#ifdef USES_P225 - ADDPLUGIN_H(225) -#endif - -#ifdef USES_P226 - ADDPLUGIN_H(226) -#endif - -#ifdef USES_P227 - ADDPLUGIN_H(227) -#endif - -#ifdef USES_P228 - ADDPLUGIN_H(228) -#endif - -#ifdef USES_P229 - ADDPLUGIN_H(229) -#endif - -#ifdef USES_P230 - ADDPLUGIN_H(230) -#endif - -#ifdef USES_P231 - ADDPLUGIN_H(231) -#endif - -#ifdef USES_P232 - ADDPLUGIN_H(232) -#endif - -#ifdef USES_P233 - ADDPLUGIN_H(233) -#endif - -#ifdef USES_P234 - ADDPLUGIN_H(234) -#endif - -#ifdef USES_P235 - ADDPLUGIN_H(235) -#endif - -#ifdef USES_P236 - ADDPLUGIN_H(236) -#endif - -#ifdef USES_P237 - ADDPLUGIN_H(237) -#endif - -#ifdef USES_P238 - ADDPLUGIN_H(238) -#endif - -#ifdef USES_P239 - ADDPLUGIN_H(239) -#endif - -#ifdef USES_P240 - ADDPLUGIN_H(240) -#endif - -#ifdef USES_P241 - ADDPLUGIN_H(241) -#endif - -#ifdef USES_P242 - ADDPLUGIN_H(242) -#endif - -#ifdef USES_P243 - ADDPLUGIN_H(243) -#endif - -#ifdef USES_P244 - ADDPLUGIN_H(244) -#endif - -#ifdef USES_P245 - ADDPLUGIN_H(245) -#endif - -#ifdef USES_P246 - ADDPLUGIN_H(246) -#endif - -#ifdef USES_P247 - ADDPLUGIN_H(247) -#endif - -#ifdef USES_P248 - ADDPLUGIN_H(248) -#endif - -#ifdef USES_P249 - ADDPLUGIN_H(249) -#endif - -#ifdef USES_P250 - ADDPLUGIN_H(250) -#endif - -#ifdef USES_P251 - ADDPLUGIN_H(251) -#endif - -#ifdef USES_P252 - ADDPLUGIN_H(252) -#endif - -#ifdef USES_P253 - ADDPLUGIN_H(253) -#endif - -#ifdef USES_P254 - ADDPLUGIN_H(254) -#endif - -#ifdef USES_P255 - ADDPLUGIN_H(255) -#endif - -#undef ADDPLUGIN_H - +#ifndef HELPERS__PLUGIN_INIT_H +#define HELPERS__PLUGIN_INIT_H + +#include "../../ESPEasy_common.h" + +#include "../DataTypes/DeviceIndex.h" +#include "../DataTypes/PluginID.h" +#include "../DataTypes/ESPEasy_plugin_functions.h" + + +struct EventStruct; + +deviceIndex_t getDeviceIndex_from_PluginID(pluginID_t pluginID); +pluginID_t getPluginID_from_DeviceIndex(deviceIndex_t deviceIndex); +bool validDeviceIndex_init(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); + +// Get the sizeof() in number of bits for the number of actually included plugins in the build +unsigned getNrBitsDeviceIndex(); +unsigned getNrBuiltInDeviceIndex(); + +void PluginSetup(); + +void PluginInit(bool priorityOnly = false); + +// Macro to forward declare the Plugin_NNN functions. +// +// Uncrustify must not be used on macros, so turn it off. +// *INDENT-OFF* +#define ADDPLUGIN_H(NNN) boolean Plugin_##NNN(uint8_t function, struct 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_P001 + ADDPLUGIN_H(001) +#endif + +#ifdef USES_P002 + ADDPLUGIN_H(002) +#endif + +#ifdef USES_P003 + ADDPLUGIN_H(003) +#endif + +#ifdef USES_P004 + ADDPLUGIN_H(004) +#endif + +#ifdef USES_P005 + ADDPLUGIN_H(005) +#endif + +#ifdef USES_P006 + ADDPLUGIN_H(006) +#endif + +#ifdef USES_P007 + ADDPLUGIN_H(007) +#endif + +#ifdef USES_P008 + ADDPLUGIN_H(008) +#endif + +#ifdef USES_P009 + ADDPLUGIN_H(009) +#endif + +#ifdef USES_P010 + ADDPLUGIN_H(010) +#endif + +#ifdef USES_P011 + ADDPLUGIN_H(011) +#endif + +#ifdef USES_P012 + ADDPLUGIN_H(012) +#endif + +#ifdef USES_P013 + ADDPLUGIN_H(013) +#endif + +#ifdef USES_P014 + ADDPLUGIN_H(014) +#endif + +#ifdef USES_P015 + ADDPLUGIN_H(015) +#endif + +#ifdef USES_P016 + ADDPLUGIN_H(016) +#endif + +#ifdef USES_P017 + ADDPLUGIN_H(017) +#endif + +#ifdef USES_P018 + ADDPLUGIN_H(018) +#endif + +#ifdef USES_P019 + ADDPLUGIN_H(019) +#endif + +#ifdef USES_P020 + ADDPLUGIN_H(020) +#endif + +#ifdef USES_P021 + ADDPLUGIN_H(021) +#endif + +#ifdef USES_P022 + ADDPLUGIN_H(022) +#endif + +#ifdef USES_P023 + ADDPLUGIN_H(023) +#endif + +#ifdef USES_P024 + ADDPLUGIN_H(024) +#endif + +#ifdef USES_P025 + ADDPLUGIN_H(025) +#endif + +#ifdef USES_P026 + ADDPLUGIN_H(026) +#endif + +#ifdef USES_P027 + ADDPLUGIN_H(027) +#endif + +#ifdef USES_P028 + ADDPLUGIN_H(028) +#endif + +#ifdef USES_P029 + ADDPLUGIN_H(029) +#endif + +#ifdef USES_P030 + ADDPLUGIN_H(030) +#endif + +#ifdef USES_P031 + ADDPLUGIN_H(031) +#endif + +#ifdef USES_P032 + ADDPLUGIN_H(032) +#endif + +#ifdef USES_P033 + ADDPLUGIN_H(033) +#endif + +#ifdef USES_P034 + ADDPLUGIN_H(034) +#endif + +#ifdef USES_P035 + ADDPLUGIN_H(035) +#endif + +#ifdef USES_P036 + ADDPLUGIN_H(036) +#endif + +#ifdef USES_P037 + ADDPLUGIN_H(037) +#endif + +#ifdef USES_P038 + ADDPLUGIN_H(038) +#endif + +#ifdef USES_P039 + ADDPLUGIN_H(039) +#endif + +#ifdef USES_P040 + ADDPLUGIN_H(040) +#endif + +#ifdef USES_P041 + ADDPLUGIN_H(041) +#endif + +#ifdef USES_P042 + ADDPLUGIN_H(042) +#endif + +#ifdef USES_P043 + ADDPLUGIN_H(043) +#endif + +#ifdef USES_P044 + ADDPLUGIN_H(044) +#endif + +#ifdef USES_P045 + ADDPLUGIN_H(045) +#endif + +#ifdef USES_P046 + ADDPLUGIN_H(046) +#endif + +#ifdef USES_P047 + ADDPLUGIN_H(047) +#endif + +#ifdef USES_P048 + ADDPLUGIN_H(048) +#endif + +#ifdef USES_P049 + ADDPLUGIN_H(049) +#endif + +#ifdef USES_P050 + ADDPLUGIN_H(050) +#endif + +#ifdef USES_P051 + ADDPLUGIN_H(051) +#endif + +#ifdef USES_P052 + ADDPLUGIN_H(052) +#endif + +#ifdef USES_P053 + ADDPLUGIN_H(053) +#endif + +#ifdef USES_P054 + ADDPLUGIN_H(054) +#endif + +#ifdef USES_P055 + ADDPLUGIN_H(055) +#endif + +#ifdef USES_P056 + ADDPLUGIN_H(056) +#endif + +#ifdef USES_P057 + ADDPLUGIN_H(057) +#endif + +#ifdef USES_P058 + ADDPLUGIN_H(058) +#endif + +#ifdef USES_P059 + ADDPLUGIN_H(059) +#endif + +#ifdef USES_P060 + ADDPLUGIN_H(060) +#endif + +#ifdef USES_P061 + ADDPLUGIN_H(061) +#endif + +#ifdef USES_P062 + ADDPLUGIN_H(062) +#endif + +#ifdef USES_P063 + ADDPLUGIN_H(063) +#endif + +#ifdef USES_P064 + ADDPLUGIN_H(064) +#endif + +#ifdef USES_P065 + ADDPLUGIN_H(065) +#endif + +#ifdef USES_P066 + ADDPLUGIN_H(066) +#endif + +#ifdef USES_P067 + ADDPLUGIN_H(067) +#endif + +#ifdef USES_P068 + ADDPLUGIN_H(068) +#endif + +#ifdef USES_P069 + ADDPLUGIN_H(069) +#endif + +#ifdef USES_P070 + ADDPLUGIN_H(070) +#endif + +#ifdef USES_P071 + ADDPLUGIN_H(071) +#endif + +#ifdef USES_P072 + ADDPLUGIN_H(072) +#endif + +#ifdef USES_P073 + ADDPLUGIN_H(073) +#endif + +#ifdef USES_P074 + ADDPLUGIN_H(074) +#endif + +#ifdef USES_P075 + ADDPLUGIN_H(075) +#endif + +#ifdef USES_P076 + ADDPLUGIN_H(076) +#endif + +#ifdef USES_P077 + ADDPLUGIN_H(077) +#endif + +#ifdef USES_P078 + ADDPLUGIN_H(078) +#endif + +#ifdef USES_P079 + ADDPLUGIN_H(079) +#endif + +#ifdef USES_P080 + ADDPLUGIN_H(080) +#endif + +#ifdef USES_P081 + ADDPLUGIN_H(081) +#endif + +#ifdef USES_P082 + ADDPLUGIN_H(082) +#endif + +#ifdef USES_P083 + ADDPLUGIN_H(083) +#endif + +#ifdef USES_P084 + ADDPLUGIN_H(084) +#endif + +#ifdef USES_P085 + ADDPLUGIN_H(085) +#endif + +#ifdef USES_P086 + ADDPLUGIN_H(086) +#endif + +#ifdef USES_P087 + ADDPLUGIN_H(087) +#endif + +#ifdef USES_P088 + ADDPLUGIN_H(088) +#endif + +#ifdef USES_P089 + #ifdef ESP8266 + // FIXME TD-er: Support Ping plugin for ESP32 + ADDPLUGIN_H(089) + #endif +#endif + +#ifdef USES_P090 + ADDPLUGIN_H(090) +#endif + +#ifdef USES_P091 + ADDPLUGIN_H(091) +#endif + +#ifdef USES_P092 + ADDPLUGIN_H(092) +#endif + +#ifdef USES_P093 + ADDPLUGIN_H(093) +#endif + +#ifdef USES_P094 + ADDPLUGIN_H(094) +#endif + +#ifdef USES_P095 + ADDPLUGIN_H(095) +#endif + +#ifdef USES_P096 + ADDPLUGIN_H(096) +#endif + +#ifdef USES_P097 + # if defined(ESP32) && !defined(ESP32C2) && !defined(ESP32C3) && !defined(ESP32C6) + ADDPLUGIN_H(097) // Touch (ESP32) + #endif +#endif + +#ifdef USES_P098 + ADDPLUGIN_H(098) +#endif + +#ifdef USES_P099 + ADDPLUGIN_H(099) +#endif + +#ifdef USES_P100 + ADDPLUGIN_H(100) +#endif + +#ifdef USES_P101 + ADDPLUGIN_H(101) +#endif + +#ifdef USES_P102 + ADDPLUGIN_H(102) +#endif + +#ifdef USES_P103 + ADDPLUGIN_H(103) +#endif + +#ifdef USES_P104 + ADDPLUGIN_H(104) +#endif + +#ifdef USES_P105 + ADDPLUGIN_H(105) +#endif + +#ifdef USES_P106 + ADDPLUGIN_H(106) +#endif + +#ifdef USES_P107 + ADDPLUGIN_H(107) +#endif + +#ifdef USES_P108 + ADDPLUGIN_H(108) +#endif + +#ifdef USES_P109 + ADDPLUGIN_H(109) +#endif + +#ifdef USES_P110 + ADDPLUGIN_H(110) +#endif + +#ifdef USES_P111 + ADDPLUGIN_H(111) +#endif + +#ifdef USES_P112 + ADDPLUGIN_H(112) +#endif + +#ifdef USES_P113 + ADDPLUGIN_H(113) +#endif + +#ifdef USES_P114 + ADDPLUGIN_H(114) +#endif + +#ifdef USES_P115 + ADDPLUGIN_H(115) +#endif + +#ifdef USES_P116 + ADDPLUGIN_H(116) +#endif + +#ifdef USES_P117 + ADDPLUGIN_H(117) +#endif + +#ifdef USES_P118 + ADDPLUGIN_H(118) +#endif + +#ifdef USES_P119 + ADDPLUGIN_H(119) +#endif + +#ifdef USES_P120 + ADDPLUGIN_H(120) +#endif + +#ifdef USES_P121 + ADDPLUGIN_H(121) +#endif + +#ifdef USES_P122 + ADDPLUGIN_H(122) +#endif + +#ifdef USES_P123 + ADDPLUGIN_H(123) +#endif + +#ifdef USES_P124 + ADDPLUGIN_H(124) +#endif + +#ifdef USES_P125 + ADDPLUGIN_H(125) +#endif + +#ifdef USES_P126 + ADDPLUGIN_H(126) +#endif + +#ifdef USES_P127 + ADDPLUGIN_H(127) +#endif + +#ifdef USES_P128 + ADDPLUGIN_H(128) +#endif + +#ifdef USES_P129 + ADDPLUGIN_H(129) +#endif + +#ifdef USES_P130 + ADDPLUGIN_H(130) +#endif + +#ifdef USES_P131 + ADDPLUGIN_H(131) +#endif + +#ifdef USES_P132 + ADDPLUGIN_H(132) +#endif + +#ifdef USES_P133 + ADDPLUGIN_H(133) +#endif + +#ifdef USES_P134 + ADDPLUGIN_H(134) +#endif + +#ifdef USES_P135 + ADDPLUGIN_H(135) +#endif + +#ifdef USES_P136 + ADDPLUGIN_H(136) +#endif + +#ifdef USES_P137 + ADDPLUGIN_H(137) +#endif + +#ifdef USES_P138 + ADDPLUGIN_H(138) +#endif + +#ifdef USES_P139 + ADDPLUGIN_H(139) +#endif + +#ifdef USES_P140 + ADDPLUGIN_H(140) +#endif + +#ifdef USES_P141 + ADDPLUGIN_H(141) +#endif + +#ifdef USES_P142 + ADDPLUGIN_H(142) +#endif + +#ifdef USES_P143 + ADDPLUGIN_H(143) +#endif + +#ifdef USES_P144 + ADDPLUGIN_H(144) +#endif + +#ifdef USES_P145 + ADDPLUGIN_H(145) +#endif + +#ifdef USES_P146 + ADDPLUGIN_H(146) +#endif + +#ifdef USES_P147 + ADDPLUGIN_H(147) +#endif + +#ifdef USES_P148 + ADDPLUGIN_H(148) +#endif + +#ifdef USES_P149 + ADDPLUGIN_H(149) +#endif + +#ifdef USES_P150 + ADDPLUGIN_H(150) +#endif + +#ifdef USES_P151 + ADDPLUGIN_H(151) +#endif + +#ifdef USES_P152 + ADDPLUGIN_H(152) +#endif + +#ifdef USES_P153 + ADDPLUGIN_H(153) +#endif + +#ifdef USES_P154 + ADDPLUGIN_H(154) +#endif + +#ifdef USES_P155 + ADDPLUGIN_H(155) +#endif + +#ifdef USES_P156 + ADDPLUGIN_H(156) +#endif + +#ifdef USES_P157 + ADDPLUGIN_H(157) +#endif + +#ifdef USES_P158 + ADDPLUGIN_H(158) +#endif + +#ifdef USES_P159 + ADDPLUGIN_H(159) +#endif + +#ifdef USES_P160 + ADDPLUGIN_H(160) +#endif + +#ifdef USES_P161 + ADDPLUGIN_H(161) +#endif + +#ifdef USES_P162 + ADDPLUGIN_H(162) +#endif + +#ifdef USES_P163 + ADDPLUGIN_H(163) +#endif + +#ifdef USES_P164 + ADDPLUGIN_H(164) +#endif + +#ifdef USES_P165 + ADDPLUGIN_H(165) +#endif + +#ifdef USES_P166 + ADDPLUGIN_H(166) +#endif + +#ifdef USES_P167 + ADDPLUGIN_H(167) +#endif + +#ifdef USES_P168 + ADDPLUGIN_H(168) +#endif + +#ifdef USES_P169 + ADDPLUGIN_H(169) +#endif + +#ifdef USES_P170 + ADDPLUGIN_H(170) +#endif + +#ifdef USES_P171 + ADDPLUGIN_H(171) +#endif + +#ifdef USES_P172 + ADDPLUGIN_H(172) +#endif + +#ifdef USES_P173 + ADDPLUGIN_H(173) +#endif + +#ifdef USES_P174 + ADDPLUGIN_H(174) +#endif + +#ifdef USES_P175 + ADDPLUGIN_H(175) +#endif + +#ifdef USES_P176 + ADDPLUGIN_H(176) +#endif + +#ifdef USES_P177 + ADDPLUGIN_H(177) +#endif + +#ifdef USES_P178 + ADDPLUGIN_H(178) +#endif + +#ifdef USES_P179 + ADDPLUGIN_H(179) +#endif + +#ifdef USES_P180 + ADDPLUGIN_H(180) +#endif + +#ifdef USES_P181 + ADDPLUGIN_H(181) +#endif + +#ifdef USES_P182 + ADDPLUGIN_H(182) +#endif + +#ifdef USES_P183 + ADDPLUGIN_H(183) +#endif + +#ifdef USES_P184 + ADDPLUGIN_H(184) +#endif + +#ifdef USES_P185 + ADDPLUGIN_H(185) +#endif + +#ifdef USES_P186 + ADDPLUGIN_H(186) +#endif + +#ifdef USES_P187 + ADDPLUGIN_H(187) +#endif + +#ifdef USES_P188 + ADDPLUGIN_H(188) +#endif + +#ifdef USES_P189 + ADDPLUGIN_H(189) +#endif + +#ifdef USES_P190 + ADDPLUGIN_H(190) +#endif + +#ifdef USES_P191 + ADDPLUGIN_H(191) +#endif + +#ifdef USES_P192 + ADDPLUGIN_H(192) +#endif + +#ifdef USES_P193 + ADDPLUGIN_H(193) +#endif + +#ifdef USES_P194 + ADDPLUGIN_H(194) +#endif + +#ifdef USES_P195 + ADDPLUGIN_H(195) +#endif + +#ifdef USES_P196 + ADDPLUGIN_H(196) +#endif + +#ifdef USES_P197 + ADDPLUGIN_H(197) +#endif + +#ifdef USES_P198 + ADDPLUGIN_H(198) +#endif + +#ifdef USES_P199 + ADDPLUGIN_H(199) +#endif + +#ifdef USES_P200 + ADDPLUGIN_H(200) +#endif + +#ifdef USES_P201 + ADDPLUGIN_H(201) +#endif + +#ifdef USES_P202 + ADDPLUGIN_H(202) +#endif + +#ifdef USES_P203 + ADDPLUGIN_H(203) +#endif + +#ifdef USES_P204 + ADDPLUGIN_H(204) +#endif + +#ifdef USES_P205 + ADDPLUGIN_H(205) +#endif + +#ifdef USES_P206 + ADDPLUGIN_H(206) +#endif + +#ifdef USES_P207 + ADDPLUGIN_H(207) +#endif + +#ifdef USES_P208 + ADDPLUGIN_H(208) +#endif + +#ifdef USES_P209 + ADDPLUGIN_H(209) +#endif + +#ifdef USES_P210 + ADDPLUGIN_H(210) +#endif + +#ifdef USES_P211 + ADDPLUGIN_H(211) +#endif + +#ifdef USES_P212 + ADDPLUGIN_H(212) +#endif + +#ifdef USES_P213 + ADDPLUGIN_H(213) +#endif + +#ifdef USES_P214 + ADDPLUGIN_H(214) +#endif + +#ifdef USES_P215 + ADDPLUGIN_H(215) +#endif + +#ifdef USES_P216 + ADDPLUGIN_H(216) +#endif + +#ifdef USES_P217 + ADDPLUGIN_H(217) +#endif + +#ifdef USES_P218 + ADDPLUGIN_H(218) +#endif + +#ifdef USES_P219 + ADDPLUGIN_H(219) +#endif + +#ifdef USES_P220 + ADDPLUGIN_H(220) +#endif + +#ifdef USES_P221 + ADDPLUGIN_H(221) +#endif + +#ifdef USES_P222 + ADDPLUGIN_H(222) +#endif + +#ifdef USES_P223 + ADDPLUGIN_H(223) +#endif + +#ifdef USES_P224 + ADDPLUGIN_H(224) +#endif + +#ifdef USES_P225 + ADDPLUGIN_H(225) +#endif + +#ifdef USES_P226 + ADDPLUGIN_H(226) +#endif + +#ifdef USES_P227 + ADDPLUGIN_H(227) +#endif + +#ifdef USES_P228 + ADDPLUGIN_H(228) +#endif + +#ifdef USES_P229 + ADDPLUGIN_H(229) +#endif + +#ifdef USES_P230 + ADDPLUGIN_H(230) +#endif + +#ifdef USES_P231 + ADDPLUGIN_H(231) +#endif + +#ifdef USES_P232 + ADDPLUGIN_H(232) +#endif + +#ifdef USES_P233 + ADDPLUGIN_H(233) +#endif + +#ifdef USES_P234 + ADDPLUGIN_H(234) +#endif + +#ifdef USES_P235 + ADDPLUGIN_H(235) +#endif + +#ifdef USES_P236 + ADDPLUGIN_H(236) +#endif + +#ifdef USES_P237 + ADDPLUGIN_H(237) +#endif + +#ifdef USES_P238 + ADDPLUGIN_H(238) +#endif + +#ifdef USES_P239 + ADDPLUGIN_H(239) +#endif + +#ifdef USES_P240 + ADDPLUGIN_H(240) +#endif + +#ifdef USES_P241 + ADDPLUGIN_H(241) +#endif + +#ifdef USES_P242 + ADDPLUGIN_H(242) +#endif + +#ifdef USES_P243 + ADDPLUGIN_H(243) +#endif + +#ifdef USES_P244 + ADDPLUGIN_H(244) +#endif + +#ifdef USES_P245 + ADDPLUGIN_H(245) +#endif + +#ifdef USES_P246 + ADDPLUGIN_H(246) +#endif + +#ifdef USES_P247 + ADDPLUGIN_H(247) +#endif + +#ifdef USES_P248 + ADDPLUGIN_H(248) +#endif + +#ifdef USES_P249 + ADDPLUGIN_H(249) +#endif + +#ifdef USES_P250 + ADDPLUGIN_H(250) +#endif + +#ifdef USES_P251 + ADDPLUGIN_H(251) +#endif + +#ifdef USES_P252 + ADDPLUGIN_H(252) +#endif + +#ifdef USES_P253 + ADDPLUGIN_H(253) +#endif + +#ifdef USES_P254 + ADDPLUGIN_H(254) +#endif + +#ifdef USES_P255 + ADDPLUGIN_H(255) +#endif + +#undef ADDPLUGIN_H + #endif \ No newline at end of file diff --git a/src/src/PluginStructs/P002_data_struct.cpp b/src/src/PluginStructs/P002_data_struct.cpp index 2e0d5c0b9..32ea337d4 100644 --- a/src/src/PluginStructs/P002_data_struct.cpp +++ b/src/src/PluginStructs/P002_data_struct.cpp @@ -1,1250 +1,1294 @@ -#include "../PluginStructs/P002_data_struct.h" - -#ifdef USES_P002 - -# include "../Globals/RulesCalculate.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 -#define P002_ADC_ATTEN_MAX ADC_ATTENDB_MAX -#endif -#endif - - -void P002_data_struct::init(struct EventStruct *event) -{ - _sampleMode = P002_OVERSAMPLING; - - # ifdef ESP8266 - _pin_analogRead = A0; - # endif // ifdef ESP8266 - # ifdef ESP32 - _pin_analogRead = CONFIG_PIN1; - _useFactoryCalibration = useFactoryCalibration(event); - _attenuation = getAttenuation(event); - int channel{}; - const int adc = getADC_num_for_gpio(_pin_analogRead, channel); - - if ((adc == 1) || (adc == 2)) { - analogSetPinAttenuation(_pin_analogRead, static_cast(_attenuation)); - } - - # endif // ifdef ESP32 - - if (P002_CALIBRATION_ENABLED) { - _use2pointCalibration = true; - _calib_adc1 = P002_CALIBRATION_POINT1; - _calib_adc2 = P002_CALIBRATION_POINT2; - _calib_out1 = P002_CALIBRATION_VALUE1; - _calib_out2 = P002_CALIBRATION_VALUE2; - } - _nrDecimals = Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0); -# ifndef LIMIT_BUILD_SIZE - _nrMultiPointItems = P002_NR_MULTIPOINT_ITEMS; - _useMultipoint = P002_MULTIPOINT_ENABLED; - - load(event); -# endif // ifndef LIMIT_BUILD_SIZE -} - -# ifndef LIMIT_BUILD_SIZE -void P002_data_struct::load(struct EventStruct *event) -{ - const size_t nr_lines = P002_Nlines; - - { - String lines[nr_lines]; - LoadCustomTaskSettings(event->TaskIndex, lines, nr_lines, 0); - const int stored_nr_lines = lines[P002_SAVED_NR_LINES].toInt(); - move_special(_formula, std::move(lines[P002_LINE_INDEX_FORMULA])); - move_special(_formula_preprocessed, RulesCalculate_t::preProces(_formula)); - - for (size_t i = P002_LINE_IDX_FIRST_MP; i < nr_lines && static_cast(i) < stored_nr_lines; i += P002_STRINGS_PER_MP) { - float adc, value = 0.0f; - - 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 - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - _multipoint.emplace_back(adc, value); - } - } - } - std::sort(_multipoint.begin(), _multipoint.end()); - { - # ifdef USE_SECOND_HEAP - HeapSelectIram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - _binning.resize(_multipoint.size(), 0); - _binningRange.resize(_multipoint.size()); - } -} - -# endif // ifndef LIMIT_BUILD_SIZE - -void P002_data_struct::webformLoad_2p_calibPoint( - const __FlashStringHelper *label, - const __FlashStringHelper *id_point, - const __FlashStringHelper *id_value, - int point, - float value) const -{ - addRowLabel_tr_id(label, id_point); - addTextBox(id_point, String(point), 10, false, false, EMPTY_STRING, F("number")); - -# ifdef ESP32 - - if (_useFactoryCalibration) { - addUnit(F("mV")); - } -# endif // ifdef ESP32 - - html_add_estimate_symbol(); - const unsigned int display_nrDecimals = _nrDecimals > 3 ? _nrDecimals : 3; - - addTextBox(id_value, toString(value, display_nrDecimals), 10, false, false, EMPTY_STRING, F("number")); -} - -void P002_data_struct::webformLoad(struct EventStruct *event) -{ - // Output the statistics for the current settings. - int raw_value = 0; - const float currentValue = P002_data_struct::getCurrentValue(event, raw_value); - -# if FEATURE_PLUGIN_STATS - PluginStats *stats = getPluginStats(0); - - if (stats != nullptr) { - stats->trackPeak(raw_value); - } -# endif // if FEATURE_PLUGIN_STATS - -# ifdef ESP32 - addRowLabel(F("Analog Pin")); - #if HAS_HALL_EFFECT_SENSOR - addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch_HallEffect, F("taskdevicepin1"), CONFIG_PIN1); - #else - addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch, F("taskdevicepin1"), CONFIG_PIN1); - #endif - - addFormNote(F("Do not use ADC2 pins with WiFi active")); - - { - const __FlashStringHelper *outputOptions[] = { - F("12 dB"), - F("6 dB"), - F("2.5 dB"), - F("0 dB") - }; - const int outputOptionValues[] = { - P002_ADC_11db, - P002_ADC_6db, - P002_ADC_2_5db, - P002_ADC_0db - }; - constexpr int nrOptions = NR_ELEMENTS(outputOptionValues); - addFormSelector(F("Attenuation"), F("attn"), nrOptions, outputOptions, outputOptionValues, P002_ATTENUATION); - } - -# endif // ifdef ESP32 - - { - const __FlashStringHelper *outputOptions[] = { - F("Use Current Sample"), - F("Oversampling") -# ifndef LIMIT_BUILD_SIZE - , F("Binning") -# endif // ifndef LIMIT_BUILD_SIZE - }; - const int outputOptionValues[] = { - P002_USE_CURENT_SAMPLE, - P002_USE_OVERSAMPLING -# ifndef LIMIT_BUILD_SIZE - , P002_USE_BINNING -# endif // ifndef LIMIT_BUILD_SIZE - }; - const int nrOptions = NR_ELEMENTS(outputOptionValues); - addFormSelector(F("Oversampling"), F("oversampling"), nrOptions, outputOptions, outputOptionValues, P002_OVERSAMPLING); - } - -# 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")); - - if (hasADC_factory_calibration()) { - addRowLabel(F("Factory Calibration Type")); - addHtml(getADC_factory_calibration_type()); - # if FEATURE_CHART_JS - webformLoad_calibrationCurve(event); - # endif // if FEATURE_CHART_JS - formatADC_statistics(F("Current ADC to mV"), raw_value); - - for (size_t att = 0; att < P002_ADC_ATTEN_MAX; ++att) { - const adc_atten_t attenuation = static_cast(att); - const int low = getADC_factory_calibrated_min(attenuation); - const int high = getADC_factory_calibrated_max(attenuation); - const float step = static_cast(high - low) / MAX_ADC_VALUE; - - String rowlabel = F("Attenuation @"); - rowlabel += AttenuationToString(attenuation); - addRowLabel(rowlabel); - addHtml(F("Range / Step: ")); - addHtmlInt(low); - addHtml(F(" ... ")); - addHtmlInt(high); - addUnit(F("mV")); - addHtml(F(" / ")); - addHtmlFloat(step, 3); // calibration output is int value in mV, so doesn't really matter how many decimals - addUnit(F("mV")); - } - } -# endif // ifdef ESP32 - - addFormSubHeader(F("Two Point Calibration")); - - addFormCheckBox(F("Calibration Enabled"), F("cal"), P002_CALIBRATION_ENABLED); - -# ifdef ESP8266 -# if FEATURE_ADC_VCC - addFormNote(F("Measuring ESP VCC, not A0. Unit is 1/1024 V. See documentation.")); -# endif // if FEATURE_ADC_VCC -# endif // ifdef ESP8266 - - - webformLoad_2p_calibPoint( - F("Point 1"), - F("adc1"), - F("out1"), - P002_CALIBRATION_POINT1, - P002_CALIBRATION_VALUE1); - webformLoad_2p_calibPoint( - F("Point 2"), - F("adc2"), - F("out2"), - P002_CALIBRATION_POINT2, - P002_CALIBRATION_VALUE2); - - addFormNote(F("Input float values will be stored as int, calibration values will be adjusted accordingly")); - - { - // Output the statistics for the current settings. - if (P002_CALIBRATION_ENABLED) { - # if FEATURE_CHART_JS - webformLoad_2pt_calibrationCurve(event); - # endif // if FEATURE_CHART_JS - - int minInputValue, maxInputValue; - getInputRange(event, minInputValue, maxInputValue); - - const float minY_value = P002_data_struct::applyCalibration(event, minInputValue); - const float maxY_value = P002_data_struct::applyCalibration(event, maxInputValue); - const float current_calibrated = P002_data_struct::applyCalibration(event, currentValue); - - format_2point_calib_statistics(F("Current"), currentValue, current_calibrated); - format_2point_calib_statistics(F("Minimum"), minInputValue, minY_value); - format_2point_calib_statistics(F("Maximum"), maxInputValue, maxY_value); - - const float stepsize = (maxY_value - minY_value) / (MAX_ADC_VALUE + 1); - addRowLabel(F("Step Size")); - addHtmlFloat(stepsize, _nrDecimals); - } else { - addRowLabel(F("Current")); - addHtmlFloat(currentValue, _nrDecimals); - } - } -# 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"), - F("multi_en"), - P002_MULTIPOINT_ENABLED); - - if (useBinning) { - addFormTextBox(F("Binning Formula"), getPluginCustomArgName(P002_LINE_INDEX_FORMULA), _formula, P002_MAX_FORMULA_LENGTH); - } - - addFormNumericBox(useBinning ? F("Nr of Bins") : F("Nr Multipoint Fields"), - F("nr_mp"), - P002_NR_MULTIPOINT_ITEMS, - 0, - P002_MAX_NR_MP_ITEMS); - - // Checkbox needed to explicitly allow to split-paste over each field - addFormCheckBox(useBinning ? F("Split-Paste Binning Fields") : F("Split-Paste Multipoint Fields"), F("splitpaste"), false); - addFormNote(F("When checked, a set of tab, space or newline separated values can be pasted at once.")); - - size_t line_nr = 0; - - for (int varNr = P002_LINE_IDX_FIRST_MP; varNr < P002_Nlines; varNr += P002_STRINGS_PER_MP) - { - const String label = String(useBinning ? F("Bin ") : F("Point ")) + String(line_nr + 1); - addFormTextBox(F("query-input widenumber"), - label, - getPluginCustomArgName(varNr), - - _multipoint.size() > line_nr ? -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - floatToString -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - (static_cast(_multipoint[line_nr]._adc), - _nrDecimals, - true) : EMPTY_STRING, - 0); - html_add_estimate_symbol(); - addTextBox(getPluginCustomArgName(varNr + 1), - _multipoint.size() > line_nr ? -# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - doubleToString -# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - floatToString -# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE - (static_cast(_multipoint[line_nr]._value), - _nrDecimals, - true) : EMPTY_STRING, - 0, - false, - false, - EMPTY_STRING, - F("query-input widenumber")); - - ++line_nr; - } - # if FEATURE_CHART_JS - webformLoad_multipointCurve(event); - # endif // if FEATURE_CHART_JS -# endif // ifndef LIMIT_BUILD_SIZE -} - -# if FEATURE_PLUGIN_STATS -bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) -{ - bool somethingAdded = false; - - const PluginStats *stats = getPluginStats(0); - - if (stats != nullptr) { - if (stats->webformLoad_show_avg(event)) { somethingAdded = true; } - - if (stats->webformLoad_show_stdev(event)) { somethingAdded = true; } - - if (stats->hasPeaks()) { - formatADC_statistics(F("ADC Peak Low"), stats->getPeakLow(), true); - formatADC_statistics(F("ADC Peak High"), stats->getPeakHigh(), true); - somethingAdded = true; - } - } - return somethingAdded; -} - -# endif // if FEATURE_PLUGIN_STATS - - -# ifdef ESP32 -# if FEATURE_CHART_JS -void P002_data_struct::webformLoad_calibrationCurve(struct EventStruct *event) -{ - if (!hasADC_factory_calibration()) { return; } - - addRowLabel(F("Calibration Curve")); - - 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); - - const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; - - size_t current_attenuation = getAttenuation(event); - - if (current_attenuation >= P002_ADC_ATTEN_MAX) { -#if ESP_IDF_VERSION_MAJOR >= 5 - current_attenuation = ADC_ATTEN_DB_12; -#else - current_attenuation = ADC_ATTEN_DB_11; -#endif - } - - 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( - config, - values, - valueCount, - Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); - } - add_ChartJS_chart_footer(); -} - -# endif // if FEATURE_CHART_JS -# endif // ifdef ESP32 - -# if FEATURE_CHART_JS -const __FlashStringHelper * P002_data_struct::getChartXaxisLabel(struct EventStruct *event) -{ - # ifdef ESP32 - - if (useFactoryCalibration(event)) { - // reading in mVolt, not ADC - return F("Input Voltage (mV)"); - } - # endif // ifdef ESP32 - return F("ADC Value"); -} - -# 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 - - if (useFactoryCalibration(event) && !ignoreCalibration) { - // reading in mVolt, not ADC - const adc_atten_t attenuation = getAttenuation(event); - - minInputValue = getADC_factory_calibrated_min(attenuation); - maxInputValue = getADC_factory_calibrated_max(attenuation); - } -# endif // ifdef ESP32 -} - -# if FEATURE_CHART_JS - -void P002_data_struct::getChartRange(struct EventStruct *event, int values[], int count, bool ignoreCalibration) -{ - int minInputValue, maxInputValue; - - getInputRange(event, minInputValue, maxInputValue, ignoreCalibration); - - const float stepSize = static_cast(maxInputValue + 1 - minInputValue) / (count - 1); - - for (int i = 0; i < count; ++i) { - values[i] = minInputValue + i * stepSize; - } -} - -void P002_data_struct::webformLoad_2pt_calibrationCurve(struct EventStruct *event) -{ - addRowLabel(F("Two Point Calibration")); - - const int valueCount = 33; - int xAxisValues[valueCount]; - - getChartRange(event, xAxisValues, valueCount); - - String axisOptions; - - { - 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]; - - for (int i = 0; i < valueCount; ++i) { - values[i] = P002_data_struct::applyCalibration(event, xAxisValues[i]); - } - - 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(); -} - -# endif // if FEATURE_CHART_JS - -void P002_data_struct::formatADC_statistics(const __FlashStringHelper *label, int raw, bool includeOutputValue) const -{ - addRowLabel(label); - addHtmlInt(raw); - - float float_value = raw; - -# ifdef ESP32 - - if (_useFactoryCalibration) { - float_value = applyADCFactoryCalibration(raw, _attenuation); - - html_add_estimate_symbol(); - addHtmlFloat(float_value, _nrDecimals); - addUnit(F("mV")); - } -# endif // ifdef ESP32 - - if (includeOutputValue) { - addHtml(' '); - addHtml(F("→ ")); - float_value = applyCalibration(float_value); - -# ifndef LIMIT_BUILD_SIZE - - switch (_sampleMode) { - case P002_USE_OVERSAMPLING: - float_value = applyMultiPointInterpolation(float_value); - break; - case P002_USE_BINNING: - { - const int index = computeADC_to_bin(raw); - - if ((index >= 0) && (static_cast(_binning.size()) > index)) { - float_value = _multipoint[index]._value; - } - - break; - } - } -# endif // ifndef LIMIT_BUILD_SIZE - addHtmlFloat(float_value, _nrDecimals); - } -} - -void P002_data_struct::format_2point_calib_statistics(const __FlashStringHelper *label, int raw, float float_value) const -{ - addRowLabel(label); - addHtmlInt(raw); - # ifdef ESP32 - addUnit(_useFactoryCalibration ? F("mV") : F("raw")); - # else // ifdef ESP32 - addUnit(F("raw")); - # endif // ifdef ESP32 - html_add_estimate_symbol(); - addHtmlFloat(float_value, _nrDecimals); -} - -# 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") }; - - if (attenuation < 4) { return datalabels[attenuation]; } - return F("Unknown"); -} - -adc_atten_t P002_data_struct::getAttenuation(struct EventStruct *event) { - if ((P002_ATTENUATION >= P002_ADC_0db) && (P002_ATTENUATION <= P002_ADC_11db)) { - // Make sure the attenuation is only set to correct values or else it may damage the board - return static_cast(P002_ATTENUATION - 10); - } - P002_ATTENUATION = P002_ADC_11db; - -#if ESP_IDF_VERSION_MAJOR >= 5 - return ADC_ATTEN_DB_12; -#else - return ADC_ATTEN_DB_11; -#endif -} - -# endif // ifdef ESP32 - -# 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; - - { - 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("],datasets:[")); - - add_ChartJS_dataset_header( - { - useBinning ? F("Bins") : F("Multipoint Values"), - F("rgb(255, 99, 132)")}); - - for (size_t i = 0; i < _multipoint.size(); ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlFloat(_multipoint[i]._value, _nrDecimals); - } - add_ChartJS_dataset_footer(); - add_ChartJS_chart_footer(); - - if (!useBinning) { - // Try to compute the expected mapping from ADC to multipoint values - addRowLabel(F("Input to Output Curve")); - const int valueCount = 33; - int xAxisValues[valueCount]; - getChartRange(event, xAxisValues, valueCount); - - String axisOptions; - - { - 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; - - 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( - config, - values, - valueCount, - Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); - } - add_ChartJS_chart_footer(); - } - } -} - -# 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 - P002_APPLY_FACTORY_CALIB = isFormItemChecked(F("fac_cal")); - P002_ATTENUATION = getFormItemInt(F("attn")); - # endif // ifdef ESP32 - - // Map the input "point" values to the nearest int. - setTwoPointCalibration( - event, - getFormItemFloat(F("adc1")), - getFormItemFloat(F("adc2")), - getFormItemFloat(F("out1")), - getFormItemFloat(F("out2"))); - -# ifndef LIMIT_BUILD_SIZE - P002_MULTIPOINT_ENABLED = isFormItemChecked(F("multi_en")); - - P002_NR_MULTIPOINT_ITEMS = getFormItemInt(F("nr_mp")); - - const size_t nr_lines = P002_Nlines; - String lines[nr_lines]; - - // Store nr of lines that were saved, so no 'old' data will be read when nr of multi-point items has changed. - lines[P002_SAVED_NR_LINES] = String(nr_lines); - - if (hasArg(getPluginCustomArgName(P002_LINE_INDEX_FORMULA))) { - lines[P002_LINE_INDEX_FORMULA] = webArg(getPluginCustomArgName(P002_LINE_INDEX_FORMULA)); - } - - // const int nrDecimals = webArg(F("TDVD1")).toInt(); - - for (size_t varNr = P002_LINE_IDX_FIRST_MP; varNr < nr_lines; varNr += P002_STRINGS_PER_MP) - { - float adc, value = 0.0f; - const String adc_str = webArg(getPluginCustomArgName(varNr)); - const String val_str = webArg(getPluginCustomArgName(varNr + 1)); - - if (validFloatFromString(adc_str, adc) && validFloatFromString(val_str, value)) { - // Only store valid floats - lines[varNr] = adc_str; - lines[varNr + 1] = val_str; - } - } - - return SaveCustomTaskSettings(event->TaskIndex, lines, nr_lines, 0); -# else // ifndef LIMIT_BUILD_SIZE - return EMPTY_STRING; -# endif // ifndef LIMIT_BUILD_SIZE -} - -void P002_data_struct::takeSample() -{ - if (_sampleMode == P002_USE_CURENT_SAMPLE) { return; } - int raw = espeasy_analogRead(_pin_analogRead); - -# if FEATURE_PLUGIN_STATS - PluginStats *stats = getPluginStats(0); - - if (stats != nullptr) { - stats->trackPeak(raw); - } -# endif // if FEATURE_PLUGIN_STATS - - switch (_sampleMode) { - case P002_USE_OVERSAMPLING: - addOversamplingValue(raw); - break; -# ifndef LIMIT_BUILD_SIZE - case P002_USE_BINNING: - addBinningValue(raw); - break; -# endif // ifndef LIMIT_BUILD_SIZE - } -} - -bool P002_data_struct::getValue(float& float_value, - int & raw_value) const -{ - bool mustTakeSample = false; - - switch (_sampleMode) { - case P002_USE_OVERSAMPLING: - - if (getOversamplingValue(float_value, raw_value)) { - return true; - } - mustTakeSample = true; - break; -# ifndef LIMIT_BUILD_SIZE - case P002_USE_BINNING: - - if (getBinnedValue(float_value, raw_value)) { - return true; - } - mustTakeSample = true; - break; -# endif // ifndef LIMIT_BUILD_SIZE - case P002_USE_CURENT_SAMPLE: - mustTakeSample = true; - break; - } - - if (!mustTakeSample) { - return false; - } - - raw_value = espeasy_analogRead(_pin_analogRead); -# if FEATURE_PLUGIN_STATS - - PluginStats *stats = getPluginStats(0); - - if (stats != nullptr) { - stats->trackPeak(raw_value); - } -# endif // if FEATURE_PLUGIN_STATS - float_value = raw_value; - # ifdef ESP32 - - if (_useFactoryCalibration) { - float_value = applyADCFactoryCalibration(raw_value, _attenuation); - } - # endif // ifdef ESP32 - - float_value = applyCalibration(float_value); - -# ifndef LIMIT_BUILD_SIZE - - switch (_sampleMode) { - case P002_USE_OVERSAMPLING: - float_value = applyMultiPointInterpolation(float_value); - break; - case P002_USE_BINNING: - { - const int index = computeADC_to_bin(raw_value); - - if ((index >= 0) && (static_cast(_binning.size()) > index)) { - float_value = _multipoint[index]._value; - } - - break; - } - } -# endif // ifndef LIMIT_BUILD_SIZE - - return true; -} - -void P002_data_struct::reset() -{ -# ifndef LIMIT_BUILD_SIZE - - switch (_sampleMode) { - case P002_USE_OVERSAMPLING: - resetOversampling(); - break; - case P002_USE_BINNING: - { - for (auto it = _binning.begin(); it != _binning.end(); ++it) { - *it = 0; - } - - break; - } - } -# else // ifndef LIMIT_BUILD_SIZE - resetOversampling(); -# endif // ifndef LIMIT_BUILD_SIZE -} - -uint32_t P002_data_struct::getOversamplingCount() const -{ - return OverSampling.getCount(); -} - -void P002_data_struct::resetOversampling() { - OverSampling.reset(); -} - -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 - - if (_useFactoryCalibration) { - float_value = applyADCFactoryCalibration(float_value, _attenuation); - } -# 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 - float_value = applyMultiPointInterpolation(float_value); -# endif // ifndef LIMIT_BUILD_SIZE - - return true; - } - return false; -} - -# ifndef LIMIT_BUILD_SIZE -int P002_data_struct::getBinIndex(float currentValue) const -{ - const size_t mp_size = _multipoint.size(); - - if (mp_size == 0) { return -1; } - - if (mp_size == 1) { return 0; } - - if (currentValue <= _multipoint[0]._adc) { return 0; } - - const size_t last_mp_index = mp_size - 1; - - if (currentValue >= _multipoint[last_mp_index]._adc) { return last_mp_index; } - - for (unsigned int i = 0; i < last_mp_index; ++i) { - const float dist_left = currentValue - _multipoint[i]._adc; - const float dist_right = _multipoint[i + 1]._adc - currentValue; - - if ((dist_left >= 0) && (dist_right >= 0)) { - // Inbetween 2 points of the multipoint array - return (dist_left < dist_right) ? i : i + 1; - } - } - - return -1; -} - -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 - - if (_useFactoryCalibration) { - calibrated_value = applyADCFactoryCalibration(calibrated_value, _attenuation); - } -# endif // ifdef ESP32 - - - calibrated_value = applyCalibration(calibrated_value); - - if (!_formula_preprocessed.isEmpty()) { - // Formula, must be applied before binning - String formula = _formula_preprocessed; - - formula.replace(F("%value%"), toString(calibrated_value, _nrDecimals)); - - ESPEASY_RULES_FLOAT_TYPE result{}; - - if (!isError(RulesCalculate.doCalculate(parseTemplate(formula).c_str(), &result))) { - calibrated_value = result; - } - } - - return getBinIndex(calibrated_value); -} - -void P002_data_struct::addBinningValue(int currentValue) -{ - for (size_t index = 0; index < _binningRange.size(); ++index) { - if (_binningRange[index].inRange(currentValue)) { - ++_binning[index]; - return; - } - } - - const int index = computeADC_to_bin(currentValue); - - if ((index >= 0) && (static_cast(_binning.size()) > index)) { - _binningRange[index].set(currentValue); - ++_binning[index]; - } -} - -bool P002_data_struct::getBinnedValue(float& float_value, int& raw_value) const -{ - unsigned int highest_bin_count = 0; - - const size_t nr_bin_elements = std::min(_binning.size(), _multipoint.size()); - - for (size_t i = 0; i < nr_bin_elements; ++i) { - if (_binning[i] > highest_bin_count) { - highest_bin_count = _binning[i]; - float_value = _multipoint[i]._value; - raw_value = _multipoint[i]._adc; - } - } - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, - strformat(F("ADC getBinnedValue: bin cnt: %u Value: %f RAW: %d"), - highest_bin_count, - float_value, - raw_value)); - } - # endif // ifndef BUILD_NO_DEBUG - - return highest_bin_count != 0; -} - -# endif // ifndef LIMIT_BUILD_SIZE - -float P002_data_struct::applyCalibration(struct EventStruct *event, float float_value, bool force) { - if (force || P002_CALIBRATION_ENABLED) - { - float_value = mapADCtoFloat(float_value, - P002_CALIBRATION_POINT1, - P002_CALIBRATION_POINT2, - P002_CALIBRATION_VALUE1, - P002_CALIBRATION_VALUE2); - } - return float_value; -} - -float P002_data_struct::getCurrentValue(struct EventStruct *event, int& raw_value) -{ - # ifdef ESP8266 - const int pin = A0; - # endif // ifdef ESP8266 - # ifdef ESP32 - const int pin = CONFIG_PIN1; - # endif // ifdef ESP32 - - raw_value = espeasy_analogRead(pin); - - # ifdef ESP32 - - if (useFactoryCalibration(event)) { - return applyADCFactoryCalibration(raw_value, getAttenuation(event)); - } - # endif // ifdef ESP32 - - return raw_value; -} - -float P002_data_struct::applyCalibration(float float_value) const -{ - if (!_use2pointCalibration) { return float_value; } - return mapADCtoFloat( - float_value, - _calib_adc1, - _calib_adc2, - _calib_out1, - _calib_out2); -} - -# 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); - - if ((adc_num == 1) || (adc_num == 2)) { - return true; - } - } - return false; -} - -# endif // ifdef ESP32 - -# ifndef LIMIT_BUILD_SIZE -float P002_data_struct::applyMultiPointInterpolation(float float_value, bool force) const -{ - if (!_useMultipoint && !force) { return float_value; } - - // First find the surrounding bins - const size_t mp_size = _multipoint.size(); - - if (mp_size == 0) { return float_value; } - - if (float_value <= _multipoint[0]._adc) { - if (mp_size > 1) { - // Just extrapolate the first multipoint line segment. - return mapADCtoFloat( - float_value, - _multipoint[0]._adc, - _multipoint[1]._adc, - _multipoint[0]._value, - _multipoint[1]._value); - } - - // just one point, so all we can do is consider it to be a slight deviation of the calibration. - return mapADCtoFloat( - float_value, - 0, - _multipoint[0]._adc, - applyCalibration(0), - _multipoint[0]._value); - } - - const size_t last_mp_index = mp_size - 1; - - if (float_value >= _multipoint[last_mp_index]._adc) - { - if (mp_size > 1) { - // Just extrapolate the last multipoint line segment. - return mapADCtoFloat( - float_value, - _multipoint[last_mp_index - 1]._adc, - _multipoint[last_mp_index]._adc, - _multipoint[last_mp_index - 1]._value, - _multipoint[last_mp_index]._value); - } - - // just one point, so all we can do is consider it to be a slight deviation of the calibration. - return mapADCtoFloat( - float_value, - _multipoint[last_mp_index]._adc, - MAX_ADC_VALUE, - _multipoint[last_mp_index]._value, - applyCalibration(MAX_ADC_VALUE)); - } - - for (unsigned int i = 0; i < last_mp_index; ++i) { - const float dist_left = float_value - _multipoint[i]._adc; - const float dist_right = _multipoint[i + 1]._adc - float_value; - - if ((dist_left >= 0) && (dist_right >= 0) && - (_multipoint[i]._adc != _multipoint[i + 1]._adc)) { - // Inbetween 2 points of the multipoint array - return mapADCtoFloat( - float_value, - _multipoint[i]._adc, - _multipoint[i + 1]._adc, - _multipoint[i]._value, - _multipoint[i + 1]._value); - } - } - - return float_value; -} - -# endif // ifndef LIMIT_BUILD_SIZE - -void P002_data_struct::setTwoPointCalibration( - struct EventStruct *event, - float adc1, - float adc2, - float out1, - float out2) -{ - P002_CALIBRATION_POINT1 = lround(adc1); - P002_CALIBRATION_POINT2 = lround(adc2); - P002_CALIBRATION_VALUE1 = mapADCtoFloat( - P002_CALIBRATION_POINT1, - adc1, adc2, - out1, out2); - P002_CALIBRATION_VALUE2 = mapADCtoFloat( - P002_CALIBRATION_POINT2, - adc1, adc2, - out1, out2); -} - -/***************************************************** - * plugin_set_config - ****************************************************/ -bool P002_data_struct::plugin_set_config(struct EventStruct *event, - String & string) { - bool success = false; - const String cmd = parseString(string, 1); - - if (equals(cmd, F("setcalib"))) { - const String sub = parseString(string, 2); - - if (equals(sub, F("twopoint"))) { - // Command: - // 1 point : adcsetcalib,twopoint,ADC1,out1 - // 2 points: adcsetcalib,twopoint,ADC1,out1,ADC2,out2 - float adc1{}; - float out1{}; - float adc2{}; - float out2{}; - - if (validFloatFromString(parseString(string, 3), adc1) && - validFloatFromString(parseString(string, 4), out1)) - { - success = true; - } - - if (!validFloatFromString(parseString(string, 5), adc2) || - !validFloatFromString(parseString(string, 6), out2)) - { - // Not a complete 2nd calibration point, so make sure to set both values to 0. - adc2 = 0; - out2 = 0; - } - - if (success) { - setTwoPointCalibration(event, adc1, adc2, out1, out2); - } - } - } - - return success; -} - -#endif // ifdef USES_P002 +#include "../PluginStructs/P002_data_struct.h" + +#ifdef USES_P002 + +# include "../Globals/RulesCalculate.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 + + +void P002_data_struct::init(struct EventStruct *event) +{ + _sampleMode = P002_OVERSAMPLING; + + # ifdef ESP8266 + _pin_analogRead = A0; + # endif // ifdef ESP8266 + # ifdef ESP32 + _pin_analogRead = CONFIG_PIN1; + _useFactoryCalibration = useFactoryCalibration(event); + _attenuation = getAttenuation(event); + int channel{}; + const int adc = getADC_num_for_gpio(_pin_analogRead, channel); + + if ((adc == 1) || (adc == 2)) { + analogSetPinAttenuation(_pin_analogRead, static_cast(_attenuation)); + } + + # endif // ifdef ESP32 + + if (P002_CALIBRATION_ENABLED) { + _use2pointCalibration = true; + _calib_adc1 = P002_CALIBRATION_POINT1; + _calib_adc2 = P002_CALIBRATION_POINT2; + _calib_out1 = P002_CALIBRATION_VALUE1; + _calib_out2 = P002_CALIBRATION_VALUE2; + } + _nrDecimals = Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0); +# ifndef LIMIT_BUILD_SIZE + _nrMultiPointItems = P002_NR_MULTIPOINT_ITEMS; + _useMultipoint = P002_MULTIPOINT_ENABLED; + + load(event); +# endif // ifndef LIMIT_BUILD_SIZE +} + +# ifndef LIMIT_BUILD_SIZE +void P002_data_struct::load(struct EventStruct *event) +{ + const size_t nr_lines = P002_Nlines; + + { + String lines[nr_lines]; + LoadCustomTaskSettings(event->TaskIndex, lines, nr_lines, 0); + const int stored_nr_lines = lines[P002_SAVED_NR_LINES].toInt(); + move_special(_formula, std::move(lines[P002_LINE_INDEX_FORMULA])); + move_special(_formula_preprocessed, RulesCalculate_t::preProces(_formula)); + + for (size_t i = P002_LINE_IDX_FIRST_MP; i < nr_lines && static_cast(i) < stored_nr_lines; i += P002_STRINGS_PER_MP) { + float adc, value = 0.0f; + + 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 + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + _multipoint.emplace_back(adc, value); + } + } + } + std::sort(_multipoint.begin(), _multipoint.end()); + { + # ifdef USE_SECOND_HEAP + HeapSelectIram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + _binning.resize(_multipoint.size(), 0); + _binningRange.resize(_multipoint.size()); + } +} + +# endif // ifndef LIMIT_BUILD_SIZE + +void P002_data_struct::webformLoad_2p_calibPoint( + const __FlashStringHelper *label, + const __FlashStringHelper *id_point, + const __FlashStringHelper *id_value, + int point, + float value) const +{ + addRowLabel_tr_id(label, id_point); + addTextBox(id_point, String(point), 10, false, false, EMPTY_STRING, F("number")); + +# ifdef ESP32 + + if (_useFactoryCalibration) { + addUnit(F("mV")); + } +# endif // ifdef ESP32 + + html_add_estimate_symbol(); + const unsigned int display_nrDecimals = _nrDecimals > 3 ? _nrDecimals : 3; + + addTextBox(id_value, toString(value, display_nrDecimals), 10, false, false, EMPTY_STRING, F("number")); +} + +void P002_data_struct::webformLoad(struct EventStruct *event) +{ + // Output the statistics for the current settings. + int raw_value = 0; + const float currentValue = P002_data_struct::getCurrentValue(event, raw_value); + +# if FEATURE_PLUGIN_STATS + PluginStats *stats = getPluginStats(0); + + if (stats != nullptr) { + stats->trackPeak(raw_value); + } +# endif // if FEATURE_PLUGIN_STATS + +# ifdef ESP32 + addRowLabel(F("Analog Pin")); + # if HAS_HALL_EFFECT_SENSOR + addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch_HallEffect, F("taskdevicepin1"), CONFIG_PIN1); + # else // if HAS_HALL_EFFECT_SENSOR + addADC_PinSelect(AdcPinSelectPurpose::ADC_Touch, F("taskdevicepin1"), CONFIG_PIN1); + # endif // if HAS_HALL_EFFECT_SENSOR + + addFormNote(F("Do not use ADC2 pins with WiFi active")); + + { + const __FlashStringHelper *outputOptions[] = { + F("12 dB"), + F("6 dB"), + F("2.5 dB"), + F("0 dB") + }; + const int outputOptionValues[] = { + P002_ADC_11db, + P002_ADC_6db, + P002_ADC_2_5db, + P002_ADC_0db + }; + constexpr int nrOptions = NR_ELEMENTS(outputOptionValues); + addFormSelector(F("Attenuation"), F("attn"), nrOptions, outputOptions, outputOptionValues, P002_ATTENUATION); + } + +# endif // ifdef ESP32 + + { + const __FlashStringHelper *outputOptions[] = { + F("Use Current Sample"), + F("Oversampling") +# ifndef LIMIT_BUILD_SIZE + , F("Binning") +# endif // ifndef LIMIT_BUILD_SIZE + }; + const int outputOptionValues[] = { + P002_USE_CURENT_SAMPLE, + P002_USE_OVERSAMPLING +# ifndef LIMIT_BUILD_SIZE + , P002_USE_BINNING +# endif // ifndef LIMIT_BUILD_SIZE + }; + constexpr int nrOptions = NR_ELEMENTS(outputOptionValues); + addFormSelector(F("Oversampling"), F("oversampling"), nrOptions, outputOptions, outputOptionValues, P002_OVERSAMPLING); + } + +# 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")); + + if (hasADC_factory_calibration()) { + addRowLabel(F("Factory Calibration Type")); + addHtml(getADC_factory_calibration_type()); + # if FEATURE_CHART_JS + webformLoad_calibrationCurve(event); + # 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 + formatADC_statistics(F("Current ADC raw value"), raw_value); + #endif + + for (size_t att = 0; att < P002_ADC_ATTEN_MAX; ++att) { + const adc_atten_t attenuation = static_cast(att); + const int low = getADC_factory_calibrated_min(attenuation); + const int high = getADC_factory_calibrated_max(attenuation); + const float step = static_cast(high - low) / MAX_ADC_VALUE; + + addRowLabel(concat(F("Attenuation @"), AttenuationToString(attenuation))); + addHtml(F("Range / Step: ")); + addHtmlInt(low); + addHtml(F(" ... ")); + addHtmlInt(high); + addUnit(F("mV")); + addHtml(F(" / ")); + addHtmlFloat(step, 3); // calibration output is int value in mV, so doesn't really matter how many decimals + addUnit(F("mV")); + } + } +# endif // ifdef ESP32 + + addFormSubHeader(F("Two Point Calibration")); + + addFormCheckBox(F("Calibration Enabled"), F("cal"), P002_CALIBRATION_ENABLED); + +# ifdef ESP8266 +# if FEATURE_ADC_VCC + addFormNote(F("Measuring ESP VCC, not A0. Unit is 1/1024 V. See documentation.")); +# endif // if FEATURE_ADC_VCC +# endif // ifdef ESP8266 + + + webformLoad_2p_calibPoint( + F("Point 1"), + F("adc1"), + F("out1"), + P002_CALIBRATION_POINT1, + P002_CALIBRATION_VALUE1); + webformLoad_2p_calibPoint( + F("Point 2"), + F("adc2"), + F("out2"), + P002_CALIBRATION_POINT2, + P002_CALIBRATION_VALUE2); + + addFormNote(F("Input float values will be stored as int, calibration values will be adjusted accordingly")); + + { + // Output the statistics for the current settings. + if (P002_CALIBRATION_ENABLED) { + # if FEATURE_CHART_JS + webformLoad_2pt_calibrationCurve(event); + # endif // if FEATURE_CHART_JS + + int minInputValue, maxInputValue; + getInputRange(event, minInputValue, maxInputValue); + + const float minY_value = P002_data_struct::applyCalibration(event, minInputValue); + const float maxY_value = P002_data_struct::applyCalibration(event, maxInputValue); + const float current_calibrated = P002_data_struct::applyCalibration(event, currentValue); + + format_2point_calib_statistics(F("Current"), currentValue, current_calibrated); + format_2point_calib_statistics(F("Minimum"), minInputValue, minY_value); + format_2point_calib_statistics(F("Maximum"), maxInputValue, maxY_value); + + const float stepsize = (maxY_value - minY_value) / (MAX_ADC_VALUE + 1); + addRowLabel(F("Step Size")); + addHtmlFloat(stepsize, _nrDecimals); + } else { + addRowLabel(F("Current")); + addHtmlFloat(currentValue, _nrDecimals); + } + } +# 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"), + F("multi_en"), + P002_MULTIPOINT_ENABLED); + + if (useBinning) { + addFormTextBox(F("Binning Formula"), getPluginCustomArgName(P002_LINE_INDEX_FORMULA), _formula, P002_MAX_FORMULA_LENGTH); + } + + addFormNumericBox(useBinning ? F("Nr of Bins") : F("Nr Multipoint Fields"), + F("nr_mp"), + P002_NR_MULTIPOINT_ITEMS, + 0, + P002_MAX_NR_MP_ITEMS); + + // Checkbox needed to explicitly allow to split-paste over each field + addFormCheckBox(useBinning ? F("Split-Paste Binning Fields") : F("Split-Paste Multipoint Fields"), F("splitpaste"), false); + addFormNote(F("When checked, a set of tab, space or newline separated values can be pasted at once.")); + + size_t line_nr = 0; + + for (int varNr = P002_LINE_IDX_FIRST_MP; varNr < P002_Nlines; varNr += P002_STRINGS_PER_MP) + { + const String label = String(useBinning ? F("Bin ") : F("Point ")) + String(line_nr + 1); + addFormTextBox(F("query-input widenumber"), + label, + getPluginCustomArgName(varNr), + + _multipoint.size() > line_nr ? +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + floatToString +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + (static_cast(_multipoint[line_nr]._adc), + _nrDecimals, + true) : EMPTY_STRING, + 0); + html_add_estimate_symbol(); + addTextBox(getPluginCustomArgName(varNr + 1), + _multipoint.size() > line_nr ? +# if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + doubleToString +# else // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + floatToString +# endif // if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE + (static_cast(_multipoint[line_nr]._value), + _nrDecimals, + true) : EMPTY_STRING, + 0, + false, + false, + EMPTY_STRING, + F("query-input widenumber")); + + ++line_nr; + } + # if FEATURE_CHART_JS + webformLoad_multipointCurve(event); + # endif // if FEATURE_CHART_JS +# endif // ifndef LIMIT_BUILD_SIZE +} + +# if FEATURE_PLUGIN_STATS +bool P002_data_struct::webformLoad_show_stats(struct EventStruct *event) +{ + bool somethingAdded = false; + + if (_plugin_stats_array != nullptr) { + somethingAdded = _plugin_stats_array->webformLoad_show_stats(event, false); + } + + const PluginStats *stats = getPluginStats(0); + + if (stats != nullptr) { + if (stats->webformLoad_show_avg(event)) { somethingAdded = true; } + + if (stats->webformLoad_show_stdev(event)) { somethingAdded = true; } + + if (stats->hasPeaks()) { + float floatvalue_low, floatvalue_high; + + if (stats->webformLoad_show_peaks( + event, + stats->getLabel(), + formatADC_statistics_to_str(stats->getPeakLow(), floatvalue_low, true), + formatADC_statistics_to_str(stats->getPeakHigh(), floatvalue_high, true), + false)) + { + addRowLabel(concat(stats->getLabel(), F(" Peak-to-peak"))); + addHtmlFloat(floatvalue_high - floatvalue_low, _nrDecimals); + somethingAdded = true; + } + } + } + return somethingAdded; +} + +# endif // if FEATURE_PLUGIN_STATS + + +# ifdef ESP32 +# if FEATURE_CHART_JS +void P002_data_struct::webformLoad_calibrationCurve(struct EventStruct *event) +{ + if (!hasADC_factory_calibration()) { return; } + + addRowLabel(F("Calibration Curve")); + + 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); + + const __FlashStringHelper *colors[] = { F("#A52422"), F("#BEA57D"), F("#0F4C5C"), F("#A4BAB7") }; + + size_t current_attenuation = getAttenuation(event); + + 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 + } + + 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; + + if (att != 0) { + addHtml(','); + } + + add_ChartJS_dataset( + config, + values, + valueCount, + Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); + } + add_ChartJS_chart_footer(); +} + +# endif // if FEATURE_CHART_JS +# endif // ifdef ESP32 + +# if FEATURE_CHART_JS +const __FlashStringHelper * P002_data_struct::getChartXaxisLabel(struct EventStruct *event) +{ + # ifdef ESP32 + + if (useFactoryCalibration(event)) { + // reading in mVolt, not ADC + return F("Input Voltage (mV)"); + } + # endif // ifdef ESP32 + return F("ADC Value"); +} + +# 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 + + if (useFactoryCalibration(event) && !ignoreCalibration) { + // reading in mVolt, not ADC + const adc_atten_t attenuation = getAttenuation(event); + + minInputValue = getADC_factory_calibrated_min(attenuation); + maxInputValue = getADC_factory_calibrated_max(attenuation); + } +# endif // ifdef ESP32 +} + +# if FEATURE_CHART_JS + +void P002_data_struct::getChartRange(struct EventStruct *event, int values[], int count, bool ignoreCalibration) +{ + int minInputValue, maxInputValue; + + getInputRange(event, minInputValue, maxInputValue, ignoreCalibration); + + const float stepSize = static_cast(maxInputValue + 1 - minInputValue) / (count - 1); + + for (int i = 0; i < count; ++i) { + values[i] = minInputValue + i * stepSize; + } +} + +void P002_data_struct::webformLoad_2pt_calibrationCurve(struct EventStruct *event) +{ + addRowLabel(F("Two Point Calibration")); + + const int valueCount = 33; + int xAxisValues[valueCount]; + + getChartRange(event, xAxisValues, valueCount); + + String axisOptions; + + { + 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]; + + for (int i = 0; i < valueCount; ++i) { + values[i] = P002_data_struct::applyCalibration(event, xAxisValues[i]); + } + + 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(); +} + +# endif // if FEATURE_CHART_JS + +void P002_data_struct::formatADC_statistics(const __FlashStringHelper *label, int raw, bool includeOutputValue) const +{ + addRowLabel(label); + float float_value{}; + + addHtml(formatADC_statistics_to_str(raw, float_value, includeOutputValue)); +} + +String P002_data_struct::formatADC_statistics_to_str( + int raw, + float& float_value, + bool includeOutputValue) const +{ + String res; + + float_value = raw; + +# ifdef ESP32 + + if (_useFactoryCalibration) { + float_value = applyADCFactoryCalibration(raw, _attenuation); + res = strformat( + F("%s [mV] ≙ %d [ADC]"), + toString(float_value, _nrDecimals).c_str(), + raw); + } else { + res += raw; + } +#else + res += raw; +# endif // ifdef ESP32 + + if (includeOutputValue) { + res += F(" → "); + float_value = applyCalibration(float_value); + +# ifndef LIMIT_BUILD_SIZE + + switch (_sampleMode) { + case P002_USE_OVERSAMPLING: + float_value = applyMultiPointInterpolation(float_value); + break; + case P002_USE_BINNING: + { + const int index = computeADC_to_bin(raw); + + if ((index >= 0) && (static_cast(_binning.size()) > index)) { + float_value = _multipoint[index]._value; + } + + break; + } + } +# endif // ifndef LIMIT_BUILD_SIZE + res += toString(float_value, _nrDecimals); + } + + return res; +} + +void P002_data_struct::format_2point_calib_statistics(const __FlashStringHelper *label, int raw, float float_value) const +{ + addRowLabel(label); + addHtmlInt(raw); + # ifdef ESP32 + addUnit(_useFactoryCalibration ? F("mV") : F("raw")); + # else // ifdef ESP32 + addUnit(F("raw")); + # endif // ifdef ESP32 + html_add_estimate_symbol(); + addHtmlFloat(float_value, _nrDecimals); +} + +# 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") }; + + if (attenuation < 4) { return datalabels[attenuation]; } + return F("Unknown"); +} + +adc_atten_t P002_data_struct::getAttenuation(struct EventStruct *event) { + if ((P002_ATTENUATION >= P002_ADC_0db) && (P002_ATTENUATION <= P002_ADC_11db)) { + // Make sure the attenuation is only set to correct values or else it may damage the board + return static_cast(P002_ATTENUATION - 10); + } + P002_ATTENUATION = P002_ADC_11db; + +# if ESP_IDF_VERSION_MAJOR >= 5 + return ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 + return ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +} + +# endif // ifdef ESP32 + +# 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; + + { + 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)") }); + + for (size_t i = 0; i < _multipoint.size(); ++i) { + if (i != 0) { + addHtml(','); + } + addHtmlFloat(_multipoint[i]._value, _nrDecimals); + } + add_ChartJS_dataset_footer(); + add_ChartJS_chart_footer(); + + if (!useBinning) { + // Try to compute the expected mapping from ADC to multipoint values + addRowLabel(F("Input to Output Curve")); + const int valueCount = 33; + int xAxisValues[valueCount]; + getChartRange(event, xAxisValues, valueCount); + + String axisOptions; + + { + 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; + + 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; + + if (step != 0) { + addHtml(','); + } + + add_ChartJS_dataset( + config, + values, + valueCount, + Cache.getTaskDeviceValueDecimals(event->TaskIndex, 0)); + } + add_ChartJS_chart_footer(); + } + } +} + +# 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 + P002_APPLY_FACTORY_CALIB = isFormItemChecked(F("fac_cal")); + P002_ATTENUATION = getFormItemInt(F("attn")); + # endif // ifdef ESP32 + + // Map the input "point" values to the nearest int. + setTwoPointCalibration( + event, + getFormItemFloat(F("adc1")), + getFormItemFloat(F("adc2")), + getFormItemFloat(F("out1")), + getFormItemFloat(F("out2"))); + +# ifndef LIMIT_BUILD_SIZE + P002_MULTIPOINT_ENABLED = isFormItemChecked(F("multi_en")); + + P002_NR_MULTIPOINT_ITEMS = getFormItemInt(F("nr_mp")); + + const size_t nr_lines = P002_Nlines; + String lines[nr_lines]; + + // Store nr of lines that were saved, so no 'old' data will be read when nr of multi-point items has changed. + lines[P002_SAVED_NR_LINES] = String(nr_lines); + + if (hasArg(getPluginCustomArgName(P002_LINE_INDEX_FORMULA))) { + lines[P002_LINE_INDEX_FORMULA] = webArg(getPluginCustomArgName(P002_LINE_INDEX_FORMULA)); + } + + // const int nrDecimals = webArg(F("TDVD1")).toInt(); + + for (size_t varNr = P002_LINE_IDX_FIRST_MP; varNr < nr_lines; varNr += P002_STRINGS_PER_MP) + { + float adc, value = 0.0f; + const String adc_str = webArg(getPluginCustomArgName(varNr)); + const String val_str = webArg(getPluginCustomArgName(varNr + 1)); + + if (validFloatFromString(adc_str, adc) && validFloatFromString(val_str, value)) { + // Only store valid floats + lines[varNr] = adc_str; + lines[varNr + 1] = val_str; + } + } + + return SaveCustomTaskSettings(event->TaskIndex, lines, nr_lines, 0); +# else // ifndef LIMIT_BUILD_SIZE + return EMPTY_STRING; +# endif // ifndef LIMIT_BUILD_SIZE +} + +void P002_data_struct::takeSample() +{ + if (_sampleMode == P002_USE_CURENT_SAMPLE) { return; } + int raw = espeasy_analogRead(_pin_analogRead); + +# if FEATURE_PLUGIN_STATS + PluginStats *stats = getPluginStats(0); + + if (stats != nullptr) { + stats->trackPeak(raw); + } +# endif // if FEATURE_PLUGIN_STATS + + switch (_sampleMode) { + case P002_USE_OVERSAMPLING: + addOversamplingValue(raw); + break; +# ifndef LIMIT_BUILD_SIZE + case P002_USE_BINNING: + addBinningValue(raw); + break; +# endif // ifndef LIMIT_BUILD_SIZE + } +} + +bool P002_data_struct::getValue(float& float_value, + int & raw_value) const +{ + bool mustTakeSample = false; + + switch (_sampleMode) { + case P002_USE_OVERSAMPLING: + + if (getOversamplingValue(float_value, raw_value)) { + return true; + } + mustTakeSample = true; + break; +# ifndef LIMIT_BUILD_SIZE + case P002_USE_BINNING: + + if (getBinnedValue(float_value, raw_value)) { + return true; + } + mustTakeSample = true; + break; +# endif // ifndef LIMIT_BUILD_SIZE + case P002_USE_CURENT_SAMPLE: + mustTakeSample = true; + break; + } + + if (!mustTakeSample) { + return false; + } + + raw_value = espeasy_analogRead(_pin_analogRead); +# if FEATURE_PLUGIN_STATS + + PluginStats *stats = getPluginStats(0); + + if (stats != nullptr) { + stats->trackPeak(raw_value); + } +# endif // if FEATURE_PLUGIN_STATS + float_value = raw_value; + # ifdef ESP32 + + if (_useFactoryCalibration) { + float_value = applyADCFactoryCalibration(raw_value, _attenuation); + } + # endif // ifdef ESP32 + + float_value = applyCalibration(float_value); + +# ifndef LIMIT_BUILD_SIZE + + switch (_sampleMode) { + case P002_USE_OVERSAMPLING: + float_value = applyMultiPointInterpolation(float_value); + break; + case P002_USE_BINNING: + { + const int index = computeADC_to_bin(raw_value); + + if ((index >= 0) && (static_cast(_binning.size()) > index)) { + float_value = _multipoint[index]._value; + } + + break; + } + } +# endif // ifndef LIMIT_BUILD_SIZE + + return true; +} + +void P002_data_struct::reset() +{ +# ifndef LIMIT_BUILD_SIZE + + switch (_sampleMode) { + case P002_USE_OVERSAMPLING: + resetOversampling(); + break; + case P002_USE_BINNING: + { + for (auto it = _binning.begin(); it != _binning.end(); ++it) { + *it = 0; + } + + break; + } + } +# else // ifndef LIMIT_BUILD_SIZE + resetOversampling(); +# endif // ifndef LIMIT_BUILD_SIZE +} + +uint32_t P002_data_struct::getOversamplingCount() const +{ + return OverSampling.getCount(); +} + +void P002_data_struct::resetOversampling() { + OverSampling.reset(); +} + +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 + + if (_useFactoryCalibration) { + float_value = applyADCFactoryCalibration(float_value, _attenuation); + } +# 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 + float_value = applyMultiPointInterpolation(float_value); +# endif // ifndef LIMIT_BUILD_SIZE + + return true; + } + return false; +} + +# ifndef LIMIT_BUILD_SIZE +int P002_data_struct::getBinIndex(float currentValue) const +{ + const size_t mp_size = _multipoint.size(); + + if (mp_size == 0) { return -1; } + + if (mp_size == 1) { return 0; } + + if (currentValue <= _multipoint[0]._adc) { return 0; } + + const size_t last_mp_index = mp_size - 1; + + if (currentValue >= _multipoint[last_mp_index]._adc) { return last_mp_index; } + + for (unsigned int i = 0; i < last_mp_index; ++i) { + const float dist_left = currentValue - _multipoint[i]._adc; + const float dist_right = _multipoint[i + 1]._adc - currentValue; + + if ((dist_left >= 0) && (dist_right >= 0)) { + // Inbetween 2 points of the multipoint array + return (dist_left < dist_right) ? i : i + 1; + } + } + + return -1; +} + +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 + + if (_useFactoryCalibration) { + calibrated_value = applyADCFactoryCalibration(calibrated_value, _attenuation); + } +# endif // ifdef ESP32 + + + calibrated_value = applyCalibration(calibrated_value); + + if (!_formula_preprocessed.isEmpty()) { + // Formula, must be applied before binning + String formula = _formula_preprocessed; + + formula.replace(F("%value%"), toString(calibrated_value, _nrDecimals)); + + ESPEASY_RULES_FLOAT_TYPE result{}; + + if (!isError(RulesCalculate.doCalculate(parseTemplate(formula).c_str(), &result))) { + calibrated_value = result; + } + } + + return getBinIndex(calibrated_value); +} + +void P002_data_struct::addBinningValue(int currentValue) +{ + for (size_t index = 0; index < _binningRange.size(); ++index) { + if (_binningRange[index].inRange(currentValue)) { + ++_binning[index]; + return; + } + } + + const int index = computeADC_to_bin(currentValue); + + if ((index >= 0) && (static_cast(_binning.size()) > index)) { + _binningRange[index].set(currentValue); + ++_binning[index]; + } +} + +bool P002_data_struct::getBinnedValue(float& float_value, int& raw_value) const +{ + unsigned int highest_bin_count = 0; + + const size_t nr_bin_elements = std::min(_binning.size(), _multipoint.size()); + + for (size_t i = 0; i < nr_bin_elements; ++i) { + if (_binning[i] > highest_bin_count) { + highest_bin_count = _binning[i]; + float_value = _multipoint[i]._value; + raw_value = _multipoint[i]._adc; + } + } + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("ADC getBinnedValue: bin cnt: %u Value: %f RAW: %d"), + highest_bin_count, + float_value, + raw_value)); + } + # endif // ifndef BUILD_NO_DEBUG + + return highest_bin_count != 0; +} + +# endif // ifndef LIMIT_BUILD_SIZE + +float P002_data_struct::applyCalibration(struct EventStruct *event, float float_value, bool force) { + if (force || P002_CALIBRATION_ENABLED) + { + float_value = mapADCtoFloat(float_value, + P002_CALIBRATION_POINT1, + P002_CALIBRATION_POINT2, + P002_CALIBRATION_VALUE1, + P002_CALIBRATION_VALUE2); + } + return float_value; +} + +float P002_data_struct::getCurrentValue(struct EventStruct *event, int& raw_value) +{ + # ifdef ESP8266 + const int pin = A0; + # endif // ifdef ESP8266 + # ifdef ESP32 + const int pin = CONFIG_PIN1; + # endif // ifdef ESP32 + + raw_value = espeasy_analogRead(pin); + + # ifdef ESP32 + + if (useFactoryCalibration(event)) { + return applyADCFactoryCalibration(raw_value, getAttenuation(event)); + } + # endif // ifdef ESP32 + + return raw_value; +} + +float P002_data_struct::applyCalibration(float float_value) const +{ + if (!_use2pointCalibration) { return float_value; } + return mapADCtoFloat( + float_value, + _calib_adc1, + _calib_adc2, + _calib_out1, + _calib_out2); +} + +# 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); + + if ((adc_num == 1) || (adc_num == 2)) { + return true; + } + } + return false; +} + +# endif // ifdef ESP32 + +# ifndef LIMIT_BUILD_SIZE +float P002_data_struct::applyMultiPointInterpolation(float float_value, bool force) const +{ + if (!_useMultipoint && !force) { return float_value; } + + // First find the surrounding bins + const size_t mp_size = _multipoint.size(); + + if (mp_size == 0) { return float_value; } + + if (float_value <= _multipoint[0]._adc) { + if (mp_size > 1) { + // Just extrapolate the first multipoint line segment. + return mapADCtoFloat( + float_value, + _multipoint[0]._adc, + _multipoint[1]._adc, + _multipoint[0]._value, + _multipoint[1]._value); + } + + // just one point, so all we can do is consider it to be a slight deviation of the calibration. + return mapADCtoFloat( + float_value, + 0, + _multipoint[0]._adc, + applyCalibration(0), + _multipoint[0]._value); + } + + const size_t last_mp_index = mp_size - 1; + + if (float_value >= _multipoint[last_mp_index]._adc) + { + if (mp_size > 1) { + // Just extrapolate the last multipoint line segment. + return mapADCtoFloat( + float_value, + _multipoint[last_mp_index - 1]._adc, + _multipoint[last_mp_index]._adc, + _multipoint[last_mp_index - 1]._value, + _multipoint[last_mp_index]._value); + } + + // just one point, so all we can do is consider it to be a slight deviation of the calibration. + return mapADCtoFloat( + float_value, + _multipoint[last_mp_index]._adc, + MAX_ADC_VALUE, + _multipoint[last_mp_index]._value, + applyCalibration(MAX_ADC_VALUE)); + } + + for (unsigned int i = 0; i < last_mp_index; ++i) { + const float dist_left = float_value - _multipoint[i]._adc; + const float dist_right = _multipoint[i + 1]._adc - float_value; + + if ((dist_left >= 0) && (dist_right >= 0) && + (_multipoint[i]._adc != _multipoint[i + 1]._adc)) { + // Inbetween 2 points of the multipoint array + return mapADCtoFloat( + float_value, + _multipoint[i]._adc, + _multipoint[i + 1]._adc, + _multipoint[i]._value, + _multipoint[i + 1]._value); + } + } + + return float_value; +} + +# endif // ifndef LIMIT_BUILD_SIZE + +void P002_data_struct::setTwoPointCalibration( + struct EventStruct *event, + float adc1, + float adc2, + float out1, + float out2) +{ + P002_CALIBRATION_POINT1 = lround(adc1); + P002_CALIBRATION_POINT2 = lround(adc2); + P002_CALIBRATION_VALUE1 = mapADCtoFloat( + P002_CALIBRATION_POINT1, + adc1, adc2, + out1, out2); + P002_CALIBRATION_VALUE2 = mapADCtoFloat( + P002_CALIBRATION_POINT2, + adc1, adc2, + out1, out2); +} + +/***************************************************** + * plugin_set_config + ****************************************************/ +bool P002_data_struct::plugin_set_config(struct EventStruct *event, + String & string) { + bool success = false; + const String cmd = parseString(string, 1); + + if (equals(cmd, F("setcalib"))) { + const String sub = parseString(string, 2); + + if (equals(sub, F("twopoint"))) { + // Command: + // 1 point : adcsetcalib,twopoint,ADC1,out1 + // 2 points: adcsetcalib,twopoint,ADC1,out1,ADC2,out2 + float adc1{}; + float out1{}; + float adc2{}; + float out2{}; + + if (validFloatFromString(parseString(string, 3), adc1) && + validFloatFromString(parseString(string, 4), out1)) + { + success = true; + } + + if (!validFloatFromString(parseString(string, 5), adc2) || + !validFloatFromString(parseString(string, 6), out2)) + { + // Not a complete 2nd calibration point, so make sure to set both values to 0. + adc2 = 0; + out2 = 0; + } + + if (success) { + setTwoPointCalibration(event, adc1, adc2, out1, out2); + } + } + } + + return success; +} + +#endif // ifdef USES_P002 diff --git a/src/src/PluginStructs/P002_data_struct.h b/src/src/PluginStructs/P002_data_struct.h index 18d529811..5db257024 100644 --- a/src/src/PluginStructs/P002_data_struct.h +++ b/src/src/PluginStructs/P002_data_struct.h @@ -1,282 +1,285 @@ -#ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H -#define PLUGINSTRUCTS_P002_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" - -#include "../Helpers/OversamplingHelper.h" - -#ifdef USES_P002 - -# include - -# ifdef ESP32 -// Needed to get ADC Vref -#if ESP_IDF_VERSION_MAJOR >= 5 - #include - -#else - # include - # include -#endif -# endif // ifdef ESP32 - - -# define P002_OVERSAMPLING PCONFIG(0) -# ifdef ESP32 -# define P002_APPLY_FACTORY_CALIB PCONFIG(1) -# define P002_ATTENUATION PCONFIG(2) -# endif // ifdef ESP32 -# define P002_CALIBRATION_ENABLED PCONFIG(3) -# define P002_CALIBRATION_POINT1 PCONFIG_LONG(0) -# define P002_CALIBRATION_POINT2 PCONFIG_LONG(1) -# define P002_CALIBRATION_VALUE1 PCONFIG_FLOAT(0) -# define P002_CALIBRATION_VALUE2 PCONFIG_FLOAT(1) - -# define P002_MULTIPOINT_ENABLED PCONFIG(4) -# define P002_NR_MULTIPOINT_ITEMS PCONFIG(5) - -# define P002_USE_CURENT_SAMPLE 0 -# define P002_USE_OVERSAMPLING 1 -# define P002_USE_BINNING 2 - -// FIXME TD-er: Must test if HTML POST on ESP8266 will not take too much ram on save -# define P002_MAX_NR_MP_ITEMS 64 - -// We store the multipoint values and formula in a number of strings -// These will be stored in CustomTaskSettings -# define P002_SAVED_NR_LINES 0 -# define P002_LINE_INDEX_FORMULA 1 - -# define P002_LINE_IDX_FIRST_MP 6 // Leave some room for extra lines in the settings later -# define P002_STRINGS_PER_MP 2 // Nr of items per multi-point set -# define P002_Nlines (P002_LINE_IDX_FIRST_MP + (P002_STRINGS_PER_MP * (P002_NR_MULTIPOINT_ITEMS))) -# define P002_MAX_FORMULA_LENGTH 64 - -// Need to define the attenuation values to make sure no old or uninitialized value may be setting this to the wrong value. -# define P002_ADC_0db (ADC_ATTEN_DB_0 + 10) -# define P002_ADC_2_5db (ADC_ATTEN_DB_2_5 + 10) -# define P002_ADC_6db (ADC_ATTEN_DB_6 + 10) -#if ESP_IDF_VERSION_MAJOR >= 5 -# define P002_ADC_11db (ADC_ATTEN_DB_12 + 10) -#else -# define P002_ADC_11db (ADC_ATTEN_DB_11 + 10) -#endif - - -struct P002_ADC_Value_pair { - P002_ADC_Value_pair(float adc, float value) : _adc(adc), _value(value) {} - - P002_ADC_Value_pair(const P002_ADC_Value_pair&) = default; - - P002_ADC_Value_pair& operator=(const P002_ADC_Value_pair&) = default; - - P002_ADC_Value_pair& operator=(P002_ADC_Value_pair&&) = default; - - - // Needed to sort based on ADC value - bool operator<(const P002_ADC_Value_pair& other) const { - return this->_adc < other._adc; - } - - float _adc; - float _value; -}; - -struct P002_binningRange { - void set(int currentValue) { - if (currentValue > _maxADC) { - _maxADC = currentValue; - } - - if (currentValue < _minADC) { - _minADC = currentValue; - } - } - - bool inRange(int currentValue) const { - return _minADC <= currentValue && currentValue <= _maxADC; - } - - int _minADC = INT_MAX; - int _maxADC = INT_MIN; -}; - -struct P002_data_struct : public PluginTaskData_base { - P002_data_struct() = default; - virtual ~P002_data_struct() = default; - - void init(struct EventStruct *event); - -private: - -# ifndef LIMIT_BUILD_SIZE - void load(struct EventStruct *event); -# endif // ifndef LIMIT_BUILD_SIZE - - void webformLoad_2p_calibPoint( - const __FlashStringHelper *label, - const __FlashStringHelper *id_point, - const __FlashStringHelper *id_value, - int point, - float value) const; - -public: - - void webformLoad(struct EventStruct *event); - -# if FEATURE_PLUGIN_STATS - bool webformLoad_show_stats(struct EventStruct *event); -# endif // if FEATURE_PLUGIN_STATS - -private: - - void formatADC_statistics(const __FlashStringHelper *label, - int raw, - bool includeOutputValue = false) const; - void format_2point_calib_statistics(const __FlashStringHelper *label, - int raw, - float float_value) const; - -# ifdef ESP32 - static adc_atten_t getAttenuation(struct EventStruct *event); - static const __FlashStringHelper* AttenuationToString(adc_atten_t attenuation); - # if FEATURE_CHART_JS - static void webformLoad_calibrationCurve(struct EventStruct *event); - # endif // if FEATURE_CHART_JS -# endif // ifdef ESP32 - -# if FEATURE_CHART_JS - static const __FlashStringHelper* getChartXaxisLabel(struct EventStruct *event); -# endif // if FEATURE_CHART_JS - static void getInputRange(struct EventStruct *event, - int & min_value, - int & max_value, - bool ignoreCalibration = false); -# if FEATURE_CHART_JS - static void getChartRange(struct EventStruct *event, - int values[], - int count, - bool ignoreCalibration = false); - - static void webformLoad_2pt_calibrationCurve(struct EventStruct *event); - - void webformLoad_multipointCurve(struct EventStruct *event) const; -# endif // if FEATURE_CHART_JS - -public: - - static String webformSave(struct EventStruct *event); - - void takeSample(); - - bool getValue(float& float_value, - int & raw_value) const; - - void reset(); - - uint32_t getOversamplingCount() const; - -private: - - void resetOversampling(); - - void addOversamplingValue(int currentValue); - - bool getOversamplingValue(float& float_value, - int & raw_value) const; - -private: - -# ifndef LIMIT_BUILD_SIZE - - // Get index of the bin to match. - // Return -1 if no bin matched. - int getBinIndex(float currentValue) const; - - int computeADC_to_bin(const int& currentValue) const; - - void addBinningValue(int currentValue); - - bool getBinnedValue(float& float_value, - int & raw_value) const; -# endif // ifndef LIMIT_BUILD_SIZE - -public: - - // This needs to be a static function, as the object may not exist if the task is not enabled. - static float applyCalibration(struct EventStruct *event, - float float_value, - bool force = false); - - static float getCurrentValue(struct EventStruct *event, - int & raw_value); - - float applyCalibration(float float_value) const; - -# ifdef ESP32 - static bool useFactoryCalibration(struct EventStruct *event); - -# endif // ifdef ESP32 - -private: - -# ifndef LIMIT_BUILD_SIZE - float applyMultiPointInterpolation(float float_value, bool force = false) const; -# endif // ifndef LIMIT_BUILD_SIZE - - - // Map the input "point" values to the nearest int. - static void setTwoPointCalibration(struct EventStruct *event, - float adc1, - float adc2, - float out1, - float out2); - -public: - - bool plugin_set_config(struct EventStruct *event, String& string); - - -private: - - - OversamplingHelper OverSampling; - - int _calib_adc1 = 0; - int _calib_adc2 = 0; - float _calib_out1 = 0.0f; - float _calib_out2 = 0.0f; - - bool _use2pointCalibration = false; -# ifndef LIMIT_BUILD_SIZE - std::vector_multipoint; - std::vector _binning; - std::vector _binningRange; - bool _useMultipoint = false; -# endif // ifndef LIMIT_BUILD_SIZE - - int _pin_analogRead = -1; - - uint8_t _sampleMode = P002_USE_CURENT_SAMPLE; - - uint8_t _nrDecimals = 0; -# ifndef LIMIT_BUILD_SIZE - uint8_t _nrMultiPointItems = 0; - String _formula; - String _formula_preprocessed; -# endif // ifndef LIMIT_BUILD_SIZE -# ifdef ESP32 - bool _useFactoryCalibration = false; - -#if ESP_IDF_VERSION_MAJOR >= 5 - adc_atten_t _attenuation = ADC_ATTEN_DB_12; -#else - adc_atten_t _attenuation = ADC_ATTEN_DB_11; -#endif -# endif // ifdef ESP32 - -}; - - -#endif // ifdef USES_P002 -#endif // ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H +#define PLUGINSTRUCTS_P002_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" + +#include "../Helpers/OversamplingHelper.h" + +#ifdef USES_P002 + +# include + +# ifdef ESP32 + +// Needed to get ADC Vref +# if ESP_IDF_VERSION_MAJOR >= 5 + # include + +# else // if ESP_IDF_VERSION_MAJOR >= 5 + # include + # include +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 + + +# define P002_OVERSAMPLING PCONFIG(0) +# ifdef ESP32 +# define P002_APPLY_FACTORY_CALIB PCONFIG(1) +# define P002_ATTENUATION PCONFIG(2) +# endif // ifdef ESP32 +# define P002_CALIBRATION_ENABLED PCONFIG(3) +# define P002_CALIBRATION_POINT1 PCONFIG_LONG(0) +# define P002_CALIBRATION_POINT2 PCONFIG_LONG(1) +# define P002_CALIBRATION_VALUE1 PCONFIG_FLOAT(0) +# define P002_CALIBRATION_VALUE2 PCONFIG_FLOAT(1) + +# define P002_MULTIPOINT_ENABLED PCONFIG(4) +# define P002_NR_MULTIPOINT_ITEMS PCONFIG(5) + +# define P002_USE_CURENT_SAMPLE 0 +# define P002_USE_OVERSAMPLING 1 +# define P002_USE_BINNING 2 + +// FIXME TD-er: Must test if HTML POST on ESP8266 will not take too much ram on save +# define P002_MAX_NR_MP_ITEMS 64 + +// We store the multipoint values and formula in a number of strings +// These will be stored in CustomTaskSettings +# define P002_SAVED_NR_LINES 0 +# define P002_LINE_INDEX_FORMULA 1 + +# define P002_LINE_IDX_FIRST_MP 6 // Leave some room for extra lines in the settings later +# define P002_STRINGS_PER_MP 2 // Nr of items per multi-point set +# define P002_Nlines (P002_LINE_IDX_FIRST_MP + (P002_STRINGS_PER_MP * (P002_NR_MULTIPOINT_ITEMS))) +# define P002_MAX_FORMULA_LENGTH 64 + +// Need to define the attenuation values to make sure no old or uninitialized value may be setting this to the wrong value. +# define P002_ADC_0db (ADC_ATTEN_DB_0 + 10) +# define P002_ADC_2_5db (ADC_ATTEN_DB_2_5 + 10) +# define P002_ADC_6db (ADC_ATTEN_DB_6 + 10) +# if ESP_IDF_VERSION_MAJOR >= 5 +# define P002_ADC_11db (ADC_ATTEN_DB_12 + 10) +# else // if ESP_IDF_VERSION_MAJOR >= 5 +# define P002_ADC_11db (ADC_ATTEN_DB_11 + 10) +# endif // if ESP_IDF_VERSION_MAJOR >= 5 + + +struct P002_ADC_Value_pair { + P002_ADC_Value_pair(float adc, float value) : _adc(adc), _value(value) {} + + P002_ADC_Value_pair(const P002_ADC_Value_pair&) = default; + + P002_ADC_Value_pair& operator=(const P002_ADC_Value_pair&) = default; + + P002_ADC_Value_pair& operator=(P002_ADC_Value_pair&&) = default; + + + // Needed to sort based on ADC value + bool operator<(const P002_ADC_Value_pair& other) const { + return this->_adc < other._adc; + } + + float _adc; + float _value; +}; + +struct P002_binningRange { + void set(int currentValue) { + if (currentValue > _maxADC) { + _maxADC = currentValue; + } + + if (currentValue < _minADC) { + _minADC = currentValue; + } + } + + bool inRange(int currentValue) const { + return _minADC <= currentValue && currentValue <= _maxADC; + } + + int _minADC = INT_MAX; + int _maxADC = INT_MIN; +}; + +struct P002_data_struct : public PluginTaskData_base { + P002_data_struct() = default; + virtual ~P002_data_struct() = default; + + void init(struct EventStruct *event); + +private: + +# ifndef LIMIT_BUILD_SIZE + void load(struct EventStruct *event); +# endif // ifndef LIMIT_BUILD_SIZE + + void webformLoad_2p_calibPoint( + const __FlashStringHelper *label, + const __FlashStringHelper *id_point, + const __FlashStringHelper *id_value, + int point, + float value) const; + +public: + + void webformLoad(struct EventStruct *event); + +# if FEATURE_PLUGIN_STATS + bool webformLoad_show_stats(struct EventStruct *event); +# endif // if FEATURE_PLUGIN_STATS + +private: + + void formatADC_statistics(const __FlashStringHelper *label, + int raw, + bool includeOutputValue = false) const; + String formatADC_statistics_to_str(int raw, + float& float_value, + bool includeOutputValue = false) const; + void format_2point_calib_statistics(const __FlashStringHelper *label, + int raw, + float float_value) const; + +# ifdef ESP32 + static adc_atten_t getAttenuation(struct EventStruct *event); + static const __FlashStringHelper* AttenuationToString(adc_atten_t attenuation); + # if FEATURE_CHART_JS + static void webformLoad_calibrationCurve(struct EventStruct *event); + # endif // if FEATURE_CHART_JS +# endif // ifdef ESP32 + +# if FEATURE_CHART_JS + static const __FlashStringHelper* getChartXaxisLabel(struct EventStruct *event); +# endif // if FEATURE_CHART_JS + static void getInputRange(struct EventStruct *event, + int & min_value, + int & max_value, + bool ignoreCalibration = false); +# if FEATURE_CHART_JS + static void getChartRange(struct EventStruct *event, + int values[], + int count, + bool ignoreCalibration = false); + + static void webformLoad_2pt_calibrationCurve(struct EventStruct *event); + + void webformLoad_multipointCurve(struct EventStruct *event) const; +# endif // if FEATURE_CHART_JS + +public: + + static String webformSave(struct EventStruct *event); + + void takeSample(); + + bool getValue(float& float_value, + int & raw_value) const; + + void reset(); + + uint32_t getOversamplingCount() const; + +private: + + void resetOversampling(); + + void addOversamplingValue(int currentValue); + + bool getOversamplingValue(float& float_value, + int & raw_value) const; + +private: + +# ifndef LIMIT_BUILD_SIZE + + // Get index of the bin to match. + // Return -1 if no bin matched. + int getBinIndex(float currentValue) const; + + int computeADC_to_bin(const int& currentValue) const; + + void addBinningValue(int currentValue); + + bool getBinnedValue(float& float_value, + int & raw_value) const; +# endif // ifndef LIMIT_BUILD_SIZE + +public: + + // This needs to be a static function, as the object may not exist if the task is not enabled. + static float applyCalibration(struct EventStruct *event, + float float_value, + bool force = false); + + static float getCurrentValue(struct EventStruct *event, + int & raw_value); + + float applyCalibration(float float_value) const; + +# ifdef ESP32 + static bool useFactoryCalibration(struct EventStruct *event); + +# endif // ifdef ESP32 + +private: + +# ifndef LIMIT_BUILD_SIZE + float applyMultiPointInterpolation(float float_value, + bool force = false) const; +# endif // ifndef LIMIT_BUILD_SIZE + + + // Map the input "point" values to the nearest int. + static void setTwoPointCalibration(struct EventStruct *event, + float adc1, + float adc2, + float out1, + float out2); + +public: + + bool plugin_set_config(struct EventStruct *event, + String & string); + +private: + + OversamplingHelperOverSampling; + + int _calib_adc1 = 0; + int _calib_adc2 = 0; + float _calib_out1 = 0.0f; + float _calib_out2 = 0.0f; + + bool _use2pointCalibration = false; +# ifndef LIMIT_BUILD_SIZE + std::vector_multipoint; + std::vector _binning; + std::vector _binningRange; + bool _useMultipoint = false; +# endif // ifndef LIMIT_BUILD_SIZE + + int _pin_analogRead = -1; + + uint8_t _sampleMode = P002_USE_CURENT_SAMPLE; + + uint8_t _nrDecimals = 0; +# ifndef LIMIT_BUILD_SIZE + uint8_t _nrMultiPointItems = 0; + String _formula; + String _formula_preprocessed; +# endif // ifndef LIMIT_BUILD_SIZE +# ifdef ESP32 + bool _useFactoryCalibration = false; + +# if ESP_IDF_VERSION_MAJOR >= 5 + adc_atten_t _attenuation = ADC_ATTEN_DB_12; +# else // if ESP_IDF_VERSION_MAJOR >= 5 + adc_atten_t _attenuation = ADC_ATTEN_DB_11; +# endif // if ESP_IDF_VERSION_MAJOR >= 5 +# endif // ifdef ESP32 +}; + + +#endif // ifdef USES_P002 +#endif // ifndef PLUGINSTRUCTS_P002_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P004_data_struct.h b/src/src/PluginStructs/P004_data_struct.h index 78f313aa5..3cef30101 100644 --- a/src/src/PluginStructs/P004_data_struct.h +++ b/src/src/PluginStructs/P004_data_struct.h @@ -1,98 +1,104 @@ -#ifndef PLUGINSTRUCTS_P004_DATA_STRUCT_H -#define PLUGINSTRUCTS_P004_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P004 - -# include "../Helpers/Dallas1WireHelper.h" - -struct P004_data_struct : public PluginTaskData_base { - /*********************************************************************************************\ - * Task data struct to simplify taking measurements of upto 4 Dallas DS18b20 (or compatible) - * temperature sensors at once. - * - * Limitations: - * - Use the same GPIO pin - * - Use the same resolution for all sensors of the same task - * - Max 4 sensors queried at the same time - * - * The limit of 4 sensors is determined by the way the settings are stored and it - * is a practical limit to make sure we don't spend too much time in a single call. - * - * Using the same resolution is to make it (a lot) simpler as all sensors then need the - * same measurement time. - * - * If those limitations are not desired, use multiple tasks. - \*********************************************************************************************/ - - // @param pin The GPIO pin used to communicate to the Dallas sensors in this task - // @param res The resolution of the Dallas sensor(s) used in this task - P004_data_struct(taskIndex_t taskIndex, - int8_t pin_rx, - int8_t pin_tx, - uint8_t res, - bool scanOnInit); - virtual ~P004_data_struct() = default; - - void init(); - - bool sensorAddressSet() const; - - // Add extra sensor address - // @param addr The address to add - // @param index The index (0...3) to store this address - void add_addr(const uint8_t addr[], - uint8_t index); - - // Send the start measuremnt command to all set sensors which have a non-zero address - // Their index determines the order in which the sensors receive this command. - bool initiate_read(); - - bool collect_values(); - - // Read temperature from the sensor at given index. - // May return false if the sensor is not present or address is zero. - bool read_temp(float & value, - uint8_t index = 0) const; - - String get_formatted_address(uint8_t index) const; - - unsigned long get_timer() const { - return _timer; - } - - unsigned long get_measurement_start() const { - return _measurementStart; - } - - int8_t get_gpio_rx() const { - return _gpio_rx; - } - - int8_t get_gpio_tx() const { - return _gpio_tx; - } - - bool measurement_active() const; - bool measurement_active(uint8_t index) const; - void set_measurement_inactive(); - - Dallas_SensorData get_sensor_data(uint8_t index) const; - -private: - - // Do not set the _timer to 0, since it may cause issues - // if this object is created (settings edited or task enabled) - // while the node is up some time between 24.9 and 49.7 days. - unsigned long _timer; - unsigned long _measurementStart; - Dallas_SensorData _sensors[VARS_PER_TASK]; - taskIndex_t _taskIndex; - int8_t _gpio_rx; - int8_t _gpio_tx; - uint8_t _res; - bool _scanOnInit; -}; - -#endif // ifdef USES_P004 -#endif // ifndef PLUGINSTRUCTS_P004_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P004_DATA_STRUCT_H +#define PLUGINSTRUCTS_P004_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P004 + +# include "../Helpers/Dallas1WireHelper.h" + +# ifndef P004_FEATURE_GET_CONFIG_VALUE +# define P004_FEATURE_GET_CONFIG_VALUE 1 // Enable by default, +// adds 468 bytes on ESP8266, 944 bytes on ESP32-C6, 490 bytes on ESP32-C3 +// 456 bytes on ESP32 Classic, 468 bytes on ESP32-S3 (ESP32 builds: IDF 5.1) +# endif // ifndef P004_FEATURE_GET_CONFIG_VALUE + +struct P004_data_struct : public PluginTaskData_base { + /*********************************************************************************************\ + * Task data struct to simplify taking measurements of upto 4 Dallas DS18b20 (or compatible) + * temperature sensors at once. + * + * Limitations: + * - Use the same GPIO pin + * - Use the same resolution for all sensors of the same task + * - Max 4 sensors queried at the same time + * + * The limit of 4 sensors is determined by the way the settings are stored and it + * is a practical limit to make sure we don't spend too much time in a single call. + * + * Using the same resolution is to make it (a lot) simpler as all sensors then need the + * same measurement time. + * + * If those limitations are not desired, use multiple tasks. + \*********************************************************************************************/ + + // @param pin The GPIO pin used to communicate to the Dallas sensors in this task + // @param res The resolution of the Dallas sensor(s) used in this task + P004_data_struct(taskIndex_t taskIndex, + int8_t pin_rx, + int8_t pin_tx, + uint8_t res, + bool scanOnInit); + virtual ~P004_data_struct() = default; + + void init(); + + bool sensorAddressSet() const; + + // Add extra sensor address + // @param addr The address to add + // @param index The index (0...3) to store this address + void add_addr(const uint8_t addr[], + uint8_t index); + + // Send the start measuremnt command to all set sensors which have a non-zero address + // Their index determines the order in which the sensors receive this command. + bool initiate_read(); + + bool collect_values(); + + // Read temperature from the sensor at given index. + // May return false if the sensor is not present or address is zero. + bool read_temp(float & value, + uint8_t index = 0) const; + + String get_formatted_address(uint8_t index) const; + + unsigned long get_timer() const { + return _timer; + } + + unsigned long get_measurement_start() const { + return _measurementStart; + } + + int8_t get_gpio_rx() const { + return _gpio_rx; + } + + int8_t get_gpio_tx() const { + return _gpio_tx; + } + + bool measurement_active() const; + bool measurement_active(uint8_t index) const; + void set_measurement_inactive(); + + Dallas_SensorData get_sensor_data(uint8_t index) const; + +private: + + // Do not set the _timer to 0, since it may cause issues + // if this object is created (settings edited or task enabled) + // while the node is up some time between 24.9 and 49.7 days. + unsigned long _timer; + unsigned long _measurementStart; + Dallas_SensorData _sensors[VARS_PER_TASK]; + taskIndex_t _taskIndex; + int8_t _gpio_rx; + int8_t _gpio_tx; + uint8_t _res; + bool _scanOnInit; +}; + +#endif // ifdef USES_P004 +#endif // ifndef PLUGINSTRUCTS_P004_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P005_data_struct.cpp b/src/src/PluginStructs/P005_data_struct.cpp index a4c915820..4429c69ed 100644 --- a/src/src/PluginStructs/P005_data_struct.cpp +++ b/src/src/PluginStructs/P005_data_struct.cpp @@ -1,361 +1,361 @@ -#include "../PluginStructs/P005_data_struct.h" - -#ifdef USES_P005 - - -// DEBUG code using logic analyzer for timings -// #define DEBUG_LOGIC_ANALYZER_PIN 27 - - -// Macros to perform direct access on GPIOs -// Macros written by Paul Stoffregen -// See: https://github.com/PaulStoffregen/OneWire/blob/master/util/ -# include - -enum struct P005_logNr { - P005_error_no_reading, - P005_error_protocol_timeout, - P005_error_checksum_error, - P005_error_invalid_NAN_reading, - P005_info_temperature, - P005_info_humidity -}; - -const __FlashStringHelper* P005_logString(P005_logNr logNr) { - switch (logNr) { - case P005_logNr::P005_error_no_reading: return F("No Reading"); - case P005_logNr::P005_error_protocol_timeout: return F("Protocol Timeout"); - case P005_logNr::P005_error_checksum_error: return F("Checksum Error"); - case P005_logNr::P005_error_invalid_NAN_reading: return F("Invalid NAN reading"); - case P005_logNr::P005_info_temperature: return F("Temperature: "); - case P005_logNr::P005_info_humidity: return F("Humidity: "); - } - return F(""); -} - -/*********************************************************************************************\ -* DHT sub to log an error -\*********************************************************************************************/ -void P005_log(struct EventStruct *event, P005_logNr logNr) -{ - bool isError = true; - - switch (logNr) { - case P005_logNr::P005_info_temperature: - case P005_logNr::P005_info_humidity: - isError = false; - break; - - default: - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - break; - } - - if (loglevelActiveFor(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO)) { - String text; - text = F("DHT : "); - text += P005_logString(logNr); - - if (logNr == P005_logNr::P005_info_temperature) { - text += formatUserVarNoCheck(event->TaskIndex, 0); - } - else if (logNr == P005_logNr::P005_info_humidity) { - text += formatUserVarNoCheck(event->TaskIndex, 1); - } - addLogMove(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO, text); - } -} - -P005_data_struct::P005_data_struct(struct EventStruct *event) { - SensorModel = PCONFIG(0); - DHT_pin = CONFIG_PIN1; -} - -/*********************************************************************************************\ -* DHT sub to wait until a pin is in a certain state -\*********************************************************************************************/ -bool P005_data_struct::waitState(uint32_t state) -{ - const uint64_t timeout = getMicros64() + 100; - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - // DEBUG code using logic analyzer for timings - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif - - while (DIRECT_pinRead(DHT_pin) != state) - { - if (usecTimeOutReached(timeout)) { return false; } - } - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif - return true; -} - -/*********************************************************************************************\ -* Perform the actual reading + interpreting of data. -\*********************************************************************************************/ -bool P005_data_struct::readDHT(struct EventStruct *event) { - // Call the "slow" function to make sure the pin is in a defined state. - // Apparently the pull-up state may not always be in a well known state - // With the direct pinmode calls we don't set the pull-up or -down resistors. - pinMode(DHT_pin, INPUT_PULLUP); - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - // DEBUG code using logic analyzer for timings - DIRECT_PINMODE_OUTPUT(DEBUG_LOGIC_ANALYZER_PIN); - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif - - // To begin asking the DHT22 for humidity and temperature data, - // Start sequence to get data from a DHTxx sensor: - // Pin must be a logic 0 (low) for at least 500 microseconds (DHT22, others may need different timing) - // followed by a logic 1 (high). - DIRECT_PINMODE_OUTPUT(DHT_pin); - DIRECT_pinWrite(DHT_pin, 0); // Pull low - - switch (SensorModel) { - case P005_DHT11: delay(19); break; // minimum 18ms - case P005_DHT22: delay(2); break; // minimum 1ms - case P005_DHT12: delay(200); break; // minimum 200ms - case P005_AM2301: delayMicroseconds(900); break; - case P005_SI7021: delayMicroseconds(500); break; - case P005_MS01: delayMicroseconds(450); break; - } - - { -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif - - DIRECT_PINMODE_INPUT(DHT_pin); - // pinMode(DHT_pin, INPUT_PULLUP); // Way too slow, takes upto 227 usec - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif - } - - bool readingAborted = false; - uint8_t dht_dat[5] = { 0 }; - - uint8_t dht_byte = 0; - uint32_t avg_low_total = 0; - - - // Response from DHTxx: (N = 80 usec for DHT22) - // Low for N usec - // Hight for N usec - // Low for 50 usec - bool receive_start; - - uint8_t timings[16] = { 0 }; - - - ISR_noInterrupts(); - receive_start = waitState(0) && waitState(1) && waitState(0); - -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif - - if (receive_start) { - // We know we're now at a "low" state. - uint64_t last_micros = getMicros64(); - uint64_t prev_edge = last_micros; - - for (dht_byte = 0; dht_byte < 5 && !readingAborted; ++dht_byte) - { - // Start reading next byte -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); -#endif - for (uint8_t t = 0; t < 16 && !readingAborted; ++t) { - // "even" index = "low" duration - // "odd" index = "high" duration - const uint32_t current_state = (t & 1); - - // Wait till pin state has changed, or timeout. - while (DIRECT_pinRead(DHT_pin) == current_state && !readingAborted) - { - // Keep track of last microsecond the state had not yet changed. - // This way we are less dependent on any jitter caused by - // the delay call or rise times of the voltage on the pin. - last_micros = getMicros64(); - - if (timeDiff64(prev_edge, last_micros) > 100) { - readingAborted = true; - } - } - - if (!readingAborted) { - // We know it is less than 100 usec, so it does fit in the uint8_t timings array. - timings[t] = usecPassedSince(prev_edge); - prev_edge = last_micros; - } else { - timings[t] = 255; - } - } -#ifdef DEBUG_LOGIC_ANALYZER_PIN - DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); -#endif - - if (!readingAborted) { - // Evaluate the timings - // timings on even indices represent "duration low" - // timings on odd indices represent "duration high" - // - // Timing for a single bit: - // Logic "1": 50 usec low, 70 usec high - // Logic "0": 50 usec low, 26 usec high - // There is a significant difference between the "high" state durations - // Thus "high duration" > "avg_low duration" means it is an "1". - // - // By taking the average low duration, we get rid of - // critical timing differences among modules and - // environmental effects which may change these timings. - // It is all about the relative timings. - uint32_t avg_low = 0; - - // Don't take the 1st "low" period into account for computing avg_low - // as there might be an extra wait between bytes. - // Just to be sure as it is not clear from the documentation if all models act the same. - for (uint8_t t = 2; t < 16; t += 2) { - avg_low += timings[t]; - } - avg_low /= 7; - avg_low_total += avg_low; - - dht_dat[dht_byte] = 0; - - for (uint8_t bit = 0; bit < 8; ++bit) { - if (timings[2 * bit + 1] > avg_low) { - dht_dat[dht_byte] |= (1 << (7 - bit)); - } - } - } - } - } - ISR_interrupts(); - - - if (!receive_start) { - P005_log(event, P005_logNr::P005_error_no_reading); - return false; - } - - # ifndef BUILD_NO_DEBUG - - if (dht_byte != 0) { - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("DHT : "); - log += F("Avg Low: "); - log += static_cast(avg_low_total) / dht_byte; - log += F(" usec "); - log += dht_byte; - log += F(" bytes:"); - for (int i = 0; i < dht_byte; ++i) { - log += ' '; - log += formatToHex_no_prefix(dht_dat[i], 2); - } - log += F(" timings:"); - for (int i = 0; i < 16; ++i) { - log += ' '; - log += timings[i]; - } - addLogMove(LOG_LEVEL_DEBUG, log); - } - } - # endif // ifndef BUILD_NO_DEBUG - - - if (readingAborted) { - P005_log(event, P005_logNr::P005_error_protocol_timeout); - return false; - } - - // Checksum calculation is a Rollover Checksum by design! - uint8_t dht_check_sum = (dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]) & 0xFF; // check check_sum - - if (dht_dat[4] != dht_check_sum) - { - P005_log(event, P005_logNr::P005_error_checksum_error); - return false; - } - - float temperature = NAN; - float humidity = NAN; - - switch (SensorModel) { - case P005_DHT11: - case P005_DHT12: - temperature = float(dht_dat[2] * 10 + (dht_dat[3] & 0x7f)) / 10.0f; // Temperature - - if (dht_dat[3] & 0x80) { temperature = -temperature; } // Negative temperature - humidity = float(dht_dat[0] * 10 + dht_dat[1]) / 10.0f; // Humidity - break; - case P005_DHT22: - case P005_AM2301: - case P005_SI7021: - - if (dht_dat[2] & 0x80) { // negative temperature - temperature = -0.1f * word(dht_dat[2] & 0x7F, dht_dat[3]); - } - else { - temperature = 0.1f * word(dht_dat[2], dht_dat[3]); - } - humidity = 0.1f * word(dht_dat[0], dht_dat[1]); // Humidity - break; - - case P005_MS01: - { - // Conversion from Tasmota: - // https://github.com/arendst/Tasmota/blob/0ea36d996c2b8b519ae5aa127f1a5fea354706af/tasmota/tasmota_xsns_sensor/xsns_06_dht_v7.ino#L297 - - - const int16_t voltage = ((dht_dat[0] << 8) | dht_dat[1]); - - // Rough approximate of soil moisture % (based on values observed in the eWeLink app) - // Observed values are available here: https://gist.github.com/minovap/654cdcd8bc37bb0d2ff338f8d144a509 - - - // Info on capacitive soil moisture sensors: - // https://makersportal.com/blog/2020/5/26/capacitive-soil-moisture-calibration-with-arduino - - if (voltage < 15037) { - const float x = voltage - 15200; - humidity = - powf(0.0024f * x, 3) - 0.0004f * x + 20.1f; - } - else if (voltage < 22300) { - humidity = - 0.00069f * voltage + 30.6f; - } - else { - const float x = voltage - 22800; - humidity = - powf(0.00046f * x, 3) - 0.0004f * x + 15; - } - - if (definitelyLessThan(humidity, 0.0f)) { - humidity = 0.0f; - } - - temperature = voltage; - break; - } - } - - if (isnan(temperature) || isnan(humidity)) { - P005_log(event, P005_logNr::P005_error_invalid_NAN_reading); - return false; - } - - UserVar.setFloat(event->TaskIndex, 0, temperature); - UserVar.setFloat(event->TaskIndex, 1, humidity); - P005_log(event, P005_logNr::P005_info_temperature); - P005_log(event, P005_logNr::P005_info_humidity); - return true; -} - -#endif // ifdef USES_P005 +#include "../PluginStructs/P005_data_struct.h" + +#ifdef USES_P005 + + +// DEBUG code using logic analyzer for timings +// #define DEBUG_LOGIC_ANALYZER_PIN 27 + + +// Macros to perform direct access on GPIOs +// Macros written by Paul Stoffregen +// See: https://github.com/PaulStoffregen/OneWire/blob/master/util/ +# include + +enum struct P005_logNr { + P005_error_no_reading, + P005_error_protocol_timeout, + P005_error_checksum_error, + P005_error_invalid_NAN_reading, + P005_info_temperature, + P005_info_humidity +}; + +const __FlashStringHelper* P005_logString(P005_logNr logNr) { + switch (logNr) { + case P005_logNr::P005_error_no_reading: return F("No Reading"); + case P005_logNr::P005_error_protocol_timeout: return F("Protocol Timeout"); + case P005_logNr::P005_error_checksum_error: return F("Checksum Error"); + case P005_logNr::P005_error_invalid_NAN_reading: return F("Invalid NAN reading"); + case P005_logNr::P005_info_temperature: return F("Temperature: "); + case P005_logNr::P005_info_humidity: return F("Humidity: "); + } + return F(""); +} + +/*********************************************************************************************\ +* DHT sub to log an error +\*********************************************************************************************/ +void P005_log(struct EventStruct *event, P005_logNr logNr) +{ + bool isError = true; + + switch (logNr) { + case P005_logNr::P005_info_temperature: + case P005_logNr::P005_info_humidity: + isError = false; + break; + + default: + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + break; + } + + if (loglevelActiveFor(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO)) { + String text = concat(F("DHT : "), + P005_logString(logNr)); + + if (logNr == P005_logNr::P005_info_temperature) { + text += formatUserVarNoCheck(event, 0); + } + else if (logNr == P005_logNr::P005_info_humidity) { + text += formatUserVarNoCheck(event, 1); + } + addLogMove(isError ? LOG_LEVEL_ERROR : LOG_LEVEL_INFO, text); + } +} + +P005_data_struct::P005_data_struct(struct EventStruct *event) { + SensorModel = PCONFIG(0); + DHT_pin = CONFIG_PIN1; +} + +/*********************************************************************************************\ +* DHT sub to wait until a pin is in a certain state +\*********************************************************************************************/ +bool P005_data_struct::waitState(uint32_t state) +{ + const uint64_t timeout = getMicros64() + 100; + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + while (DIRECT_pinRead(DHT_pin) != state) + { + if (usecTimeOutReached(timeout)) { return false; } + } + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + return true; +} + +/*********************************************************************************************\ +* Perform the actual reading + interpreting of data. +\*********************************************************************************************/ +bool P005_data_struct::readDHT(struct EventStruct *event) { + // Call the "slow" function to make sure the pin is in a defined state. + // Apparently the pull-up state may not always be in a well known state + // With the direct pinmode calls we don't set the pull-up or -down resistors. + pinMode(DHT_pin, INPUT_PULLUP); + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + + // DEBUG code using logic analyzer for timings + DIRECT_PINMODE_OUTPUT(DEBUG_LOGIC_ANALYZER_PIN); + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + // To begin asking the DHT22 for humidity and temperature data, + // Start sequence to get data from a DHTxx sensor: + // Pin must be a logic 0 (low) for at least 500 microseconds (DHT22, others may need different timing) + // followed by a logic 1 (high). + DIRECT_PINMODE_OUTPUT(DHT_pin); + DIRECT_pinWrite(DHT_pin, 0); // Pull low + + switch (SensorModel) { + case P005_DHT11: delay(19); break; // minimum 18ms + case P005_DHT22: delay(2); break; // minimum 1ms + case P005_DHT12: delay(200); break; // minimum 200ms + case P005_AM2301: delayMicroseconds(900); break; + case P005_SI7021: delayMicroseconds(500); break; + case P005_MS01: delayMicroseconds(450); break; + } + + { +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + DIRECT_PINMODE_INPUT(DHT_pin); + + // pinMode(DHT_pin, INPUT_PULLUP); // Way too slow, takes upto 227 usec + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + } + + bool readingAborted = false; + uint8_t dht_dat[5] = { 0 }; + + uint8_t dht_byte = 0; + uint32_t avg_low_total = 0; + + + // Response from DHTxx: (N = 80 usec for DHT22) + // Low for N usec + // Hight for N usec + // Low for 50 usec + bool receive_start; + + uint8_t timings[16] = { 0 }; + + + ISR_noInterrupts(); + receive_start = waitState(0) && waitState(1) && waitState(0); + +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + if (receive_start) { + // We know we're now at a "low" state. + uint64_t last_micros = getMicros64(); + uint64_t prev_edge = last_micros; + + for (dht_byte = 0; dht_byte < 5 && !readingAborted; ++dht_byte) + { + // Start reading next byte +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 1); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + for (uint8_t t = 0; t < 16 && !readingAborted; ++t) { + // "even" index = "low" duration + // "odd" index = "high" duration + const uint32_t current_state = (t & 1); + + // Wait till pin state has changed, or timeout. + while (DIRECT_pinRead(DHT_pin) == current_state && !readingAborted) + { + // Keep track of last microsecond the state had not yet changed. + // This way we are less dependent on any jitter caused by + // the delay call or rise times of the voltage on the pin. + last_micros = getMicros64(); + + if (timeDiff64(prev_edge, last_micros) > 100) { + readingAborted = true; + } + } + + if (!readingAborted) { + // We know it is less than 100 usec, so it does fit in the uint8_t timings array. + timings[t] = usecPassedSince(prev_edge); + prev_edge = last_micros; + } else { + timings[t] = 255; + } + } +# ifdef DEBUG_LOGIC_ANALYZER_PIN + DIRECT_pinWrite(DEBUG_LOGIC_ANALYZER_PIN, 0); +# endif // ifdef DEBUG_LOGIC_ANALYZER_PIN + + if (!readingAborted) { + // Evaluate the timings + // timings on even indices represent "duration low" + // timings on odd indices represent "duration high" + // + // Timing for a single bit: + // Logic "1": 50 usec low, 70 usec high + // Logic "0": 50 usec low, 26 usec high + // There is a significant difference between the "high" state durations + // Thus "high duration" > "avg_low duration" means it is an "1". + // + // By taking the average low duration, we get rid of + // critical timing differences among modules and + // environmental effects which may change these timings. + // It is all about the relative timings. + uint32_t avg_low = 0; + + // Don't take the 1st "low" period into account for computing avg_low + // as there might be an extra wait between bytes. + // Just to be sure as it is not clear from the documentation if all models act the same. + for (uint8_t t = 2; t < 16; t += 2) { + avg_low += timings[t]; + } + avg_low /= 7; + avg_low_total += avg_low; + + dht_dat[dht_byte] = 0; + + for (uint8_t bit = 0; bit < 8; ++bit) { + if (timings[2 * bit + 1] > avg_low) { + dht_dat[dht_byte] |= (1 << (7 - bit)); + } + } + } + } + } + ISR_interrupts(); + + + if (!receive_start) { + P005_log(event, P005_logNr::P005_error_no_reading); + return false; + } + + # ifndef BUILD_NO_DEBUG + + if (dht_byte != 0) { + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = strformat(F("DHT : Avg Low: %.2f usec %d bytes:"), static_cast(avg_low_total) / dht_byte, dht_byte); + + for (int i = 0; i < dht_byte; ++i) { + log += ' '; + log += formatToHex_no_prefix(dht_dat[i], 2); + } + log += F(" timings:"); + + for (int i = 0; i < 16; ++i) { + log += ' '; + log += timings[i]; + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } + # endif // ifndef BUILD_NO_DEBUG + + + if (readingAborted) { + P005_log(event, P005_logNr::P005_error_protocol_timeout); + return false; + } + + // Checksum calculation is a Rollover Checksum by design! + uint8_t dht_check_sum = (dht_dat[0] + dht_dat[1] + dht_dat[2] + dht_dat[3]) & 0xFF; // check check_sum + + if (dht_dat[4] != dht_check_sum) + { + P005_log(event, P005_logNr::P005_error_checksum_error); + return false; + } + + float temperature = NAN; + float humidity = NAN; + + switch (SensorModel) { + case P005_DHT11: + case P005_DHT12: + temperature = float(dht_dat[2] * 10 + (dht_dat[3] & 0x7f)) / 10.0f; // Temperature + + if (dht_dat[3] & 0x80) { temperature = -temperature; } // Negative temperature + humidity = float(dht_dat[0] * 10 + dht_dat[1]) / 10.0f; // Humidity + break; + case P005_DHT22: + case P005_AM2301: + case P005_SI7021: + + if (dht_dat[2] & 0x80) { // negative temperature + temperature = -0.1f * word(dht_dat[2] & 0x7F, dht_dat[3]); + } + else { + temperature = 0.1f * word(dht_dat[2], dht_dat[3]); + } + humidity = 0.1f * word(dht_dat[0], dht_dat[1]); // Humidity + break; + + case P005_MS01: + { + // Conversion from Tasmota: + // https://github.com/arendst/Tasmota/blob/0ea36d996c2b8b519ae5aa127f1a5fea354706af/tasmota/tasmota_xsns_sensor/xsns_06_dht_v7.ino#L297 + + + const int16_t voltage = ((dht_dat[0] << 8) | dht_dat[1]); + + // Rough approximate of soil moisture % (based on values observed in the eWeLink app) + // Observed values are available here: https://gist.github.com/minovap/654cdcd8bc37bb0d2ff338f8d144a509 + + + // Info on capacitive soil moisture sensors: + // https://makersportal.com/blog/2020/5/26/capacitive-soil-moisture-calibration-with-arduino + + if (voltage < 15037) { + const float x = voltage - 15200; + humidity = -powf(0.0024f * x, 3) - 0.0004f * x + 20.1f; + } + else if (voltage < 22300) { + humidity = -0.00069f * voltage + 30.6f; + } + else { + const float x = voltage - 22800; + humidity = -powf(0.00046f * x, 3) - 0.0004f * x + 15; + } + + if (definitelyLessThan(humidity, 0.0f)) { + humidity = 0.0f; + } + + temperature = voltage; + break; + } + } + + if (isnan(temperature) || isnan(humidity)) { + P005_log(event, P005_logNr::P005_error_invalid_NAN_reading); + return false; + } + + UserVar.setFloat(event->TaskIndex, 0, temperature); + UserVar.setFloat(event->TaskIndex, 1, humidity); + P005_log(event, P005_logNr::P005_info_temperature); + P005_log(event, P005_logNr::P005_info_humidity); + return true; +} + +#endif // ifdef USES_P005 diff --git a/src/src/PluginStructs/P008_data_struct.cpp b/src/src/PluginStructs/P008_data_struct.cpp index a58def65b..04105abeb 100644 --- a/src/src/PluginStructs/P008_data_struct.cpp +++ b/src/src/PluginStructs/P008_data_struct.cpp @@ -13,7 +13,7 @@ uint64_t P008_data_struct::castHexAsDec(uint64_t hexValue) { uint64_t factor = 1; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; ++i) { digit = (hexValue & 0x0000000F); if (digit > 10) { @@ -146,13 +146,11 @@ bool P008_data_struct::plugin_once_a_second(struct EventStruct *event) { } else { log += F("Old Tag: "); } - log += (unsigned long)keyBuffer; - log += F(", 0x"); - log += ull2String(keyBuffer, 16); - log += F(", mask: 0x"); - log += ull2String(keyMask, 16); - log += F(" Bits: "); - log += bitCount; + log += strformat(F("%s, 0x%s, mask: 0x%s Bits: %d"), + ull2String(keyBuffer).c_str(), + ull2String(keyBuffer, 16).c_str(), + ull2String(keyMask, 16).c_str(), + static_cast(bitCount)); addLogMove(LOG_LEVEL_INFO, log); } diff --git a/src/src/PluginStructs/P012_data_struct.cpp b/src/src/PluginStructs/P012_data_struct.cpp index 0d72e4f67..2d7415ebf 100644 --- a/src/src/PluginStructs/P012_data_struct.cpp +++ b/src/src/PluginStructs/P012_data_struct.cpp @@ -1,281 +1,280 @@ -#include "../PluginStructs/P012_data_struct.h" - -#ifdef USES_P012 - -// Needed also here for PlatformIO's library finder as the .h file -// is in a directory which is excluded in the src_filter -# include - - -P012_data_struct::P012_data_struct(uint8_t addr, - uint8_t lcd_size, - uint8_t mode, - uint8_t timer) : - lcd(addr, 20, 4), - Plugin_012_mode(mode), - displayTimer(timer) -{ - switch (lcd_size) - { - case 1: - Plugin_012_rows = 2; - Plugin_012_cols = 16; - break; - case 2: - Plugin_012_rows = 4; - Plugin_012_cols = 20; - break; - - default: - Plugin_012_rows = 2; - Plugin_012_cols = 16; - break; - } -} - -void P012_data_struct::init() { - // Setup LCD display - lcd.init(); // initialize the lcd - lcd.backlight(); - lcd.print(F("ESP Easy")); - createCustomChars(); -} - -void P012_data_struct::setBacklightTimer(uint8_t timer) { - displayTimer = timer; - lcd.backlight(); -} - -void P012_data_struct::checkTimer() { - if (displayTimer > 0) - { - displayTimer--; - - if (displayTimer == 0) { - lcd.noBacklight(); - } - } -} - -void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { - // clear line before writing new string - if (Plugin_012_mode == 2) { - lcd.setCursor(col, row); - - for (uint8_t i = col; i < Plugin_012_cols; i++) { - lcd.print(' '); - } - } - - lcd.setCursor(col, row); - - if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { - lcd.setCursor(col, row); - - for (uint8_t i = 0; i < Plugin_012_cols - col; i++) { - if (text[i]) { - lcd.print(text[i]); - } - } - } - - // message exceeding cols will continue to next line - else { - // Fix Weird (native) lcd display behaviour that split long string into row 1,3,2,4, instead of 1,2,3,4 - bool stillProcessing = 1; - uint8_t charCount = 1; - - while (stillProcessing) { - if (++col > Plugin_012_cols) { // have we printed 20 characters yet (+1 for the logic) - row += 1; - lcd.setCursor(0, row); // move cursor down - col = 1; - } - - // dont print if "lower" than the lcd - if (row < Plugin_012_rows) { - lcd.print(text[charCount - 1]); - } - - if (!text[charCount]) { // no more chars to process? - stillProcessing = 0; - } - charCount += 1; - } - - // lcd.print(text.c_str()); - // end fix - } -} - -// Perform some specific changes for LCD display -// https://www.letscontrolit.com/forum/viewtopic.php?t=2368 -String P012_data_struct::P012_parseTemplate(String& tmpString, uint8_t lineSize) { - String result = parseTemplate_padded(tmpString, lineSize); - const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol - const char degree_lcd[2] = { 0xdf, 0 }; // P012_LCD degree symbol - - result.replace(degree, degree_lcd); - - char unicodePrefix = 0xc4; - -# ifdef USES_P012_POLISH_CHARS - - if (result.indexOf(unicodePrefix) != -1) { - const char znak_a_uni[3] = { 0xc4, 0x85, 0 }; // Unicode znak a - const char znak_a_lcd[2] = { 0x05, 0 }; // P012_LCD znak a - result.replace(znak_a_uni, znak_a_lcd); - - const char znak_A_uni[3] = { 0xc4, 0x84, 0 }; // Unicode znak A - result.replace(znak_A_uni, znak_a_lcd); - - const char znak_c_uni[3] = { 0xc4, 0x87, 0 }; // Unicode znak c - const char znak_c_lcd[2] = { 0x03, 0 }; // P012_LCD znak c - result.replace(znak_c_uni, znak_c_lcd); - - const char znak_C_uni[3] = { 0xc4, 0x86, 0 }; // Unicode znak C - result.replace(znak_C_uni, znak_c_lcd); - - const char znak_e_uni[3] = { 0xc4, 0x99, 0 }; // Unicode znak e - const char znak_e_lcd[2] = { 0x02, 0 }; // P012_LCD znak e - result.replace(znak_e_uni, znak_e_lcd); - - const char znak_E_uni[3] = { 0xc4, 0x98, 0 }; // Unicode znak E - result.replace(znak_E_uni, znak_e_lcd); - } - - unicodePrefix = 0xc5; - - if (result.indexOf(unicodePrefix) != -1) { - const char znak_l_uni[3] = { 0xc5, 0x82, 0 }; // Unicode znak l - const char znak_l_lcd[2] = { 0x01, 0 }; // P012_LCD znak l - result.replace(znak_l_uni, znak_l_lcd); - - const char znak_L_uni[3] = { 0xc5, 0x81, 0 }; // Unicode znak L - result.replace(znak_L_uni, znak_l_lcd); - - const char znak_n_uni[3] = { 0xc5, 0x84, 0 }; // Unicode znak n - const char znak_n_lcd[2] = { 0x04, 0 }; // P012_LCD znak n - result.replace(znak_n_uni, znak_n_lcd); - - const char znak_N_uni[3] = { 0xc5, 0x83, 0 }; // Unicode znak N - result.replace(znak_N_uni, znak_n_lcd); - - const char znak_s_uni[3] = { 0xc5, 0x9b, 0 }; // Unicode znak s - const char znak_s_lcd[2] = { 0x06, 0 }; // P012_LCD znak s - result.replace(znak_s_uni, znak_s_lcd); - - const char znak_S_uni[3] = { 0xc5, 0x9a, 0 }; // Unicode znak S - result.replace(znak_S_uni, znak_s_lcd); - - const char znak_z1_uni[3] = { 0xc5, 0xba, 0 }; // Unicode znak z z kreska - const char znak_z1_lcd[2] = { 0x07, 0 }; // P012_LCD znak z z kropka - result.replace(znak_z1_uni, znak_z1_lcd); - - const char znak_Z1_uni[3] = { 0xc5, 0xb9, 0 }; // Unicode znak Z z kreska - result.replace(znak_Z1_uni, znak_z1_lcd); - - const char znak_z2_uni[3] = { 0xc5, 0xbc, 0 }; // Unicode znak z z kropka - const char znak_z2_lcd[2] = { 0x07, 0 }; // P012_LCD znak z z kropka - result.replace(znak_z2_uni, znak_z2_lcd); - - const char znak_Z2_uni[3] = { 0xc5, 0xbb, 0 }; // Unicode znak Z z kropka - result.replace(znak_Z2_uni, znak_z2_lcd); - } - - unicodePrefix = 0xc3; - - if (result.indexOf(unicodePrefix) != -1) { - const char znak_o_uni[3] = { 0xc3, 0xB3, 0 }; // Unicode znak o - const char znak_o_lcd[2] = { 0x08, 0 }; // P012_LCD znak o - result.replace(znak_o_uni, znak_o_lcd); - - const char znak_O_uni[3] = { 0xc3, 0x93, 0 }; // Unicode znak O - result.replace(znak_O_uni, znak_o_lcd); - } -# endif // USES_P012_POLISH_CHARS - - unicodePrefix = 0xc3; - - if (result.indexOf(unicodePrefix) != -1) { - // See: https://github.com/letscontrolit/ESPEasy/issues/2081 - - const char umlautAE_uni[3] = { 0xc3, 0x84, 0 }; // Unicode Umlaute AE - const char umlautAE_lcd[2] = { 0xe1, 0 }; // P012_LCD Umlaute - result.replace(umlautAE_uni, umlautAE_lcd); - - const char umlaut_ae_uni[3] = { 0xc3, 0xa4, 0 }; // Unicode Umlaute ae - result.replace(umlaut_ae_uni, umlautAE_lcd); - - const char umlautOE_uni[3] = { 0xc3, 0x96, 0 }; // Unicode Umlaute OE - const char umlautOE_lcd[2] = { 0xef, 0 }; // P012_LCD Umlaute - result.replace(umlautOE_uni, umlautOE_lcd); - - const char umlaut_oe_uni[3] = { 0xc3, 0xb6, 0 }; // Unicode Umlaute oe - result.replace(umlaut_oe_uni, umlautOE_lcd); - - const char umlautUE_uni[3] = { 0xc3, 0x9c, 0 }; // Unicode Umlaute UE - const char umlautUE_lcd[2] = { 0xf5, 0 }; // P012_LCD Umlaute - result.replace(umlautUE_uni, umlautUE_lcd); - - const char umlaut_ue_uni[3] = { 0xc3, 0xbc, 0 }; // Unicode Umlaute ue - result.replace(umlaut_ue_uni, umlautUE_lcd); - - const char umlaut_sz_uni[3] = { 0xc3, 0x9f, 0 }; // Unicode Umlaute sz - const char umlaut_sz_lcd[2] = { 0xe2, 0 }; // P012_LCD Umlaute - result.replace(umlaut_sz_uni, umlaut_sz_lcd); - } - return result; -} - -void P012_data_struct::createCustomChars() { -# ifdef USES_P012_POLISH_CHARS - - /* - static const char LETTER_null[8] PROGMEM = { // spacja - 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 - }; - */ - static const char LETTER_a[8] PROGMEM = { // a - 0b00000, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111, 0b00010 - }; - static const char LETTER_c[8] PROGMEM = { // c - 0b00010, 0b00100, 0b01110, 0b10000, 0b10000, 0b10001, 0b01110, 0b00000 - }; - static const char LETTER_e[8] PROGMEM = { // e - 0b00000, 0b00000, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110, 0b00010 - }; - static const char LETTER_l[8] PROGMEM = { // l - 0b01100, 0b00100, 0b00101, 0b00110, 0b01100, 0b00100, 0b01110, 0b00000 - }; - static const char LETTER_n[8] PROGMEM = { // n - 0b00010, 0b00100, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001, 0b00000 - }; - static const char LETTER_o[8] PROGMEM = { // o - 0b00010, 0b00100, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000 - }; - static const char LETTER_s[8] PROGMEM = { // s - 0b00010, 0b00100, 0b01110, 0b10000, 0b01110, 0b00001, 0b11110, 0b00000 - }; - - /* - static const char LETTER_z1[8] PROGMEM = { // z z kreska - 0b00010, 0b00100, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111, 0b00000 - }; - */ - static const char LETTER_z2[8] PROGMEM = { // z z kropka - 0b00100, 0b00000, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111, 0b00000 - }; - lcd.createChar(0, LETTER_o); // probably defected memory cell - lcd.createChar(1, LETTER_l); - lcd.createChar(2, LETTER_e); - lcd.createChar(3, LETTER_c); - lcd.createChar(4, LETTER_n); - lcd.createChar(5, LETTER_a); - lcd.createChar(6, LETTER_s); - lcd.createChar(7, LETTER_z2); - lcd.createChar(8, LETTER_o); -# endif // ifdef USES_P012_POLISH_CHARS -} - -#endif // ifdef USES_P012 +#include "../PluginStructs/P012_data_struct.h" + +#ifdef USES_P012 + +// Needed also here for PlatformIO's library finder as the .h file +// is in a directory which is excluded in the src_filter +# include + + +P012_data_struct::P012_data_struct(uint8_t addr, + uint8_t lcd_size, + uint8_t mode, + uint8_t timer) : + lcd(addr, 20, 4), + Plugin_012_mode(mode), + displayTimer(timer) +{ + switch (lcd_size) + { + case 1: + Plugin_012_rows = 2; + Plugin_012_cols = 16; + break; + case 2: + Plugin_012_rows = 4; + Plugin_012_cols = 20; + break; + + default: + Plugin_012_rows = 2; + Plugin_012_cols = 16; + break; + } +} + +void P012_data_struct::init() { + // Setup LCD display + lcd.init(); // initialize the lcd + lcd.backlight(); + lcd.print(F("ESP Easy")); + createCustomChars(); +} + +void P012_data_struct::setBacklightTimer(uint8_t timer) { + displayTimer = timer; + lcd.backlight(); +} + +void P012_data_struct::checkTimer() { + if (displayTimer > 0) + { + displayTimer--; + + if (displayTimer == 0) { + lcd.noBacklight(); + } + } +} + +void P012_data_struct::lcdWrite(const String& text, uint8_t col, uint8_t row) { + // clear line before writing new string + if (Plugin_012_mode == 2) { + lcd.setCursor(col, row); + + for (uint8_t i = col; i < Plugin_012_cols; i++) { + lcd.print(' '); + } + } + + if (row == 0) { splashState = P012_splashState_e::SplashCleared; } // Reset splashState + lcd.setCursor(col, row); + + if ((Plugin_012_mode == 1) || (Plugin_012_mode == 2)) { + for (uint8_t i = 0; i < Plugin_012_cols - col; i++) { + if (text[i]) { + lcd.print(text[i]); + } + } + } + + // message exceeding cols will continue to next line + else { + // Fix Weird (native) lcd display behaviour that split long string into row 1,3,2,4, instead of 1,2,3,4 + bool stillProcessing = 1; + uint8_t charCount = 1; + + while (stillProcessing) { + if (++col > Plugin_012_cols) { // have we printed 20 characters yet (+1 for the logic) + row += 1; + lcd.setCursor(0, row); // move cursor down + col = 1; + } + + // dont print if "lower" than the lcd + if (row < Plugin_012_rows) { + lcd.print(text[charCount - 1]); + } + + if (!text[charCount]) { // no more chars to process? + stillProcessing = 0; + } + charCount += 1; + } + + // lcd.print(text.c_str()); + // end fix + } +} + +// Perform some specific changes for LCD display +// https://www.letscontrolit.com/forum/viewtopic.php?t=2368 +String P012_data_struct::P012_parseTemplate(String& tmpString, uint8_t lineSize) { + String result = parseTemplate_padded(tmpString, lineSize); + const char degree[3] = { 0xc2, 0xb0, 0 }; // Unicode degree symbol + const char degree_lcd[2] = { 0xdf, 0 }; // P012_LCD degree symbol + + result.replace(degree, degree_lcd); + + char unicodePrefix = 0xc4; + +# ifdef USES_P012_POLISH_CHARS + + if (result.indexOf(unicodePrefix) != -1) { + const char znak_a_uni[3] = { 0xc4, 0x85, 0 }; // Unicode znak a + const char znak_a_lcd[2] = { 0x05, 0 }; // P012_LCD znak a + result.replace(znak_a_uni, znak_a_lcd); + + const char znak_A_uni[3] = { 0xc4, 0x84, 0 }; // Unicode znak A + result.replace(znak_A_uni, znak_a_lcd); + + const char znak_c_uni[3] = { 0xc4, 0x87, 0 }; // Unicode znak c + const char znak_c_lcd[2] = { 0x03, 0 }; // P012_LCD znak c + result.replace(znak_c_uni, znak_c_lcd); + + const char znak_C_uni[3] = { 0xc4, 0x86, 0 }; // Unicode znak C + result.replace(znak_C_uni, znak_c_lcd); + + const char znak_e_uni[3] = { 0xc4, 0x99, 0 }; // Unicode znak e + const char znak_e_lcd[2] = { 0x02, 0 }; // P012_LCD znak e + result.replace(znak_e_uni, znak_e_lcd); + + const char znak_E_uni[3] = { 0xc4, 0x98, 0 }; // Unicode znak E + result.replace(znak_E_uni, znak_e_lcd); + } + + unicodePrefix = 0xc5; + + if (result.indexOf(unicodePrefix) != -1) { + const char znak_l_uni[3] = { 0xc5, 0x82, 0 }; // Unicode znak l + const char znak_l_lcd[2] = { 0x01, 0 }; // P012_LCD znak l + result.replace(znak_l_uni, znak_l_lcd); + + const char znak_L_uni[3] = { 0xc5, 0x81, 0 }; // Unicode znak L + result.replace(znak_L_uni, znak_l_lcd); + + const char znak_n_uni[3] = { 0xc5, 0x84, 0 }; // Unicode znak n + const char znak_n_lcd[2] = { 0x04, 0 }; // P012_LCD znak n + result.replace(znak_n_uni, znak_n_lcd); + + const char znak_N_uni[3] = { 0xc5, 0x83, 0 }; // Unicode znak N + result.replace(znak_N_uni, znak_n_lcd); + + const char znak_s_uni[3] = { 0xc5, 0x9b, 0 }; // Unicode znak s + const char znak_s_lcd[2] = { 0x06, 0 }; // P012_LCD znak s + result.replace(znak_s_uni, znak_s_lcd); + + const char znak_S_uni[3] = { 0xc5, 0x9a, 0 }; // Unicode znak S + result.replace(znak_S_uni, znak_s_lcd); + + const char znak_z1_uni[3] = { 0xc5, 0xba, 0 }; // Unicode znak z z kreska + const char znak_z1_lcd[2] = { 0x07, 0 }; // P012_LCD znak z z kropka + result.replace(znak_z1_uni, znak_z1_lcd); + + const char znak_Z1_uni[3] = { 0xc5, 0xb9, 0 }; // Unicode znak Z z kreska + result.replace(znak_Z1_uni, znak_z1_lcd); + + const char znak_z2_uni[3] = { 0xc5, 0xbc, 0 }; // Unicode znak z z kropka + const char znak_z2_lcd[2] = { 0x07, 0 }; // P012_LCD znak z z kropka + result.replace(znak_z2_uni, znak_z2_lcd); + + const char znak_Z2_uni[3] = { 0xc5, 0xbb, 0 }; // Unicode znak Z z kropka + result.replace(znak_Z2_uni, znak_z2_lcd); + } + + unicodePrefix = 0xc3; + + if (result.indexOf(unicodePrefix) != -1) { + const char znak_o_uni[3] = { 0xc3, 0xB3, 0 }; // Unicode znak o + const char znak_o_lcd[2] = { 0x08, 0 }; // P012_LCD znak o + result.replace(znak_o_uni, znak_o_lcd); + + const char znak_O_uni[3] = { 0xc3, 0x93, 0 }; // Unicode znak O + result.replace(znak_O_uni, znak_o_lcd); + } +# endif // USES_P012_POLISH_CHARS + + unicodePrefix = 0xc3; + + if (result.indexOf(unicodePrefix) != -1) { + // See: https://github.com/letscontrolit/ESPEasy/issues/2081 + + const char umlautAE_uni[3] = { 0xc3, 0x84, 0 }; // Unicode Umlaute AE + const char umlautAE_lcd[2] = { 0xe1, 0 }; // P012_LCD Umlaute + result.replace(umlautAE_uni, umlautAE_lcd); + + const char umlaut_ae_uni[3] = { 0xc3, 0xa4, 0 }; // Unicode Umlaute ae + result.replace(umlaut_ae_uni, umlautAE_lcd); + + const char umlautOE_uni[3] = { 0xc3, 0x96, 0 }; // Unicode Umlaute OE + const char umlautOE_lcd[2] = { 0xef, 0 }; // P012_LCD Umlaute + result.replace(umlautOE_uni, umlautOE_lcd); + + const char umlaut_oe_uni[3] = { 0xc3, 0xb6, 0 }; // Unicode Umlaute oe + result.replace(umlaut_oe_uni, umlautOE_lcd); + + const char umlautUE_uni[3] = { 0xc3, 0x9c, 0 }; // Unicode Umlaute UE + const char umlautUE_lcd[2] = { 0xf5, 0 }; // P012_LCD Umlaute + result.replace(umlautUE_uni, umlautUE_lcd); + + const char umlaut_ue_uni[3] = { 0xc3, 0xbc, 0 }; // Unicode Umlaute ue + result.replace(umlaut_ue_uni, umlautUE_lcd); + + const char umlaut_sz_uni[3] = { 0xc3, 0x9f, 0 }; // Unicode Umlaute sz + const char umlaut_sz_lcd[2] = { 0xe2, 0 }; // P012_LCD Umlaute + result.replace(umlaut_sz_uni, umlaut_sz_lcd); + } + return result; +} + +void P012_data_struct::createCustomChars() { +# ifdef USES_P012_POLISH_CHARS + + /* + static const char LETTER_null[8] PROGMEM = { // spacja + 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000 + }; + */ + static const char LETTER_a[8] PROGMEM = { // a + 0b00000, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111, 0b00010 + }; + static const char LETTER_c[8] PROGMEM = { // c + 0b00010, 0b00100, 0b01110, 0b10000, 0b10000, 0b10001, 0b01110, 0b00000 + }; + static const char LETTER_e[8] PROGMEM = { // e + 0b00000, 0b00000, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110, 0b00010 + }; + static const char LETTER_l[8] PROGMEM = { // l + 0b01100, 0b00100, 0b00101, 0b00110, 0b01100, 0b00100, 0b01110, 0b00000 + }; + static const char LETTER_n[8] PROGMEM = { // n + 0b00010, 0b00100, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001, 0b00000 + }; + static const char LETTER_o[8] PROGMEM = { // o + 0b00010, 0b00100, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000 + }; + static const char LETTER_s[8] PROGMEM = { // s + 0b00010, 0b00100, 0b01110, 0b10000, 0b01110, 0b00001, 0b11110, 0b00000 + }; + + /* + static const char LETTER_z1[8] PROGMEM = { // z z kreska + 0b00010, 0b00100, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111, 0b00000 + }; + */ + static const char LETTER_z2[8] PROGMEM = { // z z kropka + 0b00100, 0b00000, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111, 0b00000 + }; + lcd.createChar(0, LETTER_o); // probably defected memory cell + lcd.createChar(1, LETTER_l); + lcd.createChar(2, LETTER_e); + lcd.createChar(3, LETTER_c); + lcd.createChar(4, LETTER_n); + lcd.createChar(5, LETTER_a); + lcd.createChar(6, LETTER_s); + lcd.createChar(7, LETTER_z2); + lcd.createChar(8, LETTER_o); +# endif // ifdef USES_P012_POLISH_CHARS +} + +#endif // ifdef USES_P012 diff --git a/src/src/PluginStructs/P012_data_struct.h b/src/src/PluginStructs/P012_data_struct.h index ef4a77b1a..8c061ae2a 100644 --- a/src/src/PluginStructs/P012_data_struct.h +++ b/src/src/PluginStructs/P012_data_struct.h @@ -1,43 +1,50 @@ -#ifndef PLUGINSTRUCTS_P012_DATA_STRUCT_H -#define PLUGINSTRUCTS_P012_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" - -#ifdef USES_P012 - -# include - -struct P012_data_struct : public PluginTaskData_base { - P012_data_struct(uint8_t addr, - uint8_t lcd_size, - uint8_t mode, - uint8_t timer); - P012_data_struct() = delete; - virtual ~P012_data_struct() = default; - - void init(); - - void setBacklightTimer(uint8_t timer); - - void checkTimer(); - - void lcdWrite(const String& text, - uint8_t col, - uint8_t row); - - String P012_parseTemplate(String& tmpString, - uint8_t lineSize); - - void createCustomChars(); - - - LiquidCrystal_I2C lcd; - int Plugin_012_cols = 16; - int Plugin_012_rows = 2; - int Plugin_012_mode = 1; - uint8_t displayTimer = 0; -}; - -#endif // ifdef USES_P012 - -#endif // ifndef PLUGINSTRUCTS_P012_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P012_DATA_STRUCT_H +#define PLUGINSTRUCTS_P012_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" + +#ifdef USES_P012 + +# include + +enum class P012_splashState_e : uint8_t { + SplashCleared = 0u, + SplashTimerRunning = 1u, + SplashInitial = 2u +}; + +struct P012_data_struct : public PluginTaskData_base { + P012_data_struct(uint8_t addr, + uint8_t lcd_size, + uint8_t mode, + uint8_t timer); + P012_data_struct() = delete; + virtual ~P012_data_struct() = default; + + void init(); + + void setBacklightTimer(uint8_t timer); + + void checkTimer(); + + void lcdWrite(const String& text, + uint8_t col, + uint8_t row); + + String P012_parseTemplate(String& tmpString, + uint8_t lineSize); + + void createCustomChars(); + + + LiquidCrystal_I2C lcd; + int Plugin_012_cols = 16; + int Plugin_012_rows = 2; + int Plugin_012_mode = 1; + uint8_t displayTimer = 0; + P012_splashState_e splashState = P012_splashState_e::SplashInitial; +}; + +#endif // ifdef USES_P012 + +#endif // ifndef PLUGINSTRUCTS_P012_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P014_data_struct.cpp b/src/src/PluginStructs/P014_data_struct.cpp index 084ebf865..9091caeb5 100644 --- a/src/src/PluginStructs/P014_data_struct.cpp +++ b/src/src/PluginStructs/P014_data_struct.cpp @@ -278,7 +278,7 @@ uint8_t P014_data_struct::checkCRC(uint16_t data, uint8_t check) // Operate on only 16 positions of max 24. // The remaining 8 are our remainder and should be zero when we're done. - for (uint8_t i = 0; i < 16; i++) { + for (uint8_t i = 0; i < 16; ++i) { // Check if there is a one in the left position if (remainder & (uint32_t)1 << (23 - i)) { remainder ^= divisor; @@ -424,7 +424,7 @@ bool P014_data_struct::enablePowerForADC(uint8_t i2caddr){ if (i2caddr == SI7013_I2C_ADDRESS_AD0_1){ - ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, (reg & B11111000) | (2+4+64) );//set last three bits (VIN bufered, Vref=VDD, VOUT=GND) and No-Hold for bit 6 + ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, (reg & 0b11111000) | (2+4+64) );//set last three bits (VIN bufered, Vref=VDD, VOUT=GND) and No-Hold for bit 6 }else{ ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2,reg | (1+2+4+64) );//set last three bits to 1 (VIN bufered, Vref=VDD, VOUT=VDD) and No-Hold for bit 6 } @@ -455,7 +455,7 @@ bool P014_data_struct::disablePowerForADC(uint8_t i2caddr){ if (i2caddr == SI7013_I2C_ADDRESS_AD0_1){ ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2,reg | (1+2+4+64) );//set last three bits to 1 (VIN bufered, Vref=VDD, VOUT=VDD) and No-Hold for bit 6 }else{ - ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, (reg & B11111000) | (2+4+64) );//set last three bits (VIN bufered, Vref=VDD, VOUT=GND) and No-Hold for bit 6 + ok = I2C_write8_reg(i2caddr,SI7013_WRITE_REG2, (reg & 0b11111000) | (2+4+64) );//set last three bits (VIN bufered, Vref=VDD, VOUT=GND) and No-Hold for bit 6 } if (!ok){ addLog(LOG_LEVEL_ERROR, F("SI7013: Could not write REG2!")); diff --git a/src/src/PluginStructs/P014_data_struct.h b/src/src/PluginStructs/P014_data_struct.h index ddb2971fc..19b426755 100644 --- a/src/src/PluginStructs/P014_data_struct.h +++ b/src/src/PluginStructs/P014_data_struct.h @@ -18,7 +18,7 @@ # define SI70xx_RESOLUTION_13T_10RH 0x80 // 10 bits RH / 13 bits Temp # define SI70xx_RESOLUTION_12T_08RH 0x01 // 8 bits RH / 12 bits Temp # define SI70xx_RESOLUTION_11T_11RH 0x81 // 11 bits RH / 11 bits Temp -# define SI70xx_RESOLUTION_MASK B01111110 +# define SI70xx_RESOLUTION_MASK 0b01111110 @@ -40,7 +40,7 @@ #define SI7013_READ_ADC 0xEE #define SI7013_READ_REG2 0x10 #define SI7013_WRITE_REG2 0x50 -#define SI7013_REG2_DEFAULT B01000110 // (MeasureMode=10) No-Hold master with no thermistor correction ; 7ms conversion; (VIN bufered, Vref=VDD, VOUT=GND) +#define SI7013_REG2_DEFAULT 0b01000110 // (MeasureMode=10) No-Hold master with no thermistor correction ; 7ms conversion; (VIN bufered, Vref=VDD, VOUT=GND) #define SI70xx_CMD_ID1 0xFA0F /**< Read Electronic ID SNA Bytes */ #define SI70xx_CMD_ID2 0xFCC9 /**< Read Electronic ID SNB Bytes */ diff --git a/src/src/PluginStructs/P016_data_struct.cpp b/src/src/PluginStructs/P016_data_struct.cpp index f3e609121..6cb173ccf 100644 --- a/src/src/PluginStructs/P016_data_struct.cpp +++ b/src/src/PluginStructs/P016_data_struct.cpp @@ -5,6 +5,9 @@ # include "../Commands/ExecuteCommand.h" # include "../Helpers/ESPEasy_Storage.h" # include +# ifdef P016_CHECK_HEAP +# include "src/Helpers/Memory.h" +# endif // ifdef P016_CHECK_HEAP # ifdef P16_SETTINGS_V1 @@ -183,35 +186,31 @@ void P016_data_struct::AddCode(uint64_t Code, decode_type_t DecodeType, uint16_t # ifdef PLUGIN_016_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(80)) { // estimated - log = F("[P016] AddCode: "); - log += typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)); - log += F(" code: 0x"); - log += uint64ToString(Code, 16); - log += F(" to index "); - log += _index; - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat( + F("[P016] AddCode: %s code: 0x%s to index %d"), + typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)).c_str(), + uint64ToString(Code, 16).c_str(), + _index)); } # endif // PLUGIN_016_DEBUG } -void P016_data_struct::ExecuteCode(uint64_t Code, decode_type_t DecodeType, uint16_t CodeFlags) { +bool P016_data_struct::ExecuteCode(uint64_t Code, decode_type_t DecodeType, uint16_t CodeFlags) { if (Code == 0) { - return; + return false; } if ((iLastCmd == Code) && (iLastDecodeType == DecodeType)) { // same code as before if (iCmdInhibitTime > timePassedSince(iLastCmdTime)) { // inhibit time not ellapsed - return; + return false; } } - for (int i = 0; i < P16_Nlines; ++i) { + const unsigned int nr_CommandLines = CommandLines.size(); + + for (unsigned int i = 0; i < nr_CommandLines; ++i) { if (validateCode(i, Code, DecodeType, CodeFlags)) { // code already saved iLastCmd = Code; @@ -220,61 +219,43 @@ void P016_data_struct::ExecuteCode(uint64_t Code, decode_type_t DecodeType, uint iLastCmdTime = millis(); if (CommandLines[i].Command[0] != 0) { - # ifdef PLUGIN_016_DEBUG - bool _success = - # endif // ifdef PLUGIN_016_DEBUG - ExecuteCommand_all(EventValueSource::Enum::VALUE_SOURCE_SYSTEM, CommandLines[i].Command); + # ifdef P016_CHECK_HEAP + CheckHeap(F("Before ExecuteCommand_all:")); + # endif // ifdef P016_CHECK_HEAP + ExecuteCommand_all( + { EventValueSource::Enum::VALUE_SOURCE_SYSTEM, CommandLines[i].Command }, true); + # ifdef P016_CHECK_HEAP + CheckHeap(F("After ExecuteCommand_all:")); + # endif // ifdef P016_CHECK_HEAP # ifdef PLUGIN_016_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(128)) { // estimated - log = F("[P016] Execute: "); - log += typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)); - log += F(" Code: 0x"); - log += uint64ToString(Code, 16); - log += F(" with command "); - log += (i + 1); - log += F(": {"); - log += String(CommandLines[i].Command); - log += '}'; - - if (!_success) { - log += F(" FAILED!"); - } - addLogMove(LOG_LEVEL_INFO, log); - } + addLogMove(LOG_LEVEL_INFO, strformat( + F("[P016] Execute added: %s Code: 0x%s with command %d: {%s}"), + typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)).c_str(), + uint64ToString(Code, 16).c_str(), + (i + 1), + CommandLines[i].Command)); } # endif // PLUGIN_016_DEBUG - } - return; - } - # ifdef PLUGIN_016_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(128)) { // estimated - log = F("[P016] ValidateCode failed: "); - log += typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)); - log += F(" Code: 0x"); - log += uint64ToString(Code, 16); - log += F(" / ["); - log += (i + 1); - log += F("] = {"); - log += typeToString(CommandLines[i].CodeDecodeType, bitRead(CommandLines[i].CodeFlags, P16_FLAGS_REPEAT)); - log += F(" Code: 0x"); - log += uint64ToString(CommandLines[i].Code, 16); - log += '}'; - addLogMove(LOG_LEVEL_INFO, log); + return true; } } - # endif // PLUGIN_016_DEBUG } + # ifdef PLUGIN_016_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat( + F("[P016] ValidateCode failed: %s Code: 0x%s"), + typeToString(DecodeType, bitRead(CodeFlags, P16_FLAGS_REPEAT)).c_str(), + uint64ToString(Code, 16).c_str())); + } + # endif // PLUGIN_016_DEBUG + return false; } bool P016_data_struct::validateCode(int i, uint64_t Code, decode_type_t DecodeType, uint16_t CodeFlags) { + if ((i >= static_cast(CommandLines.size())) || (i < 0)) { return false; } return ((CommandLines[i].Code == Code) && (CommandLines[i].CodeDecodeType == DecodeType) && (CommandLines[i].CodeFlags == CodeFlags)) @@ -284,5 +265,15 @@ bool P016_data_struct::validateCode(int i, uint64_t Code, decode_type_t DecodeTy } # endif // if P016_FEATURE_COMMAND_HANDLING +# ifdef P016_CHECK_HEAP +void P016_data_struct::CheckHeap(String dbgtxt) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("P016: %s FreeMem: %d FreeStack:%d"), + dbgtxt.c_str(), FreeMem(), getCurrentFreeStack())); + } +} + +# endif // ifdef P016_CHECK_HEAP #endif // ifdef USES_P016 diff --git a/src/src/PluginStructs/P016_data_struct.h b/src/src/PluginStructs/P016_data_struct.h index 07d1a62f0..450527c36 100644 --- a/src/src/PluginStructs/P016_data_struct.h +++ b/src/src/PluginStructs/P016_data_struct.h @@ -9,15 +9,20 @@ # include # define PLUGIN_016_DEBUG // additional debug messages in the log -# if defined(LIMIT_BUILD_SIZE) && defined(PLUGIN_016_DEBUG) -# undef PLUGIN_016_DEBUG -# endif // if defined(LIMIT_BUILD_SIZE) && defined(PLUGIN_016_DEBUG) +// # define P016_CHECK_HEAP +# if defined(LIMIT_BUILD_SIZE) +# if defined(PLUGIN_016_DEBUG) +# undef PLUGIN_016_DEBUG +# endif // if defined(PLUGIN_016_DEBUG) +# if defined(P016_CHECK_HEAP) +# undef P016_CHECK_HEAP +# endif // if defined(P016_CHECK_HEAP) +# endif // if defined(LIMIT_BUILD_SIZE) // bit definition in PCONFIG_LONG(0) # define P016_BitAddNewCode 0 // Add automatically new code into Code of the command structure # define P016_BitExecuteCmd 1 // Execute command if received code matches Code or AlternativeCode of the command structure -# define P016_BitAcceptUnknownType 2 // Accept unknown DecodeType as valid IR code (will be set to RAW before calling AddCode() or - // ExecuteCode()) +# define P016_BitAcceptUnknownType 2 // Accept unknown DecodeType as valid IR code (UNKNOWH is only the result of DECODE_HASH) # define P16_Nlines 10 // The number of different lines which can be displayed - each line is 64 chars max # define P16_Nchars 64 // max chars per command line @@ -66,8 +71,8 @@ struct tCommandLinesV2 { char Command[P16_Nchars] = { 0 }; uint64_t Code = 0; // received code (can be added automatically) uint64_t AlternativeCode = 0; // alternative code fpr the same command - decode_type_t CodeDecodeType = decode_type_t::UNKNOWN; - decode_type_t AlternativeCodeDecodeType = decode_type_t::UNKNOWN; + decode_type_t CodeDecodeType = decode_type_t::UNUSED; + decode_type_t AlternativeCodeDecodeType = decode_type_t::UNUSED; uint16_t CodeFlags = 0; uint16_t AlternativeCodeFlags = 0; }; @@ -76,7 +81,7 @@ struct tCommandLinesV2 { struct P016_data_struct : public PluginTaskData_base { public: - P016_data_struct() = default; + P016_data_struct() = default; virtual ~P016_data_struct() = default; void init(struct EventStruct *event, @@ -98,10 +103,10 @@ public: uint8_t lineNr); void AddCode(uint64_t Code, - decode_type_t DecodeType = decode_type_t::UNKNOWN, + decode_type_t DecodeType = decode_type_t::UNUSED, uint16_t CodeFlags = 0u); - void ExecuteCode(uint64_t Code, - decode_type_t DecodeType = decode_type_t::UNKNOWN, + bool ExecuteCode(uint64_t Code, + decode_type_t DecodeType = decode_type_t::UNUSED, uint16_t CodeFlags = 0u); // CustomTaskSettings @@ -120,10 +125,13 @@ private: uint64_t iLastCmd = 0; // last command send uint32_t iLastCmdTime = 0; // time while last command was send - decode_type_t iLastDecodeType = decode_type_t::UNKNOWN; // last decode_type sent + decode_type_t iLastDecodeType = decode_type_t::UNUSED; // last decode_type sent uint16_t iCmdInhibitTime = 0; // inhibit time for sending the same command again uint16_t iLastCodeFlags = 0; // last flags sent # endif // if P016_FEATURE_COMMAND_HANDLING + # ifdef P016_CHECK_HEAP + void CheckHeap(String dbgtxt); + # endif // ifdef P016_CHECK_HEAP }; #endif // ifdef USES_P016 diff --git a/src/src/PluginStructs/P020_data_struct.cpp b/src/src/PluginStructs/P020_data_struct.cpp index f0734f5db..63a9fc728 100644 --- a/src/src/PluginStructs/P020_data_struct.cpp +++ b/src/src/PluginStructs/P020_data_struct.cpp @@ -1,296 +1,581 @@ -#include "../PluginStructs/P020_data_struct.h" - -#ifdef USES_P020 - -# include "../ESPEasyCore/Serial.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" - -# include "../Globals/EventQueue.h" - -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/Misc.h" - -# define P020_RX_WAIT PCONFIG(4) -# define P020_RX_BUFFER PCONFIG(7) - - -P020_Task::P020_Task(taskIndex_t taskIndex) : _taskIndex(taskIndex) { - serial_buffer.reserve(P020_DATAGRAM_MAX_SIZE); -} - -P020_Task::~P020_Task() { - if (ser2netServer != nullptr) { - delete ser2netServer; - ser2netServer = nullptr; - } - if (ser2netSerial != nullptr) { - delete ser2netSerial; - ser2netSerial = nullptr; - } -} - -bool P020_Task::serverActive(WiFiServer *server) { -# if defined(ESP8266) - return nullptr != server && server->status() != CLOSED; -# elif defined(ESP32) - return nullptr != server && *server; -# endif // if defined(ESP8266) -} - -void P020_Task::startServer(uint16_t portnumber) { - if ((gatewayPort == portnumber) && serverActive(ser2netServer)) { - // server is already listening on this port - return; - } - stopServer(); - gatewayPort = portnumber; - ser2netServer = new (std::nothrow) WiFiServer(portnumber); - - if ((nullptr != ser2netServer) && NetworkConnected()) { - ser2netServer->begin(); - - if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, String(F("Ser2Net : WiFi server started at port ")) + portnumber); - } else { - addLog(LOG_LEVEL_ERROR, String(F("Ser2Net : WiFi server start failed at port ")) + - portnumber + String(F(", retrying..."))); - } - } -} - -void P020_Task::checkServer() { - if ((nullptr != ser2netServer) && !serverActive(ser2netServer) && NetworkConnected()) { - ser2netServer->close(); - ser2netServer->begin(); - - if (serverActive(ser2netServer)) { - addLog(LOG_LEVEL_INFO, F("Ser2net : WiFi server started")); - } - } -} - -void P020_Task::stopServer() { - if (nullptr != ser2netServer) { - if (ser2netClient) { ser2netClient.stop(); } - clientConnected = false; - ser2netServer->close(); - addLog(LOG_LEVEL_INFO, F("Ser2net : WiFi server closed")); - delete ser2netServer; - ser2netServer = nullptr; - } -} - -bool P020_Task::hasClientConnected() { - if ((nullptr != ser2netServer) && ser2netServer->hasClient()) - { - if (ser2netClient) { ser2netClient.stop(); } - ser2netClient = ser2netServer->available(); - - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - ser2netClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! - Client *pClient = &ser2netClient; - pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - ser2netClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - sendConnectedEvent(true); - addLog(LOG_LEVEL_INFO, F("Ser2Net : Client connected!")); - } - - if (ser2netClient.connected()) - { - clientConnected = true; - } - else - { - if (clientConnected) // there was a client connected before... - { - clientConnected = false; - sendConnectedEvent(false); - addLog(LOG_LEVEL_INFO, F("Ser2net : Client disconnected!")); - } - } - return clientConnected; -} - -void P020_Task::discardClientIn() { - // flush all data received from the WiFi gateway - // as a P1 meter does not receive data - while (ser2netClient.available()) { - ser2netClient.read(); - } -} - -void P020_Task::clearBuffer() { - serial_buffer = String(); - serial_buffer.reserve(P020_DATAGRAM_MAX_SIZE); -} - -void P020_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config) { - serialEnd(); - - if (rxPin >= 0) { - ser2netSerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin); - - if (nullptr != ser2netSerial) { - # if defined(ESP8266) - ser2netSerial->begin(baud, (SerialConfig)config); - # elif defined(ESP32) - ser2netSerial->begin(baud, config); - # endif // if defined(ESP8266) - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2net : Serial opened")); - # endif // ifndef BUILD_NO_DEBUG - } - } -} - -void P020_Task::serialEnd() { - if (nullptr != ser2netSerial) { - delete ser2netSerial; - clearBuffer(); - ser2netSerial = nullptr; - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2net : Serial closed")); - # endif // ifndef BUILD_NO_DEBUG - } -} - -void P020_Task::handleClientIn(struct EventStruct *event) { - int count = ser2netClient.available(); - int bytes_read = 0; - uint8_t net_buf[P020_DATAGRAM_MAX_SIZE]; - - if (count > 0) { - if (count > P020_DATAGRAM_MAX_SIZE) { count = P020_DATAGRAM_MAX_SIZE; } - bytes_read = ser2netClient.read(net_buf, count); - ser2netSerial->write(net_buf, bytes_read); - ser2netSerial->flush(); // Waits for the transmission of outgoing serial data to - - while (ser2netClient.available()) { // flush overflow data if available - ser2netClient.read(); - } - } -} - -void P020_Task::handleSerialIn(struct EventStruct *event) { - if (nullptr == ser2netSerial) { return; } - int RXWait = P020_RX_WAIT; - int timeOut = RXWait; - - do { - if (ser2netSerial->available()) { - if (serial_buffer.length() > static_cast(P020_RX_BUFFER)) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2Net : Error: Buffer overflow, discarded input.")); - # endif // ifndef BUILD_NO_DEBUG - ser2netSerial->read(); - } - else { serial_buffer += (char)ser2netSerial->read(); } - timeOut = RXWait; // if serial received, reset timeout counter - } else { - if (timeOut <= 0) { break; } - delay(1); - --timeOut; - } - } while (true); - - if (serial_buffer.length() > 0) { - if (ser2netClient.connected()) { // Only send out if a client is connected - ser2netClient.print(serial_buffer); - } - rulesEngine(serial_buffer); - ser2netClient.flush(); - clearBuffer(); - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("Ser2Net : data send!")); - # endif // ifndef BUILD_NO_DEBUG - } // done -} - -void P020_Task::discardSerialIn() { - if (nullptr != ser2netSerial) { - while (ser2netSerial->available()) { - ser2netSerial->read(); - } - } -} - -// We can also use the rules engine for local control! -void P020_Task::rulesEngine(const String& message) { - if (!Settings.UseRules || message.isEmpty()) { return; } - int NewLinePos = 0; - uint16_t StartPos = 0; - - NewLinePos = message.indexOf('\n', StartPos); - - do { - if (NewLinePos < 0) { - NewLinePos = message.length(); - } - - // Remove preceeding CR also - if ((message[NewLinePos] == '\n') && (message[NewLinePos - 1] == '\r')) { - NewLinePos--; - } - - switch (serial_processing) { - case 0: { break; } - case 1: { // Generic - if (NewLinePos > StartPos) { - eventQueue.addMove(std::move(concat( - F("!Serial#"), - message.substring(StartPos, NewLinePos)))); - } - break; - } - case 2: { // RFLink - StartPos += 6; // RFLink, strip 20;xx; from incoming message - - String eventString; - - if ((NewLinePos - StartPos) >= 8 && - message.substring(StartPos, StartPos + 8) - .startsWith(F("ESPEASY"))) { // Special treatment for gpio values, strip unneeded parts... - StartPos += 8; // Strip "ESPEASY;" - eventString = F("RFLink#"); - } else { - eventString = F("!RFLink#"); // default event as it comes in, literal match needed in rules, using '!' - } - - if (NewLinePos > StartPos) { - eventString += message.substring(StartPos, NewLinePos); - } - eventQueue.addMove(std::move(eventString)); - break; - } - } // switch - - // Skip CR/LF - StartPos = NewLinePos; // Continue after what was already handled - - while (StartPos < message.length() && (message[StartPos] == '\n' || message[StartPos] == '\r')) { - StartPos++; - } - - NewLinePos = message.indexOf('\n', StartPos); - - if (handleMultiLine && (NewLinePos < 0)) { - NewLinePos = message.length(); - } - } while (handleMultiLine && NewLinePos > StartPos); -} - -bool P020_Task::isInit() const { - return nullptr != ser2netServer && nullptr != ser2netSerial; -} - -void P020_Task::sendConnectedEvent(bool connected) -{ - eventQueue.add(_taskIndex, F("Client"), (connected ? 1 : 0)); -} - -#endif // ifdef USES_P020 +#include "../PluginStructs/P020_data_struct.h" + +#ifdef USES_P020 + +# include "../ESPEasyCore/Serial.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" + +# include "../Globals/EventQueue.h" + +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Misc.h" + +P020_Task::P020_Task(struct EventStruct *event) : _taskIndex(event->TaskIndex) { + clearBuffer(); + + if (P020_GET_LED_ENABLED) { + _ledPin = P020_LED_PIN; // Default pin (12) is already initialized in P020_Task + } + _ledEnabled = P020_GET_LED_ENABLED == 1; + _ledInverted = P020_GET_LED_INVERTED == 1; + _space = static_cast(P020_REPLACE_SPACE); + _newline = static_cast(P020_REPLACE_NEWLINE); + _port = static_cast(CONFIG_PORT); + _serialId = P020_GET_EVENT_SERIAL_ID; + _appendTaskId = P020_GET_APPEND_TASK_ID; +} + +P020_Task::~P020_Task() { + if (ser2netServer != nullptr) { + delete ser2netServer; + ser2netServer = nullptr; + } + + if (ser2netSerial != nullptr) { + delete ser2netSerial; + ser2netSerial = nullptr; + } +} + +bool P020_Task::serverActive(WiFiServer *server) { +# if defined(ESP8266) + return nullptr != server && server->status() != CLOSED; +# elif defined(ESP32) + return nullptr != server && *server; +# endif // if defined(ESP8266) +} + +void P020_Task::startServer(uint16_t portnumber) { + if ((gatewayPort == portnumber) && serverActive(ser2netServer)) { + // server is already listening on this port + return; + } + stopServer(); + gatewayPort = portnumber; + ser2netServer = new (std::nothrow) WiFiServer(portnumber); + + if ((nullptr != ser2netServer) && NetworkConnected()) { + ser2netServer->begin(); + + if (serverActive(ser2netServer)) { + addLog(LOG_LEVEL_INFO, strformat(F("Ser2Net: WiFi server started at port %d"), portnumber)); + } else { + addLog(LOG_LEVEL_ERROR, strformat(F("Ser2Net: WiFi server start FAILED at port %d, retrying..."), portnumber)); + } + } +} + +void P020_Task::checkServer() { + if ((nullptr != ser2netServer) && !serverActive(ser2netServer) && NetworkConnected()) { + ser2netServer->close(); + ser2netServer->begin(); + + if (serverActive(ser2netServer)) { + addLog(LOG_LEVEL_INFO, F("Ser2Net: WiFi server started")); + } + } +} + +void P020_Task::stopServer() { + if (nullptr != ser2netServer) { + if (ser2netClient) { ser2netClient.stop(); } + clientConnected = false; + ser2netServer->close(); + addLog(LOG_LEVEL_INFO, F("Ser2Net: WiFi server closed")); + delete ser2netServer; + ser2netServer = nullptr; + } +} + +bool P020_Task::hasClientConnected() { + if ((nullptr != ser2netServer) && ser2netServer->hasClient()) + { + if (ser2netClient) { ser2netClient.stop(); } + #if ESP_IDF_VERSION_MAJOR >= 5 + ser2netClient = ser2netServer->accept(); + #else + ser2netClient = ser2netServer->available(); + #endif + + # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + ser2netClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! + Client *pClient = &ser2netClient; + pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); + # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + ser2netClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! + # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + sendConnectedEvent(true); + addLog(LOG_LEVEL_INFO, F("Ser2Net: Client connected!")); + } + + if (ser2netClient.connected()) + { + clientConnected = true; + } + else + { + if (clientConnected) // there was a client connected before... + { + clientConnected = false; + sendConnectedEvent(false); + addLog(LOG_LEVEL_INFO, F("Ser2Net: Client disconnected!")); + } + } + return clientConnected; +} + +void P020_Task::discardClientIn() { + // flush all data received from the WiFi gateway + // as a P1 meter does not receive data + while (ser2netClient.available()) { + ser2netClient.read(); + } +} + +void P020_Task::clearBuffer() { + serial_buffer = String(); + _maxDataGramSize = serial_processing == P020_Events::P1WiFiGateway + ? P020_P1_DATAGRAM_MAX_SIZE + : P020_DATAGRAM_MAX_SIZE; + serial_buffer.reserve(_maxDataGramSize); +} + +void P020_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config) { + serialEnd(); + + if (ESPEasySerialPort::not_set != port) { + ser2netSerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin); + + if (nullptr != ser2netSerial) { + # if defined(ESP8266) + ser2netSerial->begin(baud, (SerialConfig)config); + # elif defined(ESP32) + ser2netSerial->begin(baud, config); + # endif // if defined(ESP8266) + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Serial opened")); + # endif // ifndef BUILD_NO_DEBUG + } + } +} + +void P020_Task::serialEnd() { + if (nullptr != ser2netSerial) { + delete ser2netSerial; + clearBuffer(); + ser2netSerial = nullptr; + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Serial closed")); + # endif // ifndef BUILD_NO_DEBUG + } +} + +void P020_Task::handleClientIn(struct EventStruct *event) { + size_t count = ser2netClient.available(); + size_t bytes_read = 0; + uint8_t net_buf[_maxDataGramSize]; + + if (count > 0) { + if (count > _maxDataGramSize) { count = _maxDataGramSize; } + bytes_read = ser2netClient.read(net_buf, count); + ser2netSerial->write(net_buf, bytes_read); + ser2netSerial->flush(); // Waits for the transmission of outgoing serial data to + + while (ser2netClient.available()) { // flush overflow data if available + ser2netClient.read(); + } + } +} + +void P020_Task::handleSerialIn(struct EventStruct *event) { + if (nullptr == ser2netSerial) { return; } + int RXWait = P020_RX_WAIT; + int timeOut = RXWait; + int maxExtend = 5; + bool done = false; + char ch; + + do { + if (ser2netSerial->available()) { + if ((serial_processing != P020_Events::P1WiFiGateway) // P1 handling without this check + && (serial_buffer.length() > static_cast(P020_RX_BUFFER))) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: Error: Buffer overflow, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + ser2netSerial->read(); + } + else { + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } + + ch = static_cast(ser2netSerial->read()); + + if (serial_processing == P020_Events::P1WiFiGateway) { + done = handleP1Char(ch); + } else { + addChar(ch); + } + + if (_ledEnabled) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); + } + } + + if (done) { + break; + } + timeOut = RXWait; // if serial received, reset timeout counter + } else { + if (timeOut <= 0) { + if ((RXWait > 0) && (serial_processing == P020_Events::P1WiFiGateway) && + ((_state == ParserState::READING) || + (_state == ParserState::CHECKSUM)) && + (maxExtend > 0)) { + timeOut = RXWait; + maxExtend--; + } else { + break; + } + } + delay(1); + --timeOut; + } + } while (true); + + if (serial_buffer.length() > 0) { + if (ser2netClient.connected()) { // Only send out if a client is connected + if ((serial_processing == P020_Events::P1WiFiGateway) && !serial_buffer.endsWith(F("\r\n"))) { + serial_buffer += F("\r\n"); + } + ser2netClient.print(serial_buffer); + } + + blinkLED(); + + rulesEngine(serial_buffer); + ser2netClient.flush(); + clearBuffer(); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("Ser2Net: data sent!")); + # endif // ifndef BUILD_NO_DEBUG + } // done +} + +void P020_Task::discardSerialIn() { + if (nullptr != ser2netSerial) { + while (ser2netSerial->available()) { + ser2netSerial->read(); + } + } +} + +// We can also use the rules engine for local control! +void P020_Task::rulesEngine(const String& message) { + if (!Settings.UseRules || message.isEmpty() || (P020_Events::None == serial_processing)) { return; } + int NewLinePos = 0; + uint16_t StartPos = 0; + + NewLinePos = handleMultiLine ? message.indexOf('\n', StartPos) : message.length(); + + do { + if (NewLinePos < 0) { + NewLinePos = message.length(); + } + + String eventString; + + // Remove preceeding CR also + if ((message[NewLinePos] == '\n') && (message[NewLinePos - 1] == '\r')) { + NewLinePos--; + } + + switch (serial_processing) { + case P020_Events::None: { break; } + case P020_Events::Generic: { // Generic + if (NewLinePos > StartPos) { + eventString = '!'; // F("!Serial"); + + if (_serialId) { + eventString += ESPEasySerialPort_toString(_port, true); + } else { + eventString += F("Serial"); + } + + if (_appendTaskId) { + eventString += (_taskIndex + 1); + } + eventString += '#'; + eventString += message.substring(StartPos, NewLinePos); + } + break; + } + case P020_Events::RFLink: { // RFLink + StartPos += 6; // RFLink, strip 20;xx; from incoming message + + if (((NewLinePos - StartPos) >= 8) && + message.substring(StartPos, StartPos + 8) + .startsWith(F("ESPEASY"))) { // Special treatment for gpio values, strip unneeded parts... + StartPos += 8; // Strip "ESPEASY;" + eventString = F("RFLink"); + } else { + eventString = F("!RFLink"); // default event as it comes in, literal match needed in rules, using '!' + } + + if (_appendTaskId) { + eventString += (_taskIndex + 1); + } + eventString += '#'; + + if (NewLinePos > StartPos) { + eventString += message.substring(StartPos, NewLinePos); + } + eventQueue.addMove(std::move(eventString)); + break; + } + case P020_Events::P1WiFiGateway: // P1 WiFi Gateway + eventString = getTaskDeviceName(_taskIndex); + eventString += F("#Data"); + + if (_P1EventData) { + eventString += '='; + eventString += message; // Include entire message, may cause memory overflow! + eventString.replace(F("\n"), F(",")); // Make it a single line, comma-separated, as much as possible + eventString.replace(F("\r"), F("")); // We don't need no st*n carriage returns :) + } + break; + } // switch + + // Skip CR/LF + StartPos = NewLinePos; // Continue after what was already handled + + while (StartPos < message.length() && (message[StartPos] == '\n' || message[StartPos] == '\r')) { + StartPos++; + } + + if (!eventString.isEmpty()) { + eventQueue.add(eventString); + } + NewLinePos = message.indexOf('\n', StartPos); + + if (handleMultiLine && (NewLinePos < 0)) { + NewLinePos = message.length(); + } + } while (handleMultiLine && NewLinePos > StartPos); +} + +bool P020_Task::isInit() const { + return nullptr != ser2netServer && nullptr != ser2netSerial; +} + +void P020_Task::sendConnectedEvent(bool connected) +{ + eventQueue.add(_taskIndex, F("Client"), (connected ? 1 : 0)); +} + +void P020_Task::blinkLED() { + if (_ledEnabled) { + _blinkLEDStartTime = millis(); + digitalWrite(_ledPin, _ledInverted ? 0 : 1); + } +} + +void P020_Task::checkBlinkLED() { + if (_ledEnabled && (_blinkLEDStartTime > 0) && (timePassedSince(_blinkLEDStartTime) >= 500)) { + digitalWrite(_ledPin, _ledInverted ? 1 : 0); + _blinkLEDStartTime = 0; + } +} + +void P020_Task::addChar(char ch) { + if ((ch == 0x20) && (_space > 0)) { ch = _space; } + + if (_newline > 0) { + if (ch == '\n') { ch = _newline; } + + if (ch == '\r') { return; } // Ignore CR if LF is replaced + } + + serial_buffer += ch; +} + +/* checkDatagram + checks whether the P020_CHECKSUM of the data received from P1 matches the P020_CHECKSUM + attached to the telegram + */ +bool P020_Task::checkDatagram() const { + int endChar = serial_buffer.length() - 1; + + if (_CRCcheck) { + endChar -= P020_CHECKSUM_LENGTH; + } + + if ((endChar < 0) || (serial_buffer[0] != P020_DATAGRAM_START_CHAR) || + (serial_buffer[endChar] != P020_DATAGRAM_END_CHAR)) { + return false; + } + + if (!_CRCcheck) { + return true; + } + + const int checksumStartIndex = endChar + 1; + + # if PLUGIN_020_DEBUG + + for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) { + serialPrint(serial_buffer.substring(cnt, 1)); + } + # endif // if PLUGIN_020_DEBUG + + // calculate the CRC and check if it equals the hexadecimal one attached to the datagram + unsigned int crc = CRC16(serial_buffer, checksumStartIndex); + return strtoul(serial_buffer.substring(checksumStartIndex).c_str(), nullptr, 16) == crc; +} + +/* + CRC16 + based on code written by Jan ten Hove + https://github.com/jantenhove/P1-Meter-ESP8266 + */ +unsigned int P020_Task::CRC16(const String& buf, int len) { + unsigned int crc = 0; + + for (int pos = 0; pos < len; ++pos) { + crc ^= static_cast(buf[pos]); // XOR byte into least sig. byte of crc + + for (int i = 8; i != 0; --i) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else { // Else LSB is not set + crc >>= 1; // Just shift right + } + } + } + + return crc; +} + +/* + validP1char + Checks if the character is valid as part of the P1 datagram contents and/or checksum. + Returns false on a datagram start ('/'), end ('!') or invalid character + */ +bool P020_Task::validP1char(char ch) { + return + isAlphaNumeric(ch) || + ch == '.' || + ch == ' ' || + ch == '\\' || // Single backslash, but escaped in C++ + ch == '\r' || + ch == '\n' || + ch == '(' || + ch == ')' || + ch == '-' || + ch == '*' || + ch == ':' || + ch == '_'; +} + +bool P020_Task::handleP1Char(char ch) { + if (serial_buffer.length() >= _maxDataGramSize - 2) { // room for cr/lf + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Buffer overflow, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + _state = ParserState::WAITING; // reset + } + ch &= 0x7F; // Strip off occasional 8th bit for now + + bool done = false; + bool invalid = false; + + switch (_state) { + case ParserState::WAITING: + + if (ch == P020_DATAGRAM_START_CHAR) { + clearBuffer(); + addChar(ch); + _state = ParserState::READING; + } // else ignore data + break; + case ParserState::READING: + + if (validP1char(ch)) { + addChar(ch); + } else if (ch == P020_DATAGRAM_END_CHAR) { + addChar(ch); + + if (_CRCcheck) { + checkI = 0; + _state = ParserState::CHECKSUM; + } else { + done = true; + } + } else if (ch == P020_DATAGRAM_START_CHAR) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Start detected, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + _state = ParserState::WAITING; // reset + return handleP1Char(ch); + } else { + addLog(LOG_LEVEL_ERROR, strformat(F("P1 : Receiving unknown: %d,'%c'"), ch, ch)); + invalid = true; + } + break; + case ParserState::CHECKSUM: + + if (validP1char(ch)) { + addChar(ch); + ++checkI; + + if (checkI == P020_CHECKSUM_LENGTH) { + done = true; + } + } else { + invalid = true; + } + break; + } // switch + + if (invalid) { + // input is not a datagram char + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: DATA corrupt, discarded input.")); + # endif // ifndef BUILD_NO_DEBUG + + # if PLUGIN_020_DEBUG + serialPrint(F("faulty char>")); + serialPrint(String(ch)); + serialPrintln("<"); + # endif // if PLUGIN_020_DEBUG + _state = ParserState::WAITING; // reset + } + + if (done) { + done = checkDatagram(); + + if (done) { + // add the cr/lf pair to the datagram ahead of reading both + // from serial as the datagram has already been validated + addChar('\r'); + addChar('\n'); + } else if (_CRCcheck) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid CRC, dropped data")); + # endif // ifndef BUILD_NO_DEBUG + } else { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P1 : Error: Invalid datagram, dropped data")); + # endif // ifndef BUILD_NO_DEBUG + } + _state = ParserState::WAITING; // prepare for next one + } + + return done; +} + +#endif // ifdef USES_P020 diff --git a/src/src/PluginStructs/P020_data_struct.h b/src/src/PluginStructs/P020_data_struct.h index 51300ba3c..8714ca43c 100644 --- a/src/src/PluginStructs/P020_data_struct.h +++ b/src/src/PluginStructs/P020_data_struct.h @@ -8,48 +8,130 @@ # include # ifndef PLUGIN_020_DEBUG - # define PLUGIN_020_DEBUG false // extra logging in serial out + # define PLUGIN_020_DEBUG false // when true: extra logging in serial out !?!?! # endif // ifndef PLUGIN_020_DEBUG +# define P020_SET_SERVER_PORT ExtraTaskSettings.TaskDevicePluginConfigLong[0] +# define P020_SET_BAUDRATE ExtraTaskSettings.TaskDevicePluginConfigLong[1] + +# define P020_GET_SERVER_PORT Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0) +# define P020_GET_BAUDRATE Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1) + +# define P020_REPLACE_CHAR_SET ",;:.!^|/\\" + +# define P020_LED_PIN PCONFIG(0) +# define P020_SERIAL_CONFIG PCONFIG(1) +# define P020_REPLACE_SPACE PCONFIG(2) +# define P020_REPLACE_NEWLINE PCONFIG(3) +# define P020_RX_WAIT PCONFIG(4) +# define P020_SERIAL_PROCESSING PCONFIG(5) +# define P020_RESET_TARGET_PIN PCONFIG(6) +# define P020_RX_BUFFER PCONFIG(7) + +# define P020_FLAGS PCONFIG_ULONG(0) +# define P020_FLAG_IGNORE_CLIENT 0 +# define P020_FLAG_MULTI_LINE 1 +# define P020_FLAG_LED_ENABLED 2 +# define P020_FLAG_LED_INVERTED 3 +# define P020_FLAG_P1_EVENT_DATA 4 +# define P020_FLAG_P044_MODE_SAVED 8 +# define P020_FLAG_EVENT_SERIAL_ID 9 +# define P020_FLAG_APPEND_TASK_ID 10 +# define P020_IGNORE_CLIENT_CONNECTED bitRead(P020_FLAGS, P020_FLAG_IGNORE_CLIENT) +# define P020_HANDLE_MULTI_LINE bitRead(P020_FLAGS, P020_FLAG_MULTI_LINE) +# define P020_GET_LED_ENABLED bitRead(P020_FLAGS, P020_FLAG_LED_ENABLED) +# define P020_GET_LED_INVERTED bitRead(P020_FLAGS, P020_FLAG_LED_INVERTED) +# define P020_GET_P1_EVENT_DATA bitRead(P020_FLAGS, P020_FLAG_P1_EVENT_DATA) +# define P020_GET_P044_MODE_SAVED bitRead(P020_FLAGS, P020_FLAG_P044_MODE_SAVED) +# define P020_GET_EVENT_SERIAL_ID bitRead(P020_FLAGS, P020_FLAG_EVENT_SERIAL_ID) +# define P020_GET_APPEND_TASK_ID bitRead(P020_FLAGS, P020_FLAG_APPEND_TASK_ID) + +# define P020_DEFAULT_SERVER_PORT 1234 +# define P020_DEFAULT_BAUDRATE 115200 +# define P020_DEFAULT_RESET_TARGET_PIN -1 +# define P020_DEFAULT_RX_BUFFER 256 + # define P020_STATUS_LED 12 # define P020_DATAGRAM_MAX_SIZE 256 + +# define P020_DEFAULT_P044_SERVER_PORT 0 +# define P020_DEFAULT_P044_BAUDRATE 9600 + +# define P020_CHECKSUM_LENGTH 4 +# define P020_DATAGRAM_START_CHAR '/' +# define P020_DATAGRAM_END_CHAR '!' +# define P020_P1_DATAGRAM_MAX_SIZE 2048u + +enum class P020_Events : uint8_t { + None = 0u, + Generic = 1u, + RFLink = 2u, + P1WiFiGateway = 3u, +}; + struct P020_Task : public PluginTaskData_base { - P020_Task(taskIndex_t taskIndex); - P020_Task() = delete; - virtual ~P020_Task(); + enum class ParserState : uint8_t { + WAITING, + READING, + CHECKSUM + }; + + P020_Task(struct EventStruct *event); + ~P020_Task(); inline static bool serverActive(WiFiServer *server); void startServer(uint16_t portnumber); - void checkServer(); - void stopServer(); bool hasClientConnected(); - void discardClientIn(); void clearBuffer(); - void serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin, unsigned long baud, uint8_t config); + void serialEnd(); - void serialEnd(); + void handleSerialIn(struct EventStruct *event); + void handleClientIn(struct EventStruct *event); + void discardSerialIn(); + void rulesEngine(const String& message); - void handleSerialIn(struct EventStruct *event); - void handleClientIn(struct EventStruct *event); - void rulesEngine(const String& message); + bool isInit() const; - void discardSerialIn(); + void sendConnectedEvent(bool connected); - bool isInit() const; + void blinkLED(); + void checkBlinkLED(); - void sendConnectedEvent(bool connected); + void addChar(char ch); + + /* checkDatagram + checks whether the P020_CHECKSUM of the data received from P1 matches the P020_CHECKSUM + attached to the telegram + */ + bool checkDatagram() const; + + /* + CRC16 + based on code written by Jan ten Hove + https://github.com/jantenhove/P1-Meter-ESP8266 + */ + static unsigned int CRC16(const String& buf, + int len); + + /* + validP1char + Checks if the character is valid as part of the P1 datagram contents and/or checksum. + Returns false on a datagram start ('/'), end ('!') or invalid character + */ + static bool validP1char(char ch); + bool handleP1Char(char ch); WiFiServer *ser2netServer = nullptr; uint16_t gatewayPort = 0; @@ -59,9 +141,24 @@ struct P020_Task : public PluginTaskData_base { String net_buffer; int checkI = 0; ESPeasySerial *ser2netSerial = nullptr; - uint8_t serial_processing = 0; + P020_Events serial_processing = P020_Events::None; taskIndex_t _taskIndex = INVALID_TASK_INDEX; bool handleMultiLine = false; + + unsigned long _blinkLEDStartTime = 0; + int8_t _ledPin = -1; + bool _ledInverted = false; + bool _ledEnabled = false; + bool _CRCcheck = false; + bool _P1EventData = false; + size_t _maxDataGramSize = P020_DATAGRAM_MAX_SIZE; + ParserState _state = ParserState::WAITING; + char _space = 0; + char _newline = 0; + bool _serialId = false; + bool _appendTaskId = false; + + ESPEasySerialPort _port; }; #endif // ifdef USES_P020 diff --git a/src/src/PluginStructs/P022_data_struct.cpp b/src/src/PluginStructs/P022_data_struct.cpp index e0359870f..d4c429573 100644 --- a/src/src/PluginStructs/P022_data_struct.cpp +++ b/src/src/PluginStructs/P022_data_struct.cpp @@ -107,7 +107,7 @@ void P022_data_struct::Plugin_022_initialize(int address) // default mode is open drain output, drive leds connected to VCC Plugin_022_writeRegister(i2cAddress, PLUGIN_022_PCA9685_MODE1, (uint8_t)0x01); // reset the device delay(1); - Plugin_022_writeRegister(i2cAddress, PLUGIN_022_PCA9685_MODE1, (uint8_t)B10100000); // set up for auto increment + Plugin_022_writeRegister(i2cAddress, PLUGIN_022_PCA9685_MODE1, (uint8_t)0b10100000); // set up for auto increment // Plugin_022_writeRegister(i2cAddress, PCA9685_MODE2, (uint8_t)0x10); // set to output p022_set_init(address); } diff --git a/src/src/PluginStructs/P023_data_struct.cpp b/src/src/PluginStructs/P023_data_struct.cpp index c0a818c50..5e3b3e813 100644 --- a/src/src/PluginStructs/P023_data_struct.cpp +++ b/src/src/PluginStructs/P023_data_struct.cpp @@ -261,7 +261,7 @@ void P023_data_struct::StartUp_OLED(struct EventStruct *event) { } bool P023_data_struct::plugin_read(struct EventStruct *event) { - for (uint8_t x = 0; x < 8; x++) { + for (uint8_t x = 0; x < 8; ++x) { if (strings[x].length()) { String tmp = strings[x]; const String newString = parseTemplate(tmp, 16); @@ -279,7 +279,7 @@ bool P023_data_struct::plugin_write(struct EventStruct *event, String cmd = parseString(string, 1); // Changes to lowercase if (equals(cmd, F("oledcmd"))) { - String param = parseString(string, 2); + const String param = parseString(string, 2); if (equals(param, F("off"))) { displayOff(); @@ -314,7 +314,7 @@ void P023_data_struct::setCurrentText(const String& string, int X, int Y) { if (currentLines[X].length() >= static_cast(Y)) { currentLines[X] = currentLines[X].substring(0, Y + 1) + string; } else { - for (size_t i = currentLines[X].length(); i < static_cast(Y); i++) { + for (size_t i = currentLines[X].length(); i < static_cast(Y); ++i) { currentLines[X] += ' '; } currentLines[X] += string; @@ -328,7 +328,7 @@ bool P023_data_struct::web_show_values() { bool result = true; uint8_t maxLine = P23_Nlines; - for (; maxLine > 0; maxLine--) { // Don't show trailing empty lines + for (; maxLine > 0; --maxLine) { // Don't show trailing empty lines String tmp = currentLines[maxLine - 1]; tmp.trim(); @@ -337,7 +337,7 @@ bool P023_data_struct::web_show_values() { addHtml(F("
")); // To keep spaces etc. in the shown output
 
-  for (uint8_t i = 0; i < maxLine; i++) {
+  for (uint8_t i = 0; i < maxLine; ++i) {
     addHtmlDiv(F("div_l"), currentLines[i], EMPTY_STRING, F("style='font-size:75%;'"));
 
     if (i != maxLine - 1) {
@@ -359,10 +359,10 @@ void P023_data_struct::displayOff() {
 void P023_data_struct::clearDisplay() {
   unsigned char i, k;
 
-  for (k = 0; k < 8; k++) {
+  for (k = 0; k < 8; ++k) {
     setXY(k, 0);
 
-    for (i = 0; i < 128; i++) { // clear all COL
+    for (i = 0; i < 128; ++i) { // clear all COL
       sendChar(0);              // clear all COL
     }
   }
@@ -417,7 +417,7 @@ void P023_data_struct::sendStrXY(const char *string, int X, int Y) {
       char_width = pgm_read_byte(&(Plugin_023_myFont_Size[*string - 0x20]));
     }
 
-    for (i = 0; i < char_width && currentPixels + i < maxPixels; i++) { // Prevent display overflow on the pixel-level
+    for (i = 0; i < char_width && currentPixels + i < maxPixels; ++i) { // Prevent display overflow on the pixel-level
       sendChar(pgm_read_byte(Plugin_023_myFont[*string - 0x20] + i));
     }
     currentPixels += char_width;
diff --git a/src/src/PluginStructs/P025_data_struct.cpp b/src/src/PluginStructs/P025_data_struct.cpp
index 4f68ba883..709e2b09d 100644
--- a/src/src/PluginStructs/P025_data_struct.cpp
+++ b/src/src/PluginStructs/P025_data_struct.cpp
@@ -9,6 +9,16 @@
 # define P025_CONFIG_REGISTER      0x01
 
 
+P025_VARIOUS_BITS_t::P025_VARIOUS_BITS_t(int16_t value) {
+  memcpy(this, &value, sizeof(int16_t));
+}
+
+int16_t P025_VARIOUS_BITS_t::pconfigvalue() const {
+  int16_t value{};
+  memcpy(&value, this, sizeof(int16_t));
+  return value;
+}
+
 const __FlashStringHelper* Plugin_025_valuename(uint8_t value_nr, bool displayString) {
   const __FlashStringHelper *strings[] {
     F("AIN0 - AIN1 (Differential)"),     F("AIN0_1"),
@@ -36,7 +46,7 @@ const __FlashStringHelper* toString(P025_sensorType sensorType)
          F("ADS1015") : F("ADS1115");
 }
 
-union P025_config_register {
+struct P025_config_register {
   struct {
     uint16_t comp_que        : 2;
     uint16_t comp_lat        : 1;
@@ -48,14 +58,24 @@ union P025_config_register {
     uint16_t MUX             : 3;
     uint16_t operatingStatus : 1;
   };
-  uint16_t _regval = 0x8000;
 
+  P025_config_register(uint16_t regval) {
+    memcpy(this, ®val, sizeof(uint16_t));
+  }
 
-  P025_config_register(uint16_t regval) : _regval(regval) {}
+  void setRegval(uint16_t regval) {
+    memcpy(this, ®val, sizeof(uint16_t));
+  }
+
+  uint16_t getRegval() const {
+    uint16_t regval{};
+    memcpy(®val, this, sizeof(uint16_t));
+    return regval;
+  }
 
   String toString() const {
     return strformat(F("reg: %X OS: %d MUX: %d PGA: %d mode: %d DR: %d"),
-                     _regval, operatingStatus, MUX, PGA, mode, datarate
+                     getRegval(), operatingStatus, MUX, PGA, mode, datarate
                      );
   }
 };
@@ -78,7 +98,7 @@ P025_data_struct::P025_data_struct(struct EventStruct *event) {
 
   reg.datarate         = p025_variousBits.getSampleRate();
   reg.PGA              = P025_GAIN;
-  _configRegisterValue = reg._regval;
+  _configRegisterValue = reg.getRegval();
 
   _fullScaleFactor = 1.0f;
 
@@ -105,11 +125,11 @@ bool P025_data_struct::read(float& value, taskVarIndex_t index) const {
 
   reg.MUX = _mux[index];
 
-  if (!startMeasurement(_i2cAddress, reg._regval)) {
+  if (!startMeasurement(_i2cAddress, reg.getRegval())) {
     return false;
   }
 
-  if (!I2C_write16_reg(_i2cAddress, P025_CONFIG_REGISTER, reg._regval)) {
+  if (!I2C_write16_reg(_i2cAddress, P025_CONFIG_REGISTER, reg.getRegval())) {
 # ifndef BUILD_NO_DEBUG
 
     if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
@@ -261,7 +281,7 @@ long P025_data_struct::waitReady025(uint8_t i2cAddress)
     delay(0);
 
     // Address Pointer Register is the same, so only need to read bytes again
-    reg._regval = I2C_read16(i2cAddress, &is_ok);
+    reg.setRegval(I2C_read16(i2cAddress, &is_ok));
   }
 
 # ifndef BUILD_NO_DEBUG
@@ -350,7 +370,7 @@ bool P025_data_struct::webformLoad(struct EventStruct *event)
 
 bool P025_data_struct::webformSave(struct EventStruct *event)
 {
-  for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; i++) {
+  for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; ++i) {
     const uint8_t pconfigIndex = P025_PCONFIG_INDEX(i);
     const uint8_t choice       = PCONFIG(pconfigIndex);
     sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i,
@@ -366,7 +386,7 @@ bool P025_data_struct::webformSave(struct EventStruct *event)
   p025_variousBits.setSampleRate(getFormItemInt(F("sps")));
   p025_variousBits.outputVolt = isFormItemChecked(F("volt"));
   p025_variousBits.cal        = isFormItemChecked(F("cal"));
-  P025_VARIOUS_BITS           = p025_variousBits.pconfigvalue;
+  P025_VARIOUS_BITS           = p025_variousBits.pconfigvalue();
 
   P025_CAL_ADC1 = getFormItemInt(F("adc1"));
   P025_CAL_OUT1 = getFormItemFloat(F("out1"));
@@ -381,7 +401,7 @@ bool P025_data_struct::webform_showConfig(struct EventStruct *event)
 {
   format_I2C_port_description(event->TaskIndex);
 
-  for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; i++) {
+  for (uint8_t i = 0; i < P025_NR_OUTPUT_VALUES; ++i) {
     const uint8_t choice = PCONFIG(P025_PCONFIG_INDEX(i));
 
     if ((choice >= 0) && (choice < 8)) {
diff --git a/src/src/PluginStructs/P025_data_struct.h b/src/src/PluginStructs/P025_data_struct.h
index a96d9614e..6730a0ff5 100644
--- a/src/src/PluginStructs/P025_data_struct.h
+++ b/src/src/PluginStructs/P025_data_struct.h
@@ -5,7 +5,7 @@
 #ifdef USES_P025
 
 
-union P025_VARIOUS_BITS_t {
+struct P025_VARIOUS_BITS_t {
   struct {
     uint16_t cal           : 1;
     uint16_t outputVolt    : 1;
@@ -13,9 +13,10 @@ union P025_VARIOUS_BITS_t {
     uint16_t sampleRate    : 3;
     uint16_t unused        : 10;
   };
-  int16_t pconfigvalue{};
 
-  P025_VARIOUS_BITS_t(int16_t value) : pconfigvalue(value) {}
+  P025_VARIOUS_BITS_t(int16_t value);
+
+  int16_t pconfigvalue() const;
 
   uint16_t getSampleRate() const {
     if (sampleRateSet) { return sampleRate; }
@@ -35,7 +36,7 @@ union P025_VARIOUS_BITS_t {
 // - PCONFIG(6)
 // - PCONFIG(7)
 # define P025_SENSOR_TYPE_INDEX 4 // Storing the output selector
-# define P025_PCONFIG_INDEX(x) ((x == 0) ? 2 : x + P025_SENSOR_TYPE_INDEX)
+# define P025_PCONFIG_INDEX(x) (((x) == 0) ? 2 : (x) + P025_SENSOR_TYPE_INDEX)
 
 # define P025_NR_OUTPUT_VALUES   getValueCountFromSensorType(static_cast(PCONFIG(P025_SENSOR_TYPE_INDEX)))
 
diff --git a/src/src/PluginStructs/P026_data_struct.cpp b/src/src/PluginStructs/P026_data_struct.cpp
index f72afd999..280ef56e3 100644
--- a/src/src/PluginStructs/P026_data_struct.cpp
+++ b/src/src/PluginStructs/P026_data_struct.cpp
@@ -1,290 +1,290 @@
-#include "../PluginStructs/P026_data_struct.h"
-
-#ifdef USES_P026
-
-# include "../DataStructs/ESPEasy_packed_raw_data.h"
-# include "../ESPEasyCore/ESPEasyNetwork.h"
-# include "../Globals/ESPEasyWiFiEvent.h"
-# include "../Helpers/Memory.h"
-# include "../Helpers/Hardware_temperature_sensor.h"
-# ifdef ESP32
-#  include "../Helpers/Hardware_device_info.h"
-
-# endif // ifdef ESP32
-
-# include "ESPEasy-Globals.h"
-
-// Do not change assigned values as they are stored
-// Shown selection and its order can be set in P026_value_option_indices
-// These P026_VALUETYPE_xxx values should represent the index in the p026_valuenames array
-# define P026_VALUETYPE_uptime       0
-# define P026_VALUETYPE_freeheap     1
-# define P026_VALUETYPE_rssi         2
-# define P026_VALUETYPE_vcc          3
-# define P026_VALUETYPE_load         4
-# define P026_VALUETYPE_ip1          5
-# define P026_VALUETYPE_ip2          6
-# define P026_VALUETYPE_ip3          7
-# define P026_VALUETYPE_ip4          8
-# define P026_VALUETYPE_web          9
-# define P026_VALUETYPE_freestack    10
-# define P026_VALUETYPE_none         11
-# define P026_VALUETYPE_txpwr        12
-# define P026_VALUETYPE_free2ndheap  13
-# define P026_VALUETYPE_internaltemp 14
-# define P026_VALUETYPE_freepsram    15
-
-
-const __FlashStringHelper* Plugin_026_valuename(uint8_t value_nr, bool displayString) {
-  const __FlashStringHelper *p026_valuenames[] {
-    F("Uptime"), F("uptime"),
-    F("Free RAM"), F("freeheap"),
-    F("Wifi RSSI"), F("rssi"),
-    F("Input VCC"), F("vcc"),
-    F("System load"), F("load"),
-    F("IP 1.Octet"), F("ip1"),
-    F("IP 2.Octet"), F("ip2"),
-    F("IP 3.Octet"), F("ip3"),
-    F("IP 4.Octet"), F("ip4"),
-    F("Web activity"), F("web"),
-    F("Free Stack"), F("freestack"),
-    F("None"), F(""),
-    F("WiFi TX pwr"), F("txpwr"),
-# ifdef USE_SECOND_HEAP
-    F("Free 2nd Heap"), F("free2ndheap"),
-# else // ifdef USE_SECOND_HEAP
-    F(""), F(""), // Must keep the same indexes
-# endif // ifdef USE_SECOND_HEAP
-# if FEATURE_INTERNAL_TEMPERATURE
-    F("Internal temperature (ESP32)"), F("internaltemp"),
-# else // if FEATURE_INTERNAL_TEMPERATURE
-    F(""), F(""), // Must keep the same indexes
-# endif // if FEATURE_INTERNAL_TEMPERATURE
-
-# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-    F("Free PSRAM"), F("freepsram"),
-# else // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-    F(""), F(""), // Must keep the same indexes
-# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-  };
-
-  const size_t index         = (2 * value_nr) + (displayString ? 0 : 1);
-  constexpr size_t nrStrings = NR_ELEMENTS(p026_valuenames);
-
-  if (index < nrStrings) {
-    return p026_valuenames[index];
-  }
-  return F("");
-}
-
-// List of options in the order how they will be shown in the plugin selector.
-const int P026_value_option_indices[] = {
-  P026_VALUETYPE_none, // Have the "none" option as first option
-
-  P026_VALUETYPE_uptime,
-  P026_VALUETYPE_load,
-  P026_VALUETYPE_freeheap,
-# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-  P026_VALUETYPE_freepsram,
-# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-# ifdef USE_SECOND_HEAP
-  P026_VALUETYPE_free2ndheap,
-# endif // ifdef USE_SECOND_HEAP
-  P026_VALUETYPE_freestack,
-  P026_VALUETYPE_rssi,
-  P026_VALUETYPE_txpwr,
-# if FEATURE_ADC_VCC
-  P026_VALUETYPE_vcc,
-# endif // if FEATURE_ADC_VCC
-# if FEATURE_INTERNAL_TEMPERATURE
-  P026_VALUETYPE_internaltemp,
-# endif // if FEATURE_INTERNAL_TEMPERATURE
-  P026_VALUETYPE_ip1,
-  P026_VALUETYPE_ip2,
-  P026_VALUETYPE_ip3,
-  P026_VALUETYPE_ip4,
-  P026_VALUETYPE_web,
-};
-
-
-float P026_get_value(uint8_t type)
-{
-  float res{};
-
-  switch (type)
-  {
-    case P026_VALUETYPE_uptime:   res = getUptimeMinutes(); break;
-    case P026_VALUETYPE_freeheap: res = FreeMem(); break;
-    case P026_VALUETYPE_rssi:     res = WiFi.RSSI(); break;
-    case P026_VALUETYPE_vcc:
-# if FEATURE_ADC_VCC
-      res = vcc;
-# else // if FEATURE_ADC_VCC
-      res = -1.0f;
-# endif // if FEATURE_ADC_VCC
-      break;
-    case P026_VALUETYPE_load: res = getCPUload(); break;
-    case P026_VALUETYPE_ip1:
-    case P026_VALUETYPE_ip2:
-    case P026_VALUETYPE_ip3:
-    case P026_VALUETYPE_ip4:
-      res = NetworkLocalIP()[type - 5];
-      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;
-# ifdef USE_SECOND_HEAP
-    case P026_VALUETYPE_free2ndheap:
-      res = FreeMem2ndHeap();
-      break;
-# endif // ifdef USE_SECOND_HEAP
-# if FEATURE_INTERNAL_TEMPERATURE
-    case P026_VALUETYPE_internaltemp:
-      res = getInternalTemperature();
-      break;
-# endif // if FEATURE_INTERNAL_TEMPERATURE
-# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-    case P026_VALUETYPE_freepsram:
-
-      if (UsePSRAM()) {
-        res = ESP.getFreePsram();
-      }
-      break;
-# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
-  }
-  return res;
-}
-
-bool P026_data_struct::GetDeviceValueNames(struct EventStruct *event)
-{
-  const int valueCount = P026_NR_OUTPUT_VALUES;
-
-  for (uint8_t i = 0; i < VARS_PER_TASK; ++i) {
-    if (i < valueCount) {
-      const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
-      ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_026_valuename(PCONFIG(pconfigIndex), false));
-    } else {
-      ExtraTaskSettings.clearTaskDeviceValueName(i);
-    }
-  }
-  return true;
-}
-
-bool P026_data_struct::WebformLoadOutputSelector(struct EventStruct *event)
-{
-  constexpr size_t NrOptions = NR_ELEMENTS(P026_value_option_indices);
-
-  const __FlashStringHelper *options[NrOptions];
-
-  for (uint8_t index = 0; index < NrOptions; ++index) {
-    options[index] = Plugin_026_valuename(P026_value_option_indices[index], true);
-  }
-
-  const int valueCount = P026_NR_OUTPUT_VALUES;
-
-  for (uint8_t i = 0; i < valueCount; ++i) {
-    const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
-    sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, NrOptions, options, P026_value_option_indices);
-  }
-  return true;
-}
-
-bool P026_data_struct::WebformSave(struct EventStruct *event)
-{
-  // Save output selector parameters.
-  const int valueCount = P026_NR_OUTPUT_VALUES;
-
-  for (uint8_t i = 0; i < valueCount; ++i) {
-    const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
-    const uint8_t choice       = PCONFIG(pconfigIndex);
-    sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, Plugin_026_valuename(choice, false));
-  }
-  return true;
-}
-
-bool P026_data_struct::Plugin_Read(struct EventStruct *event)
-{
-  const int valueCount = P026_NR_OUTPUT_VALUES;
-
-  for (int i = 0; i < valueCount; ++i) {
-    UserVar.setFloat(event->TaskIndex, i,  P026_get_value(PCONFIG(i)));
-  }
-      # ifndef LIMIT_BUILD_SIZE
-
-  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    String log;
-
-    if (log.reserve(7 * (valueCount + 1)))
-    {
-      log += F("SYS  : ");
-
-      for (int i = 0; i < valueCount; ++i) {
-        if (i != 0) {
-          log += ',';
-        }
-        log += formatUserVarNoCheck(event->TaskIndex, i);
-      }
-      addLogMove(LOG_LEVEL_INFO, log);
-    }
-  }
-      # endif // ifndef LIMIT_BUILD_SIZE
-  return true;
-}
-
-# ifndef PLUGIN_BUILD_MINIMAL_OTA
-bool P026_data_struct::Plugin_GetConfigValue(struct EventStruct *event, String& string)
-{
-  bool success     = false;
-  const String cmd = parseString(string, 1);
-
-  constexpr size_t P026_NR_OUTPUT_OPTIONS = NR_ELEMENTS(P026_value_option_indices);
-
-  for (uint8_t option = 0; option < P026_NR_OUTPUT_OPTIONS; ++option) {
-    if ((P026_value_option_indices[option] != P026_VALUETYPE_none) &&
-        equals(cmd, Plugin_026_valuename(P026_value_option_indices[option], false))) {     // Use default valuename
-      string  = floatToString(P026_get_value(P026_value_option_indices[option]), 2, true); // Trim trailing zeroes
-      success = true;
-      break;
-    }
-  }
-
-  return success;
-}
-
-# endif // ifndef PLUGIN_BUILD_MINIMAL_OTA
-
-
-# if FEATURE_PACKED_RAW_DATA
-bool P026_data_struct::Plugin_GetPackedRawData(struct EventStruct *event, String& string)
-{
-  // Matching JS code:
-  // return decode(bytes,
-  //  [header, uint24, uint24, int8, vcc, pct_8, uint8, uint8, uint8, uint8, uint24, uint16],
-  //  ['header', 'uptime', 'freeheap', 'rssi', 'vcc', 'load', 'ip1', 'ip2', 'ip3', 'ip4', 'web', 'freestack']);
-  // on ESP32 you can add 'internaltemperature' of type int16 (1e2) to the list (disabled for now, so not available)
-  uint8_t index = 0;
-
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // uptime
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // freeheap
-  string += LoRa_addFloat(P026_get_value(index++), PackedData_int8);  // rssi
-  string += LoRa_addFloat(P026_get_value(index++), PackedData_vcc);   // vcc
-  string += LoRa_addFloat(P026_get_value(index++), PackedData_pct_8); // load
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip1
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip2
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip3
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip4
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // web
-  string += LoRa_addInt(P026_get_value(index++), PackedData_uint16);  // freestack
-  // #  if FEATURE_INTERNAL_TEMPERATURE
-  // string += LoRa_addInt(P026_get_value(index++) * 100.0f, PackedData_int16_1e2); // internal temperature in 0.01 degrees
-  // #  endif // if FEATURE_INTERNAL_TEMPERATURE
-  event->Par1 = index; // valuecount
-  return true;
-}
-
-# endif // if FEATURE_PACKED_RAW_DATA
-
-
-#endif  // ifdef USES_P026
+#include "../PluginStructs/P026_data_struct.h"
+
+#ifdef USES_P026
+
+# include "../DataStructs/ESPEasy_packed_raw_data.h"
+# include "../ESPEasyCore/ESPEasyNetwork.h"
+# include "../Globals/ESPEasyWiFiEvent.h"
+# include "../Helpers/Memory.h"
+# include "../Helpers/Hardware_temperature_sensor.h"
+# ifdef ESP32
+#  include "../Helpers/Hardware_device_info.h"
+
+# endif // ifdef ESP32
+
+# include "ESPEasy-Globals.h"
+
+// Do not change assigned values as they are stored
+// Shown selection and its order can be set in P026_value_option_indices
+// These P026_VALUETYPE_xxx values should represent the index in the p026_valuenames array
+# define P026_VALUETYPE_uptime       0
+# define P026_VALUETYPE_freeheap     1
+# define P026_VALUETYPE_rssi         2
+# define P026_VALUETYPE_vcc          3
+# define P026_VALUETYPE_load         4
+# define P026_VALUETYPE_ip1          5
+# define P026_VALUETYPE_ip2          6
+# define P026_VALUETYPE_ip3          7
+# define P026_VALUETYPE_ip4          8
+# define P026_VALUETYPE_web          9
+# define P026_VALUETYPE_freestack    10
+# define P026_VALUETYPE_none         11
+# define P026_VALUETYPE_txpwr        12
+# define P026_VALUETYPE_free2ndheap  13
+# define P026_VALUETYPE_internaltemp 14
+# define P026_VALUETYPE_freepsram    15
+
+
+const __FlashStringHelper* Plugin_026_valuename(uint8_t value_nr, bool displayString) {
+  const __FlashStringHelper *p026_valuenames[] {
+    F("Uptime"), F("uptime"),
+    F("Free RAM"), F("freeheap"),
+    F("Wifi RSSI"), F("rssi"),
+    F("Input VCC"), F("vcc"),
+    F("System load"), F("load"),
+    F("IP 1.Octet"), F("ip1"),
+    F("IP 2.Octet"), F("ip2"),
+    F("IP 3.Octet"), F("ip3"),
+    F("IP 4.Octet"), F("ip4"),
+    F("Web activity"), F("web"),
+    F("Free Stack"), F("freestack"),
+    F("None"), F(""),
+    F("WiFi TX pwr"), F("txpwr"),
+# ifdef USE_SECOND_HEAP
+    F("Free 2nd Heap"), F("free2ndheap"),
+# else // ifdef USE_SECOND_HEAP
+    F(""), F(""), // Must keep the same indexes
+# endif // ifdef USE_SECOND_HEAP
+# if FEATURE_INTERNAL_TEMPERATURE
+    F("Internal temperature (ESP32)"), F("internaltemp"),
+# else // if FEATURE_INTERNAL_TEMPERATURE
+    F(""), F(""), // Must keep the same indexes
+# endif // if FEATURE_INTERNAL_TEMPERATURE
+
+# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+    F("Free PSRAM"), F("freepsram"),
+# else // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+    F(""), F(""), // Must keep the same indexes
+# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+  };
+
+  const size_t index         = (2 * value_nr) + (displayString ? 0 : 1);
+  constexpr size_t nrStrings = NR_ELEMENTS(p026_valuenames);
+
+  if (index < nrStrings) {
+    return p026_valuenames[index];
+  }
+  return F("");
+}
+
+// List of options in the order how they will be shown in the plugin selector.
+const int P026_value_option_indices[] = {
+  P026_VALUETYPE_none, // Have the "none" option as first option
+
+  P026_VALUETYPE_uptime,
+  P026_VALUETYPE_load,
+  P026_VALUETYPE_freeheap,
+# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+  P026_VALUETYPE_freepsram,
+# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+# ifdef USE_SECOND_HEAP
+  P026_VALUETYPE_free2ndheap,
+# endif // ifdef USE_SECOND_HEAP
+  P026_VALUETYPE_freestack,
+  P026_VALUETYPE_rssi,
+  P026_VALUETYPE_txpwr,
+# if FEATURE_ADC_VCC
+  P026_VALUETYPE_vcc,
+# endif // if FEATURE_ADC_VCC
+# if FEATURE_INTERNAL_TEMPERATURE
+  P026_VALUETYPE_internaltemp,
+# endif // if FEATURE_INTERNAL_TEMPERATURE
+  P026_VALUETYPE_ip1,
+  P026_VALUETYPE_ip2,
+  P026_VALUETYPE_ip3,
+  P026_VALUETYPE_ip4,
+  P026_VALUETYPE_web,
+};
+
+
+float P026_get_value(uint8_t type)
+{
+  float res{};
+
+  switch (type)
+  {
+    case P026_VALUETYPE_uptime:   res = getUptimeMinutes(); break;
+    case P026_VALUETYPE_freeheap: res = FreeMem(); break;
+    case P026_VALUETYPE_rssi:     res = WiFi.RSSI(); break;
+    case P026_VALUETYPE_vcc:
+# if FEATURE_ADC_VCC
+      res = vcc;
+# else // if FEATURE_ADC_VCC
+      res = -1.0f;
+# endif // if FEATURE_ADC_VCC
+      break;
+    case P026_VALUETYPE_load: res = getCPUload(); break;
+    case P026_VALUETYPE_ip1:
+    case P026_VALUETYPE_ip2:
+    case P026_VALUETYPE_ip3:
+    case P026_VALUETYPE_ip4:
+      res = 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;
+# ifdef USE_SECOND_HEAP
+    case P026_VALUETYPE_free2ndheap:
+      res = FreeMem2ndHeap();
+      break;
+# endif // ifdef USE_SECOND_HEAP
+# if FEATURE_INTERNAL_TEMPERATURE
+    case P026_VALUETYPE_internaltemp:
+      res = getInternalTemperature();
+      break;
+# endif // if FEATURE_INTERNAL_TEMPERATURE
+# if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+    case P026_VALUETYPE_freepsram:
+
+      if (UsePSRAM()) {
+        res = ESP.getFreePsram();
+      }
+      break;
+# endif // if defined(ESP32) && defined(BOARD_HAS_PSRAM)
+  }
+  return res;
+}
+
+bool P026_data_struct::GetDeviceValueNames(struct EventStruct *event)
+{
+  const int valueCount = P026_NR_OUTPUT_VALUES;
+
+  for (uint8_t i = 0; i < VARS_PER_TASK; ++i) {
+    if (i < valueCount) {
+      const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
+      ExtraTaskSettings.setTaskDeviceValueName(i, Plugin_026_valuename(PCONFIG(pconfigIndex), false));
+    } else {
+      ExtraTaskSettings.clearTaskDeviceValueName(i);
+    }
+  }
+  return true;
+}
+
+bool P026_data_struct::WebformLoadOutputSelector(struct EventStruct *event)
+{
+  constexpr size_t NrOptions = NR_ELEMENTS(P026_value_option_indices);
+
+  const __FlashStringHelper *options[NrOptions];
+
+  for (uint8_t index = 0; index < NrOptions; ++index) {
+    options[index] = Plugin_026_valuename(P026_value_option_indices[index], true);
+  }
+
+  const int valueCount = P026_NR_OUTPUT_VALUES;
+
+  for (uint8_t i = 0; i < valueCount; ++i) {
+    const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
+    sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, NrOptions, options, P026_value_option_indices);
+  }
+  return true;
+}
+
+bool P026_data_struct::WebformSave(struct EventStruct *event)
+{
+  // Save output selector parameters.
+  const int valueCount = P026_NR_OUTPUT_VALUES;
+
+  for (uint8_t i = 0; i < valueCount; ++i) {
+    const uint8_t pconfigIndex = i + P026_QUERY1_CONFIG_POS;
+    const uint8_t choice       = PCONFIG(pconfigIndex);
+    sensorTypeHelper_saveOutputSelector(event, pconfigIndex, i, Plugin_026_valuename(choice, false));
+  }
+  return true;
+}
+
+bool P026_data_struct::Plugin_Read(struct EventStruct *event)
+{
+  const int valueCount = P026_NR_OUTPUT_VALUES;
+
+  for (int i = 0; i < valueCount; ++i) {
+    UserVar.setFloat(event->TaskIndex, i,  P026_get_value(PCONFIG(i)));
+  }
+      # ifndef LIMIT_BUILD_SIZE
+
+  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+    String log;
+
+    if (log.reserve(7 * (valueCount + 1)))
+    {
+      log += F("SYS  : ");
+
+      for (int i = 0; i < valueCount; ++i) {
+        if (i != 0) {
+          log += ',';
+        }
+        log += formatUserVarNoCheck(event, i);
+      }
+      addLogMove(LOG_LEVEL_INFO, log);
+    }
+  }
+      # endif // ifndef LIMIT_BUILD_SIZE
+  return true;
+}
+
+# ifndef PLUGIN_BUILD_MINIMAL_OTA
+bool P026_data_struct::Plugin_GetConfigValue(struct EventStruct *event, String& string)
+{
+  bool success     = false;
+  const String cmd = parseString(string, 1);
+
+  constexpr size_t P026_NR_OUTPUT_OPTIONS = NR_ELEMENTS(P026_value_option_indices);
+
+  for (uint8_t option = 0; option < P026_NR_OUTPUT_OPTIONS; ++option) {
+    if ((P026_value_option_indices[option] != P026_VALUETYPE_none) &&
+        equals(cmd, Plugin_026_valuename(P026_value_option_indices[option], false))) {     // Use default valuename
+      string  = floatToString(P026_get_value(P026_value_option_indices[option]), 2, true); // Trim trailing zeroes
+      success = true;
+      break;
+    }
+  }
+
+  return success;
+}
+
+# endif // ifndef PLUGIN_BUILD_MINIMAL_OTA
+
+
+# if FEATURE_PACKED_RAW_DATA
+bool P026_data_struct::Plugin_GetPackedRawData(struct EventStruct *event, String& string)
+{
+  // Matching JS code:
+  // return decode(bytes,
+  //  [header, uint24, uint24, int8, vcc, pct_8, uint8, uint8, uint8, uint8, uint24, uint16],
+  //  ['header', 'uptime', 'freeheap', 'rssi', 'vcc', 'load', 'ip1', 'ip2', 'ip3', 'ip4', 'web', 'freestack']);
+  // on ESP32 you can add 'internaltemperature' of type int16 (1e2) to the list (disabled for now, so not available)
+  uint8_t index = 0;
+
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // uptime
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // freeheap
+  string += LoRa_addFloat(P026_get_value(index++), PackedData_int8);  // rssi
+  string += LoRa_addFloat(P026_get_value(index++), PackedData_vcc);   // vcc
+  string += LoRa_addFloat(P026_get_value(index++), PackedData_pct_8); // load
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip1
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip2
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip3
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint8);   // ip4
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint24);  // web
+  string += LoRa_addInt(P026_get_value(index++), PackedData_uint16);  // freestack
+  // #  if FEATURE_INTERNAL_TEMPERATURE
+  // string += LoRa_addInt(P026_get_value(index++) * 100.0f, PackedData_int16_1e2); // internal temperature in 0.01 degrees
+  // #  endif // if FEATURE_INTERNAL_TEMPERATURE
+  event->Par1 = index; // valuecount
+  return true;
+}
+
+# endif // if FEATURE_PACKED_RAW_DATA
+
+
+#endif  // ifdef USES_P026
diff --git a/src/src/PluginStructs/P027_data_struct.cpp b/src/src/PluginStructs/P027_data_struct.cpp
index 4e61bf196..07f937009 100644
--- a/src/src/PluginStructs/P027_data_struct.cpp
+++ b/src/src/PluginStructs/P027_data_struct.cpp
@@ -112,6 +112,25 @@ void P027_data_struct::setCalibration_16V_400mA() {
   wireWriteRegister(INA219_REG_CONFIG, config);
 }
 
+void P027_data_struct::setCalibration_26V_8A() {
+  calValue = 4096;
+
+  // Set multipliers to convert raw current/power values
+  currentDivider_mA = 1;
+
+  // Set Calibration register to 'Cal' calculated above
+  wireWriteRegister(INA219_REG_CALIBRATION, calValue);
+
+  // Set Config register to take into account the settings above
+  uint16_t config = INA219_CONFIG_BVOLTAGERANGE_32V |
+                    INA219_CONFIG_GAIN_8_320MV |
+                    INA219_CONFIG_BADCRES_12BIT |
+                    INA219_CONFIG_SADCRES_12BIT_128S_69MS |
+                    INA219_CONFIG_MODE_SANDBVOLT_CONTINUOUS;
+
+  wireWriteRegister(INA219_REG_CONFIG, config);
+}
+
 int16_t P027_data_struct::getBusVoltage_raw() {
   uint16_t value;
 
diff --git a/src/src/PluginStructs/P027_data_struct.h b/src/src/PluginStructs/P027_data_struct.h
index adf514773..c00d5f977 100644
--- a/src/src/PluginStructs/P027_data_struct.h
+++ b/src/src/PluginStructs/P027_data_struct.h
@@ -30,6 +30,11 @@ public:
   // **************************************************************************/
   void setCalibration_16V_400mA();
 
+  // **************************************************************************/
+  // Configures to INA219 to be able to measure up to 26V and 8A
+  // **************************************************************************/
+  void setCalibration_26V_8A();
+
 private:
 
   // **************************************************************************/
diff --git a/src/src/PluginStructs/P028_data_struct.cpp b/src/src/PluginStructs/P028_data_struct.cpp
index eac101d80..4da591d4e 100644
--- a/src/src/PluginStructs/P028_data_struct.cpp
+++ b/src/src/PluginStructs/P028_data_struct.cpp
@@ -126,9 +126,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
 
     // There is some offset to apply.
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      log += F(" Apply temp offset: ");
-      log += temp_offset;
-      log += 'C';
+      log += strformat(F(" Apply temp offset: %.2fC"), temp_offset);
     }
     # endif // ifndef LIMIT_BUILD_SIZE
 
@@ -136,8 +134,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
       # ifndef LIMIT_BUILD_SIZE
 
       if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        log += F(" humidity: ");
-        log += last_hum_val;
+        log += concat(F(" humidity: "), last_hum_val);
       }
       # endif // ifndef LIMIT_BUILD_SIZE
       last_hum_val = compute_humidity_from_dewpoint(last_temp_val + temp_offset, last_dew_temp_val);
@@ -145,9 +142,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
       # ifndef LIMIT_BUILD_SIZE
 
       if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        log += F("% => ");
-        log += last_hum_val;
-        log += F("%");
+        log += strformat(F("%% => %.2f%%"), last_hum_val);
       }
       # endif // ifndef LIMIT_BUILD_SIZE
     } else {
@@ -157,8 +152,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
 # ifndef LIMIT_BUILD_SIZE
 
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      log += F(" temperature: ");
-      log += last_temp_val;
+      log += concat(F(" temperature: "), last_temp_val);
     }
 # endif // ifndef LIMIT_BUILD_SIZE
     last_temp_val = last_temp_val + temp_offset;
@@ -166,9 +160,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
 # ifndef LIMIT_BUILD_SIZE
 
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      log     += F("C => ");
-      log     += last_temp_val;
-      log     += 'C';
+      log     += strformat(F("C => %.2fC"), last_temp_val);
       logAdded = true;
     }
 # endif // ifndef LIMIT_BUILD_SIZE
@@ -178,9 +170,7 @@ bool P028_data_struct::updateMeasurements(taskIndex_t task_index) {
 
   if (hasHumidity()) {
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      log     += F(" dew point: ");
-      log     += last_dew_temp_val;
-      log     += 'C';
+      log     += strformat(F(" dew point: %.2fC"), last_dew_temp_val);
       logAdded = true;
     }
   }
@@ -213,9 +203,7 @@ bool P028_data_struct::check() {
           setUninitialized();
 
           if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-            String log = F("BMx280: Detected ");
-            log += getDeviceName(sensorID);
-            addLogMove(LOG_LEVEL_INFO, log);
+            addLog(LOG_LEVEL_INFO, concat(F("BMx280: Detected "), getDeviceName(sensorID)));
           }
         }
       } else {
@@ -230,8 +218,7 @@ bool P028_data_struct::check() {
 
   if (sensorID == Unknown_DEVICE) {
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log = F("BMx280: Unable to detect chip ID (");
-      log += chip_id;
+      String log = concat(F("BMx280: Unable to detect chip ID ("), chip_id);
 
       if (!wire_status) {
         log += F(", failed");
diff --git a/src/src/PluginStructs/P031_data_struct.h b/src/src/PluginStructs/P031_data_struct.h
index ab78b665e..4a1e2c121 100644
--- a/src/src/PluginStructs/P031_data_struct.h
+++ b/src/src/PluginStructs/P031_data_struct.h
@@ -23,10 +23,10 @@ class P031_data_struct : public PluginTaskData_base {
 public:
 
   enum {
-    SHT1X_CMD_MEASURE_TEMP = B00000011,
-    SHT1X_CMD_MEASURE_RH   = B00000101,
-    SHT1X_CMD_READ_STATUS  = B00000111,
-    SHT1X_CMD_SOFT_RESET   = B00011110
+    SHT1X_CMD_MEASURE_TEMP = 0b00000011,
+    SHT1X_CMD_MEASURE_RH   = 0b00000101,
+    SHT1X_CMD_READ_STATUS  = 0b00000111,
+    SHT1X_CMD_SOFT_RESET   = 0b00011110
   };
 
   P031_data_struct() = default;
diff --git a/src/src/PluginStructs/P032_data_struct.cpp b/src/src/PluginStructs/P032_data_struct.cpp
index 81f7f425a..ac898f655 100644
--- a/src/src/PluginStructs/P032_data_struct.cpp
+++ b/src/src/PluginStructs/P032_data_struct.cpp
@@ -2,8 +2,6 @@
 
 #ifdef USES_P032
 
-#include "../Globals/I2Cdev.h"
-
 
 enum
 {
@@ -88,16 +86,18 @@ void P032_data_struct::readout() {
   D1 = read_adc(MS5xxx_CMD_ADC_D1 + MS5xxx_CMD_ADC_4096);
 
   // calculate 1st order pressure and temperature (MS5611 1st order algorithm)
-  dT                 = D2 - ms5611_prom[5] * static_cast(1 << 8);
-  Offset             = ms5611_prom[2] * static_cast(1 << 16) + dT * ms5611_prom[4] / static_cast(1 << 7);
-  SENS               = ms5611_prom[1] * static_cast(1 << 15) + dT * ms5611_prom[3] / static_cast(1 << 8);
+  dT     = D2 - ms5611_prom[5] * static_cast(1 << 8);
+  Offset = ms5611_prom[2] *
+           static_cast(1 << 16) + dT * ms5611_prom[4] / static_cast(1 << 7);
+  SENS = ms5611_prom[1] *
+         static_cast(1 << 15) + dT * ms5611_prom[3] / static_cast(1 << 8);
   ms5611_temperature = (2000 + (dT * ms5611_prom[6]) / static_cast(1 << 23));
 
   // perform higher order corrections
   ESPEASY_RULES_FLOAT_TYPE T2 = 0., OFF2 = 0., SENS2 = 0.;
 
   if (ms5611_temperature < 2000) {
-    T2    = dT * dT / static_cast(1 << 31);
+    T2 = dT * dT / static_cast(1 << 31);
     const ESPEASY_RULES_FLOAT_TYPE temp_20deg = ms5611_temperature - 2000;
     OFF2  = 5.0 * temp_20deg * temp_20deg / static_cast(1 << 1);
     SENS2 = 5.0 * temp_20deg * temp_20deg / static_cast(1 << 2);
@@ -112,8 +112,8 @@ void P032_data_struct::readout() {
   ms5611_temperature -= T2;
   Offset             -= OFF2;
   SENS               -= SENS2;
-  ms5611_pressure     = (((D1 * SENS) / static_cast(1 << 21) - Offset) / static_cast(1 << 15));
+  ms5611_pressure     =
+    (((D1 * SENS) / static_cast(1 << 21) - Offset) / static_cast(1 << 15));
 }
 
-
 #endif // ifdef USES_P032
diff --git a/src/src/PluginStructs/P032_data_struct.h b/src/src/PluginStructs/P032_data_struct.h
index 5ed36af4d..3b035b27f 100644
--- a/src/src/PluginStructs/P032_data_struct.h
+++ b/src/src/PluginStructs/P032_data_struct.h
@@ -8,11 +8,10 @@ struct P032_data_struct : public PluginTaskData_base {
 public:
 
   P032_data_struct(uint8_t i2c_addr);
-  P032_data_struct() = delete;
+  P032_data_struct()          = delete;
   virtual ~P032_data_struct() = default;
 
 
-
   // **************************************************************************/
   // Initialize MS5611
   // **************************************************************************/
@@ -38,10 +37,10 @@ public:
   // **************************************************************************/
   void readout();
 
-  uint8_t      i2cAddress;
-  unsigned int ms5611_prom[8]     = { 0 };
-  ESPEASY_RULES_FLOAT_TYPE       ms5611_pressure    = 0;
-  ESPEASY_RULES_FLOAT_TYPE       ms5611_temperature = 0;
+  uint8_t                  i2cAddress;
+  unsigned int             ms5611_prom[8]     = { 0 };
+  ESPEASY_RULES_FLOAT_TYPE ms5611_pressure    = 0;
+  ESPEASY_RULES_FLOAT_TYPE ms5611_temperature = 0;
 };
 #endif // ifdef USES_P032
 #endif // ifndef PLUGINSTRUCTS_P032_DATA_STRUCT_H
diff --git a/src/src/PluginStructs/P036_data_struct.cpp b/src/src/PluginStructs/P036_data_struct.cpp
index 366ed934d..431adef6d 100644
--- a/src/src/PluginStructs/P036_data_struct.cpp
+++ b/src/src/PluginStructs/P036_data_struct.cpp
@@ -82,7 +82,7 @@ String P036_LineContent::saveDisplayLines(taskIndex_t taskIndex) {
   // Since we're making several calls to save, make sure to consider this as a single save call.
   const uint8_t flashCounter = RTC.flashDayCounter;
 
-  for (int i = 0; i < P36_Nlines && error.length() == 0; ++i) {
+  for (int i = 0; i < P36_Nlines && error.isEmpty(); ++i) {
     tDisplayLines_storage tmp(DisplayLinesV1[i]);
     RTC.flashDayCounter = flashCounter;
     error               = SaveCustomTaskSettings(
@@ -144,19 +144,22 @@ const __FlashStringHelper * tFontSettings::FontName() const {
 // FIXME TD-er: with using functions to get the font, this object is stored in .dram0.data
 // The same as when using the DRAM_ATTR attribute used for interrupt code.
 // This is very precious memory, so we must find something other way to define this.
-const tFontSizes FontSizes[P36_MaxFontCount] = {
+
+/* *INDENT-OFF* */
+const tFontSizes FontSizes[] = {
   { getArialMT_Plain_24(), 24,  28                         }, // 9643
-# ifndef P036_LIMIT_BUILD_SIZE
+  # ifndef P036_LIMIT_BUILD_SIZE
   { getDialog_plain_18(),  19,  22                         }, // 7399
-# endif // ifndef P036_LIMIT_BUILD_SIZE
+  # endif // ifndef P036_LIMIT_BUILD_SIZE
   { getArialMT_Plain_16(), 16,  19                         }, // 5049
-# ifndef P036_LIMIT_BUILD_SIZE
+  # ifndef P036_LIMIT_BUILD_SIZE
   { getDialog_plain_12(),  13,  15                         }, // 3707
-# endif // ifndef P036_LIMIT_BUILD_SIZE
+  # endif // ifndef P036_LIMIT_BUILD_SIZE
   { getArialMT_Plain_10(), 10,  13                         }, // 2731
 };
+/* *INDENT-ON* */
 
-const tSizeSettings SizeSettings[P36_MaxSizesCount] = {
+constexpr tSizeSettings SizeSettings[] = {
   { P36_MaxDisplayWidth, P36_MaxDisplayHeight, 0,  // 128x64
     4,                                             // max. line count
     113, 15                                        // WiFi indicator
@@ -175,7 +178,7 @@ const tSizeSettings SizeSettings[P36_MaxSizesCount] = {
 const tSizeSettings& P036_data_struct::getDisplaySizeSettings(p036_resolution disp_resolution) {
   int index = static_cast(disp_resolution);
 
-  if ((index < 0) || (index >= P36_MaxSizesCount)) { index = 0; }
+  if ((index < 0) || (index >= static_cast(NR_ELEMENTS(SizeSettings)))) { index = 0; }
 
   return SizeSettings[index];
 }
@@ -254,11 +257,11 @@ bool P036_data_struct::init(taskIndex_t      taskIndex,
 
     setContrast(Contrast);
 
-    //      Display the device name, logo, time and wifi
+    // Display the device name, logo, time and wifi
     display_logo();
     update_display();
 
-    //    Initialize frame counter
+    // Initialize frame counter
     frameCounter                    = 0;
     currentFrameToDisplay           = 0;
     nextFrameToDisplay              = 0;
@@ -266,13 +269,13 @@ bool P036_data_struct::init(taskIndex_t      taskIndex,
     ScrollingPages.linesPerFrameDef = NrLines;
     bLineScrollEnabled              = false; // start without line scrolling
 
-    //    Clear scrolling line data
-    for (uint8_t i = 0; i < P36_MAX_LinesPerPage; i++) {
+    // Clear scrolling line data
+    for (uint8_t i = 0; i < P36_MAX_LinesPerPage; ++i) {
       ScrollingLines.SLine[i].Width     = 0;
       ScrollingLines.SLine[i].LastWidth = 0;
     }
 
-    //    prepare font and positions for page and line scrolling
+    // prepare font and positions for page and line scrolling
     prepare_pagescrolling(ScrollSpeed, NrLines);
   }
 
@@ -282,33 +285,33 @@ bool P036_data_struct::init(taskIndex_t      taskIndex,
 }
 
 const char p036_subcommands[] PROGMEM = "display|frame"
-# if P036_ENABLE_LINECOUNT
-"|linecount"
-#endif
-"|restore|scroll"
-# if P036_ENABLE_LEFT_ALIGN
-"|leftalign|align"
-#endif
-# if P036_USERDEF_HEADERS
-"|userdef1|userdef2"
-#endif
+                                        # if P036_ENABLE_LINECOUNT
+                                        "|linecount"
+                                        # endif // if P036_ENABLE_LINECOUNT
+                                        "|restore|scroll"
+                                        # if P036_ENABLE_LEFT_ALIGN
+                                        "|leftalign|align"
+                                        # endif // if P036_ENABLE_LEFT_ALIGN
+                                        # if P036_USERDEF_HEADERS
+                                        "|userdef1|userdef2"
+                                        # endif // if P036_USERDEF_HEADERS
 ;
 enum class p036_subcommands_e {
   display,
   frame,
-# if P036_ENABLE_LINECOUNT
+  # if P036_ENABLE_LINECOUNT
   linecount,
-#endif
+  # endif // if P036_ENABLE_LINECOUNT
   restore,
   scroll,
-# if P036_ENABLE_LEFT_ALIGN
+  # if P036_ENABLE_LEFT_ALIGN
   leftalign,
   align,
-#endif
-# if P036_USERDEF_HEADERS
+  # endif // if P036_ENABLE_LEFT_ALIGN
+  # if P036_USERDEF_HEADERS
   userdef1,
   userdef2
-#endif
+  # endif // if P036_USERDEF_HEADERS
 };
 
 
@@ -332,7 +335,7 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
   const bool sendEvents = bitRead(P036_FLAGS_0, P036_FLAG_SEND_EVENTS); // Bit 28 Send Events
       # endif // if P036_SEND_EVENTS
 
-  int  command_i = GetCommandCode(subcommand.c_str(), p036_subcommands);
+  int command_i = GetCommandCode(subcommand.c_str(), p036_subcommands);
 
   if (command_i == -1) {
     if ((LineNo > 0) && (LineNo <= P36_Nlines)) {
@@ -368,13 +371,14 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
       bUpdateDisplay = true;
     }
   } else {
+    success = true;
+
     switch (static_cast(command_i)) {
       case p036_subcommands_e::display: {
         // display functions
         const String para1 = parseString(string, 3);
 
         if (equals(para1, F("on"))) {
-          success      = true;
           displayTimer = P036_TIMER;
           display->displayOn();
 
@@ -388,7 +392,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
         }
 
         else if (equals(para1, F("off"))) {
-          success      = true;
           displayTimer = 0;
           display->displayOff();
 
@@ -402,7 +405,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
         }
 
         else if (equals(para1, F("low"))) {
-          success = true;
           setContrast(OLED_CONTRAST_LOW);
           LineNo     = 0; // is event parameter
           eventId    = P036_EVENT_CONTRAST;
@@ -410,7 +412,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
         }
 
         else if (equals(para1, F("med"))) {
-          success = true;
           setContrast(OLED_CONTRAST_MED);
           LineNo     = 1; // is event parameter
           eventId    = P036_EVENT_CONTRAST;
@@ -418,7 +419,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
         }
 
         else if (equals(para1, F("high"))) {
-          success = true;
           setContrast(OLED_CONTRAST_HIGH);
           LineNo     = 2; // is event parameter
           eventId    = P036_EVENT_CONTRAST;
@@ -430,12 +430,13 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
                  (event->Par4 >= 0) && (event->Par4 <= 255) && // precharge
                  (event->Par5 >= 0) && (event->Par5 <= 255))   // comdetect
         {
-          success = true;
           display->setContrast(static_cast(event->Par3), static_cast(event->Par4),
                                static_cast(event->Par5));
           LineNo     = 3; // is event parameter
           eventId    = P036_EVENT_CONTRAST;
           bDisplayON = true;
+        } else {
+          success = false;
         }
         break;
       }
@@ -443,8 +444,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
       {
         if ((event->Par2 >= 0) &&
             (event->Par2 <= MaxFramesToDisplay + 1)) {
-          success = true;
-
           if (!P036_DisplayIsOn) {
             // display was OFF, turn it ON
             display->displayOn();
@@ -456,8 +455,8 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             }
             # endif // if P036_SEND_EVENTS
           }
-          uint8_t nextFrame = (event->Par2 == 0 ? 0xFF : event->Par2 - 1);
-          P036_JumpToPage(event, nextFrame);                           //  Start to display the selected page, function needs
+          const uint8_t nextFrame = (event->Par2 == 0 ? 0xFF : event->Par2 - 1);
+          P036_JumpToPage(event, nextFrame); //  Start to display the selected page, function needs
           // 65ms!
           # if P036_SEND_EVENTS
 
@@ -465,10 +464,12 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             P036_SendEvent(event, P036_EVENT_FRAME, currentFrameToDisplay + 1);
           }
           # endif // if P036_SEND_EVENTS
+        } else {
+          success = false;
         }
         break;
       }
-# if P036_ENABLE_LINECOUNT
+      # if P036_ENABLE_LINECOUNT
       case p036_subcommands_e::linecount:
 
         if ((event->Par2 >= 1) &&
@@ -481,7 +482,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             return success;
           }
           #  endif // if P036_ENABLE_TICKER
-          success = true;
 
           if (P036_NLINES != event->Par2) {
             P036_NLINES = event->Par2;
@@ -492,17 +492,18 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
               P036_SendEvent(event, P036_EVENT_LINECNT, P036_NLINES);
             }
             #  endif // if P036_SEND_EVENTS
+          } else {
+            success = false;
           }
         }
         break;
-# endif // if P036_ENABLE_LINECOUNT
+      # endif // if P036_ENABLE_LINECOUNT
       case p036_subcommands_e::restore:
 
-        if ((event->Par2 >= 0) &&            // 0: restore all line contents
+        if ((event->Par2 >= 0) && // 0: restore all line contents
             (event->Par2 <= P36_Nlines)) {
           // restore content functions
-          success = true;
-          LineNo  = event->Par2;
+          LineNo = event->Par2;
           RestoreLineContent(event->TaskIndex,
                              get4BitFromUL(P036_FLAGS_0, P036_FLAG_SETTINGS_VERSION), // Bit23-20 Version CustomTaskSettings
                              LineNo);
@@ -518,7 +519,6 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
 
         if (event->Par2 >= 1) {
           // set scroll
-          success = true;
 
           switch (event->Par2) {
             case 1: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_VerySlow); break;
@@ -526,9 +526,9 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             case 3: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Fast); break;
             case 4: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_VeryFast); break;
             case 5: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Instant); break;
-# if P036_ENABLE_TICKER
+            # if P036_ENABLE_TICKER
             case 6: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Ticker); break;
-# endif // if P036_ENABLE_TICKER
+            # endif // if P036_ENABLE_TICKER
             default:
               success = false;
               break;
@@ -540,42 +540,45 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             LineNo         = 1; // after change scroll start with first Line
             bUpdateDisplay = true;
           }
+        } else {
+          success = false;
         }
         break;
-# if P036_ENABLE_LEFT_ALIGN
+      # if P036_ENABLE_LEFT_ALIGN
       case p036_subcommands_e::leftalign:
 
         if ((event->Par2 == 0) ||
             (event->Par2 == 1)) {
-          success = true;
           eAlignment aAlignment = (event->Par2 == 1 ? eAlignment::eLeft : eAlignment::eCenter);
           setTextAlignment(aAlignment);
           uint32_t lSettings = P036_FLAGS_1;
           set2BitToUL(lSettings, P036_FLAG_LEFT_ALIGNED, static_cast(aAlignment)); // Alignment
           P036_FLAGS_1 = lSettings;
+        } else {
+          success = false;
         }
         break;
       case p036_subcommands_e::align:
 
         if ((event->Par2 >= 0) &&
             (event->Par2 <= 2)) {
-          success = true;
           const eAlignment aAlignment = static_cast(event->Par2);
 
           setTextAlignment(aAlignment);
           uint32_t lSettings = P036_FLAGS_1;
           set2BitToUL(lSettings, P036_FLAG_LEFT_ALIGNED, static_cast(aAlignment)); // Alignment
           P036_FLAGS_1 = lSettings;
+        } else {
+          success = false;
         }
         break;
-# endif // if P036_ENABLE_LEFT_ALIGN
-# if P036_USERDEF_HEADERS
+      # endif // if P036_ENABLE_LEFT_ALIGN
+      # if P036_USERDEF_HEADERS
 
       case p036_subcommands_e::userdef1:
       {
         userDef1 = parseStringKeepCase(string, 3);
         userDef1.replace('$', '%'); // Allow system vars to be passed in by using $ instead of %
-        success = true;
         break;
       }
 
@@ -583,10 +586,9 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
       {
         userDef2 = parseStringKeepCase(string, 3);
         userDef2.replace('$', '%'); // Allow system vars to be passed in by using $ instead of %
-        success = true;
         break;
       }
-# endif // if P036_USERDEF_HEADERS
+      # endif // if P036_USERDEF_HEADERS
     }
   }
 
@@ -603,23 +605,23 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
         }
       }
           # endif // if P036_SEND_EVENTS
-      P036_SetDisplayOn(1);     //  Save the fact that the display is now ON
+      P036_SetDisplayOn(1); //  Save the fact that the display is now ON
     }
 
     if (bUpdateDisplay) {
       MaxFramesToDisplay = 0xff; // update frame count
 
-          # if P036_SEND_EVENTS
+      # if P036_SEND_EVENTS
       const uint8_t currentFrame = currentFrameToDisplay;
-          # endif // if P036_SEND_EVENTS
+      # endif // if P036_SEND_EVENTS
 
       if (!P036_DisplayIsOn &&
-          (!bitRead(P036_FLAGS_0, P036_FLAG_NODISPLAY_ONRECEIVE) ||     // Bit 18 NoDisplayOnReceivedText
+          (!bitRead(P036_FLAGS_0, P036_FLAG_NODISPLAY_ONRECEIVE) || // Bit 18 NoDisplayOnReceivedText
            (eventId == P036_EVENT_SCROLL))) {
         // display was OFF, turn it ON
         display->displayOn();
         P036_SetDisplayOn(1); //  Save the fact that the display is now ON
-            # if P036_SEND_EVENTS
+        # if P036_SEND_EVENTS
 
         if (sendEvents) {
           P036_SendEvent(event, P036_EVENT_DISPLAY, 1);
@@ -628,60 +630,51 @@ bool P036_data_struct::plugin_write(struct EventStruct *event, const String& str
             P036_SendEvent(event, P036_EVENT_LINE, LineNo);
           }
         }
-            # endif // if P036_SEND_EVENTS
+        # endif // if P036_SEND_EVENTS
       }
 
       if (P036_DisplayIsOn) {
         bLineScrollEnabled = false; // disable scrolling temporary
-            # if P036_ENABLE_TICKER
+        # if P036_ENABLE_TICKER
 
         if (bUseTicker) {
           P036_JumpToPage(event, 0); // Restart the Ticker
         }
         else
-            # endif // if P036_ENABLE_TICKER
+        # endif // if P036_ENABLE_TICKER
         P036_JumpToPageOfLine(event, LineNo - 1); // Start to display the selected page, function needs 65ms!
-            # if P036_SEND_EVENTS
+        # if P036_SEND_EVENTS
 
         if (sendEvents && bitRead(P036_FLAGS_0, P036_FLAG_EVENTS_FRAME_LINE) && (currentFrame != currentFrameToDisplay)) {
           P036_SendEvent(event, P036_EVENT_FRAME, currentFrameToDisplay + 1);
         }
-            # endif // if P036_SEND_EVENTS
+        # endif // if P036_SEND_EVENTS
       }
 
-# ifdef PLUGIN_036_DEBUG
+      # ifdef PLUGIN_036_DEBUG
 
       if (eventId == P036_EVENT_LINE) {
-        String log;
-
-        if (loglevelActiveFor(LOG_LEVEL_INFO) &&
-            log.reserve(200)) { // estimated
-          log  = F("[P36] Line: ");
-          log += LineNo;
-          log += F(" Content:");
-          log += LineContent->DisplayLinesV1[LineNo - 1].Content;
-          log += F(" Length:");
-          log += LineContent->DisplayLinesV1[LineNo - 1].Content.length();
-          log += F(" Pix: ");
-          log += display->getStringWidth(LineContent->DisplayLinesV1[LineNo - 1].Content);
-          log += F(" Reserved:");
-          log += LineContent->DisplayLinesV1[LineNo - 1].reserved;
-          addLogMove(LOG_LEVEL_INFO, log);
+        if (loglevelActiveFor(LOG_LEVEL_INFO)) { // estimated
+          addLogMove(LOG_LEVEL_INFO,
+                     strformat(F("[P036] Line: %d Content:%s Length:%d Pix: %d Reserved:%d"),
+                               LineNo,
+                               LineContent->DisplayLinesV1[LineNo - 1].Content,
+                               LineContent->DisplayLinesV1[LineNo - 1].Content.length(),
+                               display->getStringWidth(LineContent->DisplayLinesV1[LineNo - 1].Content),
+                               LineContent->DisplayLinesV1[LineNo - 1].reserved));
           delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
         }
       }
-# endif // PLUGIN_036_DEBUG
+      # endif // PLUGIN_036_DEBUG
     }
   }
-# ifdef PLUGIN_036_DEBUG
+  # ifdef PLUGIN_036_DEBUG
 
   if (!success && loglevelActiveFor(LOG_LEVEL_INFO)) {
-    String log = concat(F("[P36] Cmd: "), command);
-    log += concat(F(" SubCmd:"), subcommand);
-    log += F(" Success:false");
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLogMove(LOG_LEVEL_INFO,
+               strformat(F("[P036] Cmd: %s SubCmd:%s Success:false"), command.c_str(), subcommand.c_str()));
   }
-# endif // PLUGIN_036_DEBUG
+  # endif // PLUGIN_036_DEBUG
   return success;
 }
 
@@ -738,72 +731,61 @@ void P036_data_struct::setNrLines(struct EventStruct *event, uint8_t NrLines) {
 
 # endif // if P036_ENABLE_LINECOUNT
 
-
-void P036_data_struct::display_header() {
-  if (!isInitialized()) {
-    return;
-  }
-
-  if (bHideHeader) { //  hide header
-    return;
-  }
-
-  eHeaderContent iHeaderContent;
+String P036_data_struct::create_display_header_text(eHeaderContent iHeaderContent) const
+{
   String newString, strHeader;
-
-  if ((HeaderContentAlternative == HeaderContent) || !bAlternativHeader) {
-    iHeaderContent = HeaderContent;
-  } else {
-    iHeaderContent = HeaderContentAlternative;
-  }
+  const __FlashStringHelper *newString_f = F("%sysname%");
+  bool use_newString_f = true;
 
   switch (iHeaderContent) {
     case eHeaderContent::eSSID:
 
       if (NetworkConnected()) {
         strHeader = WiFi.SSID();
+        use_newString_f = false;
       }
-      else {
-        newString = F("%sysname%");
-      }
+//      else {
+//        newString_f = F("%sysname%");
+//      }
       break;
     case eHeaderContent::eSysName:
-      newString = F("%sysname%");
+//      newString_f = F("%sysname%");
       break;
     case eHeaderContent::eTime:
-      newString = F("%systime%");
+      newString_f = F("%systime%");
       break;
     case eHeaderContent::eDate:
-      newString = F("%sysday_0%.%sysmonth_0%.%sysyear%");
+      newString_f = F("%sysday_0%.%sysmonth_0%.%sysyear%");
       break;
     case eHeaderContent::eIP:
-      newString = F("%ip%");
+      newString_f = F("%ip%");
       break;
     case eHeaderContent::eMAC:
-      newString = F("%mac%");
+      newString_f = F("%mac%");
       break;
     case eHeaderContent::eRSSI:
-      newString = F("%rssi%dBm");
+      newString_f = F("%rssi%dBm");
       break;
     case eHeaderContent::eBSSID:
-      newString = F("%bssid%");
+      newString_f = F("%bssid%");
       break;
     case eHeaderContent::eWiFiCh:
-      newString = F("Channel: %wi_ch%");
+      newString_f = F("Channel: %wi_ch%");
       break;
     case eHeaderContent::eUnit:
-      newString = F("Unit: %unit%");
+      newString_f = F("Unit: %unit%");
       break;
     case eHeaderContent::eSysLoad:
-      newString = F("Load: %sysload%%");
+      newString_f = F("Load: %sysload%%");
       break;
     case eHeaderContent::eSysHeap:
-      newString = F("Mem: %sysheap%");
+      newString_f = F("Mem: %sysheap%");
       break;
     case eHeaderContent::eSysStack:
-      newString = F("Stack: %sysstack%");
+      newString_f = F("Stack: %sysstack%");
       break;
     case eHeaderContent::ePageNo:
+      use_newString_f = false;
       strHeader  = F("page ");
       strHeader += (currentFrameToDisplay + 1);
 
@@ -814,14 +796,20 @@ void P036_data_struct::display_header() {
       break;
     # if P036_USERDEF_HEADERS
     case eHeaderContent::eUserDef1:
+      use_newString_f = false;
       newString = userDef1;
       break;
     case eHeaderContent::eUserDef2:
+      use_newString_f = false;
       newString = userDef2;
       break;
     # endif // if P036_USERDEF_HEADERS
     case eHeaderContent::eNone:
-      return;
+      return EMPTY_STRING;
+  }
+
+  if (use_newString_f) {
+    newString = newString_f;
   }
 
   if (newString.length() > 0) {
@@ -831,7 +819,23 @@ void P036_data_struct::display_header() {
   }
 
   strHeader.trim();
-  display_title(strHeader);
+  return strHeader;
+}
+
+void P036_data_struct::display_header() {
+  if (!isInitialized()) {
+    return;
+  }
+
+  if (bHideHeader) { //  hide header
+    return;
+  }
+
+  const eHeaderContent iHeaderContent = ((HeaderContentAlternative == HeaderContent) || !bAlternativHeader) 
+    ? HeaderContent
+    : HeaderContentAlternative;
+  const String title = create_display_header_text(iHeaderContent);
+  display_title(title);
 
   // Display time and wifibars both clear area below, so paint them after the title.
   if (getDisplaySizeSettings(disp_resolution).Width == P36_MaxDisplayWidth) {
@@ -839,11 +843,11 @@ void P036_data_struct::display_header() {
   }
   display_wifibars();
 
-# ifdef OLEDDISPLAY_DOUBLE_BUFFER
+  # ifdef OLEDDISPLAY_DOUBLE_BUFFER
 
   // Update only small sections of the display, reducing the amount of data to be sent to the display
   update_display();
-# endif // ifdef OLEDDISPLAY_DOUBLE_BUFFER
+  # endif // ifdef OLEDDISPLAY_DOUBLE_BUFFER
 }
 
 void P036_data_struct::display_time() {
@@ -851,26 +855,27 @@ void P036_data_struct::display_time() {
     return;
   }
 
-  String dtime = F("%systime%");
-
-  parseSystemVariables(dtime, false);
+  const String dtime = SystemVariables::getSystemVariable(SystemVariables::SYSTIME);
   display->setTextAlignment(TEXT_ALIGN_LEFT);
   display->setFont(getArialMT_Plain_10());
   display->setColor(BLACK);
   display->fillRect(0, TopLineOffset, 28, GetHeaderHeight() - 2);
   display->setColor(WHITE);
-  display->drawString(0, TopLineOffset, dtime.substring(0, 5));
+  display->drawString(0, TopLineOffset, dtime);
 }
 
 void P036_data_struct::display_title(const String& title) {
   if (!isInitialized()) {
     return;
   }
-  display->setFont(getArialMT_Plain_10());
   display->setColor(BLACK);
   display->fillRect(0, TopLineOffset, P36_MaxDisplayWidth, GetHeaderHeight()); // don't clear line under title.
   display->setColor(WHITE);
 
+  if (title.isEmpty()) {
+    return;
+  }
+  display->setFont(getArialMT_Plain_10());
   if (getDisplaySizeSettings(disp_resolution).Width == P36_MaxDisplayWidth) {
     display->setTextAlignment(TEXT_ALIGN_CENTER);
     display->drawString(P36_DisplayCentre, TopLineOffset, title);
@@ -936,7 +941,7 @@ void P036_data_struct::display_indicator() {
   display->setColor(WHITE);
 
   // Display chars as required
-  for (uint8_t i = 0; i < frameCount; i++) {
+  for (uint8_t i = 0; i < frameCount; ++i) {
     const char *image;
 
     if (currentFrameToDisplay == i) {
@@ -1012,9 +1017,9 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
     return result;                  // finished
   }
 
-  for (uint8_t i = LineNo; i < P36_Nlines; i++) {
+  for (uint8_t i = LineNo; i < P36_Nlines; ++i) {
     // calculate individual font settings
-    int8_t lFontIndex             = FontIndex;
+    uint8_t lFontIndex             = FontIndex;
     const eModifyFont iModifyFont =
       static_cast(get3BitFromUL(LineContent->DisplayLinesV1[i].ModifyLayout, P036_FLAG_ModifyLayout_Font));
 
@@ -1023,9 +1028,11 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
 
         if (ScrollingPages.linesPerFrameDef > 1) {
           // Font can only be enlarged if more than 1 line is displayed
-          lFontIndex--;
-
-          if (lFontIndex < IdxForBiggestFont) { lFontIndex = IdxForBiggestFont; }
+          if (lFontIndex > IdxForBiggestFont) { 
+            lFontIndex--; 
+          } else {
+            lFontIndex = IdxForBiggestFont;
+          }
           result.IdxForBiggestFontUsed = lFontIndex;
         }
         break;
@@ -1040,12 +1047,12 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
       case eModifyFont::eReduce:
         lFontIndex++;
 
-        if (lFontIndex > (P36_MaxFontCount - 1)) {
-          lFontIndex = P36_MaxFontCount - 1;
+        if (lFontIndex >= NR_ELEMENTS(FontSizes)) {
+          lFontIndex = NR_ELEMENTS(FontSizes) - 1;
         }
         break;
       case eModifyFont::eMinimize:
-        lFontIndex = P36_MaxFontCount - 1;
+        lFontIndex = NR_ELEMENTS(FontSizes) - 1;
         break;
       case eModifyFont::eNone:
         lFontIndex = FontIndex;
@@ -1085,7 +1092,7 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
       lSpace = -1; // allow overlapping by 1 pix
 
       if (deltaHeight < (-1 * (lLinesPerFrame - 1))) {
-        if ((result.IdxForBiggestFontUsed == (P36_MaxFontCount - 1)) &&
+        if ((result.IdxForBiggestFontUsed == (NR_ELEMENTS(FontSizes) - 1)) &&
             (LinesPerFrame == SizeSettings[static_cast(disp_resolution)].MaxLines)) {
           // max lines for used display and smallest font reached -> use special space between the lines and return 'fits'
           // overlapping (lSpace<0) depends on the absolute display height
@@ -1106,22 +1113,22 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
   LineSettings[LineNo].ypos = lTop + GetHeaderHeight() + TopLineOffset;
 
   if (lLinesPerFrame > 1) {
-    for (uint8_t k = (LineNo + 1); k < NextLineNo; k++) {
+    for (uint8_t k = (LineNo + 1); k < NextLineNo; ++k) {
       LineSettings[k].ypos = LineSettings[k - 1].ypos + FontSizes[LineSettings[k - 1].fontIdx].Height + lSpace;
     }
   }
-# ifdef P036_CHECK_INDIVIDUAL_FONT
+  # ifdef P036_CHECK_INDIVIDUAL_FONT
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     String log1;
 
     if (log1.reserve(140)) { // estimated
       delay(10);             // FIXME otherwise it is maybe too fast for the serial monitor
-      log1  = F("IndividualFontSettings:");
-      log1 += concat(F(" result.NextLineNo:"), result.NextLineNo);
-      log1 += concat(F(" result.IdxForBiggestFontUsed:"), result.IdxForBiggestFontUsed);
-      log1 += concat(F(" LineNo:"), LineNo);
-      log1 += concat(F(" LinesPerFrame:"), LinesPerFrame);
+      log1 = strformat(F("IndividualFontSettings: result.NextLineNo:%d result.IdxForBiggestFontUsed:%d LineNo:%d LinesPerFrame:%d"),
+                       result.NextLineNo,
+                       result.IdxForBiggestFontUsed,
+                       LineNo,
+                       LinesPerFrame);
 
       if (result.NextLineNo != 0xFF) {
         log1 += strformat(F(" FrameNo:%d lTop:%d lSpace:%d"), FrameNo, lTop, lSpace);
@@ -1129,7 +1136,7 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_
       addLogMove(LOG_LEVEL_INFO, log1);
     }
   }
-# endif // # ifdef P036_CHECK_INDIVIDUAL_FONT
+  # endif // # ifdef P036_CHECK_INDIVIDUAL_FONT
   return result;
 }
 
@@ -1156,36 +1163,37 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     addLog(LOG_LEVEL_INFO,
-           strformat(F("P036 CalculateFontSettings lines: %d, height: %d, header: %s, footer: %s"),
+           strformat(F("P036 CalculateFontSettings lines: %d, height: %d, header: %d, footer: %d"),
                      iLinesPerFrame,
                      iHeight,
-                     boolToString(!bHideHeader).c_str(),
-                     boolToString(!bHideFooter).c_str()));
+                     !bHideHeader,
+                     !bHideFooter));
   }
   # endif // ifdef P036_FONT_CALC_LOG
 
   iMaxHeightForFont = lround(iHeight / (iLinesPerFrame * 1.0f)); // no extra space between lines
   // Fonts already have their own extra space, no need to add an extra pixel space
 
-# ifdef P036_FONT_CALC_LOG
+  # ifdef P036_FONT_CALC_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     addLog(LOG_LEVEL_INFO,
            strformat(F("CalculateFontSettings LinesPerFrame: %d, iHeight: %d, maxFontHeight: %d"),
                      iLinesPerFrame, iHeight, iMaxHeightForFont));
   }
-# endif // ifdef P036_FONT_CALC_LOG
+  # endif // ifdef P036_FONT_CALC_LOG
 
   while (iFontIndex < 0) {
-# ifdef P036_FONT_CALC_LOG
+    # ifdef P036_FONT_CALC_LOG
     String log1;
     log1.reserve(80);
-# endif // ifdef P036_FONT_CALC_LOG
+    # endif // ifdef P036_FONT_CALC_LOG
 
-    for (i = 0; i < P36_MaxFontCount - 1; i++) {
+    for (i = 0; i < NR_ELEMENTS(FontSizes) - 1; ++i) {
       // check available fonts for the line setting
       # ifdef P036_FONT_CALC_LOG
-      delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
+
+      // Appending a string won't need a delay(5) call...
       log1 = strformat(F(" -> i: %d, h: %d"), i, FontSizes[i].Height);
       # endif // ifdef P036_FONT_CALC_LOG
 
@@ -1204,10 +1212,9 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
       # ifdef P036_FONT_CALC_LOG
       log1 += concat(F(", no font fits, fontIdx: "), iFontIndex);
       addLogMove(LOG_LEVEL_INFO, log1);
+      delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
       # endif // ifdef P036_FONT_CALC_LOG
       break;
-
-      // }
     }
     # ifdef P036_FONT_CALC_LOG
     log1 += F(", font fits");
@@ -1241,7 +1248,7 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
       case p036_resolution::pix64x48:  result.Space = -1;
         break;
     }
-    iFontIndex = P36_MaxFontCount - 1;
+    iFontIndex = NR_ELEMENTS(FontSizes) - 1;
   }
 
   if (lDefaultLines == 0) {
@@ -1253,13 +1260,13 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
     uint8_t iIdxForBiggestFont = 0;
 
     while (currentLine < P36_Nlines) {
-# if P036_ENABLE_TICKER
+      # if P036_ENABLE_TICKER
 
       if (bUseTicker && (currentLine > 0)) {
         // for ticker only the first line defines the font
         break;
       }
-# endif // if P036_ENABLE_TICKER
+      # endif // if P036_ENABLE_TICKER
       // calculate individual font settings
       IndividualFontSettings = CalculateIndividualFontSettings(currentLine,
                                                                iFontIndex,
@@ -1284,13 +1291,13 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
       }
     }
 
-# ifdef P036_CHECK_INDIVIDUAL_FONT
+    # ifdef P036_CHECK_INDIVIDUAL_FONT
 
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
       String log1;
 
       if (log1.reserve(140)) { // estimated
-        for (uint8_t i = 0; i < P36_Nlines; i++) {
+        for (uint8_t i = 0; i < P36_Nlines; ++i) {
           delay(5);            // FIXME otherwise it is maybe too fast for the serial monitor
           log1 = strformat(F("Line[%d]: Frame:%d FontIdx:%d ypos:%d FontHeight:%d"), i, LineSettings[i].frame,
                            LineSettings[i].fontIdx, LineSettings[i].ypos - TopLineOffset, LineSettings[i].FontHeight);
@@ -1298,12 +1305,12 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
         }
       }
     }
-# endif // ifdef P036_CHECK_INDIVIDUAL_FONT
+    # endif // ifdef P036_CHECK_INDIVIDUAL_FONT
   }
   result.fontIdx = iFontIndex;
   result.Height  = FontSizes[iFontIndex].Height;
 
-# ifdef P036_FONT_CALC_LOG
+  # ifdef P036_FONT_CALC_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     String log1;
@@ -1324,7 +1331,7 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) {
       addLogMove(LOG_LEVEL_INFO, log1);
     }
   }
-# endif // P036_FONT_CALC_LOG
+  # endif // P036_FONT_CALC_LOG
 
   return result;
 }
@@ -1334,14 +1341,14 @@ void P036_data_struct::prepare_pagescrolling(ePageScrollSpeed lscrollspeed,
   if (!isInitialized()) {
     return;
   }
-# if P036_ENABLE_TICKER
+  # if P036_ENABLE_TICKER
   bUseTicker = (lscrollspeed == ePageScrollSpeed::ePSS_Ticker);
 
   if (bUseTicker) {
     ScrollingPages.linesPerFrameDef = 1;
   }
   else
-# endif // if P036_ENABLE_TICKER
+  # endif // if P036_ENABLE_TICKER
   {
     ScrollingPages.linesPerFrameDef = NrLines;
   }
@@ -1357,12 +1364,12 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
   int iPageScrollTime;
   int iCharToRemove = 0;
 
-# ifdef PLUGIN_036_DEBUG
+  # ifdef PLUGIN_036_DEBUG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     addLog(LOG_LEVEL_INFO, concat(F("Start Scrolling: Speed: "), static_cast(lscrollspeed)));
   }
-# endif // PLUGIN_036_DEBUG
+  # endif // PLUGIN_036_DEBUG
 
   ScrollingLines.wait = 0;
 
@@ -1378,12 +1385,12 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
   }
   int iScrollTime = static_cast(lTaskTimer * 1000 - iPageScrollTime - 2 * P36_WaitScrollLines * 100) / 100; // scrollTime in ms
 
-# ifdef PLUGIN_036_DEBUG
+  # ifdef PLUGIN_036_DEBUG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     addLog(LOG_LEVEL_INFO, concat(F("PageScrollTime: "), iPageScrollTime));
   }
-# endif // PLUGIN_036_DEBUG
+  # endif // PLUGIN_036_DEBUG
 
   uint16_t MaxPixWidthForPageScrolling = P36_MaxDisplayWidth;
 
@@ -1392,23 +1399,23 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
     MaxPixWidthForPageScrolling -= getDisplaySizeSettings(disp_resolution).PixLeft;
   }
 
-# if P036_ENABLE_TICKER
+  # if P036_ENABLE_TICKER
 
   if (bUseTicker) {
     ScrollingLines.Ticker.Tcontent = EMPTY_STRING;
     ScrollingLines.Ticker.IdxEnd   = 0;
     ScrollingLines.Ticker.IdxStart = 0;
 
-    for (uint8_t i = 0; i < P36_Nlines; i++) {
+    for (uint8_t i = 0; i < P36_Nlines; ++i) {
       String tmpString(LineContent->DisplayLinesV1[i].Content);
       tmpString.replace(F("<|>"), "   "); // replace the split token with three space char
       ScrollingLines.Ticker.Tcontent += P36_parseTemplate(tmpString, i);
     }
     ScrollingLines.Ticker.len = ScrollingLines.Ticker.Tcontent.length();
   }
-# endif // if P036_ENABLE_TICKER
+  # endif // if P036_ENABLE_TICKER
 
-  for (uint8_t j = 0; j < ScrollingPages.linesPerFrameDef; j++) {
+  for (uint8_t j = 0; j < ScrollingPages.linesPerFrameDef; ++j) {
     // default no line scrolling and strings are centered
     uint16_t PixLengthLineOut = 0; // pix length of line out
     uint16_t PixLengthLineIn  = 0; // pix length of line in
@@ -1487,7 +1494,7 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
         {
           ScrollingLines.SLine[j].SLcontent   = ScrollingPages.In[j].SPLcontent;
           ScrollingLines.SLine[j].SLidx       = ScrollingPages.In[j].SPLidx; // index to LineSettings[]
-          ScrollingLines.SLine[j].Width       = PixLengthLineIn; // while page scrolling this line is left aligned
+          ScrollingLines.SLine[j].Width       = PixLengthLineIn;             // while page scrolling this line is left aligned
           ScrollingLines.SLine[j].CurrentLeft = getDisplaySizeSettings(disp_resolution).PixLeft;
           ScrollingLines.SLine[j].fPixSum     = getDisplaySizeSettings(disp_resolution).PixLeft;
 
@@ -1499,20 +1506,19 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
         # ifdef P036_SCROLL_CALC_LOG
 
         if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
           addLog(LOG_LEVEL_INFO, strformat(F("Line: %d width: %d dPix: %d"),
                                            j + 1, ScrollingLines.SLine[j].Width, ScrollingLines.SLine[j].dPix));
+          delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
           #  if P036_ENABLE_TICKER
 
           if (bUseTicker) {
+            addLogMove(LOG_LEVEL_INFO,
+                       strformat(F("+++ iScrollTime: %d StrLength: %d StrInPix: %d PixPerChar: %d"),
+                                 iScrollTime,
+                                 ScrollingLines.Ticker.len,
+                                 display->getStringWidth(ScrollingLines.Ticker.Tcontent),
+                                 ScrollingLines.Ticker.TickerAvgPixPerChar));
             delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
-            String log1;
-            log1.reserve(200);
-            log1  = concat(F("+++ iScrollTime: "), iScrollTime);
-            log1 += concat(F(" StrLength: "), ScrollingLines.Ticker.len);
-            log1 += concat(F(" StrInPix: "), display->getStringWidth(ScrollingLines.Ticker.Tcontent));
-            log1 += concat(F(" PixPerChar: "), ScrollingLines.Ticker.TickerAvgPixPerChar);
-            addLogMove(LOG_LEVEL_INFO, log1);
           }
           #  endif // if P036_ENABLE_TICKER
         }
@@ -1523,9 +1529,9 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
     // reduce line content for page scrolling to max width
     if (PixLengthLineIn > MaxPixWidthForPageScrolling) {
       const int strlen = ScrollingPages.In[j].SPLcontent.length();
-# ifdef P036_SCROLL_CALC_LOG
+      # ifdef P036_SCROLL_CALC_LOG
       const String LineInStr = ScrollingPages.In[j].SPLcontent;
-# endif // P036_SCROLL_CALC_LOG
+      # endif // P036_SCROLL_CALC_LOG
       float fAvgPixPerChar = static_cast(PixLengthLineIn) / strlen;
 
       if (bLineScrollEnabled) {
@@ -1565,20 +1571,20 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
 
       if (loglevelActiveFor(LOG_LEVEL_INFO) &&
           log.reserve(128)) {
+        addLog(LOG_LEVEL_INFO,
+               strformat(F("Line: %d LineIn: %s Length: %d PixLength: %d AvgPixPerChar: %d CharsRemoved: %d"),
+                         j + 1,
+                         LineInStr.c_str(),
+                         strlen,
+                         PixLengthLineIn,
+                         fAvgPixPerChar, iCharToRemove));
         delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
-        log  = concat(F("Line: "), j + 1);
-        log += concat(F(" LineIn: "), LineInStr);
-        log += concat(F(" Length: "), strlen);
-        log += concat(F(" PixLength: "), PixLengthLineIn);
-        log += concat(F(" AvgPixPerChar: "), fAvgPixPerChar);
-        log += concat(F(" CharsRemoved: "), iCharToRemove);
-        addLog(LOG_LEVEL_INFO, log);
-        log.clear();
-        log += concat(F(" -> Changed to: "), ScrollingPages.In[j].SPLcontent);
-        log += concat(F(" Length: "), ScrollingPages.In[j].SPLcontent.length());
         display->setFont(FontSizes[LineSettings[ScrollingPages.In[j].SPLidx].fontIdx].fontData);
-        log += concat(F(" PixLength: "), display->getStringWidth(ScrollingPages.In[j].SPLcontent));
-        addLogMove(LOG_LEVEL_INFO, log);
+        addLogMove(LOG_LEVEL_INFO,
+                   strformat(F(" -> Changed to: %s Length: %d PixLength: %d"),
+                             ScrollingPages.In[j].SPLcontent.c_str(),
+                             ScrollingPages.In[j].SPLcontent.length(),
+                             display->getStringWidth(ScrollingPages.In[j].SPLcontent)));
       }
       # endif // P036_SCROLL_CALC_LOG
     }
@@ -1590,10 +1596,10 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
       # ifdef P036_SCROLL_CALC_LOG
       const String LineOutStr = ScrollingPages.Out[j].SPLcontent;
       # endif // P036_SCROLL_CALC_LOG
-      float fAvgPixPerChar = static_cast(PixLengthLineOut) / strlen;
+      const float fAvgPixPerChar = static_cast(PixLengthLineOut) / strlen;
 
-      boolean bCheckLengthLeft  = false;
-      boolean bCheckLengthRight = false;
+      bool bCheckLengthLeft  = false;
+      bool bCheckLengthRight = false;
 
       if (bLineScrollEnabled) {
         // shorten string on left side because line is displayed right aligned while scrolling
@@ -1666,28 +1672,27 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
         }
       }
 
-# ifdef P036_SCROLL_CALC_LOG
-      String log;
+      # ifdef P036_SCROLL_CALC_LOG
 
-      if (loglevelActiveFor(LOG_LEVEL_INFO) &&
-          log.reserve(128)) {
+      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+        addLog(LOG_LEVEL_INFO,
+               strformat(F("Line: %d LineOut: %s Length: %d PixLength: %d AvgPixPerChar: %.2f CharsRemoved: %d"),
+                         j + 1,
+                         LineOutStr.c_str(),
+                         strlen,
+                         PixLengthLineOut,
+                         fAvgPixPerChar,
+                         iCharToRemove));
         delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
-        log  = concat(F("Line: "), j + 1);
-        log += concat(F(" LineOut: "), LineOutStr);
-        log += concat(F(" Length: "), strlen);
-        log += concat(F(" PixLength: "), PixLengthLineOut);
-        log += concat(F(" AvgPixPerChar: "), fAvgPixPerChar);
-        log += concat(F(" CharsRemoved: "), iCharToRemove);
-        addLog(LOG_LEVEL_INFO, log);
-        delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
-        log.clear();
-        log += concat(F(" -> Changed to: "), ScrollingPages.Out[j].SPLcontent);
-        log += concat(F(" Length: "), ScrollingPages.Out[j].SPLcontent.length());
         display->setFont(FontSizes[LineSettings[ScrollingPages.Out[j].SPLidx].fontIdx].fontData);
-        log += concat(F(" PixLength: "), display->getStringWidth(ScrollingPages.Out[j].SPLcontent));
-        addLogMove(LOG_LEVEL_INFO, log);
+        addLogMove(LOG_LEVEL_INFO,
+                   strformat(F(" -> Changed to: %s Length: %d PixLength: %d"),
+                             ScrollingPages.Out[j].SPLcontent.c_str(),
+                             ScrollingPages.Out[j].SPLcontent.length(),
+                             display->getStringWidth(ScrollingPages.Out[j].SPLcontent)));
+        delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
       }
-# endif // P036_SCROLL_CALC_LOG
+      # endif // P036_SCROLL_CALC_LOG
     }
   }
 
@@ -1696,9 +1701,9 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas
 
   display_scroll_timer(true, lscrollspeed);                                    // Initial display of the page
 
-# ifdef PLUGIN_036_DEBUG
+  # ifdef PLUGIN_036_DEBUG
   addLog(LOG_LEVEL_INFO, F("Scrolling finished"));
-# endif // PLUGIN_036_DEBUG
+  # endif // PLUGIN_036_DEBUG
   return ScrollingPages.Scrolling;
 }
 
@@ -1738,7 +1743,7 @@ uint8_t P036_data_struct::display_scroll_timer(bool             initialScroll,
   } else
   # endif // if P036_ENABLE_TICKER
   {
-    for (uint8_t j = 0; j < ScrollingPages.linesPerFrameOut; j++) {
+    for (uint8_t j = 0; j < ScrollingPages.linesPerFrameOut; ++j) {
       if ((initialScroll && (lscrollspeed < ePageScrollSpeed::ePSS_Instant)) ||
           !initialScroll) {
         // scrolling, prepare scrolling page out to right
@@ -1746,7 +1751,7 @@ uint8_t P036_data_struct::display_scroll_timer(bool             initialScroll,
       }
     }
 
-    for (uint8_t j = 0; j < ScrollingPages.linesPerFrameIn; j++) {
+    for (uint8_t j = 0; j < ScrollingPages.linesPerFrameIn; ++j) {
       // non-scrolling or scrolling prepare scrolling page in from left
       DrawScrollingPageLine(&ScrollingPages.In[j], ScrollingLines.SLine[j].Width, TEXT_ALIGN_LEFT);
     }
@@ -1778,7 +1783,7 @@ void P036_data_struct::display_scrolling_lines() {
   bool    updateDisplay = false;
   int     iCurrentLeft;
 
-  for (i = 0; i < ScrollingPages.linesPerFrameIn; i++) {
+  for (i = 0; i < ScrollingPages.linesPerFrameIn; ++i) {
     if (ScrollingLines.SLine[i].Width != 0) {
       bscroll = true;
       break;
@@ -1791,7 +1796,7 @@ void P036_data_struct::display_scrolling_lines() {
       return; // wait before scrolling line not finished
     }
 
-    for (i = 0; i < ScrollingPages.linesPerFrameIn; i++) {
+    for (i = 0; i < ScrollingPages.linesPerFrameIn; ++i) {
       if (ScrollingLines.SLine[i].Width != 0) {
         // scroll this line
         ScrollingLines.SLine[i].fPixSum -= ScrollingLines.SLine[i].dPix;
@@ -1841,8 +1846,8 @@ void P036_data_struct::display_scrolling_lines() {
               }
 
               // remove already displayed characters
-              float fCurrentPixLeft = static_cast(getDisplaySizeSettings(disp_resolution).PixLeft) - 2.0f *
-                                      ScrollingLines.Ticker.TickerAvgPixPerChar;
+              const float fCurrentPixLeft = static_cast(getDisplaySizeSettings(disp_resolution).PixLeft) - 2.0f *
+                                            ScrollingLines.Ticker.TickerAvgPixPerChar;
 
               while (ScrollingLines.SLine[0].fPixSum < fCurrentPixLeft) {
                 const uint8_t c          = ScrollingLines.Ticker.Tcontent.charAt(ScrollingLines.Ticker.IdxStart);
@@ -1934,7 +1939,7 @@ bool P036_data_struct::display_wifibars() {
   display->setColor(WHITE);
 
   if (NetworkConnected()) {
-    for (uint8_t ibar = 0; ibar < nbars; ibar++) {
+    for (uint8_t ibar = 0; ibar < nbars; ++ibar) {
       const int16_t height = size_y * (ibar + 1) / nbars;
       const int16_t xpos   = x + ibar * width;
       const int16_t ypos   = y + size_y - height;
@@ -2008,14 +2013,14 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event)
     HeaderContentAlternative = static_cast(get8BitFromUL(PCONFIG_LONG(0), P036_FLAG_HEADER_ALTERNATIVE));
 
     // Construct the outgoing string
-    for (uint8_t i = 0; i < P36_Nlines; i++) {
+    for (uint8_t i = 0; i < P36_Nlines; ++i) {
       if (LineSettings[i].frame == frameCounter) {
         lineCounter = i;
         break;
       }
     }
 
-    for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; i++)
+    for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; ++i)
     {
       if (LineSettings[lineCounter + i].frame != frameCounter) {
         continue;
@@ -2054,7 +2059,7 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event)
       }
 
       //        Contruct incoming strings
-      for (uint8_t i = 0; i < P36_Nlines; i++) {
+      for (uint8_t i = 0; i < P36_Nlines; ++i) {
         if (nextFrameToDisplay == 0xff) {
           // showing next page
           if (LineSettings[i].frame == frameCounter) {
@@ -2071,7 +2076,7 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event)
         }
       }
 
-      for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; i++)
+      for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; ++i)
       {
         if (LineSettings[lineCounter + i].frame != frameCounter) {
           continue;
@@ -2216,7 +2221,7 @@ String P036_data_struct::P36_parseTemplate(String& tmpString, uint8_t lineIdx) {
     case TEXT_ALIGN_LEFT:
 
       // add leading spaces from tmpString to the result
-      for (uint16_t l = 0; l < tmpString.length(); l++) {
+      for (uint16_t l = 0; l < tmpString.length(); ++l) {
         if (tmpString[l] != ' ') {
           break;
         }
@@ -2226,7 +2231,7 @@ String P036_data_struct::P36_parseTemplate(String& tmpString, uint8_t lineIdx) {
     case TEXT_ALIGN_RIGHT:
 
       // add trailing spaces from tmpString to the result
-      for (int16_t l = tmpString.length() - 1; l >= 0; l--) {
+      for (int16_t l = tmpString.length() - 1; l >= 0; --l) {
         if (tmpString[l] != ' ') {
           break;
         }
@@ -2309,7 +2314,7 @@ void P036_data_struct::markButtonStateProcessed() {
 }
 
 uint16_t P036_data_struct::CalcPixLength(uint8_t LineNo) {
-  if (LineContent->DisplayLinesV1[LineNo].Content[0] == 0) {
+  if (LineContent->DisplayLinesV1[LineNo].Content.isEmpty()) {
     // empty string
     return 0;
   }
@@ -2323,7 +2328,7 @@ void P036_data_struct::CalcMaxPageCount(void) {
     // not updated yet
     uint8_t iFrame = 0;
 
-    for (uint8_t i = 0; i < P36_Nlines; i++) {
+    for (uint8_t i = 0; i < P36_Nlines; ++i) {
       if (LineContent->DisplayLinesV1[i].Content[0] != 0) {   // line is not empty
         LineSettings[i].DisplayedPageNo = MaxFramesToDisplay; // current MaxFramesToDisplay is the number of the shown page
       } else {
@@ -2332,7 +2337,7 @@ void P036_data_struct::CalcMaxPageCount(void) {
 
       if (LineSettings[i].frame != iFrame) { continue; } // line is not yet on the next page
 
-      for (uint8_t k = 0; k < ScrollingPages.linesPerFrameDef; k++) {
+      for (uint8_t k = 0; k < ScrollingPages.linesPerFrameDef; ++k) {
         if ((i + k) >= P36_Nlines) { break; }
 
         if ((LineSettings[i + k].frame) != iFrame) { // line is already on the next page
@@ -2353,30 +2358,25 @@ void P036_data_struct::CalcMaxPageCount(void) {
         }
       }
     }
-# ifdef P036_CHECK_INDIVIDUAL_FONT
+    # ifdef P036_CHECK_INDIVIDUAL_FONT
 
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log1;
+      addLog(LOG_LEVEL_INFO,
+             concat(F("CalcMaxPageCount: MaxFramesToDisplay:"), MaxFramesToDisplay));
 
-      if (log1.reserve(140)) { // estimated
-        log1 = concat(F("CalcMaxPageCount: MaxFramesToDisplay:"), MaxFramesToDisplay);
-        addLog(LOG_LEVEL_INFO, log1);
-
-        for (uint8_t i = 0; i < P36_Nlines; i++) {
-          log1.clear();
-          delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
-          log1 = strformat(F("Line[%d]: Frame:%d DisplayedPageNo:%d FontIdx:%d ypos:%d FontHeight:%d"),
-                           i,
-                           LineSettings[i].frame,
-                           LineSettings[i].DisplayedPageNo,
-                           LineSettings[i].fontIdx,
-                           LineSettings[i].ypos - TopLineOffset,
-                           LineSettings[i].FontHeight);
-          addLogMove(LOG_LEVEL_INFO, log1);
-        }
+      for (uint8_t i = 0; i < P36_Nlines; ++i) {
+        delay(5); // FIXME otherwise it is maybe too fast for the serial monitor
+        addLogMove(LOG_LEVEL_INFO,
+                   strformat(F("Line[%d]: Frame:%d DisplayedPageNo:%d FontIdx:%d ypos:%d FontHeight:%d"),
+                             i,
+                             LineSettings[i].frame,
+                             LineSettings[i].DisplayedPageNo,
+                             LineSettings[i].fontIdx,
+                             LineSettings[i].ypos - TopLineOffset,
+                             LineSettings[i].FontHeight));
       }
     }
-# endif // ifdef P036_CHECK_INDIVIDUAL_FONT
+    # endif // ifdef P036_CHECK_INDIVIDUAL_FONT
   }
 }
 
@@ -2475,7 +2475,7 @@ void P036_data_struct::CreateScrollingPageLine(tScrollingPageLines *ScrollingPag
 bool P036_data_struct::web_show_values() {
   addHtml(F("
")); // To keep spaces etc. in the shown output
 
-  for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; i++) {
+  for (uint8_t i = 0; i < ScrollingPages.linesPerFrameDef; ++i) {
     addHtmlDiv(F("div_l"), currentLines[i], EMPTY_STRING, F("style='font-size:75%;'"));
 
     if (i != ScrollingPages.linesPerFrameDef - 1) {
@@ -2491,8 +2491,8 @@ bool P036_data_struct::web_show_values() {
 
 # if P036_SEND_EVENTS
 void P036_data_struct::P036_SendEvent(struct EventStruct *event, uint8_t eventId, int16_t eventValue) {
-  const __FlashStringHelper* eventid_str = F("");
-  
+  const __FlashStringHelper *eventid_str = F("");
+
   switch (eventId) {
     case P036_EVENT_DISPLAY:   eventid_str =  F("display");  break;
     case P036_EVENT_CONTRAST:  eventid_str =  F("contrast"); break;
@@ -2504,7 +2504,7 @@ void P036_data_struct::P036_SendEvent(struct EventStruct *event, uint8_t eventId
     case P036_EVENT_RESTORE:   eventid_str =  F("restore");   break;
     case P036_EVENT_SCROLL:    eventid_str =  F("scroll");    break;
     default:
-    return;
+      return;
   }
 
 
@@ -2514,4 +2514,4 @@ void P036_data_struct::P036_SendEvent(struct EventStruct *event, uint8_t eventId
 # endif // if P036_SEND_EVENTS
 
 
-#endif  // ifdef USES_P036
+#endif  // ifdef USES_P036
\ No newline at end of file
diff --git a/src/src/PluginStructs/P036_data_struct.h b/src/src/PluginStructs/P036_data_struct.h
index 0880feaf7..cb7091c9c 100644
--- a/src/src/PluginStructs/P036_data_struct.h
+++ b/src/src/PluginStructs/P036_data_struct.h
@@ -85,12 +85,6 @@
 # define P36_Nlines 12              // The number of different lines which can be displayed - each line is 64 chars max
 # define P36_NcharsV0 32            // max chars per line up to 22.11.2019 (V0)
 # define P36_NcharsV1 64            // max chars per line from 22.11.2019 (V1)
-# define P36_MaxSizesCount 3        // number of different OLED sizes
-# ifdef P036_LIMIT_BUILD_SIZE
-#  define P36_MaxFontCount 3        // number of different fonts
-# else // ifdef P036_LIMIT_BUILD_SIZE
-#  define P36_MaxFontCount 5        // number of different fonts
-# endif // ifdef P036_LIMIT_BUILD_SIZE
 
 # define P36_MaxDisplayWidth  128
 # define P36_MaxDisplayHeight 64
@@ -203,9 +197,9 @@ typedef struct {
   uint16_t LastWidth   = 0;    // width of last line in pix
   uint16_t Width       = 0;    // width in pix
   uint8_t  SLidx       = 0;    // index to DisplayLinesV1
-  uint8_t  reserved22;         // Fillers added to achieve better instance/memory alignment (multiple of 8)
-  uint8_t  reserved23;
-  uint8_t  reserved24;
+  uint8_t  reserved22{};         // Fillers added to achieve better instance/memory alignment (multiple of 8)
+  uint8_t  reserved23{};
+  uint8_t  reserved24{};
 } tScrollLine;
 
 typedef struct {
@@ -216,8 +210,8 @@ typedef struct {
   uint16_t TickerAvgPixPerChar = 0; // max of average pixel per character or pix change per scroll time (100ms)
   int16_t  MaxPixLen           = 0; // Max pix length to display (display width + 2*TickerAvgPixPerChar)
   # ifdef ESP8266                   // Helpful on ESP8266 only, it seems
-  uint8_t reserved15;               // Fillers added to achieve better instance/memory alignment (multiple of 8)
-  uint8_t reserved16;
+  uint8_t reserved15{};             // Fillers added to achieve better instance/memory alignment (multiple of 8)
+  uint8_t reserved16{};
   # endif // ifdef ESP8266
 } tTicker;
 
@@ -416,6 +410,11 @@ struct P036_data_struct : public PluginTaskData_base {
                           uint8_t     LoadVersion,
                           uint8_t     LineNo);
 
+private:
+  String create_display_header_text(eHeaderContent iHeaderContent) const;
+
+public:
+
   // The screen is set up as:
   // - 10 rows at the top for the header
   // - 46 rows in the middle for the scroll region
diff --git a/src/src/PluginStructs/P037_data_struct.cpp b/src/src/PluginStructs/P037_data_struct.cpp
index 82de0ab4a..7b818b4b2 100644
--- a/src/src/PluginStructs/P037_data_struct.cpp
+++ b/src/src/PluginStructs/P037_data_struct.cpp
@@ -60,7 +60,7 @@ bool P037_data_struct::loadSettings() {
 String P037_data_struct::saveSettings() {
   String res;
 
-  if (_taskIndex < TASKS_MAX) {
+  if (_taskIndex < TASKS_MAX) { // TODO tonhuisman: Combine saving the settings into 1 call
     size_t offset = 0;
     res += SaveCustomTaskSettings(_taskIndex, mqttTopics,
                                   VARS_PER_TASK, 41, offset);
diff --git a/src/src/PluginStructs/P037_data_struct.h b/src/src/PluginStructs/P037_data_struct.h
index 5d6b8bc3d..7c7bd6b9c 100644
--- a/src/src/PluginStructs/P037_data_struct.h
+++ b/src/src/PluginStructs/P037_data_struct.h
@@ -94,9 +94,11 @@
 # define P037_OPERAND_LIST    F("=%")
 
 # define P037_FILTER_COUNT    3
-# define P037_FILTER_LIST     F("=-:") // Length should at least match P037_FILTER_COUNT
+# define P037_FILTER_LIST     F("=-:")           // Length should at least match P037_FILTER_COUNT
 
-# define P037_VALUE_SEPARATOR '\x02'   // Separator outside of the normal ascii character values
+# define P037_VALUE_SEPARATOR '\x02'             // Separator outside of the normal ascii character values
+
+# define P037_REPLACE_CHAR_SET  "!@$%^&*;:.|/\\" // Allowable set of characters to be replaced by a comma
 
 // Data structure
 struct P037_data_struct : public PluginTaskData_base
diff --git a/src/src/PluginStructs/P038_data_struct.cpp b/src/src/PluginStructs/P038_data_struct.cpp
index d54d2160c..4585132db 100644
--- a/src/src/PluginStructs/P038_data_struct.cpp
+++ b/src/src/PluginStructs/P038_data_struct.cpp
@@ -16,10 +16,8 @@ P038_data_struct::P038_data_struct(int8_t   gpioPin,
 // Destructor
 // **************************************************************************/
 P038_data_struct::~P038_data_struct() {
-  if (Plugin_038_pixels != nullptr) {
-    delete Plugin_038_pixels;
-    Plugin_038_pixels = nullptr;
-  }
+  delete Plugin_038_pixels;
+  Plugin_038_pixels = nullptr;
 }
 
 bool P038_data_struct::plugin_init(struct EventStruct *event) {
@@ -41,10 +39,8 @@ bool P038_data_struct::plugin_init(struct EventStruct *event) {
 }
 
 bool P038_data_struct::plugin_exit(struct EventStruct *event) {
-  if (isInitialized()) {
-    delete Plugin_038_pixels;
-    Plugin_038_pixels = nullptr;
-  }
+  delete Plugin_038_pixels;
+  Plugin_038_pixels = nullptr;
   return true;
 }
 
@@ -58,15 +54,8 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str
       return success;
     }
 
-    {
-      String log;
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO) &&
-          log.reserve(64)) {
-        log += F("P038 : write - ");
-        log += string;
-        addLogMove(LOG_LEVEL_INFO, log);
-      }
+    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+      addLogMove(LOG_LEVEL_INFO, concat(F("P038 : write - "), string));
     }
 
     success = true;
@@ -94,7 +83,7 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str
     } else
 
     if (equals(cmd, F("neopixelall"))) { // NeoPixelAll
-      for (int i = 0; i < _maxPixels; i++) {
+      for (int i = 0; i < _maxPixels; ++i) {
         Plugin_038_pixels->setPixelColor(i, Plugin_038_pixels->Color(event->Par1, event->Par2, event->Par3, event->Par4));
       }
     } else
@@ -105,7 +94,7 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str
 
       HSV2RGBWorRGBandLog(event->Par1, event->Par2, event->Par3, rgbw);
 
-      for (int i = 0; i < _maxPixels; i++) {
+      for (int i = 0; i < _maxPixels; ++i) {
         Plugin_038_pixels->setPixelColor(i, Plugin_038_pixels->Color(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
       }
     } else
@@ -114,7 +103,7 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str
       int32_t brightness = 0;
       validIntFromString(parseString(string, 7), brightness); // Get 7th argument aka Par6
 
-      for (int i = event->Par1 - 1; i < event->Par2; i++) {
+      for (int i = event->Par1 - 1; i < event->Par2; ++i) {
         Plugin_038_pixels->setPixelColor(i, Plugin_038_pixels->Color(event->Par3, event->Par4, event->Par5, brightness));
       }
     } else
@@ -125,7 +114,7 @@ bool P038_data_struct::plugin_write(struct EventStruct *event, const String& str
 
       HSV2RGBWorRGBandLog(event->Par3, event->Par4, event->Par5, rgbw);
 
-      for (int i = event->Par1 - 1; i < event->Par2; i++) {
+      for (int i = event->Par1 - 1; i < event->Par2; ++i) {
         Plugin_038_pixels->setPixelColor(i, Plugin_038_pixels->Color(rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
       }
     } else {
@@ -145,19 +134,10 @@ void P038_data_struct::HSV2RGBWorRGBandLog(float H, float S, float V, int rgbw[4
   } else {                                  // RGB
     HSV2RGB(H, S, V, rgbw);
   }
-  String log;
 
-  if (loglevelActiveFor(LOG_LEVEL_INFO) &&
-      log.reserve(48)) {
-    log += F("P038 HSV converted to RGB(W):");
-    log += rgbw[0];
-    log += ',';
-    log += rgbw[1];
-    log += ',';
-    log += rgbw[2];
-    log += ',';
-    log += rgbw[3];
-    addLogMove(LOG_LEVEL_INFO, log);
+  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+    addLog(LOG_LEVEL_INFO,
+           strformat(F("P038 HSV converted to RGB(W):%d,%d,%d,%d"), rgbw[0], rgbw[1], rgbw[2], rgbw[3]));
   }
 }
 
diff --git a/src/src/PluginStructs/P039_data_struct.cpp b/src/src/PluginStructs/P039_data_struct.cpp
index db813365a..814af8073 100644
--- a/src/src/PluginStructs/P039_data_struct.cpp
+++ b/src/src/PluginStructs/P039_data_struct.cpp
@@ -3,15 +3,15 @@
 #ifdef USES_P039
 
 /*
-P039_data_struct::P039_data_struct(
+   P039_data_struct::P039_data_struct(
       uint16_t       l_conversionResult,
       uint8_t        l_devicefaults,
       unsigned long  l_timer,
-      bool           l_sensorFault, 
+      bool           l_sensorFault,
       bool           l_convReady)
-  :  conversionResult(l_conversionResult), deviceFaults(l_devicefaults), timer(l_timer), sensorFault(l_sensorFault), convReady(l_convReady) {}
-*/
-
+   :  conversionResult(l_conversionResult), deviceFaults(l_devicefaults), timer(l_timer), sensorFault(l_sensorFault), convReady(l_convReady)
+      {}
+ */
 bool P039_data_struct::begin()
 {
   return false;
@@ -19,12 +19,12 @@ bool P039_data_struct::begin()
 
 bool P039_data_struct::read()
 {
- return false;
+  return false;
 }
 
 bool P039_data_struct::write()
 {
- return false;
+  return false;
 }
 
-#endif // ifdef USES_P039
\ No newline at end of file
+#endif // ifdef USES_P039
diff --git a/src/src/PluginStructs/P039_data_struct.h b/src/src/PluginStructs/P039_data_struct.h
index db08ca08c..92305b068 100644
--- a/src/src/PluginStructs/P039_data_struct.h
+++ b/src/src/PluginStructs/P039_data_struct.h
@@ -9,15 +9,15 @@
 struct P039_data_struct : public PluginTaskData_base {
 public:
 
-/*
-  P039_data_struct(uint16_t               conversionResult,
-                   uint8_t                deviceFaults,
-                   unsigned long          timer,
-                   bool                   sensorFault,
-                   bool                   convReady);
-*/
+  /*
+     P039_data_struct(uint16_t               conversionResult,
+                     uint8_t                deviceFaults,
+                     unsigned long          timer,
+                     bool                   sensorFault,
+                     bool                   convReady);
+   */
 
-  P039_data_struct() = default;
+  P039_data_struct()          = default;
   virtual ~P039_data_struct() = default;
 
   bool begin();
@@ -30,14 +30,13 @@ public:
 
   // uint8_t mainState = 0x00u;;
   // uint8_t command = 0x00u;
-  uint16_t conversionResult = 0x0000u;
-  uint8_t deviceFaults = 0x00u;
-  unsigned long  timer = 0;
-  bool sensorFault = false;
-  bool convReady = false;
-
+  uint16_t      conversionResult = 0x0000u;
+  uint8_t       deviceFaults     = 0x00u;
+  unsigned long timer            = 0;
+  bool          sensorFault      = false;
+  bool          convReady        = false;
 };
 
 
 #endif // ifdef USES_P039
-#endif // ifndef PLUGINSTRUCTS_P039_DATA_STRUCT_H
\ No newline at end of file
+#endif // ifndef PLUGINSTRUCTS_P039_DATA_STRUCT_H
diff --git a/src/src/PluginStructs/P044_data_struct.cpp b/src/src/PluginStructs/P044_data_struct.cpp
index 48d7103af..4b11d1f98 100644
--- a/src/src/PluginStructs/P044_data_struct.cpp
+++ b/src/src/PluginStructs/P044_data_struct.cpp
@@ -1,393 +1,420 @@
-#include "../PluginStructs/P044_data_struct.h"
-
-#ifdef USES_P044
-
-#include "../ESPEasyCore/Serial.h"
-#include "../ESPEasyCore/ESPEasyNetwork.h"
-
-#include "../Globals/EventQueue.h"
-
-#include "../Helpers/ESPEasy_Storage.h"
-#include "../Helpers/Misc.h"
-
-#define P044_RX_WAIT              PCONFIG(0)
-
-
-P044_Task::~P044_Task() {
-  if (P1GatewayServer != nullptr) {
-    delete P1GatewayServer;
-    P1GatewayServer = nullptr;
-  }
-  if (P1EasySerial != nullptr) {
-    delete P1EasySerial;
-    P1EasySerial = nullptr;
-  }
-}
-
-bool P044_Task::serverActive(WiFiServer *server) {
-#if defined(ESP8266)
-  return nullptr != server && server->status() != CLOSED;
-#elif defined(ESP32)
-  return nullptr != server && *server;
-#endif // if defined(ESP8266)
-}
-
-void P044_Task::startServer(uint16_t portnumber) {
-  if ((gatewayPort == portnumber) && serverActive(P1GatewayServer)) {
-    // server is already listening on this port
-    return;
-  }
-  stopServer();
-  gatewayPort     = portnumber;
-  P1GatewayServer = new (std::nothrow) WiFiServer(portnumber);
-
-  if ((nullptr != P1GatewayServer) && NetworkConnected()) {
-    P1GatewayServer->begin();
-
-    if (serverActive(P1GatewayServer)) {
-      addLog(LOG_LEVEL_INFO, concat(F("P1   : WiFi server started at port "), static_cast(portnumber)));
-    } else {
-      addLog(LOG_LEVEL_ERROR, concat(F("P1   : WiFi server start failed at port "), static_cast(portnumber)) + F(", retrying..."));
-    }
-  }
-}
-
-void P044_Task::checkServer() {
-  if ((nullptr != P1GatewayServer) && !serverActive(P1GatewayServer) && NetworkConnected()) {
-    P1GatewayServer->close();
-    P1GatewayServer->begin();
-
-    if (serverActive(P1GatewayServer)) {
-      addLog(LOG_LEVEL_INFO, F("P1   : WiFi server started"));
-    }
-  }
-}
-
-void P044_Task::stopServer() {
-  if (nullptr != P1GatewayServer) {
-    if (P1GatewayClient) { P1GatewayClient.stop(); }
-    clientConnected = false;
-    P1GatewayServer->close();
-    addLog(LOG_LEVEL_INFO, F("P1   : WiFi server closed"));
-    delete P1GatewayServer;
-    P1GatewayServer = nullptr;
-  }
-}
-
-bool P044_Task::hasClientConnected() {
-  if ((nullptr != P1GatewayServer) && P1GatewayServer->hasClient())
-  {
-    if (P1GatewayClient) { P1GatewayClient.stop(); }
-    P1GatewayClient = P1GatewayServer->available();
-
-    #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
-
-    // See: https://github.com/espressif/arduino-esp32/pull/6676
-    P1GatewayClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!!
-    Client *pClient = &P1GatewayClient;
-    pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT);
-    #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
-    P1GatewayClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT);                // in msec as it should be!
-    #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
-
-    addLog(LOG_LEVEL_INFO, F("P1   : Client connected!"));
-  }
-
-  if (P1GatewayClient.connected())
-  {
-    clientConnected = true;
-  }
-  else
-  {
-    if (clientConnected) // there was a client connected before...
-    {
-      clientConnected = false;
-      addLog(LOG_LEVEL_INFO, F("P1   : Client disconnected!"));
-    }
-  }
-  return clientConnected;
-}
-
-void P044_Task::discardClientIn() {
-  // flush all data received from the WiFi gateway
-  // as a P1 meter does not receive data
-  while (P1GatewayClient.available()) {
-    P1GatewayClient.read();
-  }
-}
-
-void P044_Task::blinkLED() {
-  blinkLEDStartTime = millis();
-  digitalWrite(P044_STATUS_LED, 1);
-}
-
-void P044_Task::checkBlinkLED() {
-  if ((blinkLEDStartTime > 0) && (timePassedSince(blinkLEDStartTime) >= 500)) {
-    digitalWrite(P044_STATUS_LED, 0);
-    blinkLEDStartTime = 0;
-  }
-}
-
-void P044_Task::clearBuffer() {
-  if (serial_buffer.length() > maxMessageSize) {
-    maxMessageSize = _min(serial_buffer.length(), P044_DATAGRAM_MAX_SIZE);
-  }
-
-  serial_buffer = String();
-  serial_buffer.reserve(maxMessageSize);
-}
-
-void P044_Task::addChar(char ch) {
-  serial_buffer += ch;
-}
-
-/*  checkDatagram
-    checks whether the P044_CHECKSUM of the data received from P1 matches the P044_CHECKSUM
-    attached to the telegram
- */
-bool P044_Task::checkDatagram() const {
-  int endChar = serial_buffer.length() - 1;
-
-  if (CRCcheck) {
-    endChar -= P044_CHECKSUM_LENGTH;
-  }
-
-  if ((endChar < 0) || (serial_buffer[0] != P044_DATAGRAM_START_CHAR) ||
-      (serial_buffer[endChar] != P044_DATAGRAM_END_CHAR)) { return false; }
-
-  if (!CRCcheck) { return true; }
-
-  const int checksumStartIndex = endChar + 1;
-
-  #ifdef PLUGIN_044_DEBUG
-    for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) {
-      serialPrint(serial_buffer.substring(cnt, 1));
-    }
-  #endif
-
-  // calculate the CRC and check if it equals the hexadecimal one attached to the datagram
-  unsigned int crc = CRC16(serial_buffer, checksumStartIndex);
-  return strtoul(serial_buffer.substring(checksumStartIndex).c_str(), nullptr, 16) == crc;
-}
-
-/*
-   CRC16
-      based on code written by Jan ten Hove
-     https://github.com/jantenhove/P1-Meter-ESP8266
- */
-unsigned int P044_Task::CRC16(const String& buf, int len)
-{
-  unsigned int crc = 0;
-
-  for (int pos = 0; pos < len; pos++)
-  {
-    crc ^= static_cast(buf[pos]); // XOR byte into least sig. byte of crc
-
-    for (int i = 8; i != 0; i--) {                    // Loop over each bit
-      if ((crc & 0x0001) != 0) {                      // If the LSB is set
-        crc >>= 1;                                    // Shift right and XOR 0xA001
-        crc  ^= 0xA001;
-      }
-      else {                                          // Else LSB is not set
-        crc >>= 1;                                    // Just shift right
-      }
-    }
-  }
-
-  return crc;
-}
-
-/*
-   validP1char
-       Checks if the character is valid as part of the P1 datagram contents and/or checksum.
-       Returns false on a datagram start ('/'), end ('!') or invalid character
- */
-bool P044_Task::validP1char(char ch) {
-  return
-    isAlphaNumeric(ch) ||
-    ch == '.' ||
-    ch == ' ' ||
-    ch == '\\'|| // Single backslash, but escaped in C++
-    ch == '\r'||
-    ch == '\n'||
-    ch == '(' ||
-    ch == ')' ||
-    ch == '-' ||
-    ch == '*' ||
-    ch == ':' ||
-    ch == '_';
-}
-
-void P044_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin,
-                            unsigned long baud, uint8_t config) {
-  serialEnd();
-
-  if (rxPin >= 0) {
-    P1EasySerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin);
-
-    if (nullptr != P1EasySerial) {
-#if defined(ESP8266)
-      P1EasySerial->begin(baud, (SerialConfig)config);
-#elif defined(ESP32)
-      P1EasySerial->begin(baud, config);
-#endif // if defined(ESP8266)
-# ifndef BUILD_NO_DEBUG
-      addLog(LOG_LEVEL_DEBUG, F("P1   : Serial opened"));
-#endif
-    }
-  }
-  state = ParserState::WAITING;
-}
-
-void P044_Task::serialEnd() {
-  if (nullptr != P1EasySerial) {
-    delete P1EasySerial;
-    P1EasySerial = nullptr;
-# ifndef BUILD_NO_DEBUG
-    addLog(LOG_LEVEL_DEBUG, F("P1   : Serial closed"));
-#endif
-  }
-}
-
-void P044_Task::handleSerialIn(struct EventStruct *event) {
-  if (nullptr == P1EasySerial) { return; }
-  int  RXWait  = P044_RX_WAIT;
-  bool done    = false;
-  int  timeOut = RXWait;
-
-  do {
-    if (P1EasySerial->available()) {
-      digitalWrite(P044_STATUS_LED, 1);
-      done = handleChar(P1EasySerial->read());
-      digitalWrite(P044_STATUS_LED, 0);
-
-      if (done) { break; }
-      timeOut = RXWait; // if serial received, reset timeout counter
-    } else {
-      if (timeOut <= 0) { break; }
-      delay(1);
-      --timeOut;
-    }
-  } while (true);
-
-  if (done) {
-    P1GatewayClient.print(serial_buffer);
-    P1GatewayClient.flush();
-# ifndef BUILD_NO_DEBUG
-    addLog(LOG_LEVEL_DEBUG, F("P1   : data send!"));
-#endif
-    blinkLED();
-
-    eventQueue.add(event->TaskIndex, F("Data"), EMPTY_STRING);
-  } // done
-}
-
-bool P044_Task::handleChar(char ch) {
-  if (serial_buffer.length() >= P044_DATAGRAM_MAX_SIZE - 2) { // room for cr/lf
-# ifndef BUILD_NO_DEBUG
-    addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Buffer overflow, discarded input."));
-#endif
-    state = ParserState::WAITING;                             // reset
-  }
-
-  bool done    = false;
-  bool invalid = false;
-
-  switch (state) {
-    case ParserState::WAITING:
-
-      if (ch == P044_DATAGRAM_START_CHAR)  {
-        clearBuffer();
-        addChar(ch);
-        state = ParserState::READING;
-      } // else ignore data
-      break;
-    case ParserState::READING:
-
-      if (validP1char(ch)) {
-        addChar(ch);
-      } else if (ch == P044_DATAGRAM_END_CHAR) {
-        addChar(ch);
-
-        if (CRCcheck) {
-          checkI = 0;
-          state  = ParserState::CHECKSUM;
-        } else {
-          done = true;
-        }
-      } else if (ch == P044_DATAGRAM_START_CHAR) {
-# ifndef BUILD_NO_DEBUG
-        addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Start detected, discarded input."));
-#endif
-        state = ParserState::WAITING; // reset
-        return handleChar(ch);
-      } else {
-        invalid = true;
-      }
-      break;
-    case ParserState::CHECKSUM:
-
-      if (validP1char(ch)) {
-        addChar(ch);
-        ++checkI;
-
-        if (checkI == P044_CHECKSUM_LENGTH) {
-          done = true;
-        }
-      } else {
-        invalid = true;
-      }
-      break;
-  } // switch
-
-  if (invalid) {
-    // input is not a datagram char
-# ifndef BUILD_NO_DEBUG
-    addLog(LOG_LEVEL_DEBUG, F("P1   : Error: DATA corrupt, discarded input."));
-#endif
-
-    #ifdef PLUGIN_044_DEBUG
-      serialPrint(F("faulty char>"));
-      serialPrint(String(ch));
-      serialPrintln("<");
-    #endif
-    state = ParserState::WAITING; // reset
-  }
-
-  if (done) {
-    done = checkDatagram();
-
-    if (done) {
-      // add the cr/lf pair to the datagram ahead of reading both
-      // from serial as the datagram has already been validated
-      addChar('\r');
-      addChar('\n');
-    } else if (CRCcheck) {
-# ifndef BUILD_NO_DEBUG
-      addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Invalid CRC, dropped data"));
-#endif
-    } else {
-# ifndef BUILD_NO_DEBUG
-      addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Invalid datagram, dropped data"));
-#endif
-    }
-    state = ParserState::WAITING; // prepare for next one
-  }
-
-  return done;
-}
-
-void P044_Task::discardSerialIn() {
-  if (nullptr != P1EasySerial) {
-    while (P1EasySerial->available()) {
-      P1EasySerial->read();
-    }
-  }
-  state = ParserState::WAITING;
-}
-
-bool P044_Task::isInit() const {
-  return nullptr != P1GatewayServer && nullptr != P1EasySerial;
-}
-
-#endif
+#include "../PluginStructs/P044_data_struct.h"
+
+#ifdef USES_P044_ORG
+
+# include "../ESPEasyCore/Serial.h"
+# include "../ESPEasyCore/ESPEasyNetwork.h"
+
+# include "../Globals/EventQueue.h"
+
+# include "../Helpers/ESPEasy_Storage.h"
+# include "../Helpers/Misc.h"
+
+
+P044_Task::P044_Task(struct EventStruct *event) {
+  clearBuffer();
+
+  if (P044_LED_ENABLED & 0x80) {
+    _ledPin = P044_LED_PIN;                      // Default pin (12) is already initialized in P044_Task
+  }
+  _ledEnabled  = (P044_LED_ENABLED & 0x7f) == 0; // Inverted setting and strip off new-settings bit
+  _ledInverted = P044_LED_INVERTED == 1;
+}
+
+P044_Task::~P044_Task() {
+  if (P1GatewayServer != nullptr) {
+    delete P1GatewayServer;
+    P1GatewayServer = nullptr;
+  }
+  if (P1EasySerial != nullptr) {
+    delete P1EasySerial;
+    P1EasySerial = nullptr;
+  }
+}
+
+bool P044_Task::serverActive(WiFiServer *server) {
+  # if defined(ESP8266)
+  return nullptr != server && server->status() != CLOSED;
+  # endif // if defined(ESP8266)
+  # if defined(ESP32)
+  return nullptr != server && *server;
+  # endif // if defined(ESP32)
+}
+
+void P044_Task::startServer(uint16_t portnumber) {
+  if ((gatewayPort == portnumber) && serverActive(P1GatewayServer)) {
+    // server is already listening on this port
+    return;
+  }
+  stopServer();
+  gatewayPort     = portnumber;
+  P1GatewayServer = new (std::nothrow) WiFiServer(portnumber);
+
+  if ((nullptr != P1GatewayServer) && NetworkConnected()) {
+    P1GatewayServer->begin();
+
+    if (serverActive(P1GatewayServer)) {
+      # ifndef LIMIT_BUILD_SIZE
+      addLog(LOG_LEVEL_INFO, concat(F("P1   : WiFi server started at port "), static_cast(portnumber)));
+      # endif // ifndef LIMIT_BUILD_SIZE
+    } else {
+      addLog(LOG_LEVEL_ERROR, concat(F("P1   : WiFi server start failed at port "), static_cast(portnumber)) + F(", retrying..."));
+    }
+  }
+}
+
+void P044_Task::checkServer() {
+  if ((nullptr != P1GatewayServer) && !serverActive(P1GatewayServer) && NetworkConnected()) {
+    P1GatewayServer->close();
+    P1GatewayServer->begin();
+
+    if (serverActive(P1GatewayServer)) {
+      # ifndef LIMIT_BUILD_SIZE
+      addLog(LOG_LEVEL_INFO, F("P1   : WiFi server started"));
+      # endif // ifndef LIMIT_BUILD_SIZE
+    }
+  }
+}
+
+void P044_Task::stopServer() {
+  if (nullptr != P1GatewayServer) {
+    if (P1GatewayClient) { P1GatewayClient.stop(); }
+    clientConnected = false;
+    P1GatewayServer->close();
+    # ifndef LIMIT_BUILD_SIZE
+    addLog(LOG_LEVEL_INFO, F("P1   : WiFi server closed"));
+    # endif // ifndef LIMIT_BUILD_SIZE
+    delete P1GatewayServer;
+    P1GatewayServer = nullptr;
+  }
+}
+
+bool P044_Task::hasClientConnected() {
+  if ((nullptr != P1GatewayServer) && P1GatewayServer->hasClient())
+  {
+    if (P1GatewayClient) { P1GatewayClient.stop(); }
+    P1GatewayClient = P1GatewayServer->available();
+
+    # ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
+
+    // See: https://github.com/espressif/arduino-esp32/pull/6676
+    P1GatewayClient.setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!!
+    Client *pClient = &P1GatewayClient;
+    pClient->setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT);
+    # else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
+    P1GatewayClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be!
+    # endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS
+
+    # ifndef LIMIT_BUILD_SIZE
+    addLog(LOG_LEVEL_INFO, F("P1   : Client connected!"));
+    # endif // ifndef LIMIT_BUILD_SIZE
+  }
+
+  if (P1GatewayClient.connected())
+  {
+    clientConnected = true;
+  }
+  else
+  {
+    if (clientConnected) // there was a client connected before...
+    {
+      clientConnected = false;
+      # ifndef LIMIT_BUILD_SIZE
+      addLog(LOG_LEVEL_INFO, F("P1   : Client disconnected!"));
+      # endif // ifndef LIMIT_BUILD_SIZE
+    }
+  }
+  return clientConnected;
+}
+
+void P044_Task::discardClientIn() {
+  // flush all data received from the WiFi gateway
+  // as a P1 meter does not receive data
+  while (P1GatewayClient.available()) {
+    P1GatewayClient.read();
+  }
+}
+
+void P044_Task::blinkLED() {
+  if (_ledEnabled) {
+    blinkLEDStartTime = millis();
+    digitalWrite(_ledPin, _ledInverted ? 0 : 1);
+  }
+}
+
+void P044_Task::checkBlinkLED() {
+  if (_ledEnabled && (blinkLEDStartTime > 0) && (timePassedSince(blinkLEDStartTime) >= 500)) {
+    digitalWrite(_ledPin, _ledInverted ? 1 : 0);
+    blinkLEDStartTime = 0;
+  }
+}
+
+void P044_Task::clearBuffer() {
+  if (serial_buffer.length() > maxMessageSize) {
+    maxMessageSize = _min(serial_buffer.length(), P044_DATAGRAM_MAX_SIZE);
+  }
+
+  serial_buffer = String();
+  serial_buffer.reserve(maxMessageSize);
+}
+
+void P044_Task::addChar(char ch) {
+  serial_buffer += ch;
+}
+
+/*  checkDatagram
+    checks whether the P044_CHECKSUM of the data received from P1 matches the P044_CHECKSUM
+    attached to the telegram
+ */
+bool P044_Task::checkDatagram() const {
+  int endChar = serial_buffer.length() - 1;
+
+  if (CRCcheck) {
+    endChar -= P044_CHECKSUM_LENGTH;
+  }
+
+  if ((endChar < 0) || (serial_buffer[0] != P044_DATAGRAM_START_CHAR) ||
+      (serial_buffer[endChar] != P044_DATAGRAM_END_CHAR)) { return false; }
+
+  if (!CRCcheck) { return true; }
+
+  const int checksumStartIndex = endChar + 1;
+
+  # ifdef PLUGIN_044_DEBUG
+
+  for (unsigned int cnt = 0; cnt < serial_buffer.length(); ++cnt) {
+    serialPrint(serial_buffer.substring(cnt, 1));
+  }
+  # endif // ifdef PLUGIN_044_DEBUG
+
+  // calculate the CRC and check if it equals the hexadecimal one attached to the datagram
+  unsigned int crc = CRC16(serial_buffer, checksumStartIndex);
+  return strtoul(serial_buffer.substring(checksumStartIndex).c_str(), nullptr, 16) == crc;
+}
+
+/*
+   CRC16
+      based on code written by Jan ten Hove
+     https://github.com/jantenhove/P1-Meter-ESP8266
+ */
+unsigned int P044_Task::CRC16(const String& buf, int len)
+{
+  unsigned int crc = 0;
+
+  for (int pos = 0; pos < len; pos++)
+  {
+    crc ^= static_cast(buf[pos]); // XOR byte into least sig. byte of crc
+
+    for (int i = 8; i != 0; i--) {                    // Loop over each bit
+      if ((crc & 0x0001) != 0) {                      // If the LSB is set
+        crc >>= 1;                                    // Shift right and XOR 0xA001
+        crc  ^= 0xA001;
+      }
+      else {                                          // Else LSB is not set
+        crc >>= 1;                                    // Just shift right
+      }
+    }
+  }
+
+  return crc;
+}
+
+/*
+   validP1char
+       Checks if the character is valid as part of the P1 datagram contents and/or checksum.
+       Returns false on a datagram start ('/'), end ('!') or invalid character
+ */
+bool P044_Task::validP1char(char ch) {
+  return
+    isAlphaNumeric(ch) ||
+    ch == '.' ||
+    ch == ' ' ||
+    ch == '\\' || // Single backslash, but escaped in C++
+    ch == '\r' ||
+    ch == '\n' ||
+    ch == '(' ||
+    ch == ')' ||
+    ch == '-' ||
+    ch == '*' ||
+    ch == ':' ||
+    ch == '_';
+}
+
+void P044_Task::serialBegin(const ESPEasySerialPort port, int16_t rxPin, int16_t txPin,
+                            unsigned long baud, uint8_t config) {
+  serialEnd();
+
+  if (rxPin >= 0) {
+    P1EasySerial = new (std::nothrow) ESPeasySerial(port, rxPin, txPin);
+
+    if (nullptr != P1EasySerial) {
+      # if defined(ESP8266)
+      P1EasySerial->begin(baud, (SerialConfig)config);
+      # elif defined(ESP32)
+      P1EasySerial->begin(baud, config);
+      # endif // if defined(ESP8266)
+      # ifndef BUILD_NO_DEBUG
+      addLog(LOG_LEVEL_DEBUG, F("P1   : Serial opened"));
+      # endif // ifndef BUILD_NO_DEBUG
+    }
+  }
+  state = ParserState::WAITING;
+}
+
+void P044_Task::serialEnd() {
+  if (nullptr != P1EasySerial) {
+    delete P1EasySerial;
+    P1EasySerial = nullptr;
+    # ifndef BUILD_NO_DEBUG
+    addLog(LOG_LEVEL_DEBUG, F("P1   : Serial closed"));
+    # endif // ifndef BUILD_NO_DEBUG
+  }
+}
+
+void P044_Task::handleSerialIn(struct EventStruct *event) {
+  if (nullptr == P1EasySerial) { return; }
+  int  RXWait  = P044_RX_WAIT;
+  bool done    = false;
+  int  timeOut = RXWait;
+
+  do {
+    if (P1EasySerial->available()) {
+      if (_ledEnabled) {
+        digitalWrite(_ledPin, _ledInverted ? 0 : 1);
+      }
+      done = handleChar(P1EasySerial->read());
+
+      if (_ledEnabled) {
+        digitalWrite(_ledPin, _ledInverted ? 1 : 0);
+      }
+
+      if (done) { break; }
+      timeOut = RXWait; // if serial received, reset timeout counter
+    } else {
+      if (timeOut <= 0) { break; }
+      delay(1);
+      --timeOut;
+    }
+  } while (true);
+
+  if (done) {
+    P1GatewayClient.print(serial_buffer);
+    P1GatewayClient.flush();
+    # ifndef BUILD_NO_DEBUG
+    addLog(LOG_LEVEL_DEBUG, F("P1   : data send!"));
+    # endif // ifndef BUILD_NO_DEBUG
+    blinkLED();
+
+    eventQueue.add(event->TaskIndex, F("Data"), EMPTY_STRING);
+  } // done
+}
+
+bool P044_Task::handleChar(char ch) {
+  if (serial_buffer.length() >= P044_DATAGRAM_MAX_SIZE - 2) { // room for cr/lf
+    # ifndef BUILD_NO_DEBUG
+    addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Buffer overflow, discarded input."));
+    # endif // ifndef BUILD_NO_DEBUG
+    state = ParserState::WAITING; // reset
+  }
+
+  bool done    = false;
+  bool invalid = false;
+
+  switch (state) {
+    case ParserState::WAITING:
+
+      if (ch == P044_DATAGRAM_START_CHAR)  {
+        clearBuffer();
+        addChar(ch);
+        state = ParserState::READING;
+      } // else ignore data
+      break;
+    case ParserState::READING:
+
+      if (validP1char(ch)) {
+        addChar(ch);
+      } else if (ch == P044_DATAGRAM_END_CHAR) {
+        addChar(ch);
+
+        if (CRCcheck) {
+          checkI = 0;
+          state  = ParserState::CHECKSUM;
+        } else {
+          done = true;
+        }
+      } else if (ch == P044_DATAGRAM_START_CHAR) {
+        # ifndef BUILD_NO_DEBUG
+        addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Start detected, discarded input."));
+        # endif // ifndef BUILD_NO_DEBUG
+        state = ParserState::WAITING; // reset
+        return handleChar(ch);
+      } else {
+        invalid = true;
+      }
+      break;
+    case ParserState::CHECKSUM:
+
+      if (validP1char(ch)) {
+        addChar(ch);
+        ++checkI;
+
+        if (checkI == P044_CHECKSUM_LENGTH) {
+          done = true;
+        }
+      } else {
+        invalid = true;
+      }
+      break;
+  } // switch
+
+  if (invalid) {
+    // input is not a datagram char
+    # ifndef BUILD_NO_DEBUG
+    addLog(LOG_LEVEL_DEBUG, F("P1   : Error: DATA corrupt, discarded input."));
+    # endif // ifndef BUILD_NO_DEBUG
+
+    # ifdef PLUGIN_044_DEBUG
+    serialPrint(F("faulty char>"));
+    serialPrint(String(ch));
+    serialPrintln("<");
+    # endif // ifdef PLUGIN_044_DEBUG
+    state = ParserState::WAITING; // reset
+  }
+
+  if (done) {
+    done = checkDatagram();
+
+    if (done) {
+      // add the cr/lf pair to the datagram ahead of reading both
+      // from serial as the datagram has already been validated
+      addChar('\r');
+      addChar('\n');
+    } else if (CRCcheck) {
+      # ifndef BUILD_NO_DEBUG
+      addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Invalid CRC, dropped data"));
+      # endif // ifndef BUILD_NO_DEBUG
+    } else {
+      # ifndef BUILD_NO_DEBUG
+      addLog(LOG_LEVEL_DEBUG, F("P1   : Error: Invalid datagram, dropped data"));
+      # endif // ifndef BUILD_NO_DEBUG
+    }
+    state = ParserState::WAITING; // prepare for next one
+  }
+
+  return done;
+}
+
+void P044_Task::discardSerialIn() {
+  if (nullptr != P1EasySerial) {
+    while (P1EasySerial->available()) {
+      P1EasySerial->read();
+    }
+  }
+  state = ParserState::WAITING;
+}
+
+bool P044_Task::isInit() const {
+  return nullptr != P1GatewayServer && nullptr != P1EasySerial;
+}
+
+#endif // ifdef USES_P044
diff --git a/src/src/PluginStructs/P044_data_struct.h b/src/src/PluginStructs/P044_data_struct.h
index 7b523ab72..632137e93 100644
--- a/src/src/PluginStructs/P044_data_struct.h
+++ b/src/src/PluginStructs/P044_data_struct.h
@@ -3,17 +3,29 @@
 
 #include "../../_Plugin_Helper.h"
 
-#ifdef USES_P044
+#ifdef USES_P044_ORG
 
-#include 
+# include 
 
 // #define PLUGIN_044_DEBUG  // extra logging in serial out
 
-#define P044_STATUS_LED                    12
-#define P044_CHECKSUM_LENGTH               4
-#define P044_DATAGRAM_START_CHAR           '/'
-#define P044_DATAGRAM_END_CHAR             '!'
-#define P044_DATAGRAM_MAX_SIZE             2048u
+# define P044_SET_WIFI_SERVER_PORT  ExtraTaskSettings.TaskDevicePluginConfigLong[0]
+# define P044_SET_BAUDRATE          ExtraTaskSettings.TaskDevicePluginConfigLong[1]
+# define P044_GET_WIFI_SERVER_PORT  Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 0)
+# define P044_GET_BAUDRATE          Cache.getTaskDevicePluginConfigLong(event->TaskIndex, 1)
+# define P044_RX_WAIT               PCONFIG(0)
+# define P044_SERIAL_CONFIG         PCONFIG(1)
+# define P044_RESET_TARGET_PIN      CONFIG_PIN1
+# define P044_LED_PIN               CONFIG_PIN2
+# define P044_LED_ENABLED           PCONFIG(2)
+# define P044_LED_INVERTED          PCONFIG(3)
+
+
+# define P044_STATUS_LED                    12
+# define P044_CHECKSUM_LENGTH               4
+# define P044_DATAGRAM_START_CHAR           '/'
+# define P044_DATAGRAM_END_CHAR             '!'
+# define P044_DATAGRAM_MAX_SIZE             2048u
 
 
 struct P044_Task : public PluginTaskData_base {
@@ -23,7 +35,7 @@ struct P044_Task : public PluginTaskData_base {
     CHECKSUM
   };
 
-  P044_Task() = default;
+  P044_Task(struct EventStruct *event);
 
   virtual ~P044_Task();
 
@@ -70,10 +82,10 @@ struct P044_Task : public PluginTaskData_base {
   static bool validP1char(char ch);
 
   void        serialBegin(const ESPEasySerialPort port,
-                          int16_t       rxPin,
-                          int16_t       txPin,
-                          unsigned long baud,
-                          uint8_t          config);
+                          int16_t                 rxPin,
+                          int16_t                 txPin,
+                          unsigned long           baud,
+                          uint8_t                 config);
 
   void serialEnd();
 
@@ -96,7 +108,11 @@ struct P044_Task : public PluginTaskData_base {
   ESPeasySerial *P1EasySerial      = nullptr;
   unsigned long  blinkLEDStartTime = 0;
   size_t         maxMessageSize    = P044_DATAGRAM_MAX_SIZE / 4;
+
+  int8_t _ledPin      = P044_STATUS_LED; // Former default
+  bool   _ledEnabled  = true;            // Former default
+  bool   _ledInverted = false;
 };
 
-#endif
-#endif
+#endif // ifdef USES_P044_ORG
+#endif // ifndef PLUGINSTRUCTS_P044_DATA_STRUCT_H
diff --git a/src/src/PluginStructs/P045_data_struct.cpp b/src/src/PluginStructs/P045_data_struct.cpp
index 89b576ad7..cc7c33da2 100644
--- a/src/src/PluginStructs/P045_data_struct.cpp
+++ b/src/src/PluginStructs/P045_data_struct.cpp
@@ -80,7 +80,7 @@ void P045_data_struct::loop()
     _timer = millis();
 
     // Determine the maximum measured range of each axis
-    for (uint8_t i = 0; i < 3; i++) {
+    for (uint8_t i = 0; i < 3; ++i) {
       _axis[i][2] = abs(_axis[i][1] - _axis[i][0]);
       _axis[i][0] = _axis[i][3];
       _axis[i][1] = _axis[i][3];
@@ -109,7 +109,7 @@ void P045_data_struct::getRaw6AxisMotion(int16_t *ax, int16_t *ay, int16_t *az,
   I2C_write8(i2cAddress, MPU6050_RA_ACCEL_XOUT_H);
   Wire.requestFrom(i2cAddress, (uint8_t)14);
 
-  for (; Wire.available(); count++) {
+  for (; Wire.available(); ++count) {
     buffer[count] = Wire.read();
   }
   *ax = (((int16_t)buffer[0]) << 8) | buffer[1];
diff --git a/src/src/PluginStructs/P047_data_struct.cpp b/src/src/PluginStructs/P047_data_struct.cpp
index 54d8fc9b2..57db31757 100644
--- a/src/src/PluginStructs/P047_data_struct.cpp
+++ b/src/src/PluginStructs/P047_data_struct.cpp
@@ -8,7 +8,26 @@
 // **************************************************************************/
 P047_data_struct::P047_data_struct(uint8_t address,
                                    uint8_t model) :
-  _address(address), _model(static_cast(model)) {}
+  _address(address), _model(static_cast(model)) {
+  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+    addLog(LOG_LEVEL_INFO,
+           strformat(F("SoilMoisture: Initializing sensor: %s, version: 0x%x"), String(toString(_model)).c_str(), getVersion()));
+  }
+}
+
+const __FlashStringHelper* toString(P047_SensorModels sensor) {
+  switch (sensor) {
+    case P047_SensorModels::CatnipMiceuz: return F("Catnip electronics/miceuz (default)");
+    case P047_SensorModels::BeFlE: return F("BeFlE v2.2");
+    # if P047_FEATURE_ADAFRUIT
+    case P047_SensorModels::Adafruit: return F("Adafruit (4026)");
+    # endif // if P047_FEATURE_ADAFRUIT
+    # if P047_FEATURE_BEFLE_V3
+    case P047_SensorModels::BeFlEv3: return F("BeFlE v3.x");
+    # endif // if P047_FEATURE_BEFLE_V3
+  }
+  return F("");
+}
 
 // **************************************************************************/
 // PLUGIN_READ
@@ -18,7 +37,7 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) {
 
   if (P047_SENSOR_SLEEP && (P047_ReadMode::ReadStarted != _readMode)) {
     // wake sensor when not reading
-    getVersion();
+    setToSleep(false);   // Wake sensor
     delayBackground(20); // Seems acceptable to have this relatively short delay
     # ifndef BUILD_NO_DEBUG
     addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->wake"));
@@ -35,7 +54,7 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) {
         if (!((_sensorVersion == 0x22) || (_sensorVersion == 0x23) || (_sensorVersion == 0x24) || (_sensorVersion == 0x25) ||
               (_sensorVersion == 0x26))) {
           // invalid sensor
-          addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Version, no Sensor?"));
+          addLog(LOG_LEVEL_ERROR, F("SoilMoisture: Bad Version, no Sensor?"));
           resetSensor();
         }
       }
@@ -43,8 +62,7 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) {
       // check if we want to change the sensor address
       if (P047_CHANGE_ADDR && (P047_I2C_ADDR != P047_NEW_ADDR) && (0 != P047_NEW_ADDR)) {
         if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          addLog(LOG_LEVEL_INFO, concat(F("SoilMoisture: Change Address: "), formatToHex(P047_I2C_ADDR, HEX)) +
-                 concat(F(" -> "), formatToHex(P047_NEW_ADDR)));
+          addLog(LOG_LEVEL_INFO, strformat(F("SoilMoisture: Change Address: 0x%02x -> 0x%02x"), P047_I2C_ADDR, P047_NEW_ADDR));
         }
 
         if (changeAddress(P047_NEW_ADDR)) {
@@ -67,47 +85,67 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) {
 
     case P047_ReadMode::ReadStarted:
     {
-      // Get the values
-      const float temperature = readTemperature();
-      const float moisture    = readMoisture();
-      const float light       = readLight();
+      if (measurementReady()) {
+        # if P047_FEATURE_ADAFRUIT
 
-      if ((temperature > 100.0f) || (temperature < -40.0f) || (moisture > 800.0f) || (moisture < 1.0f) || (light > 65535.0f) ||
-          (light < 0.0f)) {
-        addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Reading, resetting Sensor..."));
-        resetSensor();
-      }
-      else
-      {
-        UserVar.setFloat(event->TaskIndex, 0, temperature);
-        UserVar.setFloat(event->TaskIndex, 1, moisture);
-        UserVar.setFloat(event->TaskIndex, 2, light);
+        // Define range
+        float moisture_min = 1.0f;
+        float moisture_max = 800.0f;
 
-        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          String log = concat(F("SoilMoisture: Address: "), formatToHex(P047_I2C_ADDR));
-
-          if (P047_CHECK_VERSION) {
-            log += concat(F(" Version: "), formatToHex(_sensorVersion));
-          }
-          addLogMove(LOG_LEVEL_INFO, log);
-          addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Temperature: "), formatUserVarNoCheck(event, 0)));
-          addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Moisture: "), static_cast(moisture)));
-
-          if (P047_MODEL_CATNIP == _model) {
-            addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Light: "), formatUserVarNoCheck(event, 2)));
-          }
+        if (P047_MODEL_ADAFRUIT == _model) {
+          moisture_min = 200.0f;
+          moisture_max = 2000.0f;
         }
+        # else // if P047_FEATURE_ADAFRUIT
 
-        if (P047_SENSOR_SLEEP) {
-          // send sensor to sleep
-          setToSleep();
-          # ifndef BUILD_NO_DEBUG
-          addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->sleep"));
-          # endif // ifndef BUILD_NO_DEBUG
+        // Define range
+        const float moisture_min = 1.0f;
+        const float moisture_max = 800.0f;
+        # endif // if P047_FEATURE_ADAFRUIT
+
+        // Get the values
+        const float temperature = readTemperature();
+        const float moisture    = readMoisture();
+        const float light       = readLight();
+
+        if ((temperature > 100.0f) || (temperature < -40.0f) ||
+            (moisture > moisture_max) || (moisture < moisture_min) ||
+            (light > 65535.0f) || (light < 0.0f)) {
+          addLog(LOG_LEVEL_INFO, F("SoilMoisture: Bad Reading, resetting Sensor..."));
+          resetSensor();
         }
-        success = true;
+        else
+        {
+          UserVar.setFloat(event->TaskIndex, 0, temperature);
+          UserVar.setFloat(event->TaskIndex, 1, moisture);
+          UserVar.setFloat(event->TaskIndex, 2, light);
+
+          if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+            String log = strformat(F("SoilMoisture: Address: 0x%02x"), P047_I2C_ADDR);
+
+            if (P047_CHECK_VERSION) {
+              log += strformat(F(" Version: 0x%02x"), _sensorVersion);
+            }
+            addLogMove(LOG_LEVEL_INFO, log);
+            addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Temperature: "), formatUserVarNoCheck(event, 0)));
+            addLogMove(LOG_LEVEL_INFO, strformat(F("SoilMoisture: Moisture: %.0f"), moisture));
+
+            if (P047_MODEL_CATNIP == _model) {
+              addLogMove(LOG_LEVEL_INFO, concat(F("SoilMoisture: Light: "), formatUserVarNoCheck(event, 2)));
+            }
+          }
+
+          if (P047_SENSOR_SLEEP) {
+            // send sensor to sleep
+            setToSleep(true);
+            # ifndef BUILD_NO_DEBUG
+            addLog(LOG_LEVEL_DEBUG, F("SoilMoisture->sleep"));
+            # endif // ifndef BUILD_NO_DEBUG
+          }
+          success = true;
+        }
+        _readMode = P047_ReadMode::NotReading;
       }
-      _readMode = P047_ReadMode::NotReading;
 
       break;
     }
@@ -121,9 +159,33 @@ bool P047_data_struct::plugin_read(struct EventStruct *event) {
 float P047_data_struct::readTemperature() {
   if (P047_MODEL_CATNIP == _model) {
     return I2C_readS16_reg(_address, P047_CATNIP_GET_TEMPERATURE) / 10.0f;
-  } else {
+  } else if (P047_MODEL_BEFLE == _model
+             # if P047_FEATURE_BEFLE_V3
+             || P047_MODEL_BEFLE_V3 == _model
+             # endif // if P047_FEATURE_BEFLE_V3
+             ) {
     return static_cast(I2C_read8_reg(_address, P047_BEFLE_GET_TEMPERATURE));
+    # if P047_FEATURE_ADAFRUIT
+  } else if (P047_MODEL_ADAFRUIT == _model) {
+    uint8_t buf[4]{};
+    bool    isOk;
+    const uint8_t toRead = 4;
+
+    I2C_write8_reg(_address, P047_ADAFRUIT_STATUS_BASE, P047_ADAFRUIT_GET_TEMPERATURE);
+    delayMicroseconds(1000);
+
+    if (Wire.requestFrom(_address, toRead) == toRead) {
+      for (uint8_t b = 0; b < toRead; ++b) {
+        buf[b] = Wire.read();
+      }
+    }
+    const int32_t rTemp = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
+                          ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
+
+    return (1.0 / (1UL << 16)) * rTemp;
+    # endif // if P047_FEATURE_ADAFRUIT
   }
+  return -273.15f; // 0 K = error
 }
 
 // **************************************************************************/
@@ -134,7 +196,7 @@ float P047_data_struct::readLight() {
     return I2C_read16_reg(_address, P047_CATNIP_GET_LIGHT);
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE or Adafruit sensors
   return 0.0f;
 }
 
@@ -144,18 +206,79 @@ float P047_data_struct::readLight() {
 unsigned int P047_data_struct::readMoisture() {
   if (P047_MODEL_CATNIP == _model) {
     return I2C_read16_reg(_address, P047_CATNIP_GET_CAPACITANCE);
-  } else {
+  } else if (P047_MODEL_BEFLE == _model
+             # if P047_FEATURE_BEFLE_V3
+             || P047_MODEL_BEFLE_V3 == _model
+             # endif // if P047_FEATURE_BEFLE_V3
+             ) {
     return I2C_read16_reg(_address, P047_BEFLE_GET_CAPACITANCE) >> 8; // Get averaged value
+    # if P047_FEATURE_ADAFRUIT
+  } else if (P047_MODEL_ADAFRUIT == _model) {
+    uint8_t  p    = 0;
+    uint16_t ret  = 65535;
+    bool     isOk = false;
+
+    for (uint8_t retry = 0; retry < 5; ++retry) {
+      I2C_write8_reg(_address, P047_ADAFRUIT_TOUCH_BASE, P047_ADAFRUIT_GET_CAPACITANCE + p);
+      delayMicroseconds(3000 + retry * 1000);
+
+      ret = I2C_read16(_address, &isOk);
+
+      if (isOk) {
+        break;
+      }
+    }
+    return ret;
+
+    # endif // if P047_FEATURE_ADAFRUIT
   }
+  return 0;
 }
 
 // Read Sensor Version
-uint8_t P047_data_struct::getVersion() {
+uint32_t P047_data_struct::getVersion() {
   if (P047_MODEL_CATNIP == _model) {
     return I2C_read8_reg(_address, P047_CATNIP_GET_VERSION);
+    # if P047_FEATURE_ADAFRUIT
+  } else if (P047_MODEL_ADAFRUIT == _model) {
+    uint8_t buf[4]{};
+    bool    isOk         = false;
+    const uint8_t toRead = 4;
+
+    I2C_write8_reg(_address, P047_ADAFRUIT_STATUS_BASE, P047_ADAFRUIT_GET_VERSION);
+
+    if (Wire.requestFrom(_address, toRead) == toRead) {
+      for (uint8_t b = 0; b < toRead; ++b) {
+        buf[b] = Wire.read();
+      }
+    }
+    uint32_t ret = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
+                   ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
+    return ret;
+    # endif // if P047_FEATURE_ADAFRUIT
+    # if P047_FEATURE_BEFLE_V3
+  } else if (P047_MODEL_BEFLE_V3 == _model) {
+    uint8_t buf[4]{};
+
+    I2C_write8(_address, P047_BEFLE_V3_GET_VERSION);
+    delay(1);
+
+    if (Wire.requestFrom(_address, 4) == 4) {
+      for (int b = 0; b < 4; ++b) {
+        buf[b] = Wire.read();
+      }
+    }
+
+    if ((buf[0] == 0x76) /*'v'*/ && (buf[2] == 0x2E) /*'.'*/) {                      // Returns "v3.4" for version 3.4
+      uint32_t ret = (((uint32_t)buf[1] - 0x30) * 0x10) | ((uint32_t)buf[3] - 0x30); // Return 0x34 for 3.4
+      return ret;
+    } else {
+      return 0x22;                                                                   // Fallback to HW version 2.2
+    }
+    # endif // if P047_FEATURE_ADAFRUIT
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE v2 sensor
   return 0;
 }
 
@@ -166,12 +289,25 @@ uint8_t P047_data_struct::getVersion() {
 * Method returns true if the new address is set successfully on sensor.*
 *----------------------------------------------------------------------*/
 bool P047_data_struct::changeAddress(uint8_t new_i2cAddr) {
-  uint8_t command;
+  uint8_t command = 0;
 
   if (P047_MODEL_CATNIP == _model) {
     command = P047_CATNIP_SET_ADDRESS;
-  } else {
-    command = P047_BEFLE_SET_ADDRESS;
+  } else
+  if (P047_MODEL_BEFLE == _model) {
+    command     = P047_BEFLE_SET_ADDRESS;
+    new_i2cAddr = (new_i2cAddr << 1) & 0xFE;
+    # if P047_FEATURE_ADAFRUIT
+  } else
+  if (P047_MODEL_ADAFRUIT == _model) {
+    return true; // Is set in hardware
+    # endif // if P047_FEATURE_ADAFRUIT
+    # if P047_FEATURE_BEFLE_V3
+  } else
+  if (P047_MODEL_BEFLE_V3 == _model) {
+    command     = P047_BEFLE_SET_ADDRESS;
+    new_i2cAddr = (new_i2cAddr << 1) & 0xFE;
+    # endif // if P047_FEATURE_BEFLE_V3
   }
   I2C_write8_reg(_address, command, new_i2cAddr);
   I2C_write8_reg(_address, command, new_i2cAddr);
@@ -188,7 +324,7 @@ bool P047_data_struct::checkAddress(uint8_t new_i2cAddr) {
     return I2C_read8_reg(_address, P047_CATNIP_GET_ADDRESS) == new_i2cAddr;
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE or Adafruit sensors
   return true;
 }
 
@@ -201,24 +337,57 @@ bool P047_data_struct::resetSensor() {
     return true;
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE or Adafruit sensors
   return false;
 }
 
-void P047_data_struct::setToSleep() {
-  if (P047_MODEL_CATNIP == _model) {
-    I2C_write8(_address, P047_CATNIP_SLEEP);
+void P047_data_struct::setToSleep(bool sleep) {
+  if ((P047_MODEL_CATNIP == _model)) {
+    if (sleep) {
+      I2C_write8(_address, P047_CATNIP_SLEEP);
+    } else {
+      getVersion(); // Standard method to wake the sensor
+    }
+    # if P047_FEATURE_BEFLE_V3
+  } else
+  if (P047_MODEL_BEFLE_V3 == _model) {
+    I2C_write8_reg(_address, P047_BEFLE_V3_SLEEP, sleep); // Set low-power mode
+    # endif // if P047_FEATURE_BEFLE_V3
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE v2 or Adafruit sensors
 }
 
 void P047_data_struct::startMeasure() {
   if (P047_MODEL_CATNIP == _model) {
     I2C_write8(_address, P047_CATNIP_MEASURE_LIGHT);
+    # if P047_FEATURE_BEFLE_V3
+  } else
+  if (P047_MODEL_BEFLE_V3 == _model) {
+    I2C_write8_reg(_address, P047_BEFLE_V3_START_MEASURE, 100); // Read 100 samples
+    # endif // if P047_FEATURE_BEFLE_V3
   }
 
-  // Not supported by BeFlE sensor
+  // Not supported by BeFlE v2 or Adafruit sensors
+}
+
+/**
+ * Check if a measurement is available
+ */
+bool P047_data_struct::measurementReady() {
+  if (P047_MODEL_CATNIP == _model) {
+    const uint8_t status = I2C_read8_reg(_address, P047_BEFLE_GET_BUSY);
+    return status == 0;
+    # if P047_FEATURE_BEFLE_V3
+  } else
+  if (P047_MODEL_BEFLE_V3 == _model) {
+    const uint8_t status = I2C_read8_reg(_address, P047_BEFLE_GET_BUSY);
+    return (status & 0x01) == 0;
+    # endif // if P047_FEATURE_BEFLE_V3
+  }
+
+  // Not supported by BeFlE v2 or Adafruit sensors
+  return true;
 }
 
 #endif // ifdef USES_P047
diff --git a/src/src/PluginStructs/P047_data_struct.h b/src/src/PluginStructs/P047_data_struct.h
index 22718e63b..9df36b2ab 100644
--- a/src/src/PluginStructs/P047_data_struct.h
+++ b/src/src/PluginStructs/P047_data_struct.h
@@ -4,34 +4,79 @@
 #include "../../_Plugin_Helper.h"
 #ifdef USES_P047
 
+# ifndef P047_FEATURE_ADAFRUIT
+#  ifdef LIMIT_BUILD_SIZE
+#   define P047_FEATURE_ADAFRUIT 0
+#  else // ifdef LIMIT_BUILD_SIZE
+#   define P047_FEATURE_ADAFRUIT 1
+#  endif // ifdef LIMIT_BUILD_SIZE
+# endif // ifndef P047_FEATURE_ADAFRUIT
+
+# ifndef P047_FEATURE_BEFLE_V3
+#  ifdef LIMIT_BUILD_SIZE
+#   define P047_FEATURE_BEFLE_V3 0
+#  else // ifdef LIMIT_BUILD_SIZE
+#   define P047_FEATURE_BEFLE_V3 1
+#  endif // ifdef LIMIT_BUILD_SIZE
+# endif // ifndef P047_FEATURE_BEFLE_V3
+
 // Default I2C Address of the sensor
-# define P047_CATNIP_DEFAULT_ADDR 0x20
-# define P047_BEFLE_DEFAULT_ADDR  0x55
+# define P047_CATNIP_DEFAULT_ADDR     0x20
+# define P047_BEFLE_DEFAULT_ADDR      0x55
+# define P047_ADAFRUIT_DEFAULT_ADDR   0x36
+# define P047_BEFLE_V3_DEFAULT_ADDR   0x55
 
 // Soil Moisture Sensor Register Addresses
 // Catnip electronics / miceuz:
-# define P047_CATNIP_GET_CAPACITANCE      0x00 // (r)     2 bytes
-# define P047_CATNIP_SET_ADDRESS          0x01 //	(w)     1 uint8_t
-# define P047_CATNIP_GET_ADDRESS          0x02 // (r)     1 uint8_t
-# define P047_CATNIP_MEASURE_LIGHT        0x03 //	(w)     n/a
-# define P047_CATNIP_GET_LIGHT            0x04 //	(r)     2 bytes
-# define P047_CATNIP_GET_TEMPERATURE      0x05 //	(r)     2 bytes
-# define P047_CATNIP_RESET                0x06 //	(w)     n/a
-# define P047_CATNIP_GET_VERSION          0x07 //	(r)     1 bytes
-# define P047_CATNIP_SLEEP                0x08 // (w)     n/a
-# define P047_CATNIP_GET_BUSY             0x09 // (r)	    1 bytes
+# define P047_CATNIP_GET_CAPACITANCE      0x00   // (r)     2 bytes
+# define P047_CATNIP_SET_ADDRESS          0x01   //	(w)     1 uint8_t
+# define P047_CATNIP_GET_ADDRESS          0x02   // (r)     1 uint8_t
+# define P047_CATNIP_MEASURE_LIGHT        0x03   //	(w)     n/a
+# define P047_CATNIP_GET_LIGHT            0x04   //	(r)     2 bytes
+# define P047_CATNIP_GET_TEMPERATURE      0x05   //	(r)     2 bytes
+# define P047_CATNIP_RESET                0x06   //	(w)     n/a
+# define P047_CATNIP_GET_VERSION          0x07   //	(r)     1 bytes
+# define P047_CATNIP_SLEEP                0x08   // (w)     n/a
+# define P047_CATNIP_GET_BUSY             0x09   // (r)	    1 bytes
 
 // BeFlE: (unsupported features set to 0xFF)
-# define P047_BEFLE_GET_CAPACITANCE       0x76 // (r)     2 bytes
-# define P047_BEFLE_SET_ADDRESS           0x41 //	(w)     1 uint8_t
-# define P047_BEFLE_GET_ADDRESS           0xFF // (r)     n/a
-# define P047_BEFLE_MEASURE_LIGHT         0xFF //	(w)     2 byte, avg = 1st byte, current = 2nd byte
-# define P047_BEFLE_GET_LIGHT             0xFF //	(r)     2 byte, avg = 1st byte, current = 2nd byte
-# define P047_BEFLE_GET_TEMPERATURE       0x74 //	(r)     2 bytes
-# define P047_BEFLE_RESET                 0xFF //	(w)     n/a
-# define P047_BEFLE_GET_VERSION           0xFF //	(r)     n/a
-# define P047_BEFLE_SLEEP                 0xFF // (w)     n/a
-# define P047_BEFLE_GET_BUSY              0xFF // (r)	    n/a
+# define P047_BEFLE_GET_CAPACITANCE       0x76   // (r)     2 bytes
+# define P047_BEFLE_SET_ADDRESS           0x41   //	(w)     1 uint8_t
+# define P047_BEFLE_GET_ADDRESS           0xFF   // (r)     n/a
+# define P047_BEFLE_MEASURE_LIGHT         0xFF   //	(w)     n/a
+# define P047_BEFLE_GET_LIGHT             0xFF   //	(r)     n/a
+# define P047_BEFLE_GET_TEMPERATURE       0x74   //	(r)     1 int8_t
+# define P047_BEFLE_RESET                 0xFF   //	(w)     n/a
+# define P047_BEFLE_GET_VERSION           0xFF   //	(r)     n/a
+# define P047_BEFLE_SLEEP                 0xFF   // (w)     n/a
+# define P047_BEFLE_GET_BUSY              0xFF   // (r)	    n/a
+
+// BeFlE v3: (unsupported features set to 0xFF)
+# define P047_BEFLE_V3_GET_CAPACITANCE    0x76   // (r)     2 uint8_t
+# define P047_BEFLE_V3_SET_ADDRESS        0x41   //	(w)     1 uint8_t
+# define P047_BEFLE_V3_GET_ADDRESS        0xFF   // (r)     n/a
+# define P047_BEFLE_V3_MEASURE_LIGHT      0xFF   //	(w)     n/a
+# define P047_BEFLE_V3_START_MEASURE      0x4D   //	(w)     1 uint8_t
+# define P047_BEFLE_V3_GET_LIGHT          0xFF   //	(r)     n/a
+# define P047_BEFLE_V3_GET_TEMPERATURE    0x74   //	(r)     1 int8_t
+# define P047_BEFLE_V3_RESET              0xFF   //	(w)     n/a
+# define P047_BEFLE_V3_GET_VERSION        0x68   //	(r)     4 bytes HW version
+# define P047_BEFLE_V3_SLEEP              0x4C   // (w)     1 uint8_t
+# define P047_BEFLE_V3_GET_BUSY           0x6F   // (r)	    n/a
+
+// Adafruit I2C Capacitive Moisture Sensor
+# define P047_ADAFRUIT_GET_CAPACITANCE      0x10 // (r)     2 bytes
+# define P047_ADAFRUIT_SET_ADDRESS          0xFF //	(w)     n/a
+# define P047_ADAFRUIT_GET_ADDRESS          0xFF // (r)     n/a
+# define P047_ADAFRUIT_MEASURE_LIGHT        0xFF //	(w)     n/a
+# define P047_ADAFRUIT_GET_LIGHT            0xFF //	(r)     n/a
+# define P047_ADAFRUIT_GET_TEMPERATURE      0x04 //	(r)     4 bytes
+# define P047_ADAFRUIT_RESET                0xFF //	(w)     n/a
+# define P047_ADAFRUIT_GET_VERSION          0x02 //	(r)     4 bytes
+# define P047_ADAFRUIT_SLEEP                0xFF // (w)     n/a
+# define P047_ADAFRUIT_GET_BUSY             0xFF // (r)	    n/a
+# define P047_ADAFRUIT_STATUS_BASE          0x00 // Adafruit SeeSaw commands
+# define P047_ADAFRUIT_TOUCH_BASE           0x0F
 
 # define P047_I2C_ADDR       PCONFIG(0)
 # define P047_SENSOR_SLEEP   PCONFIG(1)
@@ -46,15 +91,29 @@ enum class P047_ReadMode : uint8_t {
   ReadStarted,
 };
 
-// Supported sensor models
+// Supported sensor models, setting is stored, so don't change values
 enum class P047_SensorModels : uint8_t {
   CatnipMiceuz = 0,
-  BeFlE,
+  BeFlE        = 1,
+  # if P047_FEATURE_ADAFRUIT
+  Adafruit = 2,
+  # endif // if P047_FEATURE_ADAFRUIT
+  # if P047_FEATURE_BEFLE_V3
+  BeFlEv3 = 3,
+  # endif // if P047_FEATURE_BEFLE_V3
 };
 
 // Shortcuts
-# define P047_MODEL_CATNIP  P047_SensorModels::CatnipMiceuz
-# define P047_MODEL_BEFLE   P047_SensorModels::BeFlE
+# define P047_MODEL_CATNIP    P047_SensorModels::CatnipMiceuz
+# define P047_MODEL_BEFLE     P047_SensorModels::BeFlE
+# if P047_FEATURE_ADAFRUIT
+#  define P047_MODEL_ADAFRUIT P047_SensorModels::Adafruit
+# endif // if P047_FEATURE_ADAFRUIT
+# if P047_FEATURE_BEFLE_V3
+#  define P047_MODEL_BEFLE_V3  P047_SensorModels::BeFlEv3
+# endif // if P047_FEATURE_BEFLE_V3
+
+const __FlashStringHelper* toString(P047_SensorModels sensor);
 
 struct P047_data_struct : public PluginTaskData_base {
 public:
@@ -71,17 +130,18 @@ private:
   float        readTemperature();
   float        readLight();
   unsigned int readMoisture();
-  uint8_t      getVersion();
+  uint32_t     getVersion();
   bool         changeAddress(uint8_t new_i2cAddr);
   bool         checkAddress(uint8_t new_i2cAddr);
   bool         resetSensor();
-  void         setToSleep();
+  void         setToSleep(bool sleep);
   void         startMeasure();
+  bool         measurementReady();
 
   uint8_t           _address       = 0;
   P047_SensorModels _model         = P047_MODEL_CATNIP;
   P047_ReadMode     _readMode      = P047_ReadMode::NotReading;
-  uint8_t           _sensorVersion = 0;
+  uint32_t          _sensorVersion = 0;
 };
 
 #endif // ifdef USES_P047
diff --git a/src/src/PluginStructs/P049_data_struct.cpp b/src/src/PluginStructs/P049_data_struct.cpp
index 49abf3368..287f12a3e 100644
--- a/src/src/PluginStructs/P049_data_struct.cpp
+++ b/src/src/PluginStructs/P049_data_struct.cpp
@@ -169,7 +169,7 @@ void P049_data_struct::setABCmode(int abcDisableSetting) {
 uint8_t P049_data_struct::calculateChecksum() const {
   uint8_t checksum = 0;
 
-  for (uint8_t i = 1; i < 8; i++) {
+  for (uint8_t i = 1; i < 8; ++i) {
     checksum += mhzResp[i];
   }
   checksum = 0xFF - checksum;
@@ -347,9 +347,7 @@ bool Plugin_049_Check_and_ApplyFilter(unsigned int prevVal, unsigned int& newVal
   }
 
   if (filterApplied) {
-    log += F("Raw PPM: ");
-    log += newVal;
-    log += F(" Filtered ");
+    log += strformat(F("Raw PPM: %d Filtered "), newVal);
   }
   newVal = static_cast(prevVal + difference);
   return true;
diff --git a/src/src/PluginStructs/P053_data_struct.cpp b/src/src/PluginStructs/P053_data_struct.cpp
index 4edd59a58..2b3af2821 100644
--- a/src/src/PluginStructs/P053_data_struct.cpp
+++ b/src/src/PluginStructs/P053_data_struct.cpp
@@ -68,17 +68,7 @@ bool P053_data_struct::init() {
   # ifndef BUILD_NO_DEBUG
 
   if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
-    String log;
-    log.reserve(25);
-    log  = F("PMSx003 : config ");
-    log += _rxPin;
-    log += ' ';
-    log += _txPin;
-    log += ' ';
-    log += _resetPin;
-    log += ' ';
-    log += _pwrPin;
-    addLogMove(LOG_LEVEL_DEBUG, log);
+    addLogMove(LOG_LEVEL_DEBUG, strformat(F("PMSx003 : config %d %d %d %d"), _rxPin, _txPin, _resetPin, _pwrPin));
   }
   # endif // ifndef BUILD_NO_DEBUG
 
@@ -86,7 +76,7 @@ bool P053_data_struct::init() {
     delete _easySerial;
     _easySerial = nullptr;
   }
-    
+
   _easySerial = new (std::nothrow) ESPeasySerial(_port, _rxPin, _txPin, false, 96); // 96 Bytes buffer, enough for up to 3 packets.
 
   if (_easySerial != nullptr) {
@@ -127,7 +117,8 @@ bool P053_data_struct::initialized() const
 void P053_data_struct::PacketRead16(uint16_t& value, uint16_t *checksum)
 {
   if (!initialized()) { return; }
-  if (_packetPos > (PMSx003_PACKET_BUFFER_SIZE - 2)) return;
+
+  if (_packetPos > (PMSx003_PACKET_BUFFER_SIZE - 2)) { return; }
   const uint8_t data_high = _packet[_packetPos++];
   const uint8_t data_low  = _packet[_packetPos++];
 
@@ -144,13 +135,8 @@ void P053_data_struct::PacketRead16(uint16_t& value, uint16_t *checksum)
 
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
     // Low-level logging to see data from sensor
-    String log = F("PMSx003 : uint8_t high=0x");
-    log += String(data_high, HEX);
-    log += F(" uint8_t low=0x");
-    log += String(data_low, HEX);
-    log += F(" result=0x");
-    log += String(value, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO,
+           strformat(F("PMSx003 : uint8_t high=0x%02x uint8_t low=0x%02x result=0x%04x"), data_high, data_low, value));
   }
   # endif // ifdef P053_LOW_LEVEL_DEBUG
 }
@@ -177,24 +163,28 @@ uint8_t P053_data_struct::packetSize() const {
 bool P053_data_struct::packetAvailable()
 {
   const uint8_t expectedSize = packetSize();
-  if (expectedSize == 0) return false;
+
+  if (expectedSize == 0) { return false; }
+
   if (_easySerial != nullptr)
   {
     if (_packetPos < expectedSize) {
       // When there is enough data in the buffer, search through the buffer to
       // find header (buffer may be out of sync)
       if (!_easySerial->available()) { return false; }
-      
+
       if (_packetPos == 0) {
         while ((_easySerial->peek() != PMSx003_SIG1) && _easySerial->available()) {
           _easySerial->read(); // Read until the buffer starts with the
           // first uint8_t of a message, or buffer
           // empty.
         }
+
         if (_easySerial->peek() == PMSx003_SIG1) {
           _packet[_packetPos++] = _easySerial->read();
         }
       }
+
       if (_packetPos > 0) {
         while (_packetPos < expectedSize) {
           if (_easySerial->available() == 0) {
@@ -211,7 +201,7 @@ bool P053_data_struct::packetAvailable()
 
 # ifdef PLUGIN_053_ENABLE_EXTRA_SENSORS
 void P053_data_struct::sendEvent(taskIndex_t TaskIndex,
-                                 uint8_t       index) {
+                                 uint8_t     index) {
   float value = 0.0f;
 
   if (!getValue(index, value)) { return; }
@@ -246,6 +236,7 @@ bool P053_data_struct::processData(struct EventStruct *event) {
   uint16_t checksum = 0, checksum2 = 0;
   uint16_t framelength   = 0;
   uint16_t packet_header = 0;
+
   _packetPos = 0;
 
   PacketRead16(packet_header, &checksum); // read PMSx003_SIG1 + PMSx003_SIG2
@@ -259,11 +250,7 @@ bool P053_data_struct::processData(struct EventStruct *event) {
 
   if ((framelength + 4) != packetSize()) {
     if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
-      String log;
-      log.reserve(34);
-      log  = F("PMSx003 : invalid framelength - ");
-      log += framelength;
-      addLogMove(LOG_LEVEL_ERROR, log);
+      addLog(LOG_LEVEL_ERROR, concat(F("PMSx003 : invalid framelength - "), framelength));
     }
     return false;
   }
@@ -276,7 +263,7 @@ bool P053_data_struct::processData(struct EventStruct *event) {
   }
   uint16_t data[PMS_RECEIVE_BUFFER_SIZE] = { 0 }; // uint8_t data_low, data_high;
 
-  for (uint8_t i = 0; i < frameData && i < PMS_RECEIVE_BUFFER_SIZE; i++) {
+  for (uint8_t i = 0; i < frameData && i < PMS_RECEIVE_BUFFER_SIZE; ++i) {
     PacketRead16(data[i], &checksum);
   }
 
@@ -292,22 +279,14 @@ bool P053_data_struct::processData(struct EventStruct *event) {
   #  ifdef P053_LOW_LEVEL_DEBUG
 
   if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { // Available on all supported sensor models
-    String log;
-    if (log.reserve(87)) {
-      log  = F("PMSx003 : pm1.0=");
-      log += data[PMS_PM1_0_ug_m3_factory];
-      log += F(", pm2.5=");
-      log += data[PMS_PM2_5_ug_m3_factory];
-      log += F(", pm10=");
-      log += data[PMS_PM10_0_ug_m3_factory];
-      log += F(", pm1.0a=");
-      log += data[PMS_PM1_0_ug_m3_normal];
-      log += F(", pm2.5a=");
-      log += data[PMS_PM2_5_ug_m3_normal];
-      log += F(", pm10a=");
-      log += data[PMS_PM10_0_ug_m3_normal];
-      addLogMove(LOG_LEVEL_DEBUG, log);
-    }
+    addLog(LOG_LEVEL_DEBUG,
+           strformat(F("PMSx003 : pm1.0=%d, pm2.5=%d, pm10=%d, pm1.0a=%d, pm2.5a=%d, pm10a=%d"),
+                     data[PMS_PM1_0_ug_m3_factory],
+                     data[PMS_PM2_5_ug_m3_factory],
+                     data[PMS_PM10_0_ug_m3_factory],
+                     data[PMS_PM1_0_ug_m3_normal],
+                     data[PMS_PM2_5_ug_m3_normal],
+                     data[PMS_PM10_0_ug_m3_normal]));
   }
 
   #   ifdef PLUGIN_053_ENABLE_EXTRA_SENSORS
@@ -316,22 +295,14 @@ bool P053_data_struct::processData(struct EventStruct *event) {
       && (GET_PLUGIN_053_SENSOR_MODEL_SELECTOR != PMSx003_type::PMS2003_3003)) { // 'Count' values not available on
     // PMS2003/PMS3003 models
     // (handled as 1 model in code)
-    String log;
-    if (log.reserve(96)) {
-      log  = F("PMSx003 : count/0.1L : 0.3um=");
-      log += data[PMS_cnt0_3_100ml];
-      log += F(", 0.5um=");
-      log += data[PMS_cnt0_5_100ml];
-      log += F(", 1.0um=");
-      log += data[PMS_cnt1_0_100ml];
-      log += F(", 2.5um=");
-      log += data[PMS_cnt2_5_100ml];
-      log += F(", 5.0um=");
-      log += data[PMS_cnt5_0_100ml];
-      log += F(", 10um=");
-      log += data[PMS_cnt10_0_100ml];
-      addLogMove(LOG_LEVEL_DEBUG, log);
-    }
+    addLog(LOG_LEVEL_DEBUG,
+           strformat(F("PMSx003 : count/0.1L : 0.3um=%d, 0.5um=%d, 1.0um=%d, 2.5um=%d, 5.0um=%d, 10um=%d"),
+                     data[PMS_cnt0_3_100ml],
+                     data[PMS_cnt0_5_100ml],
+                     data[PMS_cnt1_0_100ml],
+                     data[PMS_cnt2_5_100ml],
+                     data[PMS_cnt5_0_100ml],
+                     data[PMS_cnt10_0_100ml]));
   }
 
 
@@ -339,15 +310,14 @@ bool P053_data_struct::processData(struct EventStruct *event) {
       && ((GET_PLUGIN_053_SENSOR_MODEL_SELECTOR == PMSx003_type::PMS5003_ST)
           || (GET_PLUGIN_053_SENSOR_MODEL_SELECTOR == PMSx003_type::PMS5003_T))) { // Values only available on PMS5003ST & PMS5003T
     String log;
+
     if (log.reserve(45)) {
-      log  = F("PMSx003 : temp=");
-      log += static_cast(data[PMS_Temp_C]) / 10.0f;
-      log += F(", humi=");
-      log += static_cast(data[PMS_Hum_pct]) / 10.0f;
+      log = strformat(F("PMSx003 : temp=%.2f, humi=%.2f"),
+                      static_cast(data[PMS_Temp_C]) / 10.0f,
+                      static_cast(data[PMS_Hum_pct]) / 10.0f);
 
       if (GET_PLUGIN_053_SENSOR_MODEL_SELECTOR == PMSx003_type::PMS5003_ST) {
-        log += F(", hcho=");
-        log += static_cast(data[PMS_Formaldehyde_mg_m3]) / 1000.0f;
+        log += strformat(F(", hcho=%.4f"), static_cast(data[PMS_Formaldehyde_mg_m3]) / 1000.0f);
       }
       addLogMove(LOG_LEVEL_DEBUG, log);
     }
@@ -368,11 +338,11 @@ bool P053_data_struct::processData(struct EventStruct *event) {
   if (_last_wakeup_moment.isSet() && !_last_wakeup_moment.timeReached()) {
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
       String log;
+
       if (log.reserve(80)) {
-        log = F("PMSx003 : Less than ");
-        log += _delay_read_after_wakeup_ms / 1000ul;
-        log += F(" sec since sensor wakeup => Ignoring sample");
-        addLogMove(LOG_LEVEL_INFO, log);
+        addLog(LOG_LEVEL_INFO,
+               strformat(F("PMSx003 : Less than %d sec since sensor wakeup => Ignoring sample"),
+                         _delay_read_after_wakeup_ms / 1000ul));
       }
     }
     return false;
@@ -380,10 +350,10 @@ bool P053_data_struct::processData(struct EventStruct *event) {
 
   if (checksum == _last_checksum) {
     // Duplicate message
-      # ifndef BUILD_NO_DEBUG
+    # ifndef BUILD_NO_DEBUG
 
     addLog(LOG_LEVEL_DEBUG, F("PMSx003 : Duplicate message"));
-      # endif // ifndef BUILD_NO_DEBUG
+    # endif // ifndef BUILD_NO_DEBUG
     return false;
   }
   # ifndef PLUGIN_053_ENABLE_EXTRA_SENSORS
@@ -392,7 +362,7 @@ bool P053_data_struct::processData(struct EventStruct *event) {
   UserVar.setFloat(event->TaskIndex, 0, data[PMS_PM1_0_ug_m3_normal]);
   UserVar.setFloat(event->TaskIndex, 1, data[PMS_PM2_5_ug_m3_normal]);
   UserVar.setFloat(event->TaskIndex, 2, data[PMS_PM10_0_ug_m3_normal]);
-  _values_received                 = 1;
+  _values_received = 1;
   # else // ifndef PLUGIN_053_ENABLE_EXTRA_SENSORS
 
   // Store in the averaging buffer to process later
@@ -502,10 +472,8 @@ bool P053_data_struct::checkAndClearValuesReceived(struct EventStruct *event) {
 
   if (_oversample) {
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log = F("PMSx003: Oversampling using ");
-      log += _values_received;
-      log += F(" samples");
-      addLogMove(LOG_LEVEL_INFO, log);
+      addLogMove(LOG_LEVEL_INFO,
+                 strformat(F("PMSx003: Oversampling using %d samples"), _values_received));
     }
   }
   # endif // ifdef PLUGIN_053_ENABLE_EXTRA_SENSORS
diff --git a/src/src/PluginStructs/P061_data_struct.cpp b/src/src/PluginStructs/P061_data_struct.cpp
index a9b9bbb6e..0cf9800b8 100644
--- a/src/src/PluginStructs/P061_data_struct.cpp
+++ b/src/src/PluginStructs/P061_data_struct.cpp
@@ -34,11 +34,9 @@ bool P061_data_struct::plugin_fifty_per_second(struct EventStruct *event) {
   if (lastScanCode == actScanCode) {   // debounced? - two times the same value?
     if (sentScanCode != actScanCode) { // any change to last sent data?
       UserVar.setFloat(event->TaskIndex, 0, actScanCode);
-      event->sensorType            = Sensor_VType::SENSOR_TYPE_SWITCH;
+      event->sensorType = Sensor_VType::SENSOR_TYPE_SWITCH;
 
-      String log = F("KPad : ScanCode=0x");
-      log += String(actScanCode, HEX);
-      addLogMove(LOG_LEVEL_INFO, log);
+      addLog(LOG_LEVEL_INFO, strformat(F("KPad : ScanCode=0x%x"), actScanCode));
 
       sendData(event);
 
@@ -56,8 +54,9 @@ void P061_data_struct::MCP23017_setReg(uint8_t addr, uint8_t reg, uint8_t data)
 }
 
 uint8_t P061_data_struct::MCP23017_getReg(uint8_t addr, uint8_t reg) {
-  bool success = false;
+  bool success      = false;
   const uint8_t res = I2C_read8_reg(addr, reg, &success);
+
   return success ? res : 0xff;
 }
 
@@ -84,9 +83,7 @@ uint8_t P061_data_struct::MCP23017_KeyPadMatrixScan(uint8_t addr) {
   # if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 MCP23017 matrix, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 MCP23017 matrix, read data: 0x%x"), colData));
   }
   # endif // if P061_DEBUG_LOG
 
@@ -94,7 +91,7 @@ uint8_t P061_data_struct::MCP23017_KeyPadMatrixScan(uint8_t addr) {
     return 0;            // no key pressed!
   }
 
-  for (uint8_t row = 0; row <= 8; row++) {
+  for (uint8_t row = 0; row <= 8; ++row) {
     if (row == 0) {
       MCP23017_setReg(addr, MCP23017_IODIRA, 0xFF);     // no bit of port A to output
     } else {
@@ -107,7 +104,7 @@ uint8_t P061_data_struct::MCP23017_KeyPadMatrixScan(uint8_t addr) {
     if (colData != 0xFF) { // any key pressed?
       uint8_t colMask = 1;
 
-      for (uint8_t col = 1; col <= 8; col++) {
+      for (uint8_t col = 1; col <= 8; ++col) {
         if ((colData & colMask) == 0) {                 // this key pressed?
           MCP23017_setReg(addr, MCP23017_IODIRA, 0x00); // port A to output 0
           return (row << 4) | col;
@@ -129,9 +126,7 @@ uint8_t P061_data_struct::MCP23017_KeyPadDirectScan(uint8_t addr) {
   # if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 MCP23017 direct, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 MCP23017 direct, read data: 0x%x"), colData));
   }
   # endif // if P061_DEBUG_LOG
 
@@ -140,7 +135,7 @@ uint8_t P061_data_struct::MCP23017_KeyPadDirectScan(uint8_t addr) {
   }
   uint16_t colMask = 0x01;
 
-  for (uint8_t col = 1; col <= 16; col++) {
+  for (uint8_t col = 1; col <= 16; ++col) {
     if ((colData & colMask) == 0) { // this key pressed?
       return col;
     }
@@ -157,8 +152,9 @@ void P061_data_struct::PCF8574_setReg(uint8_t addr, uint8_t data) {
 }
 
 uint8_t P061_data_struct::PCF8574_getReg(uint8_t addr) {
-  bool success = false;
+  bool success      = false;
   const uint8_t res = I2C_read8(addr, &success);
+
   return success ? res : 0xff;
 }
 
@@ -174,9 +170,7 @@ uint8_t P061_data_struct::PCF8574_KeyPadMatrixScan(uint8_t addr) {
   # if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 PCF8574 matrix, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 PCF8574 matrix, read data: 0x%x"), colData));
   }
   # endif // if P061_DEBUG_LOG
 
@@ -184,7 +178,7 @@ uint8_t P061_data_struct::PCF8574_KeyPadMatrixScan(uint8_t addr) {
     return 0;            // no key pressed!
   }
 
-  for (uint8_t row = 0; row <= 4; row++) {
+  for (uint8_t row = 0; row <= 4; ++row) {
     if (row == 0) {
       PCF8574_setReg(addr, 0xFF);     // no bit of port A to output
     } else {
@@ -197,7 +191,7 @@ uint8_t P061_data_struct::PCF8574_KeyPadMatrixScan(uint8_t addr) {
     if (colData != 0xF0) { // any key pressed?
       uint8_t colMask = 0x10;
 
-      for (uint8_t col = 1; col <= 4; col++) {
+      for (uint8_t col = 1; col <= 4; ++col) {
         if ((colData & colMask) == 0) { // this key pressed?
           PCF8574_setReg(addr, 0xF0);   // low nibble to output 0
           return (row << 4) | col;
@@ -224,9 +218,7 @@ uint8_t P061_data_struct::PCF8574_KeyPadDirectScan(uint8_t addr) {
   # if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 PCF8574 direct, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 PCF8574 direct, read data: 0x%x"), colData));
   }
   # endif // if P061_DEBUG_LOG
 
@@ -235,7 +227,7 @@ uint8_t P061_data_struct::PCF8574_KeyPadDirectScan(uint8_t addr) {
   }
   uint8_t colMask = 0x01;
 
-  for (uint8_t col = 1; col <= 8; col++) {
+  for (uint8_t col = 1; col <= 8; ++col) {
     if ((colData & colMask) == 0) { // this key pressed?
       return col;
     }
@@ -286,9 +278,7 @@ uint8_t P061_data_struct::PCF8575_KeyPadMatrixScan(uint8_t addr) {
   #  if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 PCF8575 matrix, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 PCF8575 matrix, read data: 0x%x"), colData));
   }
   #  endif // if P061_DEBUG_LOG
 
@@ -296,7 +286,7 @@ uint8_t P061_data_struct::PCF8575_KeyPadMatrixScan(uint8_t addr) {
     return 0;              // no key pressed!
   }
 
-  for (uint8_t row = 0; row <= 8; row++) {
+  for (uint8_t row = 0; row <= 8; ++row) {
     if (row == 0) {
       PCF8575_setReg(addr, 0xFFFF);   // no bit of port A to output
     } else {
@@ -309,7 +299,7 @@ uint8_t P061_data_struct::PCF8575_KeyPadMatrixScan(uint8_t addr) {
     if (colData != 0xFF00) { // any key pressed?
       uint16_t colMask = 0x0100;
 
-      for (uint8_t col = 1; col <= 8; col++) {
+      for (uint8_t col = 1; col <= 8; ++col) {
         if ((colData & colMask) == 0) { // this key pressed?
           PCF8575_setReg(addr, 0xFF00); // low byte to output 00
           return (row << 4) | col;
@@ -337,9 +327,7 @@ uint8_t P061_data_struct::PCF8575_KeyPadDirectScan(uint8_t addr) {
   #  if P061_DEBUG_LOG
 
   if (loglevelActiveFor(LOG_LEVEL_INFO) && (millis() % 1000 < 10)) {
-    String log = F("P061 PCF8575 direct, read data: 0x");
-    log += String(colData, HEX);
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLog(LOG_LEVEL_INFO, strformat(F("P061 PCF8575 direct, read data: 0x%x"), colData));
   }
   #  endif // if P061_DEBUG_LOG
 
@@ -348,7 +336,7 @@ uint8_t P061_data_struct::PCF8575_KeyPadDirectScan(uint8_t addr) {
   }
   uint16_t colMask = 0x01;
 
-  for (uint8_t col = 1; col <= 16; col++) {
+  for (uint8_t col = 1; col <= 16; ++col) {
     if ((colData & colMask) == 0) { // this key pressed?
       return col;
     }
diff --git a/src/src/PluginStructs/P062_data_struct.cpp b/src/src/PluginStructs/P062_data_struct.cpp
index 786300a50..62bff97d9 100644
--- a/src/src/PluginStructs/P062_data_struct.cpp
+++ b/src/src/PluginStructs/P062_data_struct.cpp
@@ -15,10 +15,8 @@ P062_data_struct::P062_data_struct() {
 }
 
 P062_data_struct::~P062_data_struct() {
-  if (keypad != nullptr) {
-    delete keypad;
-    keypad = nullptr;
-  }
+  delete keypad;
+  keypad = nullptr;
 }
 
 bool P062_data_struct::init(taskIndex_t taskIndex,
@@ -71,14 +69,14 @@ bool P062_data_struct::readKey(uint16_t& key) {
   {
     uint16_t colMask = 0x01;
 
-    for (uint8_t col = 1; col <= 12; col++)
+    for (uint8_t col = 0; col < P062_MaxTouchObjects; ++col)
     {
       if (key & colMask) // this key pressed?
       {
-        updateCalibration(col - 1);
+        updateCalibration(col);
 
         if (_use_scancode) {
-          key = col;
+          key = col + 1;
           break;
         }
       }
@@ -113,9 +111,7 @@ void P062_data_struct::setThreshold(uint8_t t, uint8_t touch, uint8_t release) {
  */
 void P062_data_struct::loadTouchObjects(taskIndex_t taskIndex) {
   # ifdef PLUGIN_062_DEBUG
-  String log = F("P062 DEBUG loadTouchObjects size: ");
-  log += sizeof(StoredSettings);
-  addLogMove(LOG_LEVEL_INFO, log);
+  addLogMove(LOG_LEVEL_INFO, concat(F("P062 DEBUG loadTouchObjects size: "), sizeof(StoredSettings)));
   # endif // PLUGIN_062_DEBUG
   LoadCustomTaskSettings(taskIndex, reinterpret_cast(&StoredSettings), sizeof(StoredSettings));
 }
@@ -135,7 +131,7 @@ bool P062_data_struct::getCalibrationData(uint8_t t, uint16_t *current, uint16_t
  * Reset the touch data.
  */
 void P062_data_struct::clearCalibrationData() {
-  for (uint8_t t = 0; t < P062_MaxTouchObjects; t++) {
+  for (uint8_t t = 0; t < P062_MaxTouchObjects; ++t) {
     CalibrationData.CalibrationValues[t].current = 0;
     CalibrationData.CalibrationValues[t].min     = 0;
     CalibrationData.CalibrationValues[t].max     = 0;
diff --git a/src/src/PluginStructs/P067_data_struct.cpp b/src/src/PluginStructs/P067_data_struct.cpp
index 4183d67de..a2de8e1b6 100644
--- a/src/src/PluginStructs/P067_data_struct.cpp
+++ b/src/src/PluginStructs/P067_data_struct.cpp
@@ -1,310 +1,308 @@
-#include "../PluginStructs/P067_data_struct.h"
-
-#ifdef USES_P067
-# include 
-
-/****************************************************
-* Convert a float to 2 ints
-****************************************************/
-void P067_float2int(float valFloat, int16_t *valInt0, int16_t *valInt1) {
-  // FIXME TD-er: Casting from float* to integer* is not portable due to different binary data representations on different platforms.
-  int16_t *fti = (int16_t *)&valFloat;
-
-  *valInt0 = *fti++;
-  *valInt1 = *fti;
-}
-
-/****************************************************
-* Convert 2 ints to a float
-****************************************************/
-void P067_int2float(int16_t valInt0, int16_t valInt1, float *valFloat) {
-  // FIXME TD-er: Casting from float* to integer* is not portable due to different binary data representations on different platforms.
-  float offset = 0.0f; // Set to some value to prevent compiler warnings
-  int16_t *itf = (int16_t *)&offset;
-
-  *itf++    = valInt0;
-  *itf      = valInt1;
-  *valFloat = offset;
-}
-
-/**************************************************************************
-* Constructor
-**************************************************************************/
-P067_data_struct::P067_data_struct(struct EventStruct *event,
-                                   int8_t              pinSCL,
-                                   int8_t              pinDOUT)
-  : _pinSCL(pinSCL), _pinDOUT(pinDOUT)
-{
-  _modeChanA = P067_GET_CHANNEL_A_MODE_e;
-  _modeChanB = P067_GET_CHANNEL_B_MODE_e;
-  P067_int2float(P067_OFFSET_CHANNEL_A_1, P067_OFFSET_CHANNEL_A_2, &_offsetChanA);
-  P067_int2float(P067_OFFSET_CHANNEL_B_1, P067_OFFSET_CHANNEL_B_2, &_offsetChanB);
-}
-
-/*****************************************************
-* Destructor
-*****************************************************/
-P067_data_struct::~P067_data_struct() {}
-
-/****************************************************
-* Initialization
-****************************************************/
-bool P067_data_struct::init(struct EventStruct *event) {
-  // Log anyway
-  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    String log = concat(F("HX711: GPIO: SCL="), static_cast(_pinSCL));
-    log += concat(F(" DOUT="), static_cast(_pinDOUT));
-    addLogMove(LOG_LEVEL_INFO, log);
-  }
-  UserVar.setFloat(event->TaskIndex, 0, 0.0f); // Reset output
-  UserVar.setFloat(event->TaskIndex, 1, 0.0f);
-
-  if (isInitialized()) {
-    pinMode(_pinSCL, OUTPUT); // Keep regular pinMode functions for initialization
-    digitalWrite(_pinSCL, LOW);
-
-    pinMode(_pinDOUT, INPUT); // Checked, doesn't seem applicable: https://github.com/bogde/HX711/issues/222
-
-    return true;
-  }
-  return false;
-}
-
-/*****************************************************
-* plugin_read
-*****************************************************/
-bool P067_data_struct::plugin_read(struct EventStruct *event)           {
-  bool success = false;
-
-  if (isInitialized()) {
-    if ((_modeChanA == P067_ChannelA_State_e::modeAoff) && (_modeChanB == P067_ChannelB_State_e::modeBoff)) {
-      addLog(LOG_LEVEL_INFO, F("HX711: No channel selected"));
-    }
-
-    // Channel A activated?
-    if (_modeChanA != P067_ChannelA_State_e::modeAoff) {
-      String log = concat(F("HX711: ("), (int)event->TaskIndex + 1);
-      log += F(") ChanA: ");
-
-      float value{};
-      if (OversamplingChanA.get(value)) {
-        UserVar.setFloat(event->TaskIndex, 2, value);
-        UserVar.setFloat(event->TaskIndex, 0, UserVar[event->BaseVarIndex + 2] + _offsetChanA); // Offset
-
-        log += formatUserVarNoCheck(event->TaskIndex, 0);
-
-        if (P067_GET_CHANNEL_A_CALIB) { // Calibration channel A?
-          int   adc1 = P067_CONFIG_CHANNEL_A_ADC1;
-          int   adc2 = P067_CONFIG_CHANNEL_A_ADC2;
-          float out1 = P067_CONFIG_CHANNEL_A_OUT1;
-          float out2 = P067_CONFIG_CHANNEL_A_OUT2;
-
-          if (adc1 != adc2) {
-            const float normalized = static_cast(UserVar[event->BaseVarIndex] - adc1) / static_cast(adc2 - adc1);
-            UserVar.setFloat(event->TaskIndex, 0, normalized * (out2 - out1) + out1);
-
-            log += F(" = ");
-            log += formatUserVarNoCheck(event->TaskIndex, 0);
-          }
-        }
-      } else {
-        log += F("NO NEW VALUE");
-      }
-      addLogMove(LOG_LEVEL_INFO, log);
-      success = !firstRead;
-    }
-
-    // Channel B activated?
-    if (_modeChanB != P067_ChannelB_State_e::modeBoff) {
-      String log = concat(F("HX711: ("), (int)event->TaskIndex + 1);
-      log += F(") ChanB: ");
-
-      float value{};
-      if (OversamplingChanB.get(value)) {
-        UserVar.setFloat(event->TaskIndex, 3, value);
-        UserVar.setFloat(event->TaskIndex, 1, UserVar[event->BaseVarIndex + 3] + _offsetChanB); // Offset
-
-        log += formatUserVarNoCheck(event->TaskIndex, 1);
-
-        if (P067_GET_CHANNEL_B_CALIB) { // Calibration channel B?
-          int   adc1 = P067_CONFIG_CHANNEL_B_ADC1;
-          int   adc2 = P067_CONFIG_CHANNEL_B_ADC2;
-          float out1 = P067_CONFIG_CHANNEL_B_OUT1;
-          float out2 = P067_CONFIG_CHANNEL_B_OUT2;
-
-          if (adc1 != adc2) {
-            const float normalized = (UserVar[event->BaseVarIndex + 1] - adc1) / static_cast(adc2 - adc1);
-            UserVar.setFloat(event->TaskIndex, 1, normalized * (out2 - out1) + out1);
-
-            log += F(" = ");
-            log += formatUserVarNoCheck(event->TaskIndex, 1);
-          }
-        }
-      } else {
-        log += F("NO NEW VALUE");
-      }
-      addLogMove(LOG_LEVEL_INFO, log);
-      success = !firstRead;
-    }
-    firstRead = false;
-  }
-  return success;
-}
-
-/*****************************************************
-* plugin_fifty_per_second
-*****************************************************/
-bool P067_data_struct::plugin_fifty_per_second(struct EventStruct *event) {
-  bool success = false;
-
-  if (isInitialized() && isDataReady()) {
-    int32_t value = readHX711();
-    success = true;
-
-    switch (_channelRead) {
-      case P067_Channel_e::chanA64:  //
-      case P067_Channel_e::chanA128:
-      {
-        if (!P067_GET_CHANNEL_A_OS) { // Oversampling on channel A?
-          OversamplingChanA.reset();
-        }
-        if (OversamplingChanA.getCount() > 250) {
-          OversamplingChanA.resetKeepLast();
-        }
-        OversamplingChanA.add(value);
-        break;
-      }
-      case P067_Channel_e::chanB32:
-      {
-        if (!P067_GET_CHANNEL_B_OS) { // Oversampling on channel B?
-          OversamplingChanB.reset();
-        }
-        if (OversamplingChanB.getCount() > 250) {
-          OversamplingChanB.resetKeepLast();
-        }
-        OversamplingChanB.add(value);
-        break;
-      }
-    }
-  }
-
-  return success;
-}
-
-/*****************************************************
-* plugin_write
-*****************************************************/
-bool P067_data_struct::plugin_write(struct EventStruct *event,
-                                    String            & string) {
-  bool success = false;
-
-  String command = parseString(string, 1);
-
-  if (equals(command, F("tarechana"))) {
-    P067_float2int(-UserVar[event->BaseVarIndex + 2], &P067_OFFSET_CHANNEL_A_1, &P067_OFFSET_CHANNEL_A_2);
-    P067_int2float(P067_OFFSET_CHANNEL_A_1, P067_OFFSET_CHANNEL_A_2, &_offsetChanA);
-    OversamplingChanA.reset();
-
-    addLog(LOG_LEVEL_INFO, F("HX711: tare channel A"));
-    success = true;
-  } else if (equals(command, F("tarechanb"))) {
-    P067_float2int(-UserVar[event->BaseVarIndex + 3], &P067_OFFSET_CHANNEL_B_1, &P067_OFFSET_CHANNEL_B_2);
-    P067_int2float(P067_OFFSET_CHANNEL_B_1, P067_OFFSET_CHANNEL_B_2, &_offsetChanB);
-    OversamplingChanB.reset();
-
-    addLog(LOG_LEVEL_INFO, F("HX711: tare channel B"));
-    success = true;
-  }
-
-  return success;
-}
-
-/****************************************************************************************
-* Private stuff
-****************************************************************************************/
-/****************************************************
-* Minimal viable settings: GPIO pins valid?
-****************************************************/
-bool P067_data_struct::isInitialized() {
-  return validGpio(_pinSCL) && validGpio(_pinDOUT);
-}
-
-/****************************************************
-* New data available?
-****************************************************/
-bool P067_data_struct::isDataReady() {
-  if (isInitialized()) {
-    return !DIRECT_pinRead(_pinDOUT);
-  }
-  return false;
-}
-
-/****************************************************
-* Read data from the load sensor
-****************************************************/
-int32_t P067_data_struct::readHX711() {
-  int32_t  value = 0;
-  uint32_t mask  = 0x00800000;
-
-  _channelRead = _nextChannel;
-
-  // Both channels off
-  if ((_modeChanA == P067_ChannelA_State_e::modeAoff) && (_modeChanB == P067_ChannelB_State_e::modeBoff)) {
-    DIRECT_pinWrite(_pinSCL, HIGH);
-    return 0;
-  }
-
-  // Both channels on
-  if ((_modeChanA != P067_ChannelA_State_e::modeAoff) && (_modeChanB != P067_ChannelB_State_e::modeBoff)) {
-    // Both channels are activated -> do interleaved measurement
-    _channelToggle = !_channelToggle;
-
-    // FIXME tonhuisman: Toggling doesn't work as intended
-    if (_channelToggle) {
-      if (_modeChanA == P067_ChannelA_State_e::modeA64) {
-        _nextChannel = P067_Channel_e::chanA64;
-      } else {
-        _nextChannel = P067_Channel_e::chanA128;
-      }
-    } else {
-      _nextChannel = P067_Channel_e::chanB32;
-    }
-  } else {
-    // Only one channel is activated
-    if (_modeChanA == P067_ChannelA_State_e::modeA64) {
-      _nextChannel = P067_Channel_e::chanA64;
-    } else if (_modeChanA == P067_ChannelA_State_e::modeA128) {
-      _nextChannel = P067_Channel_e::chanA128;
-    }
-
-    if (_modeChanB == P067_ChannelB_State_e::modeB32) {
-      _nextChannel = P067_Channel_e::chanB32;
-    }
-  }
-
-  for (uint8_t i = 0; i < 24; i++) {
-    DIRECT_pinWrite(_pinSCL, HIGH);
-    delayMicroseconds(1);
-    DIRECT_pinWrite(_pinSCL, LOW);
-
-    if (DIRECT_pinRead(_pinDOUT)) {
-      value |= mask;
-    }
-    delayMicroseconds(1);
-    mask >>= 1;
-  }
-
-  for (uint8_t i = 0; i < (static_cast < uint8_t > (_nextChannel) + 1); i++) {
-    DIRECT_pinWrite(_pinSCL, HIGH);
-    delayMicroseconds(1);
-    DIRECT_pinWrite(_pinSCL, LOW);
-    delayMicroseconds(1);
-  }
-
-  if (value & 0x00800000) { // negative?
-    value |= 0xFF000000;    // expand sign bit to 32 bit
-  }
-  return value;
-}
-
-#endif // ifdef USES_P067
+#include "../PluginStructs/P067_data_struct.h"
+
+#ifdef USES_P067
+# include 
+
+/****************************************************
+* Convert a float to 2 ints
+****************************************************/
+void P067_float2int(float valFloat, int16_t *valInt0, int16_t *valInt1) {
+  // FIXME TD-er: Casting from float* to integer* is not portable due to different binary data representations on different platforms.
+  int16_t *fti = (int16_t *)&valFloat;
+
+  *valInt0 = *fti++;
+  *valInt1 = *fti;
+}
+
+/****************************************************
+* Convert 2 ints to a float
+****************************************************/
+void P067_int2float(int16_t valInt0, int16_t valInt1, float *valFloat) {
+  // FIXME TD-er: Casting from float* to integer* is not portable due to different binary data representations on different platforms.
+  float offset = 0.0f; // Set to some value to prevent compiler warnings
+  int16_t *itf = (int16_t *)&offset;
+
+  *itf++    = valInt0;
+  *itf      = valInt1;
+  *valFloat = offset;
+}
+
+/**************************************************************************
+* Constructor
+**************************************************************************/
+P067_data_struct::P067_data_struct(struct EventStruct *event,
+                                   int8_t              pinSCL,
+                                   int8_t              pinDOUT)
+  : _pinSCL(pinSCL), _pinDOUT(pinDOUT)
+{
+  _modeChanA = P067_GET_CHANNEL_A_MODE_e;
+  _modeChanB = P067_GET_CHANNEL_B_MODE_e;
+  P067_int2float(P067_OFFSET_CHANNEL_A_1, P067_OFFSET_CHANNEL_A_2, &_offsetChanA);
+  P067_int2float(P067_OFFSET_CHANNEL_B_1, P067_OFFSET_CHANNEL_B_2, &_offsetChanB);
+}
+
+/*****************************************************
+* Destructor
+*****************************************************/
+P067_data_struct::~P067_data_struct() {}
+
+/****************************************************
+* Initialization
+****************************************************/
+bool P067_data_struct::init(struct EventStruct *event) {
+  // Log anyway
+  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+    addLogMove(LOG_LEVEL_INFO, strformat(F("HX711: GPIO: SCL=%d DOUT=%d"), _pinSCL, _pinDOUT));
+  }
+  UserVar.setFloat(event->TaskIndex, 0, 0.0f); // Reset output
+  UserVar.setFloat(event->TaskIndex, 1, 0.0f);
+
+  if (isInitialized()) {
+    pinMode(_pinSCL, OUTPUT); // Keep regular pinMode functions for initialization
+    digitalWrite(_pinSCL, LOW);
+
+    pinMode(_pinDOUT, INPUT); // Checked, doesn't seem applicable: https://github.com/bogde/HX711/issues/222
+
+    return true;
+  }
+  return false;
+}
+
+/*****************************************************
+* plugin_read
+*****************************************************/
+bool P067_data_struct::plugin_read(struct EventStruct *event)           {
+  bool success = false;
+
+  if (isInitialized()) {
+    if ((_modeChanA == P067_ChannelA_State_e::modeAoff) && (_modeChanB == P067_ChannelB_State_e::modeBoff)) {
+      addLog(LOG_LEVEL_INFO, F("HX711: No channel selected"));
+    }
+
+    // Channel A activated?
+    if (_modeChanA != P067_ChannelA_State_e::modeAoff) {
+      String log = strformat(F("HX711: (%d) ChanA: "), (int)event->TaskIndex + 1);
+
+      float value{};
+
+      if (OversamplingChanA.get(value)) {
+        UserVar.setFloat(event->TaskIndex, 2, value);
+        UserVar.setFloat(event->TaskIndex, 0, UserVar.getFloat(event->TaskIndex, 2) + _offsetChanA); // Offset
+
+        log += formatUserVarNoCheck(event, 0);
+
+        if (P067_GET_CHANNEL_A_CALIB) { // Calibration channel A?
+          int   adc1 = P067_CONFIG_CHANNEL_A_ADC1;
+          int   adc2 = P067_CONFIG_CHANNEL_A_ADC2;
+          float out1 = P067_CONFIG_CHANNEL_A_OUT1;
+          float out2 = P067_CONFIG_CHANNEL_A_OUT2;
+
+          if (adc1 != adc2) {
+            const float normalized = static_cast(UserVar[event->BaseVarIndex] - adc1) / static_cast(adc2 - adc1);
+            UserVar.setFloat(event->TaskIndex, 0, normalized * (out2 - out1) + out1);
+
+            log += concat(F(" = "), formatUserVarNoCheck(event, 0));
+          }
+        }
+      } else {
+        log += F("NO NEW VALUE");
+      }
+      addLogMove(LOG_LEVEL_INFO, log);
+      success = !firstRead;
+    }
+
+    // Channel B activated?
+    if (_modeChanB != P067_ChannelB_State_e::modeBoff) {
+      String log = strformat(F("HX711: (%d) ChanB: "), (int)event->TaskIndex + 1);
+
+      float value{};
+
+      if (OversamplingChanB.get(value)) {
+        UserVar.setFloat(event->TaskIndex, 3, value);
+        UserVar.setFloat(event->TaskIndex, 1, UserVar.getFloat(event->TaskIndex, 3) + _offsetChanB); // Offset
+
+        log += formatUserVarNoCheck(event, 1);
+
+        if (P067_GET_CHANNEL_B_CALIB) { // Calibration channel B?
+          int   adc1 = P067_CONFIG_CHANNEL_B_ADC1;
+          int   adc2 = P067_CONFIG_CHANNEL_B_ADC2;
+          float out1 = P067_CONFIG_CHANNEL_B_OUT1;
+          float out2 = P067_CONFIG_CHANNEL_B_OUT2;
+
+          if (adc1 != adc2) {
+            const float normalized = (UserVar[event->BaseVarIndex + 1] - adc1) / static_cast(adc2 - adc1);
+            UserVar.setFloat(event->TaskIndex, 1, normalized * (out2 - out1) + out1);
+
+            log += concat(F(" = "), formatUserVarNoCheck(event, 1));
+          }
+        }
+      } else {
+        log += F("NO NEW VALUE");
+      }
+      addLogMove(LOG_LEVEL_INFO, log);
+      success = !firstRead;
+    }
+    firstRead = false;
+  }
+  return success;
+}
+
+/*****************************************************
+* plugin_fifty_per_second
+*****************************************************/
+bool P067_data_struct::plugin_fifty_per_second(struct EventStruct *event) {
+  bool success = false;
+
+  if (isInitialized() && isDataReady()) {
+    int32_t value = readHX711();
+    success = true;
+
+    switch (_channelRead) {
+      case P067_Channel_e::chanA64:   //
+      case P067_Channel_e::chanA128:
+      {
+        if (!P067_GET_CHANNEL_A_OS) { // Oversampling on channel A?
+          OversamplingChanA.reset();
+        }
+
+        if (OversamplingChanA.getCount() > 250) {
+          OversamplingChanA.resetKeepLast();
+        }
+        OversamplingChanA.add(value);
+        break;
+      }
+      case P067_Channel_e::chanB32:
+      {
+        if (!P067_GET_CHANNEL_B_OS) { // Oversampling on channel B?
+          OversamplingChanB.reset();
+        }
+
+        if (OversamplingChanB.getCount() > 250) {
+          OversamplingChanB.resetKeepLast();
+        }
+        OversamplingChanB.add(value);
+        break;
+      }
+    }
+  }
+
+  return success;
+}
+
+/*****************************************************
+* plugin_write
+*****************************************************/
+bool P067_data_struct::plugin_write(struct EventStruct *event,
+                                    String            & string) {
+  bool success = false;
+
+  String command = parseString(string, 1);
+
+  if (equals(command, F("tarechana"))) {
+    P067_float2int(-UserVar[event->BaseVarIndex + 2], &P067_OFFSET_CHANNEL_A_1, &P067_OFFSET_CHANNEL_A_2);
+    P067_int2float(P067_OFFSET_CHANNEL_A_1, P067_OFFSET_CHANNEL_A_2, &_offsetChanA);
+    OversamplingChanA.reset();
+
+    addLog(LOG_LEVEL_INFO, F("HX711: tare channel A"));
+    success = true;
+  } else if (equals(command, F("tarechanb"))) {
+    P067_float2int(-UserVar[event->BaseVarIndex + 3], &P067_OFFSET_CHANNEL_B_1, &P067_OFFSET_CHANNEL_B_2);
+    P067_int2float(P067_OFFSET_CHANNEL_B_1, P067_OFFSET_CHANNEL_B_2, &_offsetChanB);
+    OversamplingChanB.reset();
+
+    addLog(LOG_LEVEL_INFO, F("HX711: tare channel B"));
+    success = true;
+  }
+
+  return success;
+}
+
+/****************************************************************************************
+* Private stuff
+****************************************************************************************/
+/****************************************************
+* Minimal viable settings: GPIO pins valid?
+****************************************************/
+bool P067_data_struct::isInitialized() {
+  return validGpio(_pinSCL) && validGpio(_pinDOUT);
+}
+
+/****************************************************
+* New data available?
+****************************************************/
+bool P067_data_struct::isDataReady() {
+  if (isInitialized()) {
+    return !DIRECT_pinRead(_pinDOUT);
+  }
+  return false;
+}
+
+/****************************************************
+* Read data from the load sensor
+****************************************************/
+int32_t P067_data_struct::readHX711() {
+  int32_t  value = 0;
+  uint32_t mask  = 0x00800000;
+
+  _channelRead = _nextChannel;
+
+  // Both channels off
+  if ((_modeChanA == P067_ChannelA_State_e::modeAoff) && (_modeChanB == P067_ChannelB_State_e::modeBoff)) {
+    DIRECT_pinWrite(_pinSCL, HIGH);
+    return 0;
+  }
+
+  // Both channels on
+  if ((_modeChanA != P067_ChannelA_State_e::modeAoff) && (_modeChanB != P067_ChannelB_State_e::modeBoff)) {
+    // Both channels are activated -> do interleaved measurement
+    _channelToggle = !_channelToggle;
+
+    // FIXME tonhuisman: Toggling doesn't work as intended
+    if (_channelToggle) {
+      if (_modeChanA == P067_ChannelA_State_e::modeA64) {
+        _nextChannel = P067_Channel_e::chanA64;
+      } else {
+        _nextChannel = P067_Channel_e::chanA128;
+      }
+    } else {
+      _nextChannel = P067_Channel_e::chanB32;
+    }
+  } else {
+    // Only one channel is activated
+    if (_modeChanA == P067_ChannelA_State_e::modeA64) {
+      _nextChannel = P067_Channel_e::chanA64;
+    } else if (_modeChanA == P067_ChannelA_State_e::modeA128) {
+      _nextChannel = P067_Channel_e::chanA128;
+    }
+
+    if (_modeChanB == P067_ChannelB_State_e::modeB32) {
+      _nextChannel = P067_Channel_e::chanB32;
+    }
+  }
+
+  for (uint8_t i = 0; i < 24; i++) {
+    DIRECT_pinWrite(_pinSCL, HIGH);
+    delayMicroseconds(1);
+    DIRECT_pinWrite(_pinSCL, LOW);
+
+    if (DIRECT_pinRead(_pinDOUT)) {
+      value |= mask;
+    }
+    delayMicroseconds(1);
+    mask >>= 1;
+  }
+
+  for (uint8_t i = 0; i < (static_cast < uint8_t > (_nextChannel) + 1); i++) {
+    DIRECT_pinWrite(_pinSCL, HIGH);
+    delayMicroseconds(1);
+    DIRECT_pinWrite(_pinSCL, LOW);
+    delayMicroseconds(1);
+  }
+
+  if (value & 0x00800000) { // negative?
+    value |= 0xFF000000;    // expand sign bit to 32 bit
+  }
+  return value;
+}
+
+#endif // ifdef USES_P067
diff --git a/src/src/PluginStructs/P068_data_struct.cpp b/src/src/PluginStructs/P068_data_struct.cpp
index 46afc7f9b..acf57167a 100644
--- a/src/src/PluginStructs/P068_data_struct.cpp
+++ b/src/src/PluginStructs/P068_data_struct.cpp
@@ -2,6 +2,8 @@
 
 #ifdef USES_P068
 
+# include "../Helpers/CRC_functions.h"
+
 // ==============================================
 // P068_SHT3X LIBRARY - SHT3X.cpp
 // =============================================
@@ -30,25 +32,22 @@ void P068_SHT3X::readFromSensor()
   {
     uint16_t data[6];
 
-    data[0] = Wire.read();
-    data[1] = Wire.read();
-    data[2] = Wire.read();
-    data[3] = Wire.read();
-    data[4] = Wire.read();
-    data[5] = Wire.read();
+    for (uint8_t i = 0; i < 6u; ++i) {
+      data[i] = Wire.read();
+    }
 
     // TODO: check CRC (data[2] and data[5])
-    if (CRC8(data[0], data[1], data[2]) &&
-        CRC8(data[3], data[4], data[5]))
+    if (calc_CRC8(data[0], data[1], data[2]) &&
+        calc_CRC8(data[3], data[4], data[5]))
     {
       tmp = ((((data[0] << 8) | data[1]) * 175.0f) / 65535.0f) - 45.0f;
       hum = ((((data[3] << 8) | data[4]) * 100.0f) / 65535.0f);
 
       // Humidity temperature compensation borrowed from P028 BME280
       if (!essentiallyZero(tmpOff)) {
-        float last_dew_temp_val = compute_dew_point_temp(tmp + (tmpOff / 2.0f), hum);
-        hum = compute_humidity_from_dewpoint(tmp + tmpOff, last_dew_temp_val);
-        tmp = tmp + tmpOff;
+        const float last_dew_temp_val = compute_dew_point_temp(tmp + (tmpOff / 2.0f), hum);
+        tmp += tmpOff;
+        hum  = compute_humidity_from_dewpoint(tmp, last_dew_temp_val);
       }
     }
   }
@@ -66,29 +65,4 @@ void P068_SHT3X::readFromSensor()
   }
 }
 
-// FIXME TD-er: Try to make some collection of used CRC algorithms
-// See http://reveng.sourceforge.net/crc-catalogue/1-15.htm#crc.cat.crc-8-dvb-s2
-bool P068_SHT3X::CRC8(uint8_t MSB, uint8_t LSB, uint8_t CRC)
-{
-  /*
-   *	Name           : CRC-8
-   * Polynomial     : 0x31 (x8 + x5 + x4 + 1)
-   * Initialization : 0xFF
-   * Reflect input  : False
-   * Reflect output : False
-   * Final          : XOR 0x00
-   *	Example        : CRC8( 0xBE, 0xEF, 0x92) should be true
-   */
-  uint8_t crc = 0xFF;
-
-  for (uint8_t bytenr = 0; bytenr < 2; ++bytenr) {
-    crc ^= (bytenr == 0) ? MSB : LSB;
-
-    for (uint8_t i = 0; i < 8; ++i) {
-      crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1;
-    }
-  }
-  return crc == CRC;
-}
-
 #endif // ifdef USES_P068
diff --git a/src/src/PluginStructs/P068_data_struct.h b/src/src/PluginStructs/P068_data_struct.h
index 221f2cb1f..146df38f2 100644
--- a/src/src/PluginStructs/P068_data_struct.h
+++ b/src/src/PluginStructs/P068_data_struct.h
@@ -16,9 +16,6 @@ public:
   virtual ~P068_SHT3X() = default;
 
   void        readFromSensor(void);
-  static bool CRC8(uint8_t MSB,
-                   uint8_t LSB,
-                   uint8_t CRC);
 
   float tmp    = 0.0f;
   float hum    = 0.0f;
diff --git a/src/src/PluginStructs/P069_data_struct.cpp b/src/src/PluginStructs/P069_data_struct.cpp
index 4dd5e8d1c..53d41292b 100644
--- a/src/src/PluginStructs/P069_data_struct.cpp
+++ b/src/src/PluginStructs/P069_data_struct.cpp
@@ -10,31 +10,9 @@
 # define LM75A_REG_ADDR_TEMP     0
 
 
-P069_data_struct::P069_data_struct(bool A0_value, bool A1_value, bool A2_value)
-{
-  _i2c_device_address = LM75A_BASE_ADDRESS;
-
-  if (A0_value) {
-    _i2c_device_address += 1;
-  }
-
-  if (A1_value) {
-    _i2c_device_address += 2;
-  }
-
-  if (A2_value) {
-    _i2c_device_address += 4;
-  }
-}
-
 P069_data_struct::P069_data_struct(uint8_t addr) :
   _i2c_device_address(addr) {}
 
-void P069_data_struct::setAddress(uint8_t addr)
-{
-  _i2c_device_address = addr;
-}
-
 float P069_data_struct::getTemperatureInDegrees() const
 {
   // Go to temperature data register
diff --git a/src/src/PluginStructs/P069_data_struct.h b/src/src/PluginStructs/P069_data_struct.h
index 1ce149a2f..19f913450 100644
--- a/src/src/PluginStructs/P069_data_struct.h
+++ b/src/src/PluginStructs/P069_data_struct.h
@@ -8,17 +8,11 @@
 struct P069_data_struct : public PluginTaskData_base {
 public:
 
-  P069_data_struct(bool A0_value = false,
-                   bool A1_value = false,
-                   bool A2_value = false);
-
   P069_data_struct(uint8_t addr);
 
   P069_data_struct() = delete;
   virtual ~P069_data_struct() = default;
 
-  void  setAddress(uint8_t addr);
-
   float getTemperatureInDegrees() const;
 
 private:
diff --git a/src/src/PluginStructs/P070_data_struct.cpp b/src/src/PluginStructs/P070_data_struct.cpp
index 81fad24a1..cdc6e116f 100644
--- a/src/src/PluginStructs/P070_data_struct.cpp
+++ b/src/src/PluginStructs/P070_data_struct.cpp
@@ -4,17 +4,13 @@
 
 
 P070_data_struct::~P070_data_struct() {
-  if (Plugin_070_pixels != nullptr) {
-    delete Plugin_070_pixels;
-    Plugin_070_pixels = nullptr;
-  }
+  delete Plugin_070_pixels;
+  Plugin_070_pixels = nullptr;
 }
 
 void P070_data_struct::reset() {
-  if (Plugin_070_pixels != nullptr) {
-    delete Plugin_070_pixels;
-    Plugin_070_pixels = nullptr;
-  }
+  delete Plugin_070_pixels;
+  Plugin_070_pixels = nullptr;
 }
 
 void P070_data_struct::init(struct EventStruct *event) {
@@ -43,9 +39,9 @@ void P070_data_struct::Clock_update()
   clearClock();              // turn off the LEDs
 
   if (display_enabled > 0) { // if the display is enabled, calculate the LEDs to turn on
-    int Hours   = node_time.hour();
-    int Minutes = node_time.minute();
-    int Seconds = node_time.second();
+    const int Hours   = node_time.hour();
+    const int Minutes = node_time.minute();
+    const int Seconds = node_time.second();
     timeToStrip(Hours, Minutes, Seconds);
   }
   Plugin_070_pixels->show(); // This sends the updated pixel color to the hardware.
@@ -53,7 +49,7 @@ void P070_data_struct::Clock_update()
 
 void P070_data_struct::calculateMarks()
 { // generate a list of the LEDs that have hour marks
-  for (int i = 0; i < 12; i++) {
+  for (int i = 0; i < 12; ++i) {
     marks[i] = 5 * i + (offset_12h_mark % 5);
   }
 
@@ -78,7 +74,7 @@ void P070_data_struct::calculateMarks()
 }
 
 void P070_data_struct::clearClock() {
-  for (int i = 0; i < NUMBER_LEDS; i++) {
+  for (int i = 0; i < NUMBER_LEDS; ++i) {
     Plugin_070_pixels->setPixelColor(i, Plugin_070_pixels->Color(0, 0, 0));
   }
 }
@@ -95,7 +91,7 @@ void P070_data_struct::timeToStrip(int hours, int minutes, int seconds) {
 
   if (seconds > 59) { seconds = seconds - 60; }
 
-  for (int i = 0; i < 14; i++) {                                                                      // set the hour marks as white;
+  for (int i = 0; i < 14; ++i) {                                                                      // set the hour marks as white;
     if ((marks[i] != hours) && (marks[i] != minutes) && (marks[i] != seconds) && (marks[i] != 255)) { // do not draw a mark there is a clock
                                                                                                       // hand in that position
       Plugin_070_pixels->setPixelColor(marks[i],
@@ -105,7 +101,7 @@ void P070_data_struct::timeToStrip(int hours, int minutes, int seconds) {
   uint32_t currentColor;
   uint8_t  r_val, g_val;                  // , b_val;
 
-  for (int i = 0; i < NUMBER_LEDS; i++) { // draw the clock hands, adding the colors together
+  for (int i = 0; i < NUMBER_LEDS; ++i) { // draw the clock hands, adding the colors together
     if (i == hours) {                     // hours hand is RED
       Plugin_070_pixels->setPixelColor(i, Plugin_070_pixels->Color(brightness, 0, 0));
     }
diff --git a/src/src/PluginStructs/P070_data_struct.h b/src/src/PluginStructs/P070_data_struct.h
index a48f6b9a0..1d69a836a 100644
--- a/src/src/PluginStructs/P070_data_struct.h
+++ b/src/src/PluginStructs/P070_data_struct.h
@@ -5,7 +5,7 @@
 #ifdef USES_P070
 
 
-#include 
+# include 
 
 
 # define NUMBER_LEDS      60 // number of LED in the strip
diff --git a/src/src/PluginStructs/P073_data_struct.cpp b/src/src/PluginStructs/P073_data_struct.cpp
index ebcd20fd0..75771b3c3 100644
--- a/src/src/PluginStructs/P073_data_struct.cpp
+++ b/src/src/PluginStructs/P073_data_struct.cpp
@@ -192,7 +192,7 @@ void P073_data_struct::FillBufferWithDualTemp(long leftTemperature,
     }
   }
 
-  // addLog(LOG_LEVEL_INFO, String(F("7dgt format")) + format);
+  // addLog(LOG_LEVEL_INFO, concat(F("7dgt format: "), format));
 }
 
 # endif // ifdef P073_7DDT_COMMAND
diff --git a/src/src/PluginStructs/P073_data_struct.h b/src/src/PluginStructs/P073_data_struct.h
index 065232dd9..868e43da1 100644
--- a/src/src/PluginStructs/P073_data_struct.h
+++ b/src/src/PluginStructs/P073_data_struct.h
@@ -40,8 +40,8 @@
 #  undef P073_SUPPRESS_ZERO // Optionally activate if .bin file space is really problematic, to remove the Suppress leading zero feature
 # endif // ifndef PLUGIN_SET_COLLECTION
 
-# define TM1637_POWER_ON    B10001000
-# define TM1637_POWER_OFF   B10000000
+# define TM1637_POWER_ON    0b10001000
+# define TM1637_POWER_OFF   0b10000000
 # define TM1637_CLOCKDELAY  40
 # define TM1637_4DIGIT      4
 # define TM1637_6DIGIT      2
@@ -57,13 +57,13 @@
 //   - pos 15    - underscore "_"
 //   - pos 16-41 - Letters from A to Z
 static const uint8_t DefaultCharTable[42] PROGMEM = {
-  B01111110, B00110000, B01101101, B01111001, B00110011, B01011011,
-  B01011111, B01110000, B01111111, B01111011, B00000000, B00000001,
-  B01100011, B00001001, B01001001, B00001000, B01110111, B00011111,
-  B01001110, B00111101, B01001111, B01000111, B01011110, B00110111,
-  B00000110, B00111100, B01010111, B00001110, B01010100, B01110110,
-  B01111110, B01100111, B01101011, B01100110, B01011011, B00001111,
-  B00111110, B00111110, B00101010, B00110111, B00111011, B01101101 };
+  0b01111110, 0b00110000, 0b01101101, 0b01111001, 0b00110011, 0b01011011,
+  0b01011111, 0b01110000, 0b01111111, 0b01111011, 0b00000000, 0b00000001,
+  0b01100011, 0b00001001, 0b01001001, 0b00001000, 0b01110111, 0b00011111,
+  0b01001110, 0b00111101, 0b01001111, 0b01000111, 0b01011110, 0b00110111,
+  0b00000110, 0b00111100, 0b01010111, 0b00001110, 0b01010100, 0b01110110,
+  0b01111110, 0b01100111, 0b01101011, 0b01100110, 0b01011011, 0b00001111,
+  0b00111110, 0b00111110, 0b00101010, 0b00110111, 0b00111011, 0b01101101 };
 
 # ifdef P073_EXTRA_FONTS
 
@@ -78,46 +78,46 @@ static const uint8_t DefaultCharTable[42] PROGMEM = {
 //   - pos 14    - slash "/"
 //   - pos 15    - underscore "_"
 //   - pos 16-40 - Special characters not handled yet -- MAX7219 -- -- TM1637 --
-//   - pos 16    - percent "%"                          B00010010
-//   - pos 17    - at "@"                               B01110100
-//   - pos 18    - period "."                           B00000100
-//   - pos 10    - comma ","                            B00011000
-//   - pos 20    - semicolon ";"                        B00101000
-//   - pos 21    - colon ":"                            B01001000
-//   - pos 22    - plus "+"                             B00110001
-//   - pos 23    - asterisk "*"                         B01001001
-//   - pos 24    - hash "#"                             B00110110
-//   - pos 25    - exclamation mark "!"                 B01101011
-//   - pos 26    - question mark "?"                    B01101001
-//   - pos 27    - single quote "'"                     B00000010
-//   - pos 28    - double quote '"'                     B00100010
-//   - pos 29    - left sharp bracket "<"               B01000010
-//   - pos 30    - right sharp bracket ">"              B01100000
-//   - pos 31    - backslash "\"                        B00010011
-//   - pos 32    - left round bracket "("               B01001110
-//   - pos 33    - right round bracket ")"              B01111000
-//   - pos 34    - overscore "|" (the top-most line)    B01000000
-//   - pos 35    - uppercase C "C" (optionally enabled) B01001110
-//   - pos 36    - uppercase H "H"                      B00110111
-//   - pos 37    - uppercase N "N"                      B01110110
-//   - pos 38    - uppercase O "O"                      B01111110
-//   - pos 39    - uppercase R "R"                      B01100110
-//   - pos 40    - uppercase U "U"                      B00111110
-//   - pos 41    - uppercase X "X"                      B00110111
+//   - pos 16    - percent "%"                          0b00010010
+//   - pos 17    - at "@"                               0b01110100
+//   - pos 18    - period "."                           0b00000100
+//   - pos 10    - comma ","                            0b00011000
+//   - pos 20    - semicolon ";"                        0b00101000
+//   - pos 21    - colon ":"                            0b01001000
+//   - pos 22    - plus "+"                             0b00110001
+//   - pos 23    - asterisk "*"                         0b01001001
+//   - pos 24    - hash "#"                             0b00110110
+//   - pos 25    - exclamation mark "!"                 0b01101011
+//   - pos 26    - question mark "?"                    0b01101001
+//   - pos 27    - single quote "'"                     0b00000010
+//   - pos 28    - double quote '"'                     0b00100010
+//   - pos 29    - left sharp bracket "<"               0b01000010
+//   - pos 30    - right sharp bracket ">"              0b01100000
+//   - pos 31    - backslash "\"                        0b00010011
+//   - pos 32    - left round bracket "("               0b01001110
+//   - pos 33    - right round bracket ")"              0b01111000
+//   - pos 34    - overscore "|" (the top-most line)    0b01000000
+//   - pos 35    - uppercase C "C" (optionally enabled) 0b01001110
+//   - pos 36    - uppercase H "H"                      0b00110111
+//   - pos 37    - uppercase N "N"                      0b01110110
+//   - pos 38    - uppercase O "O"                      0b01111110
+//   - pos 39    - uppercase R "R"                      0b01100110
+//   - pos 40    - uppercase U "U"                      0b00111110
+//   - pos 41    - uppercase X "X"                      0b00110111
 //   - pos 42-67 - Letters from A to Z Siekoo style
 static const uint8_t SiekooCharTable[68] PROGMEM = {
-  B01111110, B00110000, B01101101, B01111001, B00110011, B01011011,
-  B01011111, B01110000, B01111111, B01111011, B00000000, B00000001,
-  B01100011, B00001001, B00100101, B00001000, B00010010, B01110100,
-  B00000100, B00011000, B00101000, B01001000, B00110001, B01001001,
-  B00110110, B01101011, B01101001, B00000010, B00100010, B01000010,
-  B01100000, B00010011, B01001110, B01111000, B01000000, B01001110,
-  B00110111, B01110110, B01111110, B01100110, B00111110, B00110111,
-  B01111101, B00011111, B00001101, B00111101, B01001111, B01000111, /* ABCDEF */
-  B01011110, B00010111, B01000100, B01011000, B01010111, B00001110,
-  B01010101, B00010101, B00011101, B01100111, B01110011, B00000101,
-  B01011010, B00001111, B00011100, B00101010, B00101011, B00010100,
-  B00111011, B01101100 };
+  0b01111110, 0b00110000, 0b01101101, 0b01111001, 0b00110011, 0b01011011,
+  0b01011111, 0b01110000, 0b01111111, 0b01111011, 0b00000000, 0b00000001,
+  0b01100011, 0b00001001, 0b00100101, 0b00001000, 0b00010010, 0b01110100,
+  0b00000100, 0b00011000, 0b00101000, 0b01001000, 0b00110001, 0b01001001,
+  0b00110110, 0b01101011, 0b01101001, 0b00000010, 0b00100010, 0b01000010,
+  0b01100000, 0b00010011, 0b01001110, 0b01111000, 0b01000000, 0b01001110,
+  0b00110111, 0b01110110, 0b01111110, 0b01100110, 0b00111110, 0b00110111,
+  0b01111101, 0b00011111, 0b00001101, 0b00111101, 0b01001111, 0b01000111, /* ABCDEF */
+  0b01011110, 0b00010111, 0b01000100, 0b01011000, 0b01010111, 0b00001110,
+  0b01010101, 0b00010101, 0b00011101, 0b01100111, 0b01110011, 0b00000101,
+  0b01011010, 0b00001111, 0b00011100, 0b00101010, 0b00101011, 0b00010100,
+  0b00111011, 0b01101100 };
 
 // dSEG7 https://www.keshikan.net/fonts-e.html
 // specials:
@@ -130,13 +130,13 @@ static const uint8_t SiekooCharTable[68] PROGMEM = {
 //   - pos 15    - underscore "_"
 //   - pos 16-41 - Letters from A to Z dSEG7 style
 static const uint8_t Dseg7CharTable[42] PROGMEM = {
-  B01111110, B00110000, B01101101, B01111001, B00110011, B01011011,
-  B01011111, B01110000, B01111111, B01111011, B00000000, B00000001,
-  B01100011, B00001001, B01001001, B00001000, B01110111, B00011111, /* AB */
-  B00001101, B00111101, B01001111, B01000111, B01011110, B00010111,
-  B00010000, B00111100, B01010111, B00001110, B01110110, B00010101,
-  B00011101, B01100111, B01110011, B00000101, B00011011, B00001111,
-  B00011100, B00111110, B00111111, B00110111, B00111011, B01101100 };
+  0b01111110, 0b00110000, 0b01101101, 0b01111001, 0b00110011, 0b01011011,
+  0b01011111, 0b01110000, 0b01111111, 0b01111011, 0b00000000, 0b00000001,
+  0b01100011, 0b00001001, 0b01001001, 0b00001000, 0b01110111, 0b00011111, /* AB */
+  0b00001101, 0b00111101, 0b01001111, 0b01000111, 0b01011110, 0b00010111,
+  0b00010000, 0b00111100, 0b01010111, 0b00001110, 0b01110110, 0b00010101,
+  0b00011101, 0b01100111, 0b01110011, 0b00000101, 0b00011011, 0b00001111,
+  0b00011100, 0b00111110, 0b00111111, 0b00110111, 0b00111011, 0b01101100 };
 
 # endif // P073_EXTRA_FONTS
 
diff --git a/src/src/PluginStructs/P077_data_struct.cpp b/src/src/PluginStructs/P077_data_struct.cpp
index 6a3bf60bc..6e7504e0e 100644
--- a/src/src/PluginStructs/P077_data_struct.cpp
+++ b/src/src/PluginStructs/P077_data_struct.cpp
@@ -353,6 +353,13 @@ bool P077_data_struct::plugin_write(struct EventStruct *event,
     P077_PREF = CSE_PREF_PULSE;
     success   = true;
     changed   = true;
+  } else if (equals(cmd, F("cseclearpulses"))) {
+    // Clear the pulses count
+    cf_pulses = 0;
+    setOutputValue(event, P077_query::P077_QUERY_KWH,    cf_pulses);
+    setOutputValue(event, P077_query::P077_QUERY_PULSES, cf_pulses);
+    Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10);
+    success   = true;
   } else if (equals(cmd, F("csecalibrate"))) { // Set 1 or more calibration values, 0 will skip that value
     success = true;
     float CalibVolt  = 0.0f;
diff --git a/src/src/PluginStructs/P078_data_struct.cpp b/src/src/PluginStructs/P078_data_struct.cpp
index f4a50c5eb..45532b5d3 100644
--- a/src/src/PluginStructs/P078_data_struct.cpp
+++ b/src/src/PluginStructs/P078_data_struct.cpp
@@ -211,7 +211,7 @@ void SDM_loadOutputSelector(struct EventStruct *event, uint8_t pconfigIndex, uin
 {
   const SDM_MODEL model = static_cast(P078_MODEL);
   const String    label = concat(F("Value "), valuenr + 1);
-  const String    id    = PCONFIG_LABEL(pconfigIndex);
+  const String    id    = sensorTypeHelper_webformID(pconfigIndex);
 
   addRowLabel_tr_id(label, id);
   do_addSelector_Head(id, F("wide"), EMPTY_STRING, false);
@@ -270,12 +270,13 @@ String p078_register_description::getDescription(SDM_MODEL model) const
 {
   String res;
   const SDM_DIRECTION direction = getDirection();
-  const SDM_UOM uom             = getUnitOfMeasure();
+  const SDM_UOM  uom            = getUnitOfMeasure();
+  const uint16_t reg            = getRegister();
   bool showFullUnitOfMeasure    = true;
 
   // Check first for specific strings not generated using the description bitmap
 
-  switch (getRegister())
+  switch (reg)
   {
     case SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND:
     case SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND:
@@ -317,7 +318,7 @@ String p078_register_description::getDescription(SDM_MODEL model) const
       break;
   }
 
-  switch (getRegister())
+  switch (reg)
   {
     case SDM_NEUTRAL_CURRENT_DEMAND:
     case SDM_MAXIMUM_NEUTRAL_CURRENT:
@@ -365,7 +366,7 @@ String p078_register_description::getDescription(SDM_MODEL model) const
     res += SDM_UOMtoString(uom, true);
   }
 
-  switch (getRegister())
+  switch (reg)
   {
     case SDM_TOTAL_SYSTEM_POWER_DEMAND:
     case SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND:
@@ -390,7 +391,7 @@ String p078_register_description::getDescription(SDM_MODEL model) const
       break;
   }
 
-  switch (getRegister())
+  switch (reg)
   {
     case SDM_VAH_SINCE_LAST_RESET:
     case SDM_AH_SINCE_LAST_RESET:
@@ -399,11 +400,8 @@ String p078_register_description::getDescription(SDM_MODEL model) const
       break;
   }
 
-  res += ' ';
-  res += '(';
-  res += SDM_UOMtoString(uom, false);
-  res += ')';
-  res += getPhaseDescription(model, ' ');
+  res += concat(F(" ("), SDM_UOMtoString(uom, false));
+  res += concat(F(")"), getPhaseDescription(model, ' '));
   return res;
 }
 
@@ -468,7 +466,7 @@ void SDM_loopRegisterReadQueue(SDM *sdm)
       const float value = sdm->decodeFloatValue();
       UserVar.setFloat(it->taskIndex, it->taskVarIndex, value);
 
-# if FEATURE_PLUGIN_STATS
+      # if FEATURE_PLUGIN_STATS
       PluginTaskData_base *taskdata = getPluginTaskDataBaseClassOnly(it->taskIndex);
 
       if (taskdata != nullptr) {
@@ -478,7 +476,7 @@ void SDM_loopRegisterReadQueue(SDM *sdm)
           stats->trackPeak(value);
         }
       }
-# endif // if FEATURE_PLUGIN_STATS
+      # endif // if FEATURE_PLUGIN_STATS
     } else {
       sdm->clearErrCode();
     }
diff --git a/src/src/PluginStructs/P079_data_struct.cpp b/src/src/PluginStructs/P079_data_struct.cpp
index 81c4f233b..ec65aaa3b 100644
--- a/src/src/PluginStructs/P079_data_struct.cpp
+++ b/src/src/PluginStructs/P079_data_struct.cpp
@@ -3,7 +3,6 @@
 #ifdef USES_P079
 
 
-
 WemosMotor::WemosMotor(uint8_t address, uint8_t motor, uint32_t freq)
   : _address(address), _freq(freq), _use_STBY_IO(false)
 {
@@ -28,6 +27,7 @@ WemosMotor::WemosMotor(uint8_t address, uint8_t motor, uint32_t freq, uint8_t ST
 
 void WemosMotor::init() {
   setfreq(_freq);
+
   if (_use_STBY_IO) {
     pinMode(_STBY_IO, OUTPUT);
     digitalWrite(_STBY_IO, LOW);
@@ -105,11 +105,6 @@ void WemosMotor::setmotor(uint8_t dir, float pwm_val)
   delay(0);
 }
 
-void WemosMotor::setmotor(uint8_t dir)
-{
-  setmotor(dir, 100);
-}
-
 LOLIN_I2C_MOTOR::LOLIN_I2C_MOTOR(uint8_t address) : _address(address) {}
 
 /*
diff --git a/src/src/PluginStructs/P079_data_struct.h b/src/src/PluginStructs/P079_data_struct.h
index 1589876a8..8c03240e1 100644
--- a/src/src/PluginStructs/P079_data_struct.h
+++ b/src/src/PluginStructs/P079_data_struct.h
@@ -77,8 +77,7 @@ public:
   void init();
   void setfreq(uint32_t freq);
   void setmotor(uint8_t dir,
-                float   pwm_val);
-  void setmotor(uint8_t dir);
+                float   pwm_val = 100.0f);
 
 private:
 
diff --git a/src/src/PluginStructs/P081_data_struct.cpp b/src/src/PluginStructs/P081_data_struct.cpp
index e9af991e6..7d0cd67af 100644
--- a/src/src/PluginStructs/P081_data_struct.cpp
+++ b/src/src/PluginStructs/P081_data_struct.cpp
@@ -1,202 +1,202 @@
-#include "../PluginStructs/P081_data_struct.h"
-
-#ifdef USES_P081
-
-P081_data_struct::P081_data_struct(const String& expression)
-{
-  const char *error;
-
-  memset(&_expr, 0, sizeof(_expr));
-  cron_parse_expr(expression.c_str(), &_expr, &error);
-
-  if (!error) {
-    _initialized = true;
-  } else {
-    _error = String(error);
-  }
-}
-
-bool P081_data_struct::hasError(String& error) const {
-  if (_initialized) { return false; }
-  error = _error;
-  return true;
-}
-
-time_t P081_data_struct::get_cron_next(time_t date) const {
-  if (!_initialized) { return CRON_INVALID_INSTANT; }
-  return cron_next((cron_expr *)&_expr, date);
-}
-
-time_t P081_data_struct::get_cron_prev(time_t date) const {
-  if (!_initialized) { return CRON_INVALID_INSTANT; }
-  return cron_prev((cron_expr *)&_expr, date);
-}
-
-String P081_getCronExpr(taskIndex_t taskIndex)
-{
-  char expression[PLUGIN_081_EXPRESSION_SIZE + 1];
-
-  ZERO_FILL(expression);
-  LoadCustomTaskSettings(taskIndex, reinterpret_cast(&expression), PLUGIN_081_EXPRESSION_SIZE);
-  String res(expression);
-
-  res.trim();
-  return res;
-}
-
-time_t P081_computeNextCronTime(taskIndex_t taskIndex, time_t last)
-{
-  P081_data_struct *P081_data =
-    static_cast(getPluginTaskData(taskIndex));
-
-  if ((nullptr != P081_data) && P081_data->isInitialized()) {
-    //    int32_t freeHeapStart = ESP.getFreeHeap();
-
-    time_t res = P081_data->get_cron_next(last);
-
-    /*
-        int32_t freeHeapEnd = ESP.getFreeHeap();
-
-        if (freeHeapEnd < freeHeapStart) {
-          String log = F("Cron: Free Heap Decreased: ");
-          log += String(freeHeapStart - freeHeapEnd);
-          log += F(" (");
-          log += freeHeapStart;
-          log += F(" -> ");
-          log += freeHeapEnd;
-          addLog(LOG_LEVEL_INFO, log);
-        }
-     */
-    return res;
-  }
-  return CRON_INVALID_INSTANT;
-}
-
-time_t P081_getCronExecTime(taskIndex_t taskIndex, uint8_t varNr)
-{
-  return static_cast(UserVar.getUint32(taskIndex, varNr));
-}
-
-void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_t nextExecTime) {
-  UserVar.setUint32(event->TaskIndex, LASTEXECUTION, static_cast(lastExecTime));
-  UserVar.setUint32(event->TaskIndex, NEXTEXECUTION, static_cast(nextExecTime));
-}
-
-time_t P081_getCurrentTime()
-{
-  node_time.now();
-
-  // FIXME TD-er: Why work on a deepcopy of tm?
-  struct tm current = node_time.local_tm;
-
-  return mktime((struct tm *)¤t);
-}
-
-void P081_check_or_init(struct EventStruct *event)
-{
-  if (node_time.systemTimePresent()) {
-    const time_t current_time = P081_getCurrentTime();
-    time_t last_exec_time     = P081_getCronExecTime(event->TaskIndex, LASTEXECUTION);
-    time_t next_exec_time     = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION);
-
-    // Must check if the values of LASTEXECUTION and NEXTEXECUTION make sense.
-    // These can be invalid values from a reboot, or simply contain uninitialized values.
-    if ((last_exec_time > current_time) || (last_exec_time == CRON_INVALID_INSTANT) || (next_exec_time == CRON_INVALID_INSTANT)) {
-      // Last execution time cannot be correct.
-      last_exec_time = CRON_INVALID_INSTANT;
-      const time_t tmp_next = P081_computeNextCronTime(event->TaskIndex, current_time);
-
-      if ((tmp_next < next_exec_time) || (next_exec_time == CRON_INVALID_INSTANT)) {
-        next_exec_time = tmp_next;
-      }
-      P081_setCronExecTimes(event, CRON_INVALID_INSTANT, next_exec_time);
-    }
-  }
-}
-
-# if PLUGIN_081_DEBUG
-void PrintCronExp(struct cron_expr_t e) {
-  serialPrintln(F("===DUMP Cron Expression==="));
-  serialPrint(F("Seconds:"));
-
-  for (int i = 0; i < 8; i++)
-  {
-    serialPrint(e.seconds[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrint(F("Minutes:"));
-
-  for (int i = 0; i < 8; i++)
-  {
-    serialPrint(e.minutes[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrint(F("hours:"));
-
-  for (int i = 0; i < 3; i++)
-  {
-    serialPrint(e.hours[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrint(F("months:"));
-
-  for (int i = 0; i < 2; i++)
-  {
-    serialPrint(e.months[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrint(F("days_of_week:"));
-
-  for (int i = 0; i < 1; i++)
-  {
-    serialPrint(e.days_of_week[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrint(F("days_of_month:"));
-
-  for (int i = 0; i < 4; i++)
-  {
-    serialPrint(e.days_of_month[i]);
-    serialPrint(",");
-  }
-  serialPrintln();
-  serialPrintln(F("END=DUMP Cron Expression==="));
-}
-
-# endif // if PLUGIN_081_DEBUG
-
-
-String P081_formatExecTime(taskIndex_t taskIndex, uint8_t varNr) {
-  time_t exec_time = P081_getCronExecTime(taskIndex, varNr);
-
-  if (exec_time != CRON_INVALID_INSTANT) {
-    return formatDateTimeString(*gmtime(&exec_time));
-  }
-  return F("-");
-}
-
-void P081_html_show_cron_expr(struct EventStruct *event) {
-  P081_data_struct *P081_data =
-    static_cast(getPluginTaskData(event->TaskIndex));
-
-  if ((nullptr != P081_data) && P081_data->isInitialized()) {
-    String error;
-
-    if (P081_data->hasError(error)) {
-      addRowLabel(F("Error"));
-      addHtml(error);
-    } else {
-      addRowLabel(F("Last Exec Time"));
-      addHtml(P081_formatExecTime(event->TaskIndex, LASTEXECUTION));
-      addRowLabel(F("Next Exec Time"));
-      addHtml(P081_formatExecTime(event->TaskIndex, NEXTEXECUTION));
-    }
-  }
-}
-
-#endif // ifdef USES_P081
+#include "../PluginStructs/P081_data_struct.h"
+
+#ifdef USES_P081
+
+P081_data_struct::P081_data_struct(const String& expression)
+{
+  const char *error;
+
+  memset(&_expr, 0, sizeof(_expr));
+  cron_parse_expr(expression.c_str(), &_expr, &error);
+
+  if (!error) {
+    _initialized = true;
+  } else {
+    _error = String(error);
+  }
+}
+
+bool P081_data_struct::hasError(String& error) const {
+  if (_initialized) { return false; }
+  error = _error;
+  return true;
+}
+
+time_t P081_data_struct::get_cron_next(time_t date) const {
+  if (!_initialized) { return CRON_INVALID_INSTANT; }
+  return cron_next((cron_expr *)&_expr, date);
+}
+
+time_t P081_data_struct::get_cron_prev(time_t date) const {
+  if (!_initialized) { return CRON_INVALID_INSTANT; }
+  return cron_prev((cron_expr *)&_expr, date);
+}
+
+String P081_getCronExpr(taskIndex_t taskIndex)
+{
+  char expression[PLUGIN_081_EXPRESSION_SIZE + 1];
+
+  ZERO_FILL(expression);
+  LoadCustomTaskSettings(taskIndex, reinterpret_cast(&expression), PLUGIN_081_EXPRESSION_SIZE);
+  String res(expression);
+
+  res.trim();
+  return res;
+}
+
+time_t P081_computeNextCronTime(taskIndex_t taskIndex, time_t last)
+{
+  P081_data_struct *P081_data =
+    static_cast(getPluginTaskData(taskIndex));
+
+  if ((nullptr != P081_data) && P081_data->isInitialized()) {
+    //    int32_t freeHeapStart = ESP.getFreeHeap();
+
+    time_t res = P081_data->get_cron_next(last);
+
+    /*
+        int32_t freeHeapEnd = ESP.getFreeHeap();
+
+        if (freeHeapEnd < freeHeapStart) {
+          String log = F("Cron: Free Heap Decreased: ");
+          log += String(freeHeapStart - freeHeapEnd);
+          log += F(" (");
+          log += freeHeapStart;
+          log += F(" -> ");
+          log += freeHeapEnd;
+          addLog(LOG_LEVEL_INFO, log);
+        }
+     */
+    return res;
+  }
+  return CRON_INVALID_INSTANT;
+}
+
+time_t P081_getCronExecTime(taskIndex_t taskIndex, uint8_t varNr)
+{
+  return static_cast(UserVar.getUint32(taskIndex, varNr));
+}
+
+void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_t nextExecTime) {
+  UserVar.setUint32(event->TaskIndex, LASTEXECUTION, static_cast(lastExecTime));
+  UserVar.setUint32(event->TaskIndex, NEXTEXECUTION, static_cast(nextExecTime));
+}
+
+time_t P081_getCurrentTime()
+{
+  node_time.now_();
+
+  // FIXME TD-er: Why work on a deepcopy of tm?
+  struct tm current = node_time.local_tm;
+
+  return mktime((struct tm *)¤t);
+}
+
+void P081_check_or_init(struct EventStruct *event)
+{
+  if (node_time.systemTimePresent()) {
+    const time_t current_time = P081_getCurrentTime();
+    time_t last_exec_time     = P081_getCronExecTime(event->TaskIndex, LASTEXECUTION);
+    time_t next_exec_time     = P081_getCronExecTime(event->TaskIndex, NEXTEXECUTION);
+
+    // Must check if the values of LASTEXECUTION and NEXTEXECUTION make sense.
+    // These can be invalid values from a reboot, or simply contain uninitialized values.
+    if ((last_exec_time > current_time) || (last_exec_time == CRON_INVALID_INSTANT) || (next_exec_time == CRON_INVALID_INSTANT)) {
+      // Last execution time cannot be correct.
+      last_exec_time = CRON_INVALID_INSTANT;
+      const time_t tmp_next = P081_computeNextCronTime(event->TaskIndex, current_time);
+
+      if ((tmp_next < next_exec_time) || (next_exec_time == CRON_INVALID_INSTANT)) {
+        next_exec_time = tmp_next;
+      }
+      P081_setCronExecTimes(event, CRON_INVALID_INSTANT, next_exec_time);
+    }
+  }
+}
+
+# if PLUGIN_081_DEBUG
+void PrintCronExp(struct cron_expr_t e) {
+  serialPrintln(F("===DUMP Cron Expression==="));
+  serialPrint(F("Seconds:"));
+
+  for (int i = 0; i < 8; i++)
+  {
+    serialPrint(e.seconds[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrint(F("Minutes:"));
+
+  for (int i = 0; i < 8; i++)
+  {
+    serialPrint(e.minutes[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrint(F("hours:"));
+
+  for (int i = 0; i < 3; i++)
+  {
+    serialPrint(e.hours[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrint(F("months:"));
+
+  for (int i = 0; i < 2; i++)
+  {
+    serialPrint(e.months[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrint(F("days_of_week:"));
+
+  for (int i = 0; i < 1; i++)
+  {
+    serialPrint(e.days_of_week[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrint(F("days_of_month:"));
+
+  for (int i = 0; i < 4; i++)
+  {
+    serialPrint(e.days_of_month[i]);
+    serialPrint(",");
+  }
+  serialPrintln();
+  serialPrintln(F("END=DUMP Cron Expression==="));
+}
+
+# endif // if PLUGIN_081_DEBUG
+
+
+String P081_formatExecTime(taskIndex_t taskIndex, uint8_t varNr) {
+  time_t exec_time = P081_getCronExecTime(taskIndex, varNr);
+
+  if (exec_time != CRON_INVALID_INSTANT) {
+    return formatDateTimeString(*gmtime(&exec_time));
+  }
+  return F("-");
+}
+
+void P081_html_show_cron_expr(struct EventStruct *event) {
+  P081_data_struct *P081_data =
+    static_cast(getPluginTaskData(event->TaskIndex));
+
+  if ((nullptr != P081_data) && P081_data->isInitialized()) {
+    String error;
+
+    if (P081_data->hasError(error)) {
+      addRowLabel(F("Error"));
+      addHtml(error);
+    } else {
+      addRowLabel(F("Last Exec Time"));
+      addHtml(P081_formatExecTime(event->TaskIndex, LASTEXECUTION));
+      addRowLabel(F("Next Exec Time"));
+      addHtml(P081_formatExecTime(event->TaskIndex, NEXTEXECUTION));
+    }
+  }
+}
+
+#endif // ifdef USES_P081
diff --git a/src/src/PluginStructs/P082_data_struct.cpp b/src/src/PluginStructs/P082_data_struct.cpp
index 0eb4b7680..707669659 100644
--- a/src/src/PluginStructs/P082_data_struct.cpp
+++ b/src/src/PluginStructs/P082_data_struct.cpp
@@ -1,560 +1,639 @@
-#include "../PluginStructs/P082_data_struct.h"
-
-#ifdef USES_P082
-
-
-// Needed also here for PlatformIO's library finder as the .h file
-// is in a directory which is excluded in the src_filter
-# include 
-# include 
-
-
-const __FlashStringHelper * Plugin_082_valuename(P082_query value_nr, bool displayString) {
-  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");
-    case P082_query::P082_QUERY_SPD:         return displayString ? F("Speed (m/s)")        : F("spd");
-    case P082_query::P082_QUERY_SATVIS:      return displayString ? F("Satellites Visible") : F("sat_vis");
-    case P082_query::P082_QUERY_SATUSE:      return displayString ? F("Satellites Tracked") : F("sat_tr");
-    case P082_query::P082_QUERY_HDOP:        return displayString ? F("HDOP")               : F("hdop");
-    case P082_query::P082_QUERY_FIXQ:        return displayString ? F("Fix Quality")        : F("fix_qual");
-    case P082_query::P082_QUERY_DB_MAX:      return displayString ? F("Max SNR in dBHz")    : F("snr_max");
-    case P082_query::P082_QUERY_CHKSUM_FAIL: return displayString ? F("Checksum Fail")      : F("chksum_fail");
-    case P082_query::P082_QUERY_DISTANCE:    return displayString ? F("Distance (ODO)")     : F("dist");
-    case P082_query::P082_QUERY_DIST_REF:    return displayString ? F("Distance from Reference Point") : F("dist_ref");
-    case P082_query::P082_NR_OUTPUT_OPTIONS: break;
-  }
-  return F("");
-}
-
-P082_query Plugin_082_from_valuename(const String& valuename)
-{
-  for (uint8_t query = 0; query < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++query) {
-    if (valuename.equalsIgnoreCase(Plugin_082_valuename(static_cast(query), false))) {
-      return static_cast(query);
-    }
-  }
-  return P082_query::P082_NR_OUTPUT_OPTIONS;
-}
-
-const __FlashStringHelper* toString(P082_PowerMode 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");
-  }
-  return F("");
-}
-
-const __FlashStringHelper* toString(P082_DynamicModel model) {
-  switch (model) {
-    case P082_DynamicModel::Portable:    return F("Portable");
-    case P082_DynamicModel::Stationary:  return F("Stationary");
-    case P082_DynamicModel::Pedestrian:  return F("Pedestrian");
-    case P082_DynamicModel::Automotive:  return F("Automotive");
-    case P082_DynamicModel::Sea:         return F("Sea");
-    case P082_DynamicModel::Airborne_1g: return F("Airborne_1g");
-    case P082_DynamicModel::Airborne_2g: return F("Airborne_2g");
-    case P082_DynamicModel::Airborne_4g: return F("Airborne_4g");
-    case P082_DynamicModel::Wrist:       return F("Wrist");
-    case P082_DynamicModel::Bike:        return F("Bike");
-  }
-  return F("");
-}
-
-P082_data_struct::P082_data_struct() : gps(nullptr), easySerial(nullptr) {
-  for (size_t i = 0; i < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++i) {
-    _cache[i] = 0.0f;
-  }
-}
-
-P082_data_struct::~P082_data_struct() {
-  if (gps != nullptr) {
-    delete gps;
-    gps = nullptr;
-  }
-
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-
-/*
-void P082_data_struct::reset() {
-  if (gps != nullptr) {
-    delete gps;
-    gps = nullptr;
-  }
-
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-*/
-
-bool P082_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx) {
-  if (serial_rx < 0) {
-    return false;
-  }
-  if (gps != nullptr) {
-    delete gps;
-    gps = nullptr;
-  }
-
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-
-  # ifdef USE_SECOND_HEAP
-  HeapSelectDram ephemeral;
-  # endif // ifdef USE_SECOND_HEAP
-
-
-  gps        = new (std::nothrow) TinyGPSPlus();
-  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx, false, 512);
-
-  if (easySerial != nullptr) {
-    easySerial->begin(9600);
-    wakeUp();
-  }
-  return isInitialized();
-}
-
-bool P082_data_struct::loop() {
-  if (!isInitialized()) {
-    return false;
-  }
-  bool completeSentence = false;
-
-  if (easySerial != nullptr) {
-    int available           = easySerial->available();
-    unsigned long startLoop = millis();
-
-    while (available > 0 && timePassedSince(startLoop) < 10) {
-      --available;
-      int c = easySerial->read();
-      if (c >= 0) {
-# ifdef P082_SEND_GPS_TO_LOG
-        if (_currentSentence.length() <= 80) {
-          // No need to capture more than 80 bytes as a NMEA message is never that long.
-          if (c != 0) {
-            _currentSentence += static_cast(c);
-          }
-        }
-# endif // ifdef P082_SEND_GPS_TO_LOG
-
-        if (c == 0x85) {
-          // Found possible start of u-blox message
-          unsigned long timeout = millis() + 200;
-          unsigned int bytesRead = 0;
-          bool done = false;
-          bool ack_nak_read = false;
-          while (!timeOutReached(timeout) && !done)
-          {
-            if (available == 0) {
-              available = easySerial->available();
-            } else {
-              const int c = easySerial->read();
-              if (c >= 0) {
-                switch (bytesRead) {
-                  case 0:
-                    if (c != 0x62) {
-                      done = true;
-                    }
-                    ++bytesRead;
-                    break;
-                  case 1:
-                    if (c != 0x05) {
-                      done = true;
-                    }
-                    ++bytesRead;
-                    break;
-                  case 2:
-                    if (c == 0x01) {
-                      ack_nak_read = true;
-                      addLog(LOG_LEVEL_INFO, F("GPS  : ACK-ACK"));
-                    } else if (c == 0x00) {
-                      ack_nak_read = true;
-                      addLog(LOG_LEVEL_ERROR, F("GPS  : ACK-NAK"));
-                    }
-                    done = true;
-                    break;
-                  default:
-                    done = true;
-                    break;
-                }
-              }
-            }
-          }
-          if (!done) {
-            addLog(LOG_LEVEL_ERROR, F("GPS  : Ack/Nack timeout"));
-          } else if (!ack_nak_read) {
-            addLog(LOG_LEVEL_ERROR, F("GPS  : Unexpected reply"));
-          }
-        }
-
-        if (gps->encode(c)) {
-          // Full sentence received
-# ifdef P082_SEND_GPS_TO_LOG
-          _lastSentence    = _currentSentence;
-          _currentSentence = String();
-# endif // ifdef P082_SEND_GPS_TO_LOG
-          completeSentence = true;
-        } else {
-          if (available == 0) {
-            available = easySerial->available();
-          }
-          if (c == '$') {
-            _start_prev_sentence = _start_sentence;
-            _start_sentence = millis();
-            const unsigned long baudrate = easySerial->getBaudRate();
-            if (baudrate != 0) {
-              // Subtract the time (msec) taken to send the nr of bytes present in the serial buffer
-              // Assume 10 bits per byte. (8N1)
-              _start_sentence -= (available * 10000) / baudrate;
-            }
-            const int32_t max_sentence_duration = (160 * 10000) / baudrate;
-            if (timeDiff(_start_prev_sentence, _start_sentence) > max_sentence_duration) {
-              _start_sequence = _start_sentence;
-              // Debug accuracy of computing the time stability
-//              addLog(LOG_LEVEL_INFO, concat(F("GPS  : Start Sequence: "), _start_sequence));
-            }
-          }
-        }
-      }
-    }
-  }
-  return completeSentence;
-}
-
-bool P082_data_struct::hasFix(unsigned int maxAge_msec) {
-  if (!isInitialized()) {
-    return false;
-  }
-  return gps->location.isValid() && gps->location.age() < maxAge_msec;
-}
-
-bool P082_data_struct::storeCurPos(unsigned int maxAge_msec) {
-  if (!hasFix(maxAge_msec)) {
-    return false;
-  }
-
-  _distance += distanceSinceLast(maxAge_msec);
-  _last_lat  = gps->location.lat();
-  _last_lng  = gps->location.lng();
-  return true;
-}
-
-// Return the distance in meters compared to last stored position.
-// @retval  -1 when no fix.
-ESPEASY_RULES_FLOAT_TYPE P082_data_struct::distanceSinceLast(unsigned int maxAge_msec) {
-  if (!hasFix(maxAge_msec)) {
-    return -1.0;
-  }
-
-  if (((_last_lat < 0.0001) && (_last_lat > -0.0001)) || ((_last_lng < 0.0001) && (_last_lng > -0.0001))) {
-    return -1.0;
-  }
-  return gps->distanceBetween(_last_lat, _last_lng, gps->location.lat(), gps->location.lng());
-}
-
-// Return the GPS time stamp, which is in UTC.
-// @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,
-  uint32_t & age,
-  bool     & updated,
-  bool     & pps_sync) {
-  updated = false;
-
-  if (!isInitialized()) {
-    return false;
-  }
-
-  if (!gps->time.isUpdated() || !gps->date.isUpdated()) {
-    return false;
-  }
-
-  if (_pps_time != 0) {
-    age       = timePassedSince(_pps_time);
-    _pps_time = 0;
-    pps_sync  = true;
-
-    if ((age > P082_TIMESTAMP_AGE) || (gps->time.age() > age)) {
-      return false;
-    }
-  } else {
-    age      = gps->time.age();
-    pps_sync = false;
-  }
-
-  if (age > P082_TIMESTAMP_AGE) {
-    return false;
-  }
-
-  if (!gps->time.isUpdated() || !gps->date.isUpdated()) {
-    return false;
-  }
-
-  if (gps->date.age() > P082_TIMESTAMP_AGE) {
-    return false;
-  }
-
-  if (!gps->time.isValid()) {
-    gps->time.value(); // Clear the 'updated' state
-    return false;
-  }
-  if (!gps->date.isValid()) {
-    gps->date.value(); // Clear the 'updated' state
-    return false;
-  }
-  dateTime.tm_year = gps->date.year() - 1900;
-  dateTime.tm_mon  = gps->date.month() - 1; // GPS month starts at 1, tm_mon at 0
-  dateTime.tm_mday = gps->date.day();
-
-  dateTime.tm_hour = gps->time.hour();
-  dateTime.tm_min  = gps->time.minute();
-  dateTime.tm_sec  = gps->time.second();
-
-  const uint32_t reported_time = gps->time.value();
-  const uint32_t reported_date = gps->date.value();
-  updated = reported_time != _last_time;
-
-  _last_time = reported_time;
-  _last_date = reported_date;
-  // FIXME TD-er: Must the offset in centisecond be added when pps_sync active?
-  if (!pps_sync) {
-    // Don't use the "commit" time when the sentence was read, but use the timestamp when the first sentence of a NMEA sequence was received.
-    const long time_since_start_seq = timePassedSince(_start_sequence);
-    if (time_since_start_seq < P082_TIMESTAMP_AGE) {
-      age = time_since_start_seq;
-    }
-    age += (gps->time.centisecond() * 10);
-  }
-
-  return true;
-}
-
-bool P082_data_struct::powerDown() {
-  const uint8_t UBLOX_GPSStandby[] = {0xB5, 0x62, 0x02, 0x41, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4D, 0x3B}; 
-  return writeToGPS(UBLOX_GPSStandby, sizeof(UBLOX_GPSStandby));
-}
-
-bool P082_data_struct::wakeUp() {
-  if (isInitialized()) {
-    if (easySerial->isTxEnabled()) {
-      easySerial->println();   // Send some character to wake it up.
-    }
-  }
-  return false;
-}
-
-#ifdef P082_USE_U_BLOX_SPECIFIC
-bool P082_data_struct::setPowerMode(P082_PowerMode mode) {
-  switch (mode) {
-    case P082_PowerMode::Max_Performance: 
-    {
-      const uint8_t UBLOX_command[] = {0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x00, 0x21, 0x91}; 
-      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
-    }
-    case P082_PowerMode::Power_Save:      
-    {
-      const uint8_t UBLOX_command[] = {0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x01, 0x22, 0x92}; 
-      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
-    }
-    case P082_PowerMode::Eco:             
-    {
-      const uint8_t UBLOX_command[] = {0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x04, 0x25, 0x95}; 
-      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
-    }
-  }
-  return false;
-}
-
-bool P082_data_struct::setDynamicModel(P082_DynamicModel model) {
-
-  const uint8_t dynModel = static_cast(model);
-  if (dynModel == 1 || dynModel > 10) {
-    return false;
-  }
-
-  uint8_t UBLOX_command[] = {
-    0xB5, 0x62, // header
-    0x06, // class
-    0x24, // ID, UBX-CFG-NAV5
-    0x24, 0x00, // length
-    0x01, 0x00, // mask
-    dynModel, // dynModel
-    0x03, // fixMode auto 2D/3D
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00, 
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-  };
-  setUbloxChecksum(UBLOX_command, sizeof(UBLOX_command));
-  return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
-}
-#endif
-
-#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;
-  for (size_t i = 0; i < size; ++i) {
-    CK_A = CK_A + data[i];
-    CK_B = CK_B + CK_A;
-  }
-}
-
-void P082_data_struct::setUbloxChecksum(uint8_t* data, size_t size) {
-  uint8_t CK_A;
-  uint8_t CK_B;
-  computeUbloxChecksum(data + 2, size - 4, CK_A, CK_B);
-  data[size - 2] = CK_A;
-  data[size - 1] = CK_B;
-}
-#endif
-
-bool P082_data_struct::writeToGPS(const uint8_t* data, size_t size) {
-  if (isInitialized()) {
-    if (easySerial->isTxEnabled()) {
-      if (size != easySerial->write(data, size)) {
-        addLog(LOG_LEVEL_ERROR, F("GPS  : Written less bytes than expected"));
-        return false;
-      }
-      return true;
-    }
-  }
-  addLog(LOG_LEVEL_ERROR, F("GPS  : Cannot send to GPS"));
-  return false;
-}
-
-# 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;
-
-  const PluginStats *stats = getPluginStats(var_index);
-
-
-  if (stats != nullptr) {
-    if (stats->webformLoad_show_avg(event)) {
-      somethingAdded = true;
-    }
-
-    bool show_custom = false;
-    ESPEASY_RULES_FLOAT_TYPE dist_p2p{};
-    ESPEASY_RULES_FLOAT_TYPE dist_stddev{};
-
-    if (gps != nullptr) {
-      switch (query_type) {
-        case P082_query::P082_QUERY_LAT:
-          show_custom = true;
-
-          // Compute distance between min and max peak
-          dist_p2p = gps->distanceBetween(
-            stats->getPeakLow(),  _last_lng,
-            stats->getPeakHigh(), _last_lng);
-          dist_stddev = gps->distanceBetween(
-            _last_lat,                            _last_lng,
-            _last_lat + stats->getSampleStdDev(), _last_lng);
-          break;
-        case P082_query::P082_QUERY_LONG:
-          show_custom = true;
-
-          // Compute distance between min and max peak
-          dist_p2p = gps->distanceBetween(
-            _last_lat, stats->getPeakLow(),
-            _last_lat, stats->getPeakHigh());
-
-          // Compute distance for std.dev
-          dist_stddev = gps->distanceBetween(
-            _last_lat, _last_lng,
-            _last_lat, _last_lng + stats->getSampleStdDev());
-          break;
-        default:
-          break;
-      }
-    }
-
-    // Only show standard deviation in meters, which is more useful than std. dev in degrees.
-    if (somethingAdded) {
-      if (show_custom) {
-        stats->webformLoad_show_val(
-          event,
-          F(" std. dev"),
-          dist_stddev,
-          F("m"));
-      } else {
-        stats->webformLoad_show_stdev(event);
-      }
-    }
-
-    if (stats->webformLoad_show_peaks(event, !show_custom)) {
-      somethingAdded = true;
-
-      if (show_custom) {
-        stats->webformLoad_show_val(
-          event,
-          F(" Peak-to-peak coordinates"),
-          stats->getPeakHigh() - stats->getPeakLow(),
-          F("deg"));
-        stats->webformLoad_show_val(
-          event,
-          F(" Peak-to-peak distance"),
-          dist_p2p,
-          F("m"));
-      }
-    }
-
-    if (somethingAdded) {
-      addFormSeparator(4);
-    }
-  }
-  return somethingAdded;
-}
-
-#  if FEATURE_CHART_JS
-void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct *event)
-{
-  taskVarIndex_t stats_long = INVALID_TASKVAR_INDEX;
-  taskVarIndex_t stats_lat  = INVALID_TASKVAR_INDEX;
-
-  for (uint8_t var_index = 0; var_index < P082_NR_OUTPUT_VALUES; ++var_index) {
-    const uint8_t pconfigIndex = var_index + P082_QUERY1_CONFIG_POS;
-    const P082_query query     = static_cast(PCONFIG(pconfigIndex));
-
-    switch (query) {
-      case P082_query::P082_QUERY_LONG:
-        stats_long = var_index;
-        break;
-      case P082_query::P082_QUERY_LAT:
-        stats_lat = var_index;
-        break;
-      default:
-        break;
-    }
-  }
-
-  plot_ChartJS_scatter(
-    stats_long,
-    stats_lat,
-    F("positionscatter"),
-    { F("Position Scatter Plot") },
-    { F("Coordinates"), F("rgb(255, 99, 132)") },
-    500,
-    500);
-}
-
-#  endif // if FEATURE_CHART_JS
-# endif  // if FEATURE_PLUGIN_STATS
-#endif   // ifdef USES_P082
+#include "../PluginStructs/P082_data_struct.h"
+
+#ifdef USES_P082
+
+
+// Needed also here for PlatformIO's library finder as the .h file
+// is in a directory which is excluded in the src_filter
+# include 
+# include 
+
+
+const __FlashStringHelper* Plugin_082_valuename(P082_query value_nr, bool displayString) {
+  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");
+    case P082_query::P082_QUERY_SPD:         return displayString ? F("Speed (m/s)")        : F("spd");
+    case P082_query::P082_QUERY_SATVIS:      return displayString ? F("Satellites Visible") : F("sat_vis");
+    case P082_query::P082_QUERY_SATUSE:      return displayString ? F("Satellites Tracked") : F("sat_tr");
+    case P082_query::P082_QUERY_HDOP:        return displayString ? F("HDOP")               : F("hdop");
+    case P082_query::P082_QUERY_FIXQ:        return displayString ? F("Fix Quality")        : F("fix_qual");
+    case P082_query::P082_QUERY_DB_MAX:      return displayString ? F("Max SNR in dBHz")    : F("snr_max");
+    case P082_query::P082_QUERY_CHKSUM_FAIL: return displayString ? F("Checksum Fail")      : F("chksum_fail");
+    case P082_query::P082_QUERY_DISTANCE:    return displayString ? F("Distance (ODO)")     : F("dist");
+    case P082_query::P082_QUERY_DIST_REF:    return displayString ? F("Distance from Reference Point") : F("dist_ref");
+    case P082_query::P082_NR_OUTPUT_OPTIONS: break;
+  }
+  return F("");
+}
+
+P082_query Plugin_082_from_valuename(const String& valuename)
+{
+  for (uint8_t query = 0; query < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++query) {
+    if (valuename.equalsIgnoreCase(Plugin_082_valuename(static_cast(query), false))) {
+      return static_cast(query);
+    }
+  }
+  return P082_query::P082_NR_OUTPUT_OPTIONS;
+}
+
+const __FlashStringHelper* toString(P082_PowerMode 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");
+  }
+  return F("");
+}
+
+const __FlashStringHelper* toString(P082_DynamicModel model) {
+  switch (model) {
+    case P082_DynamicModel::Portable:    return F("Portable");
+    case P082_DynamicModel::Stationary:  return F("Stationary");
+    case P082_DynamicModel::Pedestrian:  return F("Pedestrian");
+    case P082_DynamicModel::Automotive:  return F("Automotive");
+    case P082_DynamicModel::Sea:         return F("Sea");
+    case P082_DynamicModel::Airborne_1g: return F("Airborne_1g");
+    case P082_DynamicModel::Airborne_2g: return F("Airborne_2g");
+    case P082_DynamicModel::Airborne_4g: return F("Airborne_4g");
+    case P082_DynamicModel::Wrist:       return F("Wrist");
+    case P082_DynamicModel::Bike:        return F("Bike");
+  }
+  return F("");
+}
+
+P082_data_struct::P082_data_struct() : gps(nullptr), easySerial(nullptr) {
+  for (size_t i = 0; i < static_cast(P082_query::P082_NR_OUTPUT_OPTIONS); ++i) {
+    _cache[i] = 0.0f;
+  }
+}
+
+P082_data_struct::~P082_data_struct() {
+  if (gps != nullptr) {
+    delete gps;
+    gps = nullptr;
+  }
+
+  if (easySerial != nullptr) {
+    delete easySerial;
+    easySerial = nullptr;
+  }
+}
+
+/*
+   void P082_data_struct::reset() {
+   if (gps != nullptr) {
+    delete gps;
+    gps = nullptr;
+   }
+
+   if (easySerial != nullptr) {
+    delete easySerial;
+    easySerial = nullptr;
+   }
+   }
+ */
+bool P082_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx) {
+  if (serial_rx < 0) {
+    return false;
+  }
+
+  if (gps != nullptr) {
+    delete gps;
+    gps = nullptr;
+  }
+
+  if (easySerial != nullptr) {
+    delete easySerial;
+    easySerial = nullptr;
+  }
+
+  # ifdef USE_SECOND_HEAP
+  HeapSelectDram ephemeral;
+  # endif // ifdef USE_SECOND_HEAP
+
+
+  gps        = new (std::nothrow) TinyGPSPlus();
+  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx, false, 512);
+
+  if (easySerial != nullptr) {
+    easySerial->begin(9600);
+    wakeUp();
+  }
+  return isInitialized();
+}
+
+bool P082_data_struct::loop() {
+  if (!isInitialized()) {
+    return false;
+  }
+  bool completeSentence = false;
+
+  if (easySerial != nullptr) {
+    int available           = easySerial->available();
+    unsigned long startLoop = millis();
+
+    while (available > 0 && timePassedSince(startLoop) < 10) {
+      --available;
+      int c = easySerial->read();
+
+      if (c >= 0) {
+# ifdef P082_SEND_GPS_TO_LOG
+
+        if (_currentSentence.length() <= 80) {
+          // No need to capture more than 80 bytes as a NMEA message is never that long.
+          if (c != 0) {
+            _currentSentence += static_cast(c);
+          }
+        }
+# endif // ifdef P082_SEND_GPS_TO_LOG
+
+        if (c == 0x85) {
+          // Found possible start of u-blox message
+          unsigned long timeout   = millis() + 200;
+          unsigned int  bytesRead = 0;
+          bool done               = false;
+          bool ack_nak_read       = false;
+
+          while (!timeOutReached(timeout) && !done)
+          {
+            if (available == 0) {
+              available = easySerial->available();
+            } else {
+              const int c = easySerial->read();
+
+              if (c >= 0) {
+                switch (bytesRead) {
+                  case 0:
+
+                    if (c != 0x62) {
+                      done = true;
+                    }
+                    ++bytesRead;
+                    break;
+                  case 1:
+
+                    if (c != 0x05) {
+                      done = true;
+                    }
+                    ++bytesRead;
+                    break;
+                  case 2:
+
+                    if (c == 0x01) {
+                      ack_nak_read = true;
+                      addLog(LOG_LEVEL_INFO, F("GPS  : ACK-ACK"));
+                    } else if (c == 0x00) {
+                      ack_nak_read = true;
+                      addLog(LOG_LEVEL_ERROR, F("GPS  : ACK-NAK"));
+                    }
+                    done = true;
+                    break;
+                  default:
+                    done = true;
+                    break;
+                }
+              }
+            }
+          }
+
+          if (!done) {
+            addLog(LOG_LEVEL_ERROR, F("GPS  : Ack/Nack timeout"));
+          } else if (!ack_nak_read) {
+            addLog(LOG_LEVEL_ERROR, F("GPS  : Unexpected reply"));
+          }
+        }
+
+        if (gps->encode(c)) {
+          // Full sentence received
+# ifdef P082_SEND_GPS_TO_LOG
+          _lastSentence    = _currentSentence;
+          _currentSentence = String();
+# endif // ifdef P082_SEND_GPS_TO_LOG
+          completeSentence = true;
+        } else {
+          if (available == 0) {
+            available = easySerial->available();
+          }
+
+          if (c == '$') {
+            _start_prev_sentence = _start_sentence;
+            _start_sentence      = millis();
+            const unsigned long baudrate = easySerial->getBaudRate();
+
+            if (baudrate != 0) {
+              // Subtract the time (msec) taken to send the nr of bytes present in the serial buffer
+              // Assume 10 bits per byte. (8N1)
+              _start_sentence -= (available * 10000) / baudrate;
+            }
+            const int32_t max_sentence_duration = (160 * 10000) / baudrate;
+
+            if (timeDiff(_start_prev_sentence, _start_sentence) > max_sentence_duration) {
+              _start_sequence = _start_sentence;
+
+              // Debug accuracy of computing the time stability
+              //              addLog(LOG_LEVEL_INFO, concat(F("GPS  : Start Sequence: "), _start_sequence));
+            }
+          }
+        }
+      }
+    }
+  }
+  return completeSentence;
+}
+
+bool P082_data_struct::hasFix(unsigned int maxAge_msec) {
+  if (!isInitialized()) {
+    return false;
+  }
+  return gps->location.isValid() && gps->location.age() < maxAge_msec;
+}
+
+bool P082_data_struct::storeCurPos(unsigned int maxAge_msec) {
+  if (!hasFix(maxAge_msec)) {
+    return false;
+  }
+
+  _distance += distanceSinceLast(maxAge_msec);
+  _last_lat  = gps->location.lat();
+  _last_lng  = gps->location.lng();
+  return true;
+}
+
+// Return the distance in meters compared to last stored position.
+// @retval  -1 when no fix.
+ESPEASY_RULES_FLOAT_TYPE P082_data_struct::distanceSinceLast(unsigned int maxAge_msec) {
+  if (!hasFix(maxAge_msec)) {
+    return -1.0;
+  }
+
+  if (((_last_lat < 0.0001) && (_last_lat > -0.0001)) || ((_last_lng < 0.0001) && (_last_lng > -0.0001))) {
+    return -1.0;
+  }
+  return gps->distanceBetween(_last_lat, _last_lng, gps->location.lat(), gps->location.lng());
+}
+
+// Return the GPS time stamp, which is in UTC.
+// @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,
+  uint32_t & age,
+  bool     & updated,
+  bool     & pps_sync) {
+  updated = false;
+
+  if (!isInitialized()) {
+    return false;
+  }
+
+  if (!gps->time.isUpdated() || !gps->date.isUpdated()) {
+    return false;
+  }
+
+  if (_pps_time != 0) {
+    age       = timePassedSince(_pps_time);
+    _pps_time = 0;
+    pps_sync  = true;
+
+    if ((age > P082_TIMESTAMP_AGE) || (gps->time.age() > age)) {
+      return false;
+    }
+  } else {
+    age      = gps->time.age();
+    pps_sync = false;
+  }
+
+  if (age > P082_TIMESTAMP_AGE) {
+    return false;
+  }
+
+  if (!gps->time.isUpdated() || !gps->date.isUpdated()) {
+    return false;
+  }
+
+  if (gps->date.age() > P082_TIMESTAMP_AGE) {
+    return false;
+  }
+
+  if (!gps->time.isValid()) {
+    gps->time.value(); // Clear the 'updated' state
+    return false;
+  }
+
+  if (!gps->date.isValid()) {
+    gps->date.value(); // Clear the 'updated' state
+    return false;
+  }
+  dateTime.tm_year = gps->date.year() - 1900;
+  dateTime.tm_mon  = gps->date.month() - 1; // GPS month starts at 1, tm_mon at 0
+  dateTime.tm_mday = gps->date.day();
+
+  dateTime.tm_hour = gps->time.hour();
+  dateTime.tm_min  = gps->time.minute();
+  dateTime.tm_sec  = gps->time.second();
+
+  const uint32_t reported_time = gps->time.value();
+  const uint32_t reported_date = gps->date.value();
+
+  updated = reported_time != _last_time;
+
+  _last_time = reported_time;
+  _last_date = reported_date;
+
+  // FIXME TD-er: Must the offset in centisecond be added when pps_sync active?
+  if (!pps_sync) {
+    // Don't use the "commit" time when the sentence was read, but use the timestamp when the first sentence of a NMEA sequence was
+    // received.
+    const long time_since_start_seq = timePassedSince(_start_sequence);
+
+    if (time_since_start_seq > P082_TIMESTAMP_AGE) {
+      return false;
+    }
+    age = time_since_start_seq;
+
+    // FIXME TD-er: Are centiseconds expressed as 0.01 sec, or is it some fraction?
+    const uint8_t centiseconds = gps->time.centisecond();
+
+    if (centiseconds < 100) {
+      age += (gps->time.centisecond() * 10);
+    }
+  }
+
+  return true;
+}
+
+bool P082_data_struct::getDateTime(struct tm& dateTime) const
+{
+  uint64_t value_usec{};
+
+  if (_oversampling_gps_time_offset_usec.peek(value_usec)) {
+    const double time = (getMicros64() + value_usec) / 1000000.0;
+    breakTime(static_cast(time), dateTime);
+    return true;
+  }
+  return false;
+}
+
+bool P082_data_struct::tryUpdateSystemTime() {
+  struct tm dateTime;
+  uint32_t  age{};
+  bool updated{};
+  bool pps_sync{};
+
+  if (getDateTime(dateTime, age, updated, pps_sync)) {
+    if (updated) {
+      // Use floating point precision to use the time since last update from GPS
+      // and the given offset in centisecond.
+      const uint32_t unixTime_sec       = makeTime(dateTime) + (age / 1000);
+      const uint64_t uptime_offset_usec =
+        sec_time_frac_to_uptime_offset_usec(
+          unixTime_sec,
+          millis_to_unix_time_frac(age % 1000));
+      _oversampling_gps_time_offset_usec.add(uptime_offset_usec);
+
+      // Compute average over offset between system micros and GPS reported timestamp.
+      // Both extremes will be filtered out
+      if (_oversampling_gps_time_offset_usec.getCount() == 5) {
+        uint64_t value_usec{};
+
+        if (_oversampling_gps_time_offset_usec.get(value_usec)) {
+          const double time = (getMicros64() + value_usec) / 1000000.0;
+
+          if (node_time.setExternalTimeSource(time, timeSource_t::GPS_time_source)) {
+            return true;
+          } else {
+            _oversampling_gps_time_offset_usec.add(value_usec);
+          }
+        }
+      }
+    }
+  }
+  return false;
+}
+
+bool P082_data_struct::powerDown() {
+  const uint8_t UBLOX_GPSStandby[] = { 0xB5, 0x62, 0x02, 0x41, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x4D, 0x3B };
+
+  return writeToGPS(UBLOX_GPSStandby, sizeof(UBLOX_GPSStandby));
+}
+
+bool P082_data_struct::wakeUp() {
+  if (isInitialized()) {
+    if (easySerial->isTxEnabled()) {
+      easySerial->println(); // Send some character to wake it up.
+    }
+  }
+  return false;
+}
+
+# ifdef P082_USE_U_BLOX_SPECIFIC
+bool P082_data_struct::setPowerMode(P082_PowerMode mode) {
+  switch (mode) {
+    case P082_PowerMode::Max_Performance:
+    {
+      const uint8_t UBLOX_command[] = { 0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x00, 0x21, 0x91 };
+      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
+    }
+    case P082_PowerMode::Power_Save:
+    {
+      const uint8_t UBLOX_command[] = { 0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x01, 0x22, 0x92 };
+      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
+    }
+    case P082_PowerMode::Eco:
+    {
+      const uint8_t UBLOX_command[] = { 0xB5, 0x62, 0x06, 0x11, 0x02, 0x00, 0x08, 0x04, 0x25, 0x95 };
+      return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
+    }
+  }
+  return false;
+}
+
+bool P082_data_struct::setDynamicModel(P082_DynamicModel model) {
+  const uint8_t dynModel = static_cast(model);
+
+  if ((dynModel == 1) || (dynModel > 10)) {
+    return false;
+  }
+
+  uint8_t UBLOX_command[] = {
+    0xB5, 0x62, // header
+    0x06,       // class
+    0x24,       // ID, UBX-CFG-NAV5
+    0x24, 0x00, // length
+    0x01, 0x00, // mask
+    dynModel,   // dynModel
+    0x03,       // fixMode auto 2D/3D
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,0x00, 0x00
+  };
+
+  setUbloxChecksum(UBLOX_command, sizeof(UBLOX_command));
+  return writeToGPS(UBLOX_command, sizeof(UBLOX_command));
+}
+
+# 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;
+
+  for (size_t i = 0; i < size; ++i) {
+    CK_A = CK_A + data[i];
+    CK_B = CK_B + CK_A;
+  }
+}
+
+void P082_data_struct::setUbloxChecksum(uint8_t *data, size_t size) {
+  uint8_t CK_A;
+  uint8_t CK_B;
+
+  computeUbloxChecksum(data + 2, size - 4, CK_A, CK_B);
+  data[size - 2] = CK_A;
+  data[size - 1] = CK_B;
+}
+
+# endif // ifdef P082_USE_U_BLOX_SPECIFIC
+
+bool P082_data_struct::writeToGPS(const uint8_t *data, size_t size) {
+  if (isInitialized()) {
+    if (easySerial->isTxEnabled()) {
+      if (size != easySerial->write(data, size)) {
+        addLog(LOG_LEVEL_ERROR, F("GPS  : Written less bytes than expected"));
+        return false;
+      }
+      return true;
+    }
+  }
+  addLog(LOG_LEVEL_ERROR, F("GPS  : Cannot send to GPS"));
+  return false;
+}
+
+# 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;
+
+  const PluginStats *stats = getPluginStats(var_index);
+
+
+  if (stats != nullptr) {
+    if (stats->webformLoad_show_avg(event)) {
+      somethingAdded = true;
+    }
+
+    bool show_custom = false;
+    ESPEASY_RULES_FLOAT_TYPE dist_p2p{};
+    ESPEASY_RULES_FLOAT_TYPE dist_stddev{};
+
+    if (gps != nullptr) {
+      switch (query_type) {
+        case P082_query::P082_QUERY_LAT:
+          show_custom = true;
+
+          // Compute distance between min and max peak
+          dist_p2p = gps->distanceBetween(
+            stats->getPeakLow(),  _last_lng,
+            stats->getPeakHigh(), _last_lng);
+          dist_stddev = gps->distanceBetween(
+            _last_lat,                            _last_lng,
+            _last_lat + stats->getSampleStdDev(), _last_lng);
+          break;
+        case P082_query::P082_QUERY_LONG:
+          show_custom = true;
+
+          // Compute distance between min and max peak
+          dist_p2p = gps->distanceBetween(
+            _last_lat, stats->getPeakLow(),
+            _last_lat, stats->getPeakHigh());
+
+          // Compute distance for std.dev
+          dist_stddev = gps->distanceBetween(
+            _last_lat, _last_lng,
+            _last_lat, _last_lng + stats->getSampleStdDev());
+          break;
+        default:
+          break;
+      }
+    }
+
+    // Only show standard deviation in meters, which is more useful than std. dev in degrees.
+    if (somethingAdded) {
+      if (show_custom) {
+        stats->webformLoad_show_val(
+          event,
+          F(" std. dev"),
+          dist_stddev,
+          F("m"));
+      } else {
+        stats->webformLoad_show_stdev(event);
+      }
+    }
+
+    if (stats->webformLoad_show_peaks(event, !show_custom)) {
+      somethingAdded = true;
+
+      if (show_custom) {
+        stats->webformLoad_show_val(
+          event,
+          F(" Peak-to-peak coordinates"),
+          stats->getPeakHigh() - stats->getPeakLow(),
+          F("deg"));
+        stats->webformLoad_show_val(
+          event,
+          F(" Peak-to-peak distance"),
+          dist_p2p,
+          F("m"));
+      }
+    }
+
+    if (somethingAdded) {
+      addFormSeparator(4);
+    }
+  }
+  return somethingAdded;
+}
+
+#  if FEATURE_CHART_JS
+void P082_data_struct::webformLoad_show_position_scatterplot(struct EventStruct *event)
+{
+  taskVarIndex_t stats_long = INVALID_TASKVAR_INDEX;
+  taskVarIndex_t stats_lat  = INVALID_TASKVAR_INDEX;
+
+  for (uint8_t var_index = 0; var_index < P082_NR_OUTPUT_VALUES; ++var_index) {
+    const uint8_t pconfigIndex = var_index + P082_QUERY1_CONFIG_POS;
+    const P082_query query     = static_cast(PCONFIG(pconfigIndex));
+
+    switch (query) {
+      case P082_query::P082_QUERY_LONG:
+        stats_long = var_index;
+        break;
+      case P082_query::P082_QUERY_LAT:
+        stats_lat = var_index;
+        break;
+      default:
+        break;
+    }
+  }
+
+  plot_ChartJS_scatter(
+    stats_long,
+    stats_lat,
+    F("positionscatter"),
+    { F("Position Scatter Plot") },
+    { F("Coordinates"), F("rgb(255, 99, 132)") },
+    500,
+    500);
+}
+
+#  endif // if FEATURE_CHART_JS
+# endif  // if FEATURE_PLUGIN_STATS
+#endif   // ifdef USES_P082
diff --git a/src/src/PluginStructs/P082_data_struct.h b/src/src/PluginStructs/P082_data_struct.h
index 87effe401..0c2a7d7fe 100644
--- a/src/src/PluginStructs/P082_data_struct.h
+++ b/src/src/PluginStructs/P082_data_struct.h
@@ -1,194 +1,203 @@
-#ifndef PLUGINSTRUCTS_P082_DATA_STRUCT_H
-#define PLUGINSTRUCTS_P082_DATA_STRUCT_H
-
-#include "../../_Plugin_Helper.h"
-#ifdef USES_P082
-
-# include 
-# include 
-
-# ifndef BUILD_NO_DEBUG
-# define P082_SEND_GPS_TO_LOG
-//# define P082_USE_U_BLOX_SPECIFIC // TD-er: Disabled for now, as it is not working reliable/predictable
-#endif
-
-# define P082_TIMESTAMP_AGE       1000
-# define P082_DEFAULT_FIX_TIMEOUT 2500 // TTL of fix status in ms since last update
-
-
-# define P082_TIMEOUT        PCONFIG(0)
-# define P082_TIMEOUT_LABEL  PCONFIG_LABEL(0)
-# define P082_BAUDRATE       PCONFIG(1)
-# define P082_BAUDRATE_LABEL PCONFIG_LABEL(1)
-# define P082_DISTANCE       PCONFIG(2)
-# define P082_DISTANCE_LABEL PCONFIG_LABEL(2)
-
-# define P082_QUERY1_CONFIG_POS  3
-# define P082_QUERY1         PCONFIG(3) // P082_QUERY1_CONFIG_POS
-# define P082_QUERY2         PCONFIG(4) // P082_QUERY1_CONFIG_POS + 1
-# define P082_QUERY3         PCONFIG(5) // P082_QUERY1_CONFIG_POS + 2
-# define P082_QUERY4         PCONFIG(6) // P082_QUERY1_CONFIG_POS + 3
-
-# define P082_LONG_REF       PCONFIG_FLOAT(0)
-# define P082_LAT_REF        PCONFIG_FLOAT(1)
-# ifdef P082_USE_U_BLOX_SPECIFIC
-#  define P082_POWER_MODE     PCONFIG(7)
-#  define P082_DYNAMIC_MODEL  PCONFIG_LONG(0)
-# endif // P082_USE_U_BLOX_SPECIFIC
-
-# define P082_NR_OUTPUT_VALUES   VARS_PER_TASK
-
-
-# define P082_DISTANCE_DFLT       0 // Disable update per distance travelled.
-# define P082_QUERY1_DFLT         P082_query::P082_QUERY_LONG
-# define P082_QUERY2_DFLT         P082_query::P082_QUERY_LAT
-# define P082_QUERY3_DFLT         P082_query::P082_QUERY_ALT
-# define P082_QUERY4_DFLT         P082_query::P082_QUERY_SPD
-
-
-enum class P082_query : uint8_t {
-  P082_QUERY_LONG        = 0,
-  P082_QUERY_LAT         = 1,
-  P082_QUERY_ALT         = 2,
-  P082_QUERY_SPD         = 3,
-  P082_QUERY_SATVIS      = 4,
-  P082_QUERY_SATUSE      = 5,
-  P082_QUERY_HDOP        = 6,
-  P082_QUERY_FIXQ        = 7,
-  P082_QUERY_DB_MAX      = 8,
-  P082_QUERY_CHKSUM_FAIL = 9,
-  P082_QUERY_DISTANCE    = 10,
-  P082_QUERY_DIST_REF    = 11,
-  P082_NR_OUTPUT_OPTIONS
-};
-
-const __FlashStringHelper * Plugin_082_valuename(P082_query value_nr, bool displayString);
-
-P082_query Plugin_082_from_valuename(const String& valuename);
-
-
-enum class P082_PowerMode : uint8_t {
-  Max_Performance = 0,
-  Power_Save = 1,
-  Eco = 2
-};
-
-const __FlashStringHelper* toString(P082_PowerMode mode);
-
-
-enum class P082_DynamicModel : uint8_t {
-  Portable    = 0,
-  Stationary  = 2,
-  Pedestrian  = 3,
-  Automotive  = 4,
-  Sea         = 5,
-  Airborne_1g = 6, // airborne with <1g acceleration
-  Airborne_2g = 7, // airborne with <2g acceleration
-  Airborne_4g = 8, // airborne with <4g acceleration
-  Wrist       = 9, // Only recommended for wrist-worn applications. Receiver will filter out armmotion (just available for protocol version > 17).
-  Bike        = 10  // Used for applications with equivalent dynamics to those of a motor bike. Lowvertical acceleration assumed. (supported in protocol versions 19.2)
-};
-
-const __FlashStringHelper* toString(P082_DynamicModel model);
-
-struct P082_data_struct : public PluginTaskData_base {
-
-  // Enum is being stored, so don't change int values
-  
-
-  P082_data_struct();
-
-  virtual ~P082_data_struct();
-
-//  void reset();
-
-  bool init(ESPEasySerialPort port,
-            const int16_t     serial_rx,
-            const int16_t     serial_tx);
-
-  bool isInitialized() const {
-    return gps != nullptr && easySerial != nullptr;
-  }
-
-  bool loop();
-
-  bool hasFix(unsigned int maxAge_msec);
-
-  bool storeCurPos(unsigned int maxAge_msec);
-
-  // Return the distance in meters compared to last stored position.
-  // @retval  -1 when no fix.
-  ESPEASY_RULES_FLOAT_TYPE distanceSinceLast(unsigned int maxAge_msec);
-
-  // Return the GPS time stamp, which is in UTC.
-  // @param age is the time in msec since the last update of the time +
-  // additional centiseconds given by the GPS.
-  bool getDateTime(struct tm& dateTime,
-                   uint32_t & age,
-                   bool     & updated,
-                   bool     & pps_sync);
-
-  // Send command to GPS to put it in PMREQ backup mode (UBLOX only)
-  // @retval true when successful in sending command
-  bool powerDown();
-
-  // Send some characters to GPS to wake up
-  bool wakeUp();
-#ifdef P082_USE_U_BLOX_SPECIFIC
-  bool setPowerMode(P082_PowerMode mode);
-
-  bool setDynamicModel(P082_DynamicModel model);
-#endif
-
-# if FEATURE_PLUGIN_STATS
-  bool webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type) const;
-
-#if FEATURE_CHART_JS
-  void webformLoad_show_position_scatterplot(struct EventStruct *event);
-#endif
-# endif // if FEATURE_PLUGIN_STATS
-
-private:
-#ifdef P082_USE_U_BLOX_SPECIFIC
-  // Compute checksum
-  // Caller should offset the data pointer to the correct start where the CRC should start.
-  // @param size  The length over which the CRC should be computed
-  // @param CK_A, CK_B The 2 checksum bytes.
-  static void computeUbloxChecksum(const uint8_t* data, size_t size, uint8_t & CK_A, uint8_t & CK_B);
-
-  // Set checksum.
-  // First 2 bytes of the array are skipped
-  static void setUbloxChecksum(uint8_t* data, size_t size);
-#endif
-
-  bool writeToGPS(const uint8_t* data, size_t size);
-public:
-
-  TinyGPSPlus   *gps        = nullptr;
-  ESPeasySerial *easySerial = nullptr;
-
-  ESPEASY_RULES_FLOAT_TYPE _last_lat{};
-  ESPEASY_RULES_FLOAT_TYPE _last_lng{};
-  ESPEASY_RULES_FLOAT_TYPE _ref_lat{};
-  ESPEASY_RULES_FLOAT_TYPE _ref_lng{};
-  ESPEASY_RULES_FLOAT_TYPE _distance{};
-
-
-  unsigned long _pps_time            = 0;
-  unsigned long _last_measurement    = 0;
-  uint32_t      _last_time           = 0;
-  uint32_t      _last_date           = 0;
-  uint32_t      _last_setSystemTime  = 0;
-  uint32_t      _start_sentence      = 0;
-  uint32_t      _start_prev_sentence = 0;
-  uint32_t      _start_sequence      = 0;
-# ifdef P082_SEND_GPS_TO_LOG
-  String _lastSentence;
-  String _currentSentence;
-# endif // ifdef P082_SEND_GPS_TO_LOG
-
-  float _cache[static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)]{};
-};
-
-#endif // ifdef USES_P082
-#endif // ifndef PLUGINSTRUCTS_P082_DATA_STRUCT_H
+#ifndef PLUGINSTRUCTS_P082_DATA_STRUCT_H
+#define PLUGINSTRUCTS_P082_DATA_STRUCT_H
+
+#include "../../_Plugin_Helper.h"
+#ifdef USES_P082
+
+# include 
+# include 
+# include "../Helpers/OversamplingHelper.h"
+
+# ifndef BUILD_NO_DEBUG
+# define P082_SEND_GPS_TO_LOG
+//# define P082_USE_U_BLOX_SPECIFIC // TD-er: Disabled for now, as it is not working reliable/predictable
+#endif
+
+# define P082_TIMESTAMP_AGE       1000
+# define P082_DEFAULT_FIX_TIMEOUT 2500 // TTL of fix status in ms since last update
+
+
+# define P082_TIMEOUT        PCONFIG(0)
+# define P082_TIMEOUT_LABEL  PCONFIG_LABEL(0)
+# define P082_BAUDRATE       PCONFIG(1)
+# define P082_BAUDRATE_LABEL PCONFIG_LABEL(1)
+# define P082_DISTANCE       PCONFIG(2)
+# define P082_DISTANCE_LABEL PCONFIG_LABEL(2)
+
+# define P082_QUERY1_CONFIG_POS  3
+# define P082_QUERY1         PCONFIG(3) // P082_QUERY1_CONFIG_POS
+# define P082_QUERY2         PCONFIG(4) // P082_QUERY1_CONFIG_POS + 1
+# define P082_QUERY3         PCONFIG(5) // P082_QUERY1_CONFIG_POS + 2
+# define P082_QUERY4         PCONFIG(6) // P082_QUERY1_CONFIG_POS + 3
+
+# define P082_LONG_REF       PCONFIG_FLOAT(0)
+# define P082_LAT_REF        PCONFIG_FLOAT(1)
+# ifdef P082_USE_U_BLOX_SPECIFIC
+#  define P082_POWER_MODE     PCONFIG(7)
+#  define P082_DYNAMIC_MODEL  PCONFIG_LONG(0)
+# endif // P082_USE_U_BLOX_SPECIFIC
+
+# define P082_NR_OUTPUT_VALUES   VARS_PER_TASK
+
+
+# define P082_DISTANCE_DFLT       0 // Disable update per distance travelled.
+# define P082_QUERY1_DFLT         P082_query::P082_QUERY_LONG
+# define P082_QUERY2_DFLT         P082_query::P082_QUERY_LAT
+# define P082_QUERY3_DFLT         P082_query::P082_QUERY_ALT
+# define P082_QUERY4_DFLT         P082_query::P082_QUERY_SPD
+
+
+enum class P082_query : uint8_t {
+  P082_QUERY_LONG        = 0,
+  P082_QUERY_LAT         = 1,
+  P082_QUERY_ALT         = 2,
+  P082_QUERY_SPD         = 3,
+  P082_QUERY_SATVIS      = 4,
+  P082_QUERY_SATUSE      = 5,
+  P082_QUERY_HDOP        = 6,
+  P082_QUERY_FIXQ        = 7,
+  P082_QUERY_DB_MAX      = 8,
+  P082_QUERY_CHKSUM_FAIL = 9,
+  P082_QUERY_DISTANCE    = 10,
+  P082_QUERY_DIST_REF    = 11,
+  P082_NR_OUTPUT_OPTIONS
+};
+
+const __FlashStringHelper * Plugin_082_valuename(P082_query value_nr, bool displayString);
+
+P082_query Plugin_082_from_valuename(const String& valuename);
+
+
+enum class P082_PowerMode : uint8_t {
+  Max_Performance = 0,
+  Power_Save = 1,
+  Eco = 2
+};
+
+const __FlashStringHelper* toString(P082_PowerMode mode);
+
+
+enum class P082_DynamicModel : uint8_t {
+  Portable    = 0,
+  Stationary  = 2,
+  Pedestrian  = 3,
+  Automotive  = 4,
+  Sea         = 5,
+  Airborne_1g = 6, // airborne with <1g acceleration
+  Airborne_2g = 7, // airborne with <2g acceleration
+  Airborne_4g = 8, // airborne with <4g acceleration
+  Wrist       = 9, // Only recommended for wrist-worn applications. Receiver will filter out armmotion (just available for protocol version > 17).
+  Bike        = 10  // Used for applications with equivalent dynamics to those of a motor bike. Lowvertical acceleration assumed. (supported in protocol versions 19.2)
+};
+
+const __FlashStringHelper* toString(P082_DynamicModel model);
+
+struct P082_data_struct : public PluginTaskData_base {
+
+  // Enum is being stored, so don't change int values
+  
+
+  P082_data_struct();
+
+  virtual ~P082_data_struct();
+
+//  void reset();
+
+  bool init(ESPEasySerialPort port,
+            const int16_t     serial_rx,
+            const int16_t     serial_tx);
+
+  bool isInitialized() const {
+    return gps != nullptr && easySerial != nullptr;
+  }
+
+  bool loop();
+
+  bool hasFix(unsigned int maxAge_msec);
+
+  bool storeCurPos(unsigned int maxAge_msec);
+
+  // Return the distance in meters compared to last stored position.
+  // @retval  -1 when no fix.
+  ESPEASY_RULES_FLOAT_TYPE distanceSinceLast(unsigned int maxAge_msec);
+
+private:
+  // Return the GPS time stamp, which is in UTC.
+  // @param age is the time in msec since the last update of the time +
+  // additional centiseconds given by the GPS.
+  bool getDateTime(struct tm& dateTime,
+                   uint32_t & age,
+                   bool     & updated,
+                   bool     & pps_sync);
+public:
+
+  bool getDateTime(struct tm& dateTime) const;
+
+  // Try to fetch 5 timestamps in a row, filter out the peaks and use the average to set the 
+  bool tryUpdateSystemTime();
+
+  // Send command to GPS to put it in PMREQ backup mode (UBLOX only)
+  // @retval true when successful in sending command
+  bool powerDown();
+
+  // Send some characters to GPS to wake up
+  bool wakeUp();
+#ifdef P082_USE_U_BLOX_SPECIFIC
+  bool setPowerMode(P082_PowerMode mode);
+
+  bool setDynamicModel(P082_DynamicModel model);
+#endif
+
+# if FEATURE_PLUGIN_STATS
+  bool webformLoad_show_stats(struct EventStruct *event, uint8_t var_index, P082_query query_type) const;
+
+#if FEATURE_CHART_JS
+  void webformLoad_show_position_scatterplot(struct EventStruct *event);
+#endif
+# endif // if FEATURE_PLUGIN_STATS
+
+private:
+#ifdef P082_USE_U_BLOX_SPECIFIC
+  // Compute checksum
+  // Caller should offset the data pointer to the correct start where the CRC should start.
+  // @param size  The length over which the CRC should be computed
+  // @param CK_A, CK_B The 2 checksum bytes.
+  static void computeUbloxChecksum(const uint8_t* data, size_t size, uint8_t & CK_A, uint8_t & CK_B);
+
+  // Set checksum.
+  // First 2 bytes of the array are skipped
+  static void setUbloxChecksum(uint8_t* data, size_t size);
+#endif
+
+  bool writeToGPS(const uint8_t* data, size_t size);
+public:
+
+  TinyGPSPlus   *gps        = nullptr;
+  ESPeasySerial *easySerial = nullptr;
+
+  ESPEASY_RULES_FLOAT_TYPE _last_lat{};
+  ESPEASY_RULES_FLOAT_TYPE _last_lng{};
+  ESPEASY_RULES_FLOAT_TYPE _ref_lat{};
+  ESPEASY_RULES_FLOAT_TYPE _ref_lng{};
+  ESPEASY_RULES_FLOAT_TYPE _distance{};
+
+
+  unsigned long _pps_time            = 0;
+  unsigned long _last_measurement    = 0;
+  uint32_t      _last_time           = 0;
+  uint32_t      _last_date           = 0;
+  uint32_t      _start_sentence      = 0;
+  uint32_t      _start_prev_sentence = 0;
+  uint32_t      _start_sequence      = 0;
+# ifdef P082_SEND_GPS_TO_LOG
+  String _lastSentence;
+  String _currentSentence;
+# endif // ifdef P082_SEND_GPS_TO_LOG
+
+  float _cache[static_cast(P082_query::P082_NR_OUTPUT_OPTIONS)]{};
+
+  OversamplingHelper _oversampling_gps_time_offset_usec;
+};
+
+#endif // ifdef USES_P082
+#endif // ifndef PLUGINSTRUCTS_P082_DATA_STRUCT_H
diff --git a/src/src/PluginStructs/P087_data_struct.cpp b/src/src/PluginStructs/P087_data_struct.cpp
index fad12222c..5adc8c637 100644
--- a/src/src/PluginStructs/P087_data_struct.cpp
+++ b/src/src/PluginStructs/P087_data_struct.cpp
@@ -1,394 +1,441 @@
-#include "../PluginStructs/P087_data_struct.h"
-
-#ifdef USES_P087
-
-
-// Needed also here for PlatformIO's library finder as the .h file
-// is in a directory which is excluded in the src_filter
-# include 
-# include 
-
-
-# include 
-
-
-P087_data_struct::~P087_data_struct() {
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-
-void P087_data_struct::reset() {
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-
-bool P087_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate,
-                            uint8_t config) {
-  if ((serial_rx < 0) && (serial_tx < 0)) {
-    return false;
-  }
-  reset();
-  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx);
-
-  if (isInitialized()) {
-    # if defined(ESP8266)
-    easySerial->begin(baudrate, (SerialConfig)config);
-    # elif defined(ESP32)
-    easySerial->begin(baudrate, config);
-    # endif // if defined(ESP8266)
-    return true;
-  }
-  return false;
-}
-
-void P087_data_struct::post_init() {
-  for (uint8_t i = 0; i < P87_MAX_CAPTURE_INDEX; ++i) {
-    capture_index_used[i] = false;
-  }
-  regex_empty = _lines[P087_REGEX_POS].isEmpty();
-  # ifndef BUILD_NO_DEBUG
-  String log = F("P087_post_init:");
-  # endif // ifndef BUILD_NO_DEBUG
-
-  for (uint8_t i = 0; i < P087_NR_FILTERS; ++i) {
-    // Create some quick lookup table to see if we have a filter for the specific index
-    capture_index_must_not_match[i] = _lines[i * 3 + P087_FIRST_FILTER_POS + 1].toInt() == P087_Filter_Comp::NotEqual;
-    int index = _lines[i * 3 + P087_FIRST_FILTER_POS].toInt();
-
-    // Index is negative when not used.
-    if ((index >= 0) && (index < P87_MAX_CAPTURE_INDEX) && (_lines[i * 3 + P087_FIRST_FILTER_POS + 2].length() > 0)) {
-      # ifndef BUILD_NO_DEBUG
-      log += ' ';
-      log += String(i);
-      log += ':';
-      log += String(index);
-      # endif // ifndef BUILD_NO_DEBUG
-      capture_index[i]          = index;
-      capture_index_used[index] = true;
-    }
-  }
-  # ifndef BUILD_NO_DEBUG
-  addLogMove(LOG_LEVEL_DEBUG, log);
-  # endif // ifndef BUILD_NO_DEBUG
-}
-
-bool P087_data_struct::isInitialized() const {
-  return easySerial != nullptr;
-}
-
-void P087_data_struct::sendString(const String& data) {
-  if (isInitialized() && (!data.isEmpty())) {
-    setDisableFilterWindowTimer();
-    easySerial->write(data.c_str());
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log = F("Proxy: Sending: ");
-      log += data;
-      addLogMove(LOG_LEVEL_INFO, log);
-    }
-  }
-}
-
-void P087_data_struct::sendData(uint8_t *data, size_t size) {
-  if (isInitialized() && size) {
-    setDisableFilterWindowTimer();
-    easySerial->write(data, size);
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log = F("Proxy: Sending ");
-      log += size;
-      log += F(" bytes.");
-      addLogMove(LOG_LEVEL_INFO, log);
-    }
-  }
-}
-
-bool P087_data_struct::loop() {
-  if (!isInitialized()) {
-    return false;
-  }
-  bool fullSentenceReceived = false;
-
-  if (easySerial != nullptr) {
-    int available = easySerial->available();
-
-    while (available > 0 && !fullSentenceReceived) {
-      // Look for end marker
-      char c = easySerial->read();
-      --available;
-
-      if (available == 0) {
-        available = easySerial->available();
-        delay(0);
-      }
-
-      switch (c) {
-        case 13:
-        {
-          const size_t length = sentence_part.length();
-          bool valid          = length > 0;
-
-          for (size_t i = 0; i < length && valid; ++i) {
-            if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) {
-              sentence_part = String();
-              ++sentences_received_error;
-              valid = false;
-            }
-          }
-
-          if (valid) {
-            fullSentenceReceived = true;
-            last_sentence        = sentence_part;
-            sentence_part        = String();
-          }
-          break;
-        }
-        case 10:
-
-          // Ignore LF
-          break;
-        default:
-          sentence_part += c;
-          break;
-      }
-
-      if (max_length_reached()) { fullSentenceReceived = true; }
-    }
-  }
-
-  if (fullSentenceReceived) {
-    ++sentences_received;
-    length_last_received = last_sentence.length();
-  }
-  return fullSentenceReceived;
-}
-
-bool P087_data_struct::getSentence(String& string) {
-  string = last_sentence;
-
-  if (string.isEmpty()) {
-    return false;
-  }
-  last_sentence = String();
-  return true;
-}
-
-void P087_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const {
-  succes      = sentences_received;
-  error       = sentences_received_error;
-  length_last = length_last_received;
-}
-
-void P087_data_struct::setMaxLength(uint16_t maxlenght) {
-  max_length = maxlenght;
-}
-
-void P087_data_struct::setLine(uint8_t varNr, const String& line) {
-  if (varNr < P87_Nlines) {
-    _lines[varNr] = line;
-  }
-}
-
-String P087_data_struct::getRegEx() const {
-  return _lines[P087_REGEX_POS];
-}
-
-uint16_t P087_data_struct::getRegExpMatchLength() const {
-  return _lines[P087_NR_CHAR_USE_POS].toInt();
-}
-
-uint32_t P087_data_struct::getFilterOffWindowTime() const {
-  return _lines[P087_FILTER_OFF_WINDOW_POS].toInt();
-}
-
-P087_Match_Type P087_data_struct::getMatchType() const {
-  return static_cast(_lines[P087_MATCH_TYPE_POS].toInt());
-}
-
-bool P087_data_struct::invertMatch() const {
-  switch (getMatchType()) {
-    case Regular_Match:          // fallthrough
-    case Global_Match:
-      break;
-    case Regular_Match_inverted: // fallthrough
-    case Global_Match_inverted:
-      return true;
-    case Filter_Disabled:
-      break;
-  }
-  return false;
-}
-
-bool P087_data_struct::globalMatch() const {
-  switch (getMatchType()) {
-    case Regular_Match: // fallthrough
-    case Regular_Match_inverted:
-      break;
-    case Global_Match:  // fallthrough
-    case Global_Match_inverted:
-      return true;
-    case Filter_Disabled:
-      break;
-  }
-  return false;
-}
-
-String P087_data_struct::getFilter(uint8_t lineNr, uint8_t& capture, P087_Filter_Comp& comparator) const
-{
-  uint8_t varNr = lineNr * 3 + P087_FIRST_FILTER_POS;
-
-  if ((varNr + 3) > P87_Nlines) { return ""; }
-
-  capture    = _lines[varNr++].toInt();
-  comparator = _lines[varNr++] == "1" ? P087_Filter_Comp::NotEqual : P087_Filter_Comp::Equal;
-  return _lines[varNr];
-}
-
-void P087_data_struct::setDisableFilterWindowTimer() {
-  if (getFilterOffWindowTime() == 0) {
-    disable_filter_window = 0;
-  }
-  else {
-    disable_filter_window = millis() + getFilterOffWindowTime();
-  }
-}
-
-bool P087_data_struct::disableFilterWindowActive() const {
-  if (disable_filter_window != 0) {
-    if (!timeOutReached(disable_filter_window)) {
-      // We're still in the window where filtering is disabled
-      return true;
-    }
-  }
-  return false;
-}
-
-typedef std::pair capture_tuple;
-static std::vector capture_vector;
-
-
-// called for each match
-void P087_data_struct::match_callback(const char *match, const unsigned int length, const MatchState& ms)
-{
-  for (uint8_t i = 0; i < ms.level; i++)
-  {
-    capture_tuple tuple;
-    tuple.first  = i;
-    tuple.second = ms.GetCapture(i);
-    capture_vector.push_back(tuple);
-  } // end of for each capture
-}
-
-bool P087_data_struct::matchRegexp(String& received) const {
-  size_t strlength = received.length();
-
-  if (strlength == 0) {
-    return false;
-  }
-
-  if (regex_empty || (getMatchType() == Filter_Disabled)) {
-    return true;
-  }
-
-
-  uint32_t regexp_match_length = getRegExpMatchLength();
-
-  if ((regexp_match_length > 0) && (strlength > regexp_match_length)) {
-    strlength = regexp_match_length;
-  }
-
-  // We need to do a const_cast here, but this only is valid as long as we
-  // don't call a replace function from regexp.
-  MatchState ms(const_cast(received.c_str()), strlength);
-
-  bool match_result = false;
-
-  if (globalMatch()) {
-    capture_vector.clear();
-    ms.GlobalMatch(_lines[P087_REGEX_POS].c_str(), match_callback);
-    const uint8_t vectorlength = capture_vector.size();
-
-    for (uint8_t i = 0; i < vectorlength; ++i) {
-      if ((capture_vector[i].first < P87_MAX_CAPTURE_INDEX) && capture_index_used[capture_vector[i].first]) {
-        for (uint8_t n = 0; n < P087_NR_FILTERS; ++n) {
-          unsigned int lines_index = n * 3 + P087_FIRST_FILTER_POS + 2;
-
-          if ((capture_index[n] == capture_vector[i].first) && !(_lines[lines_index].isEmpty())) {
-            String log;
-            log.reserve(32);
-            log  = F("P087: Index: ");
-            log += capture_vector[i].first;
-            log += F(" Found ");
-            log += capture_vector[i].second;
-
-            // Found a Capture Filter with this capture index.
-            if (capture_vector[i].second == _lines[lines_index]) {
-              log += F(" Matches");
-
-              // Found a match. Now check if it is supposed to be one or not.
-              if (capture_index_must_not_match[n]) {
-                log += F(" (!=)");
-                addLogMove(LOG_LEVEL_INFO, log);
-                return false;
-              } else {
-                match_result = true;
-                log         += F(" (==)");
-              }
-            } else {
-              log += F(" No Match");
-
-              if (capture_index_must_not_match[n]) {
-                log += F(" (!=) ");
-              } else {
-                log += F(" (==) ");
-              }
-              log += _lines[lines_index];
-            }
-            addLogMove(LOG_LEVEL_INFO, log);
-          }
-        }
-      }
-    }
-    capture_vector.clear();
-  } else {
-    char result = ms.Match(_lines[P087_REGEX_POS].c_str());
-
-    if (result == REGEXP_MATCHED) {
-      # ifndef BUILD_NO_DEBUG
-
-      if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
-        String log = F("Match at: ");
-        log += ms.MatchStart;
-        log += F(" Match Length: ");
-        log += ms.MatchLength;
-        addLogMove(LOG_LEVEL_DEBUG, log);
-      }
-      # endif // ifndef BUILD_NO_DEBUG
-      match_result = true;
-    }
-  }
-  return match_result;
-}
-
-const __FlashStringHelper * P087_data_struct::MatchType_toString(P087_Match_Type matchType) {
-  switch (matchType)
-  {
-    case P087_Match_Type::Regular_Match:          return F("Regular Match");
-    case P087_Match_Type::Regular_Match_inverted: return F("Regular Match inverted");
-    case P087_Match_Type::Global_Match:           return F("Global Match");
-    case P087_Match_Type::Global_Match_inverted:  return F("Global Match inverted");
-    case P087_Match_Type::Filter_Disabled:        return F("Filter Disabled");
-  }
-  return F("");
-}
-
-bool P087_data_struct::max_length_reached() const {
-  if (max_length == 0) { return false; }
-  return sentence_part.length() >= max_length;
-}
-
-#endif // USES_P087
+#include "../PluginStructs/P087_data_struct.h"
+
+#ifdef USES_P087
+
+
+// Needed also here for PlatformIO's library finder as the .h file
+// is in a directory which is excluded in the src_filter
+# include 
+# include 
+
+
+# include 
+
+
+P087_data_struct::~P087_data_struct() {
+  delete easySerial;
+  easySerial = nullptr;
+}
+
+void P087_data_struct::reset() {
+  delete easySerial;
+  easySerial = nullptr;
+}
+
+bool P087_data_struct::init(ESPEasySerialPort port, const int16_t serial_rx, const int16_t serial_tx, unsigned long baudrate,
+                            uint8_t config) {
+  if ((serial_rx < 0) && (serial_tx < 0)) {
+    return false;
+  }
+  reset();
+  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx);
+
+  if (isInitialized()) {
+    # if defined(ESP8266)
+    easySerial->begin(baudrate, (SerialConfig)config);
+    # elif defined(ESP32)
+    easySerial->begin(baudrate, config);
+    # endif // if defined(ESP8266)
+    return true;
+  }
+  return false;
+}
+
+void P087_data_struct::post_init() {
+  for (uint8_t i = 0; i < P87_MAX_CAPTURE_INDEX; ++i) {
+    capture_index_used[i] = false;
+  }
+  regex_empty = _lines[P087_REGEX_POS].isEmpty();
+  # ifndef BUILD_NO_DEBUG
+  String log = F("P087_post_init:");
+  # endif // ifndef BUILD_NO_DEBUG
+
+  for (uint8_t i = 0; i < P087_NR_FILTERS; ++i) {
+    // Create some quick lookup table to see if we have a filter for the specific index
+    capture_index_must_not_match[i] = _lines[i * 3 + P087_FIRST_FILTER_POS + 1].toInt() == P087_Filter_Comp::NotEqual;
+    int index = _lines[i * 3 + P087_FIRST_FILTER_POS].toInt();
+
+    // Index is negative when not used.
+    if ((index >= 0) && (index < P87_MAX_CAPTURE_INDEX) && (_lines[i * 3 + P087_FIRST_FILTER_POS + 2].length() > 0)) {
+      # ifndef BUILD_NO_DEBUG
+      log += strformat(F(" %d:%d"), i, index);
+      # endif // ifndef BUILD_NO_DEBUG
+      capture_index[i]          = index;
+      capture_index_used[index] = true;
+    }
+  }
+  # ifndef BUILD_NO_DEBUG
+  addLogMove(LOG_LEVEL_DEBUG, log);
+  # endif // ifndef BUILD_NO_DEBUG
+}
+
+bool P087_data_struct::isInitialized() const {
+  return easySerial != nullptr;
+}
+
+void P087_data_struct::sendString(const String& data) {
+  if (isInitialized() && (!data.isEmpty())) {
+    setDisableFilterWindowTimer();
+    easySerial->write(data.c_str());
+
+    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+      addLogMove(LOG_LEVEL_INFO, concat(F("Proxy: Sending: "), data));
+    }
+  }
+}
+
+void P087_data_struct::sendData(uint8_t *data, size_t size) {
+  if (isInitialized() && size) {
+    setDisableFilterWindowTimer();
+    easySerial->write(data, size);
+
+    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+      addLogMove(LOG_LEVEL_INFO, strformat(F("Proxy: Sending %d bytes."), size));
+    }
+  }
+}
+
+bool P087_data_struct::loop() {
+  if (!isInitialized()) {
+    return false;
+  }
+  bool fullSentenceReceived = false;
+
+  if (easySerial != nullptr) {
+    int available = easySerial->available();
+
+    while (available > 0 && !fullSentenceReceived) {
+      // Look for end marker
+      char c = easySerial->read();
+      --available;
+
+      if (available == 0) {
+        available = easySerial->available();
+        delay(0);
+      }
+
+      switch (c) {
+        case 13:
+        {
+          const size_t length = sentence_part.length();
+          bool valid          = length > 0;
+
+          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;
+        }
+        case 10:
+
+          // Ignore LF
+          break;
+        default:
+          sentence_part += c;
+          break;
+      }
+
+      if (max_length_reached()) { fullSentenceReceived = true; }
+    }
+  }
+
+  if (fullSentenceReceived) {
+    ++sentences_received;
+    length_last_received = last_sentence.length();
+  }
+  return fullSentenceReceived;
+}
+
+bool P087_data_struct::getSentence(String& string) {
+  string = last_sentence;
+
+  if (string.isEmpty()) {
+    return false;
+  }
+  last_sentence = EMPTY_STRING;
+  return true;
+}
+
+void P087_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const {
+  succes      = sentences_received;
+  error       = sentences_received_error;
+  length_last = length_last_received;
+}
+
+void P087_data_struct::setMaxLength(uint16_t maxlenght) {
+  max_length = maxlenght;
+}
+
+void P087_data_struct::setLine(uint8_t varNr, const String& line) {
+  if (varNr < P87_Nlines) {
+    _lines[varNr] = line;
+  }
+}
+
+String P087_data_struct::getRegEx() const {
+  return _lines[P087_REGEX_POS];
+}
+
+uint16_t P087_data_struct::getRegExpMatchLength() const {
+  return _lines[P087_NR_CHAR_USE_POS].toInt();
+}
+
+uint32_t P087_data_struct::getFilterOffWindowTime() const {
+  return _lines[P087_FILTER_OFF_WINDOW_POS].toInt();
+}
+
+P087_Match_Type P087_data_struct::getMatchType() const {
+  return static_cast(_lines[P087_MATCH_TYPE_POS].toInt());
+}
+
+bool P087_data_struct::invertMatch() const {
+  switch (getMatchType()) {
+    case Regular_Match:          // fallthrough
+    case Global_Match:
+      break;
+    case Regular_Match_inverted: // fallthrough
+    case Global_Match_inverted:
+      return true;
+    case Filter_Disabled:
+      break;
+  }
+  return false;
+}
+
+bool P087_data_struct::globalMatch() const {
+  switch (getMatchType()) {
+    case Regular_Match: // fallthrough
+    case Regular_Match_inverted:
+      break;
+    case Global_Match:  // fallthrough
+    case Global_Match_inverted:
+      return true;
+    case Filter_Disabled:
+      break;
+  }
+  return false;
+}
+
+String P087_data_struct::getFilter(uint8_t lineNr, uint8_t& capture, P087_Filter_Comp& comparator) const
+{
+  uint8_t varNr = lineNr * 3 + P087_FIRST_FILTER_POS;
+
+  if ((varNr + 3) > P87_Nlines) { return EMPTY_STRING; }
+
+  capture    = _lines[varNr++].toInt();
+  comparator = equals(_lines[varNr++], '1') ? P087_Filter_Comp::NotEqual : P087_Filter_Comp::Equal;
+  return _lines[varNr];
+}
+
+void P087_data_struct::setDisableFilterWindowTimer() {
+  if (getFilterOffWindowTime() == 0) {
+    disable_filter_window = 0;
+  }
+  else {
+    disable_filter_window = millis() + getFilterOffWindowTime();
+  }
+}
+
+bool P087_data_struct::disableFilterWindowActive() const {
+  if (disable_filter_window != 0) {
+    if (!timeOutReached(disable_filter_window)) {
+      // We're still in the window where filtering is disabled
+      return true;
+    }
+  }
+  return false;
+}
+
+typedef std::pair capture_tuple;
+static std::vector capture_vector;
+
+
+// called for each match
+void P087_data_struct::match_callback(const char *match, const unsigned int length, const MatchState& ms)
+{
+  for (uint8_t i = 0; i < ms.level; i++)
+  {
+    capture_tuple tuple;
+    tuple.first  = i;
+    tuple.second = ms.GetCapture(i);
+    capture_vector.push_back(tuple);
+  } // end of for each capture
+}
+
+bool P087_data_struct::matchRegexp(String& received) const {
+  size_t strlength = received.length();
+
+  if (strlength == 0) {
+    return false;
+  }
+
+  if (regex_empty || (getMatchType() == Filter_Disabled)) {
+    return true;
+  }
+
+
+  const uint32_t regexp_match_length = getRegExpMatchLength();
+
+  if ((regexp_match_length > 0) && (strlength > regexp_match_length)) {
+    strlength = regexp_match_length;
+  }
+
+  // We need to do a const_cast here, but this only is valid as long as we
+  // don't call a replace function from regexp.
+  MatchState ms(const_cast(received.c_str()), strlength);
+
+  bool match_result = false;
+
+  capture_vector.clear();
+  ms.GlobalMatch(getRegEx().c_str(), match_callback); // To allow the matched values be retrieved also when not using Global Match option
+
+  if (globalMatch()) {
+    const uint8_t vectorlength = capture_vector.size();
+
+    for (uint8_t i = 0; i < vectorlength; ++i) {
+      if ((capture_vector[i].first < P87_MAX_CAPTURE_INDEX) && capture_index_used[capture_vector[i].first]) {
+        for (uint8_t n = 0; n < P087_NR_FILTERS; ++n) {
+          unsigned int lines_index = n * 3 + P087_FIRST_FILTER_POS + 2;
+
+          if ((capture_index[n] == capture_vector[i].first) && !(_lines[lines_index].isEmpty())) {
+            String log;
+            log.reserve(32);
+            log = strformat(F("P087: Index: %d Found %s"), capture_vector[i].first, capture_vector[i].second.c_str());
+
+            // Found a Capture Filter with this capture index.
+            if (capture_vector[i].second.equals(_lines[lines_index])) {
+              log += F(" Matches");
+
+              // Found a match. Now check if it is supposed to be one or not.
+              if (capture_index_must_not_match[n]) {
+                log += F(" (!=)");
+                addLogMove(LOG_LEVEL_INFO, log);
+                return false;
+              } else {
+                match_result = true;
+                log         += F(" (==)");
+              }
+            } else {
+              log += F(" No Match");
+
+              if (capture_index_must_not_match[n]) {
+                log += F(" (!=)");
+              } else {
+                log += F(" (==)");
+              }
+              log += ' ';
+              log += _lines[lines_index];
+            }
+            addLogMove(LOG_LEVEL_INFO, log);
+          }
+        }
+      }
+    }
+
+    // capture_vector.clear(); // KEEP so we can use plugin_get_config_value to retrieve the values
+  } else {
+    char result = ms.Match(getRegEx().c_str());
+
+    if (result == REGEXP_MATCHED) {
+      # ifndef BUILD_NO_DEBUG
+
+      if (loglevelActiveFor(LOG_LEVEL_DEBUG)) {
+        addLogMove(LOG_LEVEL_DEBUG, strformat(F("Match at: %d Match Length: %d"), ms.MatchStart, ms.MatchLength));
+      }
+      # endif // ifndef BUILD_NO_DEBUG
+      match_result = true;
+    }
+  }
+  return match_result;
+}
+
+const __FlashStringHelper * P087_data_struct::MatchType_toString(P087_Match_Type matchType) {
+  switch (matchType)
+  {
+    case P087_Match_Type::Regular_Match:          return F("Regular Match");
+    case P087_Match_Type::Regular_Match_inverted: return F("Regular Match inverted");
+    case P087_Match_Type::Global_Match:           return F("Global Match");
+    case P087_Match_Type::Global_Match_inverted:  return F("Global Match inverted");
+    case P087_Match_Type::Filter_Disabled:        return F("Filter Disabled");
+  }
+  return F("");
+}
+
+bool P087_data_struct::max_length_reached() const {
+  if (max_length == 0) { return false; }
+  return sentence_part.length() >= max_length;
+}
+
+void P087_data_struct::setLastSentence(String string) {
+  last_sentence = string;
+}
+
+bool P087_data_struct::plugin_get_config_value(struct EventStruct *event,
+                                               String            & string) {
+  bool success               = false;
+  const uint8_t vectorlength = capture_vector.size();
+  char sep                   = '.';
+
+  if ((-1 == string.indexOf(sep)) && (string.indexOf(',') >= 0)) {
+    sep = ',';
+  }
+  const String cmd = parseString(string, 1, sep);
+
+  # ifndef BUILD_NO_DEBUG
+  addLog(LOG_LEVEL_DEBUG, concat(F("P087: Before GetConfig: "), string));
+  # endif // ifndef BUILD_NO_DEBUG
+
+  if (equals(cmd, F("group"))) {
+    int32_t par2;
+
+    if (validIntFromString(parseString(string, 2, sep), par2) &&
+        (par2 >= 0)) {
+      for (uint8_t i = 0; i < vectorlength && !success; ++i) { // Stop when we find the requested group
+        # ifndef BUILD_NO_DEBUG
+        addLog(LOG_LEVEL_DEBUG, strformat(F("P087: get group: %d = %s"),
+                                          capture_vector[i].first,
+                                          capture_vector[i].second.c_str()));
+        # endif // ifndef BUILD_NO_DEBUG
+
+        if (par2 == capture_vector[i].first) {
+          string  = capture_vector[i].second;
+          success = true;
+        }
+      }
+    }
+  }  else
+  if (equals(cmd, F("next"))) {                                    // Get next group value after matching name
+    const String name_ = parseString(string, 2, sep);
+
+    for (uint8_t i = 0; i < (vectorlength - 1) && !success; ++i) { // Stop when we find the requested name
+                                                                   // Loops until 1 BEFORE the end of the vector!
+      # ifndef BUILD_NO_DEBUG
+      addLog(LOG_LEVEL_DEBUG, strformat(F("P087: get next: %d = %s => %s"),
+                                        capture_vector[i].first,
+                                        capture_vector[i].second.c_str(),
+                                        capture_vector[i + 1].second.c_str()));
+      # endif // ifndef BUILD_NO_DEBUG
+
+      if (name_.equalsIgnoreCase(capture_vector[i].second)) {
+        string  = capture_vector[i + 1].second; // Take NEXT value
+        success = true;
+      }
+    }
+  } // else...
+  # ifndef BUILD_NO_DEBUG
+  addLog(LOG_LEVEL_DEBUG, concat(F("P087: After GetConfig: "), string));
+  # endif // ifndef BUILD_NO_DEBUG
+
+  return success;
+}
+
+#endif // USES_P087
diff --git a/src/src/PluginStructs/P087_data_struct.h b/src/src/PluginStructs/P087_data_struct.h
index a96eeed12..031e88a52 100644
--- a/src/src/PluginStructs/P087_data_struct.h
+++ b/src/src/PluginStructs/P087_data_struct.h
@@ -67,6 +67,7 @@ public:
   // Get the received sentence
   // @retval true when the string is not empty.
   bool getSentence(String& string);
+  void setLastSentence(String string);
 
   void getSentencesReceived(uint32_t& succes,
                             uint32_t& error,
@@ -110,6 +111,10 @@ public:
   // Made public so we don't have to copy the values when loading/saving.
   String _lines[P87_Nlines];
 
+  // Plugin handler functions:
+  bool plugin_get_config_value(struct EventStruct *event,
+                               String            & string);
+
 private:
 
   bool max_length_reached() const;
diff --git a/src/src/PluginStructs/P090_data_struct.cpp b/src/src/PluginStructs/P090_data_struct.cpp
index 31bcf4288..f772a2f7c 100644
--- a/src/src/PluginStructs/P090_data_struct.cpp
+++ b/src/src/PluginStructs/P090_data_struct.cpp
@@ -57,12 +57,13 @@ CCS811Core::status CCS811Core::beginCore(void)
     # endif
 
   // Spin for a few ms
-  ESPEASY_VOLATILE(uint8_t) temp = 0;
+  // ESPEASY_VOLATILE(uint8_t) temp = 0;
   // FIXME TD-er: This is a rather odd way to avoid calling "delay"
-  for (uint16_t i = 0; i < 10000; i++)
-  {
-    temp++;
-  }
+  // for (uint16_t i = 0; i < 10000; i++)
+  // {
+  //   temp++;
+  // }
+  // FIX tonhuisman: No need to 'wait' if no action has been started yet
 
   while (Wire.available()) // Clear wire as a precaution
   {
@@ -223,7 +224,7 @@ CCS811Core::status CCS811::begin(void)
     return SENSOR_I2C_ERROR;
   }
 
-  delay(200);
+  delay(200); // FIXME OOPS?
 
   // returnError = setDriveMode(1); //Read every second
   //    ESPEASY_SERIAL_0.println();
diff --git a/src/src/PluginStructs/P092_data_struct.cpp b/src/src/PluginStructs/P092_data_struct.cpp
index 8ed7ca642..b42118eda 100644
--- a/src/src/PluginStructs/P092_data_struct.cpp
+++ b/src/src/PluginStructs/P092_data_struct.cpp
@@ -31,7 +31,7 @@ DLBus::DLBus()
     ISR_PtrChangeBitStream = DLbus_ChangeBitStream;
 # ifndef P092_LIMIT_BUILD_SIZE
     addLog(LOG_LEVEL_INFO, F("Class DLBus created"));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   }
 }
 
@@ -43,7 +43,7 @@ DLBus::~DLBus()
     ISR_PtrChangeBitStream = nullptr;
 # ifndef P092_LIMIT_BUILD_SIZE
     addLog(LOG_LEVEL_INFO, F("Class DLBus destroyed"));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   }
 }
 
@@ -54,7 +54,8 @@ void DLBus::AddToInfoLog(const String& string)
     addLog(LogLevelInfo, string);
   }
 }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
 void DLBus::AddToErrorLog(const String& string)
 {
@@ -154,7 +155,7 @@ boolean DLBus::CheckTimings(void) {
 
   ISR_PulseCount = 0;
 
-  for (i = 0; i <= ISR_PulseNumber; i++) {
+  for (i = 0; i <= ISR_PulseNumber; ++i) {
     // store DLbus_ChangeBitStream into ByteStream
     rawval = *(ISR_PtrChangeBitStream + i);
 
@@ -214,7 +215,7 @@ boolean DLBus::CheckTimings(void) {
       String log = F("Wrong Timings: ");
       AddToInfoLog(log);
 
-      for (i = 0; i < WrongTimeCnt; i++) {
+      for (i = 0; i < WrongTimeCnt; ++i) {
         log  = i + 1;
         log += F(": PulseCount:");
         log += WrongTimingArray[i][1];
@@ -267,7 +268,7 @@ boolean DLBus::Processing(void) {
 
 # ifndef P092_LIMIT_BUILD_SIZE
   AddToInfoLog(F("Processing..."));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   StartBit = Analyze(); // find the data frame's beginning
 
   // inverted signal?
@@ -291,25 +292,27 @@ boolean DLBus::Processing(void) {
       AddToErrorLog(F("Start bit too close to end of stream!"));
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
       if (IsLogLevelInfo) {
         AddToInfoLog(strformat(
-          F("# Required bits: %d StartBit: %d / EndBit: %d"), 
-          RequiredBitStreamLength, 
-          StartBit, 
-          BitNumber));
+                       F("# Required bits: %d StartBit: %d / EndBit: %d"),
+                       RequiredBitStreamLength,
+                       StartBit,
+                       BitNumber));
       }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
       return false;
     }
   }
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
   if (IsLogLevelInfo) {
     AddToInfoLog(strformat(
-      F("StartBit: %d / EndBit: %d"),
-       StartBit, BitNumber));
+                   F("StartBit: %d / EndBit: %d"),
+                   StartBit, BitNumber));
   }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   Trim(StartBit);      // remove start and stop bits
 
   if (CheckDevice()) { // check connected device
@@ -325,7 +328,7 @@ int DLBus::Analyze(void) {
   uint8_t sync = 0;
 
   // find SYNC (16 * sequential 1)
-  for (int i = 0; i < BitNumber; i++) {
+  for (int i = 0; i < BitNumber; ++i) {
     if (ReadBit(i)) {
       sync++;
     }
@@ -349,9 +352,9 @@ int DLBus::Analyze(void) {
 void DLBus::Invert(void) {
 # ifndef P092_LIMIT_BUILD_SIZE
   AddToInfoLog(F("Invert bit stream..."));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
-  for (int i = 0; i < BitNumber; i++) {
+  for (int i = 0; i < BitNumber; ++i) {
     WriteBit(i, ReadBit(i) ? 0 : 1); // invert every bit
   }
 }
@@ -376,7 +379,7 @@ void DLBus::WriteBit(int pos, uint8_t set) {
 }
 
 void DLBus::Trim(int start_bit) {
-  for (int i = start_bit, bit = 0; i < BitNumber; i++) {
+  for (int i = start_bit, bit = 0; i < BitNumber; ++i) {
     int offset = i - start_bit;
 
     // ignore start and stop bits:
@@ -398,22 +401,21 @@ boolean DLBus::CheckDevice(void) {
   }
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
   if (IsLogLevelInfo) {
-    String log = F("# Received DeviceByte(s): 0x");
-    log += String(ByteStream[0], HEX);
+    String log = strformat(F("# Received DeviceByte(s): 0x%02x"), ByteStream[0]);
 
     if (DeviceBytes[1] != 0) {
-      log += String(ByteStream[1], HEX);
+      log += strformat(F("%02x"), ByteStream[1]);
     }
-    log += F(" Requested: 0x");
-    log += String(DeviceBytes[0], HEX);
+    log += strformat(F(" Requested: 0x%02x"), DeviceBytes[0]);
 
     if (DeviceBytes[1] != 0) {
-      log += String(DeviceBytes[1], HEX);
+      log += strformat(F("%02x"), DeviceBytes[1]);
     }
     AddToInfoLog(log);
   }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   return false;
 }
 
@@ -424,10 +426,10 @@ boolean DLBus::CheckCRC(uint8_t IdxCRC) {
   }
 # ifndef P092_LIMIT_BUILD_SIZE
   AddToInfoLog(F("Check CRC..."));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   uint16_t dataSum = 0;
 
-  for (int i = 0; i < IdxCRC; i++) {
+  for (int i = 0; i < IdxCRC; ++i) {
     dataSum = dataSum + ByteStream[i];
   }
   dataSum = dataSum & 0xff;
@@ -438,13 +440,14 @@ boolean DLBus::CheckCRC(uint8_t IdxCRC) {
   AddToErrorLog(F("Check CRC failed!"));
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
   if (IsLogLevelInfo) {
     AddToInfoLog(strformat(
-      F("# Calculated CRC: 0x%x Received: 0x%x"), 
-      dataSum, 
-      ByteStream[IdxCRC]));
+                   F("# Calculated CRC: 0x%x Received: 0x%x"),
+                   dataSum,
+                   ByteStream[IdxCRC]));
   }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   return false;
 }
 
@@ -493,13 +496,13 @@ bool P092_data_struct::init(int8_t pin1, int P092DeviceIndex, eP092pinmode P092p
     case eP092pinmode::ePPM_InputPullUp:
 # ifndef P092_LIMIT_BUILD_SIZE
       addLog(LOG_LEVEL_INFO, F("P092_init: Set input pin with pullup"));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
       pinMode(pin1, INPUT_PULLUP);
-    break;
+      break;
     default:
 # ifndef P092_LIMIT_BUILD_SIZE
       addLog(LOG_LEVEL_INFO, F("P092_init: Set input pin"));
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
       pinMode(pin1, INPUT);
   }
 
@@ -647,12 +650,11 @@ void P092_data_struct::Plugin_092_StartReceiving(taskIndex_t taskindex) {
   uint32_t start = millis();
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    String log = F("P092_receiving ... TaskIndex:");
-    log += taskindex;
-    addLogMove(LOG_LEVEL_INFO, log);
+    addLogMove(LOG_LEVEL_INFO, concat(F("P092_receiving ... TaskIndex:"), taskindex));
   }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
   while ((timePassedSince(start) < 100) && (DLbus_Data->ISR_PulseCount == 0)) {
     // wait for first pulse received (timeout 100ms)
@@ -672,16 +674,15 @@ void P092_data_struct::Plugin_092_StartReceiving(taskIndex_t taskindex) {
 \****************/
 boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData *ReadData) {
 # ifndef P092_LIMIT_BUILD_SIZE
-  String  log;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+  String log;
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   boolean result = false;
 
   switch (OptionIdx) {
     case 1: // F("Sensor")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get Sensor");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get Sensor");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.MaxSensors) {
         result = false;
@@ -692,9 +693,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 2: // F("Sensor")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get ExtSensor");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get ExtSensor");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.MaxExtSensors) {
         result = false;
@@ -705,9 +705,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 3: // F("Digital output")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get DigitalOutput");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get DigitalOutput");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > (8 * P092_DataSettings.OutputBytes)) {
         result = false;
@@ -717,9 +716,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 4: // F("Speed step")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get SpeedStep");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get SpeedStep");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.SpeedBytes) {
         result = false;
@@ -729,9 +727,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 5: // F("Analog output")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get AnalogOutput");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get AnalogOutput");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.AnalogBytes) {
         result = false;
@@ -741,9 +738,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 6: // F("Heat power (kW)")
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get HeatPower");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get HeatPower");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.MaxHeatMeters) {
         result = false;
@@ -753,9 +749,8 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
       break;
     case 7: // F("Heat meter (MWh)"
 # ifndef P092_LIMIT_BUILD_SIZE
-      log  = F("Get HeatMeter");
-      log += CurIdx;
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+      log = F("Get HeatMeter");
+# endif // ifndef P092_LIMIT_BUILD_SIZE
 
       if (CurIdx > P092_DataSettings.MaxHeatMeters) {
         result = false;
@@ -766,8 +761,9 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
   }
 
 # ifndef P092_LIMIT_BUILD_SIZE
+
   if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    log += F(": ");
+    log += strformat(F("%d: "), CurIdx);
 
     if (result) {
       log += String(ReadData->value, 1);
@@ -777,7 +773,7 @@ boolean P092_data_struct::P092_GetData(int OptionIdx, int CurIdx, sP092_ReadData
     }
     addLogMove(LOG_LEVEL_INFO, log);
   }
-#endif // ifndef P092_LIMIT_BUILD_SIZE
+# endif // ifndef P092_LIMIT_BUILD_SIZE
   return result;
 }
 
diff --git a/src/src/PluginStructs/P093_data_struct.cpp b/src/src/PluginStructs/P093_data_struct.cpp
index abda89fe6..9a5a6063d 100644
--- a/src/src/PluginStructs/P093_data_struct.cpp
+++ b/src/src/PluginStructs/P093_data_struct.cpp
@@ -10,7 +10,7 @@
  *
  * Plugin is based on "Arduino library to control Mitsubishi Heat Pumps" from
  * https://github.com/SwiCago/HeatPump.
- * 
+ *
  * SetRemoteTemperature is based on following Issue and Resolve
  * https://github.com/SwiCago/HeatPump/pull/144#issue-514996963
  * https://github.com/SwiCago/HeatPump/pull/144/commits/c50372c7632b9e7324caf0c0fc0773871645688e
@@ -58,7 +58,7 @@ bool P093_data_struct::read(String& result) const {
   result.reserve(150);
 
   // FIXME TD-er: See if this macro can be simpler as it does expand to quite some code which is not changing.
-    # define map_list(x, list) findByValue(x, list, sizeof(list) / sizeof(Tuple))
+  # define map_list(x, list) findByValue(x, list, sizeof(list) / sizeof(Tuple))
 
   result  = F("{\"roomTemperature\":");
   result += toString(_currentValues.roomTemperature, 1);
@@ -133,18 +133,18 @@ bool P093_data_struct::plugin_get_config_value(struct EventStruct *event,
     success = false;
   }
 
-    # undef map_list
+  # undef map_list
 
   return success;
 }
 
 void P093_data_struct::write(const String& command, const String& value) {
-    # define lookup(x, list, placeholder) findByMapping(x, list, sizeof(list) / sizeof(Tuple), placeholder)
+  # define lookup(x, list, placeholder) findByMapping(x, list, sizeof(list) / sizeof(Tuple), placeholder)
 
   if (equals(command, F("temperature"))) {
-    float temperature = 0;
+    float temperature = 0.0f;
 
-    if (string2float(value, temperature) && (temperature >= 16) && (temperature <= 31)) {
+    if (validFloatFromString(value, temperature) && (temperature >= 16) && (temperature <= 31)) {
       _wantedSettings.temperature = temperature;
       _writeStatus.set(Temperature);
     }
@@ -159,15 +159,15 @@ void P093_data_struct::write(const String& command, const String& value) {
   } else if ((equals(command, F("widevane"))) && lookup(value, _mappings.wideVane, _wantedSettings.wideVane)) {
     _writeStatus.set(WideVane);
   } else if (equals(command, F("remotetemperature"))) {
-   float remotetemperature = 0;
+    float remotetemperature = 0.0f;
 
-    if (string2float(value, remotetemperature)) {
+    if (validFloatFromString(value, remotetemperature)) {
       _wantedSettings.remoteTemperature = remotetemperature;
       _writeStatus.set(RemoteTemperature);
     }
   }
 
-    # undef lookup
+  # undef lookup
 }
 
 void P093_data_struct::setState(P093_data_struct::State newState) {
@@ -176,10 +176,11 @@ void P093_data_struct::setState(P093_data_struct::State newState) {
     _state = newState;
     didTransition(currentState, newState);
   } else {
-# ifdef PLUGIN_093_DEBUG
-    addLog(LOG_LEVEL_DEBUG, String(F("M-AC: SS - ignoring ")) +
-           stateToString(_state) + F(" -> ") + stateToString(newState));
-# endif // ifdef PLUGIN_093_DEBUG
+    # ifdef PLUGIN_093_DEBUG
+    addLog(LOG_LEVEL_DEBUG, strformat(F("M-AC: SS - ignoring %s -> %s"),
+                                      stateToString(_state).c_str(),
+                                      stateToString(newState).c_str()));
+    # endif // ifdef PLUGIN_093_DEBUG
   }
 }
 
@@ -218,10 +219,11 @@ bool P093_data_struct::shouldTransition(P093_data_struct::State from, P093_data_
 }
 
 void P093_data_struct::didTransition(P093_data_struct::State from, P093_data_struct::State to) {
-# ifdef PLUGIN_093_DEBUG
-  addLog(LOG_LEVEL_DEBUG, String(F("M-AC: didTransition: ")) +
-         stateToString(from) + " -> " + stateToString(to));
-# endif // ifdef PLUGIN_093_DEBUG
+  # ifdef PLUGIN_093_DEBUG
+  addLog(LOG_LEVEL_DEBUG, strformat(F("M-AC: didTransition: %s -> %s"),
+                                    stateToString(from).c_str(),
+                                    stateToString(to).c_str()));
+  # endif // ifdef PLUGIN_093_DEBUG
 
   switch (to) {
     case ReadTimeout:
@@ -327,7 +329,7 @@ void P093_data_struct::responseReceived() {
 
 void P093_data_struct::updateStatus() {
   # ifdef PLUGIN_093_DEBUG
-  addLog(LOG_LEVEL_DEBUG, String(F("M-AC: US: ")) + _infoModeIndex);
+  addLog(LOG_LEVEL_DEBUG, concat(F("M-AC: US: "), _infoModeIndex));
   # endif // ifdef PLUGIN_093_DEBUG
 
   uint8_t packet[PACKET_LEN] = { 0xfc, 0x42, 0x01, 0x30, 0x10 };
@@ -382,45 +384,52 @@ void P093_data_struct::applySettings() {
   if (_writeStatus.isDirty(RemoteTemperature)) {
     memset(packet + 6, 0, 15);
     packet[5] = 0x07;
-    if(_wantedSettings.remoteTemperature > 0) {
-      packet[6] |= 0x01;
+
+    if (_wantedSettings.remoteTemperature > 0) {
+      packet[6]                        |= 0x01;
       _wantedSettings.remoteTemperature = _wantedSettings.remoteTemperature * 2;
       _wantedSettings.remoteTemperature = round(_wantedSettings.remoteTemperature);
       _wantedSettings.remoteTemperature = _wantedSettings.remoteTemperature / 2;
-      if (_tempMode) {        //units that don't support 0.5 increment 
+
+      if (_tempMode) { // units that don't support 0.5 increment
         packet[8] = static_cast(_wantedSettings.remoteTemperature * 2.0f + 128.0f);
-      } else {                //units that do support 0.5 increment 
+      } else {         // units that do support 0.5 increment
         packet[7] = static_cast(3.0f + ((_wantedSettings.remoteTemperature - 10.0f) * 2.0f));
       }
     }
     else {
       packet[6] = 0x00;
-      packet[8] = 0x80; //MHK1 send 80, even though it could be 00, since ControlByte is 00
-    } 
- }
+      packet[8] = 0x80; // MHK1 send 80, even though it could be 00, since ControlByte is 00
+    }
+  }
   packet[21] = checkSum(packet, 21);
   sendPacket(packet, PACKET_LEN);
 }
 
 void P093_data_struct::connect() {
+  const unsigned long baud = getBaudRate();
+
   # ifdef PLUGIN_093_DEBUG
-  addLog(LOG_LEVEL_DEBUG, String(F("M-AC: Connect ")) + getBaudRate());
+  addLog(LOG_LEVEL_DEBUG, concat(F("M-AC: Connect "), baud));
   # endif // ifdef PLUGIN_093_DEBUG
 
-  _serial.begin(getBaudRate(), SERIAL_8E1);
+  _serial.begin(baud, SERIAL_8E1);
   const uint8_t buffer[] = { 0xfc, 0x5a, 0x01, 0x30, 0x02, 0xca, 0x01, 0xa8 };
 
   sendPacket(buffer, sizeof(buffer));
 }
 
 unsigned long P093_data_struct::getBaudRate() const {
-  return _fastBaudRate ? 9600 : 2400;
+  return _fastBaudRate ? 9600ul : 2400ul;
 }
 
 void P093_data_struct::sendPacket(const uint8_t *packet, size_t size) {
-# ifdef PLUGIN_093_DEBUG
-  addLog(LOG_LEVEL_DEBUG_MORE, dumpOutgoingPacket(packet, size));
-# endif // ifdef PLUGIN_093_DEBUG
+  # ifdef PLUGIN_093_DEBUG
+
+  if (loglevelActiveFor(LOG_LEVEL_DEBUG_MORE)) {
+    addLog(LOG_LEVEL_DEBUG_MORE, dumpOutgoingPacket(packet, size));
+  }
+  # endif // ifdef PLUGIN_093_DEBUG
 
   _serial.write(packet, size);
   _writeTimeout = millis() + 2000;
@@ -449,7 +458,7 @@ bool P093_data_struct::readIncommingBytes() {
   static const uint8_t DATA_LEN_INDEX = 4;
 
   while (_serial.available() > 0) {
-    uint8_t value = _serial.read();
+    const uint8_t value = _serial.read();
 
     if (_readPos == 0) {
       // Wait for start uint8_t.
@@ -457,7 +466,7 @@ bool P093_data_struct::readIncommingBytes() {
         addByteToReadBuffer(value);
       } else {
         # ifdef PLUGIN_093_DEBUG
-        addLog(LOG_LEVEL_DEBUG, String(F("M-AC: RIB(0) ")) + formatToHex(value));
+        addLog(LOG_LEVEL_DEBUG, strformat(F("M-AC: RIB(0) 0x%x"), value));
         # endif // ifdef PLUGIN_093_DEBUG
       }
     } else if ((_readPos <= DATA_LEN_INDEX) || (_readPos <= DATA_LEN_INDEX + _readBuffer[DATA_LEN_INDEX])) {
@@ -465,7 +474,7 @@ bool P093_data_struct::readIncommingBytes() {
       addByteToReadBuffer(value);
     } else {
       // Done, last uint8_t is checksum.
-      uint8_t length = _readPos;
+      const uint8_t length = _readPos;
       _readPos = 0;
       return processIncomingPacket(_readBuffer, length, value);
     }
@@ -475,7 +484,7 @@ bool P093_data_struct::readIncommingBytes() {
 }
 
 bool P093_data_struct::processIncomingPacket(const uint8_t *packet, uint8_t length, uint8_t checksum) {
-  P093_data_struct::State state = checkIncomingPacket(packet, length, checksum);
+  const P093_data_struct::State state = checkIncomingPacket(packet, length, checksum);
 
   if (state == StatusUpdated) {
     static const uint8_t dataPartOffset = 5;
@@ -557,9 +566,9 @@ bool P093_data_struct::parseValues(const uint8_t *data, size_t length) {
 }
 
 P093_data_struct::State P093_data_struct::checkIncomingPacket(const uint8_t *packet, uint8_t length, uint8_t checksum) {
-# ifdef PLUGIN_093_DEBUG
+  # ifdef PLUGIN_093_DEBUG
   addLog(LOG_LEVEL_DEBUG_MORE, dumpIncomingPacket(packet, length));
-# endif // ifdef PLUGIN_093_DEBUG
+  # endif // ifdef PLUGIN_093_DEBUG
 
   if ((packet[2] != 0x01) || (packet[3] != 0x30)) {
     # ifdef PLUGIN_093_DEBUG
@@ -568,11 +577,11 @@ P093_data_struct::State P093_data_struct::checkIncomingPacket(const uint8_t *pac
     return Invalid;
   }
 
-  uint8_t calculatedChecksum = checkSum(packet, length);
+  const uint8_t calculatedChecksum = checkSum(packet, length);
 
   if (calculatedChecksum != checksum) {
     # ifdef PLUGIN_093_DEBUG
-    addLog(LOG_LEVEL_DEBUG, String(F("M-AC: CIP(1) ")) + calculatedChecksum);
+    addLog(LOG_LEVEL_DEBUG, concat(F("M-AC: CIP(1) "), calculatedChecksum));
     # endif // ifdef PLUGIN_093_DEBUG
     return Invalid;
   }
@@ -613,7 +622,7 @@ bool P093_data_struct::findByMapping(const String& mapping, const Tuple list[],
   for (size_t index = 0; index < count; ++index) {
     const Tuple& tuple = list[index];
 
-    if (mapping.equals(tuple.mapping)) {
+    if (equals(mapping, tuple.mapping)) {
       value = tuple.value;
       return true;
     }
@@ -621,7 +630,7 @@ bool P093_data_struct::findByMapping(const String& mapping, const Tuple list[],
   return false;
 }
 
-  # ifdef PLUGIN_093_DEBUG
+# ifdef PLUGIN_093_DEBUG
 const __FlashStringHelper * P093_data_struct::stateToString_f(P093_data_struct::State state) {
   switch (state) {
     case Invalid: return F("Invalid");
@@ -643,15 +652,14 @@ String P093_data_struct::stateToString(P093_data_struct::State state) {
   String res = stateToString_f(state);
 
   if (res.isEmpty()) {
-    return String(F(" ")) + state;
+    return concat(F(" "), state);
   }
   return res;
 }
 
 void P093_data_struct::dumpPacket(const uint8_t *packet, size_t length, String& result) {
   for (size_t idx = 0; idx < length; ++idx) {
-    result += formatToHex(packet[idx], F(""));
-    result += ' ';
+    result += strformat(F("%02x "), packet[idx]);
   }
 }
 
@@ -669,7 +677,7 @@ String P093_data_struct::dumpIncomingPacket(const uint8_t *packet, int length) {
   return message;
 }
 
-  # endif // ifdef PLUGIN_093_DEBUG
+# endif // ifdef PLUGIN_093_DEBUG
 
 
 #endif // ifdef USES_P093
diff --git a/src/src/PluginStructs/P093_data_struct.h b/src/src/PluginStructs/P093_data_struct.h
index df95e65c3..af44c3768 100644
--- a/src/src/PluginStructs/P093_data_struct.h
+++ b/src/src/PluginStructs/P093_data_struct.h
@@ -118,13 +118,13 @@ private:
     ReadTimeout
   };
 
-  static const uint8_t Temperature        = 0x01;
-  static const uint8_t Power              = 0x02;
-  static const uint8_t Mode               = 0x04;
-  static const uint8_t Fan                = 0x08;
-  static const uint8_t Vane               = 0x10;
-  static const uint8_t WideVane           = 0x20;
-  static const uint8_t RemoteTemperature  = 0x30;
+  static const uint8_t Temperature       = 0x01;
+  static const uint8_t Power             = 0x02;
+  static const uint8_t Mode              = 0x04;
+  static const uint8_t Fan               = 0x08;
+  static const uint8_t Vane              = 0x10;
+  static const uint8_t WideVane          = 0x20;
+  static const uint8_t RemoteTemperature = 0x30;
 
   struct WriteStatus {
     WriteStatus() : _flags(0) {}
diff --git a/src/src/PluginStructs/P094_Filter.cpp b/src/src/PluginStructs/P094_Filter.cpp
new file mode 100644
index 000000000..5de2ec70c
--- /dev/null
+++ b/src/src/PluginStructs/P094_Filter.cpp
@@ -0,0 +1,463 @@
+#include "../PluginStructs/P094_Filter.h"
+
+#ifdef USES_P094
+
+
+# include "../DataStructs/mBusPacket.h"
+
+# include "../Globals/ESPEasy_time.h"
+# include "../Globals/TimeZone.h"
+# include "../Helpers/ESPEasy_Storage.h"
+# include "../Helpers/StringConverter.h"
+
+
+// *INDENT-OFF*
+# define P094_FILTER_WEBARG_LABEL(x)         getPluginCustomArgName((x * 10) + 10)
+# define P094_FILTER_WEBARG_MANUFACTURER(x)  getPluginCustomArgName((x * 10) + 11)
+# define P094_FILTER_WEBARG_METERTYPE(x)     getPluginCustomArgName((x * 10) + 12)
+# define P094_FILTER_WEBARG_SERIAL(x)        getPluginCustomArgName((x * 10) + 13)
+# define P094_FILTER_WEBARG_FILTER_WINDOW(x) getPluginCustomArgName((x * 10) + 14)
+// *INDENT-ON*
+
+const char P094_Filter_Window_names[] PROGMEM = "none|all|1m|5m|15m|1h|day|month|once";
+
+P094_Filter_Window get_FilterWindow(const String& str)
+{
+  char tmp[10]{};
+  const int command_i = GetCommandCode(tmp, sizeof(tmp), str.c_str(), P094_Filter_Window_names);
+
+  if (command_i == -1) {
+    // No match found
+    return P094_Filter_Window::None;
+  }
+  return static_cast(command_i);
+}
+
+String Filter_WindowToString(P094_Filter_Window filterWindow)
+{
+  char   tmp[10]{};
+  String res(GetTextIndexed(tmp, sizeof(tmp), static_cast(filterWindow), P094_Filter_Window_names));
+
+  return res;
+}
+
+P094_filter::P094_filter() {
+  _filter._manufacturer = mBus_packet_wildcard_manufacturer;
+  _filter._meterType    = mBus_packet_wildcard_metertype;
+  _filter._serialNr     = mBus_packet_wildcard_serial;
+  _filter._filterWindow = static_cast(P094_Filter_Window::None);
+}
+
+void P094_filter::fromString(String str)
+{
+  // Set everything to wildcards
+  _filter._manufacturer = mBus_packet_wildcard_manufacturer;
+  _filter._meterType    = mBus_packet_wildcard_metertype;
+  _filter._serialNr     = mBus_packet_wildcard_serial;
+  _filter._filterWindow = static_cast(P094_Filter_Window::None);
+
+  const int semicolonPos = str.indexOf(';');
+
+  if (semicolonPos != -1) {
+    _filter._filterWindow = static_cast(get_FilterWindow(str.substring(semicolonPos + 1)));
+    str                   = str.substring(0, semicolonPos);
+  }
+
+  for (size_t i = 0; i < 3; ++i) {
+    String tmp;
+
+    if (GetArgv(str.c_str(), tmp, (i + 1), '.')) {
+      if (!(tmp.isEmpty() || tmp.startsWith(F("*")))) {
+        if (i != 0) {
+          // Make sure the numerical values are parsed as HEX
+          if (!tmp.startsWith(F("0x")) && !tmp.startsWith(F("0X"))) {
+            tmp = concat(F("0x"), tmp);
+          }
+        }
+
+        switch (i) {
+          case 0: // Manufacturer
+            _filter._manufacturer = mBusPacket_header_t::encodeManufacturerID(tmp);
+            break;
+          case 1: // Meter type
+          {
+            int32_t metertype = mBus_packet_wildcard_metertype;
+
+            if (validIntFromString(tmp, metertype)) {
+              _filter._meterType = metertype;
+            }
+            break;
+          }
+          case 2: // Serial
+          {
+            int32_t serial = mBus_packet_wildcard_serial;
+
+            if (validIntFromString(tmp, serial)) {
+              _filter._serialNr = serial;
+            }
+            break;
+          }
+        }
+      }
+    }
+  }
+}
+
+String P094_filter::toString() const
+{
+  String res;
+
+  res += getManufacturer();
+  res += '.';
+
+  res += getMeterType();
+  res += '.';
+
+  res += getSerial();
+  res += ';';
+
+  res += Filter_WindowToString(getFilterWindow());
+
+  return res;
+}
+
+const uint8_t * P094_filter::toBinary(size_t& size) const
+{
+  size = getBinarySize();
+  return (uint8_t *)this;
+}
+
+size_t P094_filter::fromBinary(const uint8_t *data)
+{
+  memcpy(this, data, getBinarySize());
+  return getBinarySize();
+}
+
+bool P094_filter::isValid() const
+{
+  if ((_filter._manufacturer == 0) &&
+      (_filter._meterType == 0) &&
+      (_filter._serialNr == 0) &&
+      (getFilterWindow() == P094_Filter_Window::None)) {
+    return false;
+  }
+  return
+    !isWildcardManufacturer() ||
+    !isWildcardMeterType() ||
+    !isWildcardSerial() ||
+    getFilterWindow() != P094_Filter_Window::None;
+}
+
+bool P094_filter::operator<(const P094_filter& rhs) const
+{
+  if (isValid() != rhs.isValid()) {
+    return isValid();
+  }
+/*
+  // Disable sorting, only sort by having valid filters at top.
+  if (isWildcardManufacturer() != rhs.isWildcardManufacturer()) {
+    return rhs.isWildcardManufacturer();
+  }
+
+  if (isWildcardMeterType() != rhs.isWildcardMeterType()) {
+    return rhs.isWildcardMeterType();
+  }
+
+  if (isWildcardSerial() != rhs.isWildcardSerial()) {
+    return rhs.isWildcardSerial();
+  }
+
+  if (!isWildcardManufacturer() && (_filter._manufacturer != rhs._filter._manufacturer)) {
+    return _filter._manufacturer < rhs._filter._manufacturer;
+  }
+
+  if (!isWildcardMeterType() && (_filter._meterType != rhs._filter._meterType)) {
+    return _filter._meterType < rhs._filter._meterType;
+  }
+
+  if (!isWildcardSerial() && (_filter._serialNr != rhs._filter._serialNr)) {
+    return _filter._serialNr < rhs._filter._serialNr;
+  }
+*/
+  return false;
+}
+
+bool P094_filter::operator==(const P094_filter& rhs) const
+{
+  return equals(*this, rhs);
+}
+
+bool P094_filter::operator!=(const P094_filter& rhs) const
+{
+  return !equals(*this, rhs);
+}
+
+bool P094_filter::equals(const P094_filter& lhs, const P094_filter& rhs)
+{
+  if (!lhs.isValid() && !rhs.isValid()) { return true; }
+
+  return lhs.toString() == rhs.toString();
+}
+
+size_t P094_filter::getBinarySize()
+{
+  // Only store the filter
+  constexpr size_t P094_filter_size = sizeof(_filter);
+
+  return P094_filter_size;
+}
+
+bool P094_filter::matches(const mBusPacket_header_t& other) const
+{
+  if (!isWildcardManufacturer()) {
+    if (_filter._manufacturer != other._manufacturer) { return false; }
+  }
+
+  if (!isWildcardMeterType()) {
+    if (_filter._meterType != other._meterType) { return false; }
+  }
+
+  if (!isWildcardSerial()) {
+    if (_filter._serialNr != other._serialNr) { return false; }
+  }
+
+  return true;
+}
+
+unsigned long P094_filter::computeUnixTimeExpiration() const
+{
+  // Match the interval window.
+  const P094_Filter_Window filterWindow = getFilterWindow();
+
+  if ((filterWindow == P094_Filter_Window::None) ||
+      (filterWindow == P094_Filter_Window::Once)) {
+    // Return date infinitely far in the future
+    return 0xFFFFFFFF;
+  }
+
+  if (filterWindow == P094_Filter_Window::All) {
+    // Return timestamp in the past
+    return 0;
+  }
+
+  // Using UnixTime
+  const unsigned long currentTime = node_time.getUnixTime();
+  unsigned long window_max        = currentTime;
+
+  if ((filterWindow == P094_Filter_Window::One_hour) ||
+      (filterWindow == P094_Filter_Window::Day) ||
+      (filterWindow == P094_Filter_Window::Month))
+  {
+    // Create time struct in local time.
+    struct tm tm_max;
+    breakTime(time_zone.toLocal(currentTime), tm_max);
+    tm_max.tm_sec = 59;
+    tm_max.tm_min = 59;
+
+    if (filterWindow == P094_Filter_Window::Day) {
+      // Using local time, thus incl. timezone and DST.
+      if (tm_max.tm_hour < 23) {
+        // Either:
+        // - between 00:00 and 12:00 => Max: 11:59:59
+        // - between 12:00 and 23:00 => Max: 22:59:59
+
+        tm_max.tm_hour = (tm_max.tm_hour < 12) ? 11 : 22;
+      } else {
+        // between 23:00 and 00:00 => Max: 23:59:59
+        tm_max.tm_hour = 23;
+      }
+    } else if (filterWindow == P094_Filter_Window::Month) {
+      // First set minute to midnight of today => Max: 23:59:59
+      tm_max.tm_hour = 23;
+
+      if (tm_max.tm_mday < 15) {
+        // - between 1st of month 00:00:00 and 15th of month 00:00:00
+        tm_max.tm_mday = 14;
+      } else {
+        // Check if this is the last day of the month.
+        // Add 24h to the time and see if it is still the same month.
+        const uint8_t maxMonthDay = getMonthDays(tm_max);
+
+        if (tm_max.tm_mday < maxMonthDay) {
+          // - between 15th of month 00:00:00 and last of month 00:00:00
+          // So we must subtract one day.
+          tm_max.tm_mday = maxMonthDay - 1;
+        } else {
+          // - between last of month 00:00:00 and 1st of next month 00:00:00
+          // Thus do not change the date as it is already at the last day of the month
+        }
+      }
+    }
+
+    // Convert from local time.
+    window_max = time_zone.fromLocal(makeTime(tm_max));
+  } else {
+    switch (filterWindow) {
+      case P094_Filter_Window::One_minute:
+        window_max = currentTime - (currentTime % (1 * 60)) + (1 * 60 - 1);
+        break;
+      case P094_Filter_Window::Five_minutes:
+        window_max = currentTime - (currentTime % (5 * 60)) + (5 * 60 - 1);
+        break;
+      case P094_Filter_Window::Fifteen_minutes:
+        window_max = currentTime - (currentTime % (15 * 60)) + (15 * 60 - 1);
+        break;
+
+      default:
+        break;
+    }
+  }
+  return window_max;
+}
+
+void P094_filter::WebformLoad(uint8_t filterIndex) const
+{
+  addRowLabel_tr_id(
+    concat(F("Filter "), static_cast(filterIndex + 1)),
+    P094_FILTER_WEBARG_LABEL(filterIndex));
+
+  // Manufacturer
+  addTextBox(
+    P094_FILTER_WEBARG_MANUFACTURER(filterIndex),
+    getManufacturer(),
+    3, false, false, EMPTY_STRING, F("widenumber")
+# if FEATURE_TOOLTIPS
+    , F("Manufacturer")
+# endif // if FEATURE_TOOLTIPS
+    );
+
+  // Meter Type
+  addTextBox(
+    P094_FILTER_WEBARG_METERTYPE(filterIndex),
+    getMeterType(),
+    4, false, false, EMPTY_STRING, F("widenumber")
+# if FEATURE_TOOLTIPS
+    , F("Meter Type (HEX)")
+# endif // if FEATURE_TOOLTIPS
+    );
+
+  // Serial nr
+  addTextBox(
+    P094_FILTER_WEBARG_SERIAL(filterIndex),
+    getSerial(),
+    10, false, false, EMPTY_STRING, F("widenumber")
+# if FEATURE_TOOLTIPS
+    , F("Serial (HEX)")
+# endif // if FEATURE_TOOLTIPS
+    );
+
+  {
+    // Filter Window
+    const int optionValues[] = {
+      static_cast(P094_Filter_Window::All),
+      static_cast(P094_Filter_Window::One_minute),
+      static_cast(P094_Filter_Window::Five_minutes),
+      static_cast(P094_Filter_Window::Fifteen_minutes),
+      static_cast(P094_Filter_Window::One_hour),
+      static_cast(P094_Filter_Window::Day),
+      static_cast(P094_Filter_Window::Month),
+      static_cast(P094_Filter_Window::Once),
+      static_cast(P094_Filter_Window::None)
+    };
+
+    constexpr size_t nrOptions = sizeof(optionValues) / sizeof(optionValues[0]);
+
+    String options[nrOptions];
+
+    for (size_t i = 0; i < nrOptions; ++i) {
+      const P094_Filter_Window filterWindow = static_cast(optionValues[i]);
+      options[i] = Filter_WindowToString(filterWindow);
+    }
+    addSelector(P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex),
+                nrOptions,
+                options,
+                optionValues,
+                nullptr,
+                _filter._filterWindow,
+                false,
+                true,
+                F("widenumber")
+# if FEATURE_TOOLTIPS
+                , F("Filter Window")
+# endif // if FEATURE_TOOLTIPS
+                );
+  }
+}
+
+String P094_WebformSave_GetWebArg(const String& id) {
+  String webarg_str = webArg(id);
+
+  if (webarg_str.isEmpty()) {
+    webarg_str = '*';
+  }
+  return webarg_str;
+}
+
+bool P094_filter::WebformSave(uint8_t filterIndex)
+{
+  String filterString;
+
+  // Manufacturer
+  filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_MANUFACTURER(filterIndex));
+  filterString += '.';
+
+  // Meter Type
+  filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_METERTYPE(filterIndex));
+  filterString += '.';
+
+  // Serial nr
+  filterString += P094_WebformSave_GetWebArg(P094_FILTER_WEBARG_SERIAL(filterIndex));
+
+  fromString(filterString);
+
+  // Filter Window
+  _filter._filterWindow = getFormItemInt(
+    P094_FILTER_WEBARG_FILTER_WINDOW(filterIndex),
+    0);
+
+  return isValid();
+}
+
+String P094_filter::getManufacturer() const
+{
+  String manufacturer;
+
+  if (isWildcardManufacturer()) {
+    manufacturer = '*';
+  }  else {
+    manufacturer = mBusPacket_header_t::decodeManufacturerID(_filter._manufacturer);
+  }
+  return manufacturer;
+}
+
+String P094_filter::getMeterType() const
+{
+  String metertype;
+
+  if (isWildcardMeterType()) {
+    metertype = '*';
+  } else {
+    metertype = formatToHex_no_prefix(_filter._meterType, 2);
+  }
+  return metertype;
+}
+
+String P094_filter::getSerial() const
+{
+  String serial;
+
+  if (isWildcardSerial()) {
+    serial = '*';
+  } else {
+    serial = formatToHex_no_prefix(_filter._serialNr, 8);
+  }
+  return serial;
+}
+
+P094_Filter_Window P094_filter::getFilterWindow() const
+{
+  return static_cast(_filter._filterWindow);
+}
+
+
+#endif // ifdef USES_P094
\ No newline at end of file
diff --git a/src/src/PluginStructs/P094_Filter.h b/src/src/PluginStructs/P094_Filter.h
new file mode 100644
index 000000000..683923da0
--- /dev/null
+++ b/src/src/PluginStructs/P094_Filter.h
@@ -0,0 +1,95 @@
+#ifndef PLUGINSTRUCTS_P094_FILTER_H
+#define PLUGINSTRUCTS_P094_FILTER_H
+
+#include "../../_Plugin_Helper.h"
+#ifdef USES_P094
+
+# include "../DataStructs/mBusPacket.h"
+
+// Is stored, so do not change the int values.
+enum class P094_Filter_Window : uint8_t {
+  None            = 0, // no messages pass the filter
+  All             = 1, // Realtime, every message passes the filter
+  One_minute      = 2, // a message passes the filter every 1 minutes, aligned to time (00:00:00, 00:01:00, ...)
+  Five_minutes    = 3, // a message passes the filter every 5 minutes, aligned to time (00:00:00, 00:05:00, ...)
+  Fifteen_minutes = 4, // a message passes the filter every 15 minutes, aligned to time
+  One_hour        = 5, // a message passes the filter every hour, aligned to time
+  Day             = 6, // a message passes the filter once every day
+                       // - between 00:00 and 12:00,
+                       // - between 12:00 and 23:00 and
+                       // - between 23:00 and 00:00
+  Month = 7,           // a message passes the filter
+                       // - between 1st of month 00:00:00 and 15th of month 00:00:00
+                       // - between 15th of month 00:00:00 and last of month 00:00:00
+                       // - between last of month 00:00:00 and 1st of next month 00:00:00
+  Once = 8             // only one message passes the filter until next reboot
+};
+
+
+// Examples for a filter definition list
+//   EBZ.02.12345678;all
+//   *.02.*;15m
+//   TCH.44.*;Once
+//   *.*.*;5m
+
+struct P094_filter {
+  P094_filter();
+
+  void           fromString(String str);
+  String         toString() const;
+
+  const uint8_t* toBinary(size_t& size) const;
+  size_t         fromBinary(const uint8_t *data);
+
+  // Is valid when it doesn't match: *.*.*;none
+  bool           isValid() const;
+
+  bool operator<(const P094_filter& rhs) const;
+  bool operator==(const P094_filter& rhs) const;
+  bool operator!=(const P094_filter& rhs) const;
+
+  static bool equals(const P094_filter& lhs, const P094_filter& rhs);
+
+  static size_t  getBinarySize();
+
+
+  // Check to see if the manufacturer, metertype and serial matches.
+  bool          matches(const mBusPacket_header_t& other) const;
+
+  // Compute expiration UnixTime
+  unsigned long computeUnixTimeExpiration() const;
+
+  void          WebformLoad(uint8_t filterIndex) const;
+  bool          WebformSave(uint8_t filterIndex);
+
+  bool          isWildcardManufacturer() const {
+    return _filter._manufacturer == mBus_packet_wildcard_manufacturer;
+  }
+
+  bool isWildcardMeterType() const {
+    return _filter._meterType == mBus_packet_wildcard_metertype;
+  }
+
+  bool isWildcardSerial() const {
+    return _filter._serialNr == mBus_packet_wildcard_serial;
+  }
+
+  String             getManufacturer() const;
+  String             getMeterType() const;
+  String             getSerial() const;
+  P094_Filter_Window getFilterWindow() const;
+
+  // Keep this order of members as this is how it will be stored.
+  struct {
+    uint64_t _serialNr     : 32;
+    uint64_t _manufacturer : 16;
+    uint64_t _meterType    : 8;
+
+    // Use for filtering
+    uint64_t _filterWindow : 8;
+  } _filter;
+};
+
+#endif // ifdef USES_P094
+
+#endif // ifndef PLUGINSTRUCTS_P094_FILTER_H
\ No newline at end of file
diff --git a/src/src/PluginStructs/P094_data_struct.cpp b/src/src/PluginStructs/P094_data_struct.cpp
index 30a8449a7..6431e7930 100644
--- a/src/src/PluginStructs/P094_data_struct.cpp
+++ b/src/src/PluginStructs/P094_data_struct.cpp
@@ -1,492 +1,848 @@
-#include "../PluginStructs/P094_data_struct.h"
-
-#ifdef USES_P094
-
-// Needed also here for PlatformIO's library finder as the .h file 
-// is in a directory which is excluded in the src_filter
-#include 
-
-#include 
-
-#include "../Globals/ESPEasy_time.h"
-#include "../Helpers/StringConverter.h"
-
-
-P094_data_struct::P094_data_struct() :  easySerial(nullptr) {
-  for (int i = 0; i < P094_NR_FILTERS; ++i) {
-    valueType_index[i] = P094_Filter_Value_Type::P094_not_used;
-    filter_comp[i] = P094_Filter_Comp::P094_Equal_OR;
-  }
-}
-
-P094_data_struct::~P094_data_struct() {
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-
-void P094_data_struct::reset() {
-  if (easySerial != nullptr) {
-    delete easySerial;
-    easySerial = nullptr;
-  }
-}
-
-bool P094_data_struct::init(ESPEasySerialPort port, 
-                            const int16_t serial_rx, 
-                            const int16_t serial_tx, 
-                            unsigned long baudrate) {
-  if ((serial_rx < 0) && (serial_tx < 0)) {
-    return false;
-  }
-  reset();
-  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx);
-
-  if (isInitialized()) {
-    easySerial->begin(baudrate);
-    return true;
-  }
-  return false;
-}
-
-void P094_data_struct::post_init() {
-  for (uint8_t i = 0; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) {
-    valueType_used[i] = false;
-  }
-
-  for (uint8_t i = 0; i < P094_NR_FILTERS; ++i) {
-    size_t lines_baseindex            = P094_Get_filter_base_index(i);
-    int    index                      = _lines[lines_baseindex].toInt();
-    int    tmp_filter_comp            = _lines[lines_baseindex + 2].toInt();
-    const bool filter_string_notempty = _lines[lines_baseindex + 3].length() > 0;
-    const bool valid_index            = index >= 0 && index < P094_FILTER_VALUE_Type_NR_ELEMENTS;
-    const bool valid_filter_comp      = tmp_filter_comp >= 0 && tmp_filter_comp < P094_FILTER_COMP_NR_ELEMENTS;
-
-    valueType_index[i] = P094_not_used;
-
-    if (valid_index && valid_filter_comp && filter_string_notempty) {
-      valueType_used[index] = true;
-      valueType_index[i]    = static_cast(index);
-      filter_comp[i]        = static_cast(tmp_filter_comp);
-    }
-  }
-}
-
-bool P094_data_struct::isInitialized() const {
-  return easySerial != nullptr;
-}
-
-void P094_data_struct::sendString(const String& data) {
-  if (isInitialized()) {
-    if (data.length() > 0) {
-      setDisableFilterWindowTimer();
-      easySerial->write(data.c_str());
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        String log = F("Proxy: Sending: ");
-        log += data;
-        addLogMove(LOG_LEVEL_INFO, log);
-      }
-    }
-  }
-}
-
-bool P094_data_struct::loop() {
-  if (!isInitialized()) {
-    return false;
-  }
-  bool fullSentenceReceived = false;
-
-  if (easySerial != nullptr) {
-    int available = easySerial->available();
-
-    unsigned long timeout = millis() + 10;
-
-    while (available > 0 && !fullSentenceReceived) {
-      // Look for end marker
-      char c = easySerial->read();
-      --available;
-
-      if (available == 0) {
-        if (!timeOutReached(timeout)) {
-          available = easySerial->available();
-        }
-        delay(0);
-      }
-
-      switch (c) {
-        case 13:
-        {
-          const size_t length = sentence_part.length();
-          bool valid          = length > 0;
-
-          for (size_t i = 0; i < length && valid; ++i) {
-            if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) {
-              sentence_part = String();
-              ++sentences_received_error;
-              valid = false;
-            }
-          }
-          if (valid) {
-            fullSentenceReceived = true;
-          }
-          break;
-        }
-        case 10:
-
-          // Ignore LF
-          break;
-        default:
-          if (c >= 32 && c < 127) {
-            sentence_part += c;
-          } else {
-            current_sentence_errored = true;
-          }
-          break;
-      }
-
-      if (max_length_reached()) { fullSentenceReceived = true; }
-    }
-  }
-
-  if (fullSentenceReceived) {
-    ++sentences_received;
-    length_last_received = sentence_part.length();
-  }
-  return fullSentenceReceived;
-}
-
-const String& P094_data_struct::peekSentence() const {
-  return sentence_part;
-}
-
-void P094_data_struct::getSentence(String& string, bool appendSysTime) {
-  string = std::move(sentence_part);
-  sentence_part = String(); // FIXME TD-er: Should not be needed as move already cleared it.
-  if (appendSysTime) {
-    // Unix timestamp = 10 decimals + separator
-    if (string.reserve(sentence_part.length() + 11)) {
-      string += ';';
-      string += node_time.getUnixTime();
-    }
-  }
-  sentence_part.reserve(string.length());
-}
-
-void P094_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const {
-  succes      = sentences_received;
-  error       = sentences_received_error;
-  length_last = length_last_received;
-}
-
-void P094_data_struct::setMaxLength(uint16_t maxlenght) {
-  max_length = maxlenght;
-}
-
-void P094_data_struct::setLine(uint8_t varNr, const String& line) {
-  if (varNr < P94_Nlines) {
-    _lines[varNr] = line;
-  }
-}
-
-uint32_t P094_data_struct::getFilterOffWindowTime() const {
-  return _lines[P094_FILTER_OFF_WINDOW_POS].toInt();
-}
-
-P094_Match_Type P094_data_struct::getMatchType() const {
-  return static_cast(_lines[P094_MATCH_TYPE_POS].toInt());
-}
-
-bool P094_data_struct::invertMatch() const {
-  switch (getMatchType()) {
-    case P094_Regular_Match:
-      break;
-    case P094_Regular_Match_inverted:
-      return true;
-    case P094_Filter_Disabled:
-      break;
-  }
-  return false;
-}
-
-bool P094_data_struct::filterUsed(uint8_t lineNr) const
-{
-  if (valueType_index[lineNr] == P094_Filter_Value_Type::P094_not_used) { return false; }
-  uint8_t varNr = P094_Get_filter_base_index(lineNr);
-  return _lines[varNr + 3].length() > 0;
-}
-
-String P094_data_struct::getFilter(uint8_t lineNr, P094_Filter_Value_Type& filterValueType, uint32_t& optional,
-                                   P094_Filter_Comp& comparator) const
-{
-  uint8_t varNr = P094_Get_filter_base_index(lineNr);
-
-  filterValueType = P094_Filter_Value_Type::P094_not_used;
-
-  if ((varNr + 3) >= P94_Nlines) { return ""; }
-  optional        = _lines[varNr + 1].toInt();
-  filterValueType = valueType_index[lineNr];
-  comparator      = filter_comp[lineNr];
-
-  //  filterValueType = static_cast(_lines[varNr].toInt());
-  //  comparator      = static_cast(_lines[varNr + 2].toInt());
-  return _lines[varNr + 3];
-}
-
-void P094_data_struct::setDisableFilterWindowTimer() {
-  if (getFilterOffWindowTime() == 0) {
-    disable_filter_window = 0;
-  }
-  else {
-    disable_filter_window = millis() + getFilterOffWindowTime();
-  }
-}
-
-bool P094_data_struct::disableFilterWindowActive() const {
-  if (disable_filter_window != 0) {
-    if (!timeOutReached(disable_filter_window)) {
-      // We're still in the window where filtering is disabled
-      return true;
-    }
-  }
-  return false;
-}
-
-bool P094_data_struct::parsePacket(const String& received) const {
-  size_t strlength = received.length();
-
-  if (strlength == 0) {
-    return false;
-  }
-
-
-  if (getMatchType() == P094_Filter_Disabled) {
-    return true;
-  }
-
-  bool match_result = false;
-
-  // FIXME TD-er: For now added '$' to test with GPS.
-  if ((received[0] == 'b') || (received[0] == '$')) {
-    // Received a data packet in CUL format.
-    if (strlength < 21) {
-      return false;
-    }
-
-    // Decoded packet
-
-    unsigned long packet_header[P094_FILTER_VALUE_Type_NR_ELEMENTS];
-    packet_header[P094_packet_length] = hexToUL(received, 1, 2);
-    packet_header[P094_unknown1]      = hexToUL(received, 3, 2);
-    packet_header[P094_manufacturer]  = hexToUL(received, 5, 4);
-    packet_header[P094_serial_number] = hexToUL(received, 9, 8);
-    packet_header[P094_unknown2]      = hexToUL(received, 17, 2);
-    packet_header[P094_meter_type]    = hexToUL(received, 19, 2);
-
-    // FIXME TD-er: Is this also correct?
-    packet_header[P094_rssi] = hexToUL(received, strlength - 4, 4);
-
-    // FIXME TD-er: Is this correct?
-    // match_result = packet_length == (strlength - 21) / 2;
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log;
-      if (log.reserve(128)) {
-        log  = F("CUL Reader: ");
-        log += F(" length: ");
-        log += packet_header[P094_packet_length];
-        log += F(" (header: ");
-        log += strlength - (packet_header[P094_packet_length] * 2);
-        log += F(") manu: ");
-        log += formatToHex_decimal(packet_header[P094_manufacturer]);
-        log += F(" serial: ");
-        log += formatToHex_decimal(packet_header[P094_serial_number]);
-        log += F(" mType: ");
-        log += formatToHex_decimal(packet_header[P094_meter_type]);
-        log += F(" RSSI: ");
-        log += formatToHex_decimal(packet_header[P094_rssi]);
-        addLogMove(LOG_LEVEL_INFO, log);
-      }
-    }
-
-    bool filter_matches[P094_NR_FILTERS];
-
-    for (unsigned int f = 0; f < P094_NR_FILTERS; ++f) {
-      filter_matches[f] = false;
-    }
-
-    // Do not check for "not used" (0)
-    for (unsigned int i = 1; i < P094_FILTER_VALUE_Type_NR_ELEMENTS; ++i) {
-      if (valueType_used[i]) {
-        for (unsigned int f = 0; f < P094_NR_FILTERS; ++f) {
-          if (valueType_index[f] == i) {
-            // Have a matching filter
-
-            uint32_t optional;
-            P094_Filter_Value_Type filterValueType;
-            P094_Filter_Comp comparator;
-            bool   match = false;
-            String inputString;
-            String valueString;
-
-            if (i == P094_Filter_Value_Type::P094_position) {
-              valueString = getFilter(f, filterValueType, optional, comparator);
-
-              if (received.length() >= (optional + valueString.length())) {
-                // received string is long enough to fit the expression.
-                inputString = received.substring(optional, optional + valueString.length());
-                match = inputString.equalsIgnoreCase(valueString);
-              }
-            } else {
-              unsigned long value = hexToUL(getFilter(f, filterValueType, optional, comparator));
-              match       = (value == packet_header[i]);
-              inputString = formatToHex_decimal(packet_header[i]);
-              valueString = formatToHex_decimal(value);
-            }
-
-
-            if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-              String log;
-              if (log.reserve(64)) {
-                log += F("CUL Reader: ");
-                log += P094_FilterValueType_toString(valueType_index[f]);
-                log += F(":  in:");
-                log += inputString;
-                log += ' ';
-                log += P094_FilterComp_toString(comparator);
-                log += ' ';
-                log += valueString;
-
-                switch (comparator) {
-                  case P094_Filter_Comp::P094_Equal_OR:
-                  case P094_Filter_Comp::P094_Equal_MUST:
-
-                    if (match) { log += F(" expected MATCH"); } 
-                    break;
-                  case P094_Filter_Comp::P094_NotEqual_OR:
-                  case P094_Filter_Comp::P094_NotEqual_MUST:
-
-                    if (!match) { log += F(" expected NO MATCH"); }
-                    break;
-                }
-                addLogMove(LOG_LEVEL_INFO, log);
-              }
-            }
-
-            switch (comparator) {
-              case P094_Filter_Comp::P094_Equal_OR:
-
-                if (match) { filter_matches[f] = true; }
-                break;
-              case P094_Filter_Comp::P094_NotEqual_OR:
-
-                if (!match) { filter_matches[f] = true; }
-                break;
-
-              case P094_Filter_Comp::P094_Equal_MUST:
-
-                if (!match) { return false; }
-                break;
-
-              case P094_Filter_Comp::P094_NotEqual_MUST:
-
-                if (match) { return false; }
-                break;
-            }
-          }
-        }
-      }
-    }
-
-    // Now we have to check if all rows per filter line in filter_matches[f] are true or not used.
-    int nrMatches = 0;
-    int nrNotUsed = 0;
-
-    for (unsigned int f = 0; !match_result && f < P094_NR_FILTERS; ++f) {
-      if (f % P094_AND_FILTER_BLOCK == 0) {
-        if ((nrMatches > 0) && ((nrMatches + nrNotUsed) == P094_AND_FILTER_BLOCK)) {
-          match_result = true;
-        }
-        nrMatches = 0;
-        nrNotUsed = 0;
-      }
-
-      if (filter_matches[f]) {
-        ++nrMatches;
-      } else {
-        if (!filterUsed(f)) {
-          ++nrNotUsed;
-        }
-      }
-    }
-  } else {
-    switch (received[0]) {
-      case 'C': // CMODE
-      case 'S': // SMODE
-      case 'T': // TMODE
-      case 'O': // OFF
-      case 'V': // Version info
-
-        // FIXME TD-er: Must test the result of the other possible answers.
-        match_result = true;
-        break;
-    }
-  }
-
-  return match_result;
-}
-
-const __FlashStringHelper * P094_data_struct::MatchType_toString(P094_Match_Type matchType) {
-  switch (matchType)
-  {
-    case P094_Match_Type::P094_Regular_Match:          return F("Regular Match");
-    case P094_Match_Type::P094_Regular_Match_inverted: return F("Regular Match inverted");
-    case P094_Match_Type::P094_Filter_Disabled:        return F("Filter Disabled");
-  }
-  return F("");
-}
-
-const __FlashStringHelper * P094_data_struct::P094_FilterValueType_toString(P094_Filter_Value_Type valueType)
-{
-  switch (valueType) {
-    case P094_Filter_Value_Type::P094_not_used:      return F("---");
-    case P094_Filter_Value_Type::P094_packet_length: return F("Packet Length");
-    case P094_Filter_Value_Type::P094_unknown1:      return F("unknown1");
-    case P094_Filter_Value_Type::P094_manufacturer:  return F("Manufacturer");
-    case P094_Filter_Value_Type::P094_serial_number: return F("Serial Number");
-    case P094_Filter_Value_Type::P094_unknown2:      return F("unknown2");
-    case P094_Filter_Value_Type::P094_meter_type:    return F("Meter Type");
-    case P094_Filter_Value_Type::P094_rssi:          return F("RSSI");
-    case P094_Filter_Value_Type::P094_position:      return F("Position");
-
-      //    default: break;
-  }
-  return F("unknown");
-}
-
-const __FlashStringHelper * P094_data_struct::P094_FilterComp_toString(P094_Filter_Comp comparator)
-{
-  switch (comparator) {
-    case P094_Filter_Comp::P094_Equal_OR:      return F("==");
-    case P094_Filter_Comp::P094_NotEqual_OR:   return F("!=");
-    case P094_Filter_Comp::P094_Equal_MUST:    return F("== (must)");
-    case P094_Filter_Comp::P094_NotEqual_MUST: return F("!= (must)");
-  }
-  return F("");
-}
-
-bool P094_data_struct::max_length_reached() const {
-  if (max_length == 0) { return false; }
-  return sentence_part.length() >= max_length;
-}
-
-size_t P094_data_struct::P094_Get_filter_base_index(size_t filterLine) {
-  return filterLine * P094_ITEMS_PER_FILTER + P094_FIRST_FILTER_POS;
-}
-
-uint32_t P094_data_struct::getDebugCounter() {
-  return debug_counter++;
-}
-
-#endif // USES_P094
\ No newline at end of file
+#include "../PluginStructs/P094_data_struct.h"
+
+#ifdef USES_P094
+
+// Needed also here for PlatformIO's library finder as the .h file
+// is in a directory which is excluded in the src_filter
+# include 
+
+# include 
+
+#include 
+
+# include "../DataStructs/mBusPacket.h"
+# include "../Globals/MQTT.h"
+
+// # include "../Globals/ESPEasy_time.h"
+// # include "../Globals/TimeZone.h"
+// # include "../Helpers/ESPEasy_Storage.h"
+// # include "../Helpers/StringConverter.h"
+
+
+P094_data_struct::P094_data_struct() :  easySerial(nullptr) {}
+
+P094_data_struct::~P094_data_struct() {
+  if (easySerial != nullptr) {
+    delete easySerial;
+    easySerial = nullptr;
+  }
+}
+
+void P094_data_struct::reset() {
+  if (easySerial != nullptr) {
+    delete easySerial;
+    easySerial = nullptr;
+  }
+}
+
+bool P094_data_struct::init(ESPEasySerialPort port,
+                            const int16_t     serial_rx,
+                            const int16_t     serial_tx,
+                            unsigned long     baudrate) {
+  if ((serial_rx < 0) && (serial_tx < 0)) {
+    return false;
+  }
+  reset();
+  easySerial = new (std::nothrow) ESPeasySerial(port, serial_rx, serial_tx);
+
+  if (easySerial == nullptr) {
+    return false;
+  }
+  easySerial->begin(baudrate);
+  return true;
+}
+
+void  P094_data_struct::setFlags(unsigned long filterOffWindowTime_ms,
+                bool          intervalFilterEnabled,
+                bool          mute,
+                bool          collectStats)
+{
+  filterOffWindowTime     = filterOffWindowTime_ms;
+  interval_filter.enabled = intervalFilterEnabled;
+  collect_stats           = collectStats;
+  mute_messages           = mute;
+}
+
+
+void P094_data_struct::loadFilters(struct EventStruct *event, uint8_t nrFilters)
+{
+  int offset_in_block = 0;
+
+
+  const size_t chunkSize    = P094_filter::getBinarySize();
+  const size_t maxNrFilters = 1024u / chunkSize;
+
+  if (nrFilters > maxNrFilters) { nrFilters = maxNrFilters; }
+
+  _filters.clear();
+
+  size_t nrChunks = 8;
+
+  if (nrFilters < nrChunks) {
+    nrChunks = nrFilters;
+  }
+
+  const size_t bufferSize = nrChunks * chunkSize;
+
+  while (nrFilters > 0) {
+    uint8_t buffer[bufferSize];
+    ZERO_FILL(buffer);
+
+    LoadCustomTaskSettings(event->TaskIndex, buffer, bufferSize, offset_in_block);
+    offset_in_block += bufferSize;
+
+    uint8_t *readPos = buffer;
+
+    for (size_t i = 0; i < nrChunks && nrFilters > 0; ++i) {
+      P094_filter filter;
+      filter.fromBinary(readPos);
+
+      if (filter.isValid()) {
+        _filters.push_back(filter);
+      }
+
+      --nrFilters;
+      readPos += chunkSize;
+    }
+  }
+}
+
+String P094_data_struct::saveFilters(struct EventStruct *event) const
+{
+  int offset_in_block = 0;
+
+  String res;
+  const size_t nrFilters = _filters.size();
+  size_t currentFilter = 0;
+  const size_t chunkSize = P094_filter::getBinarySize();
+  const size_t nrChunks  = 8;
+  #ifdef ESP32
+  const size_t bufferSize = 1024;
+  #else
+  const size_t bufferSize = 256;
+  #endif
+
+  std::vector buffer;
+  buffer.resize(bufferSize);
+  
+
+  while ((offset_in_block + bufferSize) <= 1024 && res.isEmpty()) {
+    for (auto it = buffer.begin(); it != buffer.end(); ++it) {
+      *it = 0;
+    }
+
+    uint8_t *writePos  = &buffer[0];
+    size_t   writeSize = 0;
+
+    while (writeSize < bufferSize && currentFilter < nrFilters) {
+      if (_filters[currentFilter].isValid()) {
+        size_t size{};
+        const uint8_t *binaryData = _filters[currentFilter].toBinary(size);
+        memcpy(writePos, binaryData, size);
+        writePos  += size;
+        writeSize += size;
+      }
+      ++currentFilter;
+    }
+    res              = SaveCustomTaskSettings(event->TaskIndex, &buffer[0], bufferSize, offset_in_block);
+    offset_in_block += bufferSize;
+  }
+  return res;
+}
+
+void P094_data_struct::clearFilters()
+{
+  _filters.clear();
+}
+
+bool P094_data_struct::addFilter(struct EventStruct *event, const String& filter)
+{
+  P094_filter f;
+
+  f.fromString(filter);
+
+  if (!f.isValid()) {
+    return false;
+  }
+
+  if (isDuplicate(f)) {
+    if (loglevelActiveFor(LOG_LEVEL_ERROR)) {
+      addLogMove(LOG_LEVEL_ERROR, concat(F("CUL Reader : Duplicate filter found: "), f.toString()));
+    }
+
+    return false;
+  }
+
+  _filters.push_back(f);
+
+  std::sort(_filters.begin(), _filters.end());
+
+  if (P094_NR_FILTERS < _filters.size()) {
+    P094_NR_FILTERS = _filters.size();
+  }
+  return true;
+}
+
+String P094_data_struct::getFiltersMD5() const
+{
+  if (mute_messages) {
+    return F("blockall");
+  }
+
+  MD5Builder md5;
+  uint8_t  checksum[16]{};
+  md5.begin();
+
+  uint8_t nrFiltersAdded = 0;
+  const char separator[] = {'|', 0};
+  for (auto it = _filters.begin(); it != _filters.end(); ++it) {
+    if (it->isValid()) {
+      if (nrFiltersAdded != 0) {
+        md5.add(separator);
+      }
+      md5.add(it->toString().c_str());
+      ++nrFiltersAdded;
+    }
+  }
+
+  if (nrFiltersAdded == 0) {
+    // No filters, thus all messages will just pass
+    return F("pass");
+  }
+
+  md5.calculate();
+  md5.getBytes(checksum);
+
+  return formatToHex_array(checksum, sizeof(checksum));
+}
+
+void P094_data_struct::WebformLoadFilters(uint8_t nrFilters) const
+{
+  if (nrFilters > 0) {
+    addFormNote(F("Filter Fields: Manufacturer, Meter Type, Serial, Filter Window"));
+  }
+
+  for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine)
+  {
+    if (filterLine < _filters.size()) {
+      _filters[filterLine].WebformLoad(filterLine);
+    } else {
+      P094_filter dummy;
+      dummy.WebformLoad(filterLine);
+    }
+  }
+}
+
+void P094_data_struct::WebformSaveFilters(struct EventStruct *event, uint8_t nrFilters)
+{
+  _filters.clear();
+
+  for (uint8_t filterLine = 0; filterLine < nrFilters; ++filterLine)
+  {
+    P094_filter dummy;
+
+    if (dummy.WebformSave(filterLine)) {
+      // Filter with filled in values, worth storing
+      if (!isDuplicate(dummy)) {
+        _filters.push_back(dummy);
+      }
+    }
+  }
+  addHtmlError(saveFilters(event));
+}
+
+bool P094_data_struct::isInitialized() const {
+  return easySerial != nullptr;
+}
+
+void P094_data_struct::sendString(const String& data) {
+  if (isInitialized()) {
+    if (data.length() > 0) {
+      setDisableFilterWindowTimer();
+      easySerial->write(data.c_str());
+
+      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+        addLogMove(LOG_LEVEL_INFO, concat(F("Proxy: Sending: "), data));
+      }
+    }
+  }
+}
+
+# if P094_DEBUG_OPTIONS
+
+const __FlashStringHelper* getDebugSentences(int& count) {
+  // *INDENT-OFF*
+  switch (count) {
+    case 1: return F("b3C449344369291352337D55472593107009344230A920000200C0538ECE32625004C0527262500426CBF2CCC0805BDF032262500C2086CDF21326CFFFF046D26BB1103DA22B4E093E2"); break; //QDS.0A.00073159"); break; //QDS.37.35919236
+    case 2: return F("b9644A732260729700A0AB8487A4E10002002747D00046D030AC1270CB02E0600000000446D3B17BF2C4C0600000083410084016D3B17DE268C010600000000CC190B0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22030402000F841001245C84E7"); break; //LUG.0A.70290726
+    case 3: return F("bCE44A8153132000801022ADC7F6900C005098D2F2D70E24F3E43458739FD572B2DB7CB22EA563C57F3017308E093A4CBC662DF70F000A2E2B18215FC7098DBC7DC8A2ABAD8202F700C5A7D8B0FC89094823FC6B54565730369E73039146898536381B5DE8B8F3A5377A807EB30383ACC0176176C6C18265932082844F0B5A3F69B0A66FD0E35FAED9A53B825E073FC1E67193A727C97BC1025229C87421FA0381443A5F2F2897AE44D383FE125614F08BABEC6B46DF0FFCB910DAD1B3CD53B44AA83726492D845F840A2D20B738E9FB212D5C74FF91FD2796A22D669CBF0B0FEC1BAFA171A65FFB165B2E9"); break; //EMH.02.08003231
+    case 4: return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162
+    case 5: return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180
+    case 6: return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346
+    case 7: return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000
+    case 8: return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966
+    case 9: return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036
+    case 10 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103
+    case 11 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039
+    case 12 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490
+    case 13 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015
+    case 14 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510
+    case 15 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337
+    case 16 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356
+    case 17 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224
+    case 18 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199
+    case 19 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075
+    case 20 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA
+    case 21 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424
+    case 22 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003
+    case 23 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003
+    case 24 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003
+    case 25 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182
+    case 26 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002
+    case 27 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764
+    case 28 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096
+    case 29 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869
+    case 30 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356
+    case 31 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608
+    case 32 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984
+    case 33 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984
+    case 34 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525
+    case 35 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660
+    case 36 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984
+    case 37 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672
+    case 38 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672
+    case 39 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371
+    case 40 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371
+    case 41 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917
+    case 42 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917
+    case 43 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177
+    case 44 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177
+    case 45 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484
+    case 46 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384
+    case 47 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384
+    case 48 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909
+    case 49 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909
+    case 50 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909
+    case 51 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234
+    case 52 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419
+    case 53 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082
+    case 54 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492
+    case 55 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105
+    case 56 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105
+    case 57 : return F("b8E44A81524229207010276807F680080056E68ED762185BEDAB68F79E2A8BF6562ED1C21105B74E82AA9D1B5E79311AB713DC2F86C785D18F6E470067B86284E8A9D1AF9F36330FE18968F826F8D1B21E99F1C08D45FB9CDBF04B37136E2FC1CED74FF3186482FA3058F6F1BD712BBE20098CCB0C72D51F6013CBB974678706CF2F4D6D0D07A793FE1EC8EAF701AD2BEE5922E69F72C356AC1B009A0E0646B0413DD45802B"); break; //EMH.02.07922224
+    case 58 : return F("b374465B2118222001604DDD67A26140000046D1107152A01FD0C06326E796CFFFF0DFF5F0C00083D300001061308131C0BFFFC02FD1700140C7896145559D9FF8032"); break; //LSE.04.00228211
+    case 59 : return F("b4BC465B251A16000F193DC2CA083110049244E01394465324604401311FC17067A4F0000000C13714800004C1310BC7A000000426C7F2C02BB560000326CFFFF420A046D2009912682046C9F258C04136643DF650000FFFF80DE"); break; //LSE.00.00000000
+    case 60 : return F("b4BC465B251A18000F1950519A08311009E2423003944653252044013781E17067A500000000C13063401004C1310623C000000426C7F2C02BB560000326CFFFF420A046D330E912682046C9F258C04138720452E01000B2780DD"); break; //LSE.00.00000000
+    case 61 : return F("b43C465B251A12001F0DDDD95A08310008A2460013144653221587296186735087A4F0000000B6E9102004B6E000041BB00426CFFFF326CFFFF046D2108912682BC3E046C9F258B046E9102000B2A80DD"); break; //LSE.00.00000000
+    case 62 : return F("b25C465B251A0000AF1033454A2653205834710290E862400005F3573BB1B9A276422A2271D001D0004C9253580E4"); break; //LSE.00.00000000
+    case 63 : return F("b4344A511031008667607CFF48C0031900F002C2531D311006D980E9CC8D28524C6417AE570210710CFF2DB65BFE77C51602690DAEB954A5455A2ABED621B74AC56A79D81390EBFCADC9D3D34F5928DDC"); break; //DME.07.66081003
+    case 64 : return F("b4344A511031008667607CFF48C0031900F002C2532D31100BC55A548A0082663A6FC7AE670210710C7B178D748F610E2CB16DE82C823EF83334EF1A0C383FB42DF7BED1846323211FC25DCC1EBF085DE"); break; //DME.07.66081003
+    case 65 : return F("b4344A511031008667607CFF48C0031900F002C2533D311004E0C2E86D68C08F425747AE77021071013F9284E0CB5896FFB27D33882716D09F7EC8175FD04516AAE9122E584095D6441E14E5B4ED189DD"); break; //DME.07.66081003
+    case 66 : return F("b6E44A511825169584004B8737AB500600554B11A7F9F7DDCAC35695A3191EA83FD79D1876F378419D5AB37CA1BD857243492B6379B258A4831015F9F0D4B7098218D7E7C44421422FCABC0770F0E67C16EFA13ABEE798D58062CDB0F06AA312592C085F046D29B64F7031FE17CC7EC8B44D61FB5E2F37301ACA7AAC025666C802C"); break; //DME.04.58695182
+    case 67 : return F("bYB04424341931031950067A800000002F2F0413F1570000046D092EC52504FD17004000000E780000000000004413184B0000426CBF2C840113F057000082016CDE24D3013B4E0400C4016D3128692C8104FD280182046CDE24840413F0570000C40413FD560000840513D1530000C40513CC4F0000840613184B0000C40678D313EA47000084071323430000C40713153F0000840813FD3B0000C4081393380000840913A3340000C4091311310000271081D7"); break; //MAD.06.19033119
+    case 68 : return F("b4E44A5113748906370076F917A570040053A66831E6B6C8F06FB2C7CC1F60A673B63ACBF2F6A8D3347D34DE4BBDD4677E5DEC850D5CBBDF5B0AA7095B415EFCD93A124AC3B884FAF226845C439DBF3A2EB486CBE7D8D845383E3E480ED"); break; //DME.07.63904837
+    case 69 : return F("b5344A511292535727507858F8C000E900F002C25823E090045AF7EFEDB43FBE691577A82003107105692A21A8C0C7531EEEAC3AA631D6BD790CA1B271C7E3C8860A189DA37AE30E89AB63AD316663EB4AD472596156E592602F8A016E51A5E0D8753"); break; //DME.07.72352529
+    case 70 : return F("b5344A5117157367276075A048C00B0900F002C25218501000C373A89C912675F09407A2100310710DEF25F58D772CBD51AD6D2B487ED868B6B086976F51AE52E65D877F0310E9DCA942F4E7F94196DD2821329E662FD4E4C9F20A1A8829C54328EEC"); break; //DME.07.72365771
+    case 71 : return F("b2E44A511880110827B0780AF7A4573200592F4DF44D5F258DF39AB376DD4A5BC338BBB2493257A98DC52C5E6299029DC79C01ED377C7F48028"); break; //DME.07.82100188
+    case 72 : return F("b3644E61E95413900010E89957212517615E61E3C074230206593240B570397DF6230FBC015B276D63CD694FA79B17D1436E0E9FA1003C0763761BD8FBD67B73A8D"); break; //GWF.07.15765112"); break; //GWF.0E.00394195
+    case 73 : return F("b9644FA12237704190007308E7AA8000020046D1E2B862B0413D1EA01F9CA0002FD17000001FD481D426C7F2C4413455C9E8A000084011370E50100C40113A0C200F70100840213EA9E0100C4021349870100735F8403130A700100C40313595301008404C386139F2B0100C4041345FA0000840513BABE2BCD0000C40513DEAA00008406139E8A00A1F700C40613D966000084071390400000C4F6040713E11C00008408136200000087F382DE"); break; //DWZ.07.19047723
+    case 74 : return F("bA944FA1283211320020766737A6B009025ADD27B2058B2ADAAE5B5D2C8B11BD295CE460BD1ECA50B533A58FF19F7F9DEC291893DA502B763E7C80CEF440B899C92DE87D5548838FC2C9FF3873927441B0B17222FB14545D2E5A0CF81216074B3DF0911CB99BD20A558F2D873A9A649092F3C7B01BA2BEBCC26A58C0FD99ABC2E5E0E5AFC241A27B8C0863C571C3D586B7E2A06044B792CFE021C21BB1B71BCEE4D5D84E75C3FC0624DF07C8C8832C82140086903FD0C08000002FD0B011116A380EF"); break; //DWZ.07.20132183
+    case 75 : return F("b3944934419504913170628617A660000200C13480900004C130000007BBF00426CFFFFCC081315020000C2086C9FC6D42A02BB560000326CFFFF046D1316952B82B084D3"); break; //QDS.06.13495019
+    case 76 : return F("b2E446850915944496262C568A0009F276D03B017BC0000060A0A0809A71C0908090706070A0A09090A0B0B080708CB640A060A07071EA586E2"); break; //TCH.62.49445991
+    case 77 : return F("b2E446850573060566562ADA6A0007E293200C0191400001400000000CC37000000000000000001010101010101023FB20101010001328686E1"); break; //TCH.62.56603057
+    case 78 : return F("b2E4468505162250070628CD3A0009F277D00600A0B00000001010101002A010302010101020102010101010101018F6C0001010102AF7786F8"); break; //TCH.62.00256251
+    case 79 : return F("b2F446850302976627462A01BA2064D280000600A240004000307070747EA0706060000000000000000000000000077A2000000000000FFFF80F6"); break; //TCH.62.62762930
+    case 80 : return F("b2F446850370321809562C217A2069F270C00A00E0700000001000001B0E800000100010100000101000100000100B1AD000101010001328680F9"); break; //TCH.62.80210337
+    case 81 : return F("b2D44653277993613170784017ABA0000000C13196100004C131100003AE500426C7F2C02BB560000326CFFFF046D2ACD2E068527CCD580DF"); break; //LSE.07.13369977
+    case 82 : return F("bY24442D2C394864681D168D20AE91BF1622CC255857FE7B524DD67C4944CD428FBE5E0DFAB98021"); break; //KAM.16.68644839
+    case 83 : return F("b2D446532490340131706417B7AB20000000C13814000004C13100000A12600426C7F2C02BB560000326CFFFF046D2ACD3B168327AC23800F"); break; //LSE.06.13400349
+    case 84 : return F("b3644E61E08106100020E7F627236090021AE4C01071B0020A56EE84D172DF9648D9C4409131B88D3FD85BFD022BCB54971CACB714D617E5563719B95E9B2059A59"); break; //SEN.07.21000936"); break; //GWF.0E.00611008
+    case 85 : return F("b3E44FA1287530019011691D07AC6002025D541F7DE1D910813127884F7DF4D80BF600A4323BD730B4639E4E0EA8B86129BDE9DD71D0F800C000109002487721866530201310101B10187CE"); break; //DWZ.16.19005387
+    case 86 : return F("b9644FA1261281221000689447AB9000020046D302BAF2704136300009A0E0002FD17000001FD481C426C000044130E9B0000000084011300000000C4011300002A2D000084021300000000C402130000000098F984031300000000C40313000000008404B0D51300000000C404130000000084051300A00E000000C4051300000000840613000000B5CF00C406130000000084071300000000C4E74A071300000000840813000000009995812A"); break; //DWZ.06.21122861
+    case 87 : return F("b3E44FA12336300190106617D7A81002025F1114AE0F19A04778065DD02E84809E9F7163157F05FB2506D26A3835904CED370FBCA9E0F800C00010900E10E03000A70207F3101013FFE80E3"); break; //DWZ.06.19006333
+    case 88 : return F("b4C44B40968440303170787F77A3F0000000C1305000000046D1A2EAAA19F250F8F00010000000000000000000000D5BD00000000000000000000000000000000FFFF00000000000000000000000000000000FFFF000000FFFF80EE"); break; //BMT.07.03034468
+    case 89 : return F("bY29442D2C394864681D168D20B3B0BF162236090AB83DFCC84216131495B5A2DF59242760EDECA043AF20801F"); break; //KAM.16.68644839
+    case 90 : return F("b2844C51473278071030605C97237253816C5140006612000202F2F0464E66D243498260413070B010001FD1700146B80DD"); break; //EFE.06.16382537"); break; //EFE.06.71802773
+    case 91 : return F("b39449344715387121606AE447AD40000200C13969799994C132798998CC999426C7F2CCC081396979999C2086C9F18DC2502BB560000326CFFFF046D2E0D8D261542803D"); break; //QDS.06.12875371
+    case 92 : return F("b39449344724733131706336E7A270000200C13330600004C13100000218B00426C7F2CCC081382010000C2086C9FF2142A02BB560000326CFFFF046D1116952BF4D081DC"); break; //QDS.06.13334772
+    case 93 : return F("b3944934451926513180637967A7D0000200C13110000004C13000000B0B100426CFFFFCC081311000000C2086C9F52D52A02BB560000326CFFFF046D3A0B862B28D08027"); break; //QDS.06.13659251
+    case 94 : return F("bY5444A85C3281262703077AA90040254A1AED9189683FF741015BCF9C9FD17914758544A14121969793DAA718C7C091F9E26BF16197828BD514A4E66C5460849605A64ACFBD3D3167332F6AF040711E426CBF237DD6802A"); break; //WEH.07.27268132
+    case 95 : return F("b394493444993671216075B197AD00000200C13850441004C13286138A3F900426CBF2CCC081345574000C2086CDF284D2302BB560000326CFFFF046D330BD62497B485F8"); break; //QDS.07.12679349
+    case 96 : return F("b39449344782049131707F5A37A480000200C13121902004C13000000D42000426CFFFFCC081353750100C2086C9F077F2A02BB560000326CFFFF046D0716952B9ADF80EA"); break; //QDS.07.13492078
+    case 97 : return F("b3944934462526513180768437A880000200C13060400004C13000000950300426CFFFFCC081398000000C2086C9F0D7D2A02BB560000326CFFFF046D320B862BCC358012"); break; //QDS.07.13655262
+    case 98 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909
+    case 99 : return F("b6644496A721102551437F5C77224831715496A000726005005827161FD1AB240AE86C2A6D7F691E4C8531E4530AE4FC8A294BE87862FDDDCE843D7679005C55E082A744BC18EA87FF12298AE4258EE9D89B1F9511318B8D7152464FB11007F5CEB827893513C954A6E9735185FE268124771D567A080E8"); break; //ZRI.07.15178324"); break; //ZRI.37.55021172
+    case 100 : return F("bY50449726041331160007728499181897A60030E10030A59E74DF73596296EE08CF3F1BA88B47A3A264C30177EC921B750B03B99F992A0EEB064560EFAD6C5EC7CCA1009D72C63B0E79000000000000697E801E"); break; //ITW.07.16311304"); break; //ITW.30.18189984
+    case 101 : return F("b2F44090773754205100794947ADD100000046D070DBB2404130F000029E0000259F0D834FD17010000000424132B5729820001FD7462313A802A"); break; //AXI.07.05427573
+    case 102 : return F("bAE44EE4D449858203C07C8B87A0100A0257AB76440C2E16196857D0FAC3205AFB9D036CD7885C1F60C61095F2300D08DF56026E74FFB2F876BD89D27F65B3EA3729F53B26A48F2A5684EDD67A16177F8DD127C2CD8FE1C42CD5035E7EE110515C7369DB07AA59D5954E077C30AA29423D12429C77F6A7DEF679B3A90D7FD075AB7ED262466ECB4FDB66C609FC5DCBEA89FBCEB7BAA4EB8C312CF1A242B39A696A25E81CF0B5994B25CBDC06749D29F5B0F9E9F98476FFF9ED428840C0082459878C8FCEBA90EC380F7"); break; //SON.07.20589844
+    case 103 : return F("b4344010648020780011656A78C001D900F002C2561675D61AD80F90FDEC404B8CAB47A80032007100A387268EF9F9CC8106E5D7FEE6E9D41D7E8780E2B18C2F71743F96AC442C6191D9B8E8E2AEE85F1"); break; //APA.16.80070248
+    case 104 : return F("b2E44685017467703627254FFA0009F299A23A02C3A000008060605050B45060504020407060412140C0F0F100E111BFE070B0A0908C0EC81DC"); break; //TCH.72.03774617
+    case 105 : return F("b2E446850812896626572C47AA0009F290D1620272200060001018DE4606A430F1101000701010302010403020403DC700002050302E7F581EE"); break; //TCH.72.62962881
+    case 106 : return F("b2E44685007457014707281B0A0009F270400600A0100000000000001A1A60000000000010000000000000000010086A10000000000FFFF8007"); break; //TCH.72.14704507
+    case 107 : return F("b2F4468509851603374725693A2069F27CE00600A600000000409090A8C140B090C0C0F0E070F0C0D0F0E0E0B0F0A640F09090D0E1009932C80FB"); break; //TCH.72.33605198
+    case 108 : return F("b2F446850396450729572A1CAA2069F27FC09800D3E0200000238383EF87E353F3A3B3A2C2E1E2B3830111E2A181FA0AB2432273805208FCC86D2"); break; //TCH.72.72506439
+    case 109 : return F("b76447916987435614037E4C2729646866079161102FC0060058CA7D7567090FFC84CBDE4FE2996BF8080E6A2C41D6DF46D8921894C814CC0BB36C550312F03A2734512ED7A69349DE3C349CFC079B62A3AD93BAA84A087C739785EFC9A415F6AF0662B05401FD0C4C9D4A676DA7FC1F6C592823EA6B6DFCAA3DDB2E4D5B6E87C5EA1B92E903D62"); break; //ESY.02.60864696"); break; //ESY.37.61357498
+    case 110 : return F("bCE44A8154457060801022C397FDE00C005F4FEE3A225126DE3E8344CEACB8502C615B4F7EE9D9CA99F2E3AB5AEA9B3417ABD2B8DF835C0A8C31510DC184ACC8E261E24B717F51C01887D9B39D57965A4004CB68B65206E173F7374489CCBBAB63D4F4B33B488DE06DD33C93EB719BDD805E331238755D22C7E94ECD3636C1CCD965CF4E6DC37123EA8D95771BBCD12431AD12B646EBD21FCB77BD2D100C9CCCE7546268B3AF080DEC8F6B61BCC209013295A74A2730D9CB11E52056D0C0879EA29A2CE24210EAF58587855A790FF343CF82914E0164ED08C70FD54DB800BD9B1C0F2431883324E43A1D293"); break; //EMH.02.08065744
+    case 111 : return F("bCE44A8153132000801022ADC7F9000C00546CF1AA7C3268079B9F3A0D8D2FA9AE941DDCDA1D0D8CFA6358FB9FDAD0AF38BB344D954ADD742E77428CA48DD918F361E23D5805BA6BB4CD2470016F66B92CF5D2AF69C7751F1FD2D887A10F26865E6AD95F58754980416935DA7A6C669823CDA9A3CF4D0EBE233D7F706DA6ECFD8209C8F140A7666B9AB2D9ED8C0D5AE04C3600B0899207B54AA98FABF9D14F773F41A53378E9A5678516B95EDEDED334821972EC196927F794E72725C78300FE65155E4E49B43A329EE44AD6FD90070548499CCFFD9FB71ACB4183B4B14DEF7D8B99BF02B754E651850960D80F6"); break; //EMH.02.08003231
+    case 112 : return F("b7B445A1415200000023724958C2049900F002C259D7515009EC6163C4A7CB4E8935772324371005A140102490050646D0710FDE80DD548AA00BBCFEB3152BF11CDD91D4D31F1C73144BBB17696622104EB91D19A32FF7CA939126767495134EE598AF96AC9F0081A83B4D7EAF7A497981C15604C85E4E3B9FBA393BDAD8E0DED15EED4BA887D9970D17797849FE68045"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015
+    case 113 : return F("b5B445A14152000000237E4CE8C2048900F002C259C751500003AD02FC41ACF50E92F72324371005A140102480030E8FC8710B2BC8D30DC227AD803ED24F3B6FCC6E5131442E65A7F5CC4589D51AE2FB2690E9457810BDAAAC81988A3E5E81E6CDA24BE256218D907511C8044"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015
+    case 114 : return F("b1E44C418557901000102E4437ADB00108561D52810CD237CA87FB7C5A2F62A65C25C457BF78028"); break; //FFD.02.00017955
+    case 115 : return F("bY4344471325798761013672193170604713010210000000000004788F419E030DFD110E39313133303730363030475A4431060200000000000006823C0000000000004E88"); break; //DZG.02.60703119"); break; //DZG.36.61877925
+    case 116 : return F("b7B447916419806613037A83A8C204C900F002C25B4AA0000CC2128E94C8F3D946F737210552961791611024C00500ACF0710FCFB475BDA1FD49D52FF6FB45E833D5D97609373AB3EB562E01CE23D1389FFEC7EE41D4E7B20D35D8B80581C289834F4E3C542C09B0C37331332A321B79DAF7F60CDB028452AE3A26291EFD25C87DDC9EACF462C7B441510340F755C800B"); break; //ESY.02.61295510"); break; //ESY.37.61069841
+    case 117 : return F("b6B44791641980661303756A58C204B900F002C25B3AA0000ABEB03EC47E933C072B97210552961791611024B00400536871036D216BF2B74E11F1952539F406B7D4DB7721B1154E21C36F0F55BD4B5B5FF9977572BCD27D93A909347A0D84AE9E804C361E9E6D98F9BE296F76E3A5100357CDD3FA09AABE44103EB508044"); break; //ESY.02.61295510"); break; //ESY.37.61069841
+    case 118 : return F("b73447916856565601002F8DC8C0037900F012C25809A62002B5C02299AC829AD82267A370050471010A9AE87B893A51E226DD3203AD775D84E2B2E04D96F85DA0CD258EBC10912241ACAEB1406433A637F6C70811B7FC99565C388A0365A9B57F94E0380317F5CA1052D6FE55C7BE3118A4D1CD05AABC646F5D288B129D7934E68CF030A"); break; //ESY.02.60656585
+    case 119 : return F("b7B447916419806613037A83A8C20EC900F002C259EB10000F76F8B1B2D9E402B9498721055296179161102EC00508B8E0710072DE1601CB75C3C9C30E9E037024B1D516151D3724CCF1654B2AC7707B5B41F2BBE3F2C19E9467A0740AB6A4F7F03F5E764FCCECC689EEA66BB4A9A6F25FDDCE8A60E9D998A43936B8A3B2EB57A8F667B476D1ED1CD2381D0ABF6118033"); break; //ESY.02.61295510"); break; //ESY.37.61069841
+    case 120 : return F("b6344FA30883161560002C10A8C2042900F002C2549030000BC2C8A72651D570AEB967AA7004007103CB056B91B8007B8882351411FD19256641A220725C724891287445B85FDFDA2DB638B542BB7FC7209833DE16EDCBB90FA686C10076EED63BD40AB0EE3EBCDFDF40032F5C0DCD66322BE804A"); break; //LGZ.02.56613188
+    case 121 : return F("b6B445A14043300000237C1FB8C200E900F002C250E000000F96A649A04D7DC457E8972807759005A1401020E0040A6D20710A07B663F2ED91A0DEA4515C0C1D4A296569A7C4DC27AE724F38CF0E518949AFAA9C3AA8703638F5305E2A333941756C4F5BF4703A411B7FE92F58D7F2724E31E10DCDCBF2C7EE18CD4738043"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304
+    case 122 : return F("b5B445A14043300000237FF3F8C2001900F002C2501000000574262230ACE2FB2FB5072807759005A140102010030303C8710DD05806D5E2EFB0ACD9DA1964A191D7BADA8E1A508EE7CFA86336B14B1C756AA1E20FA5CE029D2CE99CC84BFA0CAFD723A5D0BC20E6FECC585FF"); break; //EBZ.02.00597780"); break; //EBZ.37.00003304
+    case 123 : return F("b4E447916110000600702D8607A99004005860F4BADF0716289AEE56290239E4549E908B33CF4C280DEDD3382DF293865824ADFA25EBDDDF28DB046A59DA2A4C2DB62CF177F2E77EF3E62D6A67DEA6BBD01BEA1DFB9ED3DC8743E2E"); break; //ESY.02.60000011
+    case 124 : return F("bD344A815394643100002D3DD8C2003900F002C25741E0000A17997475A31870EDCF27A0300B007101E6BCA68B34F4603824A04FC0B87A398438300B31366E3B94F45C0EEBFF210719F87A8382509BC329B59A4031ECAC4CA734374B06825329A3665EAA2C626DA096C8E50002E95443D540F92C90EE305931E8E066BB8A76AA2DDE94042B90B94E9D3174612DBF8755145BBA760A85B7C84DDF075950FFDE6EEB4EF76A664F4A474CE050E12021434806BB9C4CDDA5CA8F13DDE8010C5DB0EAD57FD5E6DF042A1C0AE83DA23EB2B8C3444A11D8A6F6274072E516F42DA449DC460D14C1D9E12F95CAE43112976493AD493F0"); break; //EMH.02.10434639
+    case 125 : return F("b8E44A815262292070102C8F57FED00800592BC9808524F745DE09733D6B862F1FEF9AC816CCD59B730C0BC8D173E7B1B187735C505768A284780C3B7E53CCF252EC84F44DF2A8949DB12139FD80D000930321F57E73C8F22B44AB57C03F93C35B02B853E45A0960F0FCD326F4760CA76945EADF6F294356AC8308DE284EEBCECB57EDF9BB63BCC5D424A764348B2D40E86C812AEA58CA14B652DD3853BEF5BDF56A27F8031"); break; //EMH.02.07922226
+    case 126 : return F("b5344A81594176710020267788C20D7900F002C256A000000E4D7497D043A0EE5B2A47AD7003007102F390C301AD7CB52A6B8633CA25AA26BDE4FDF94F3230E43CCBF66EEC0D4C0C7A6E66310DC6376768FE891C0CA84DD365D3FBF23690BB55E812B"); break; //EMH.02.10671794
+    case 127 : return F("b2644AC482711000050378A347201271100AC4850021B0000002F2F0C01C103895936002F2F2F2F2F2F2F2F84F48029"); break; //REL.02.00112701"); break; //REL.37.00001127
+    case 128 : return F("b7C44361C120001000002CA9C8C203F7A3F00000004050000000004FBCBA782750000000004FB82F53C000000000412B22A0000000004FB140000000004FB943C59F00000000004FDD9FC010000000004FDD954D8FC020000000004FDD9FC030000000004CF6EFDC8FC012909000004FDC8FC02F10400CDDC0004FDC8FC03F104000002FB2EF40101DC37FD1700DE8C80FE"); break; //GAV.02.00010012
+    case 129 : return F("b3244361C373601000102603A8C20457A4500000004050900000004FBDA3482750800000004FB82F53C0000000004C3AE2A2415000001FD17001EB2801B"); break; //GAV.02.00013637
+    case 130 : return F("bY394447135523636001027A830000000478232D9D030DFD110E35353332333630363030475A44310602ED8B0000000006823C00000000000004E4802B"); break; //DZG.02.60632355
+    case 131 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672
+    case 132 : return F("b494493447236379635083FD9780DFF5F350082030000F00007B06EFFC6F5FF310300007F2C070300009E24310300E13000008000800000000000001F007300A145C9006BFF64003D000C002F046D200E86255AA081DF"); break; //QDS.08.96373672
+    case 133 : return F("b494493447153871216062D53780DFF5F350082DA00007F0007C113FF8331FF969799997F2C279899999F2596979988C999000000000000FFFF000000000000008B35000000FFFFFEFF00002F046D25068C2608D2803F"); break; //QDS.06.12875371
+    case 134 : return F("b344493447153871216069A667A8000082004ED3926096C2701FD0C110157046D17138F2602FD3CC2010DFF5F0C005FC50861FF000006130701FFFC138B803F"); break; //QDS.06.12875371
+    case 135 : return F("b3D44934417196746221AD6FF7AA210000081027C034955230182026CAC3BB62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B62102FDFE8FAC7E19004F33802D"); break; //QDS.1A.46671917
+    case 136 : return F("b3744934417196746221A40247AA310002081027C034955230182026C5AE5B62181037C034C41230082036CFFFF0238E2FD171000326CFFFF046D0311B621AD04802B"); break; //QDS.1A.46671917
+    case 137 : return F("b39449344775149131706E1D57A680000200C13750000004C1300000010FC00426CFFFFCC081358000000C2086C9F61212502BB560000326CFFFF046D070B8A268D5880FA"); break; //QDS.06.13495177
+    case 138 : return F("b4944934477514913170662C2780DFF5F3500827B0000810007C113FF5A69FF75000000FFFF000000009F255800004659000080008000800080008000800080009B248001000000000004002F046D1F0D8A266CB280FA"); break; //QDS.06.13495177
+    case 139 : return F("b3744934484145047231AAE907A9600002081027C034955230082026CD1DAFFFF81037C034C41230082036CFFFF02DDAAFD170000326CFFFF046D0503AD21AB3580D9"); break; //QDS.1A.47501484
+    case 140 : return F("b39449344843350131707BA4A7AF00000200C13000100004C13000000C11300426CFFFFCC081361000000C2086C9F6DF62502BB560000326CFFFF046D370A8A26486681E6"); break; //QDS.07.13503384
+    case 141 : return F("b49449344843350131707395D780DFF5F350082000000EE0007C113FF4BC0FF00010000FFFF000000009F256100009FB5000080008000800080008000800080009B248001000000000005002F046D020D8A263BD880E7"); break; //QDS.07.13503384
+    case 142 : return F("b3C4493440989323533372A78728903252493443307C50000200C133698392007004C1331430400426C7F2CCC081319DD26200700C2086C9F25326CFFFF046D058B67079026F78582DC"); break; //QDS.07.24250389"); break; //QDS.37.35328909
+    case 143 : return F("b2C449344098932353337D4E7728903252493443307C000082004ED39ACEB2C06702501FD0C10046D1C06902602FD705D3CC20189FB81DA"); break; //QDS.07.24250389"); break; //QDS.37.35328909
+    case 144 : return F("b5344934409893235333714F478077989032524934433070DFF5F3500D11E82CE0000100007C113FFFF362007007FCFC92C314304009F252620070020029102810C8E02730241023902940287023F0254029062770227012F046D14108D26710885D5"); break; //QDS.37.35328909
+    case 145 : return F("b624468501509651494085A758C002A900F002C25DECE080028421E778E39B0665A707ADE0030071080A1255F4529D6628D1BD017B641EF0A8046B680C77DAB285B3C2A943522663000821E48556555C93D048F5DF327EFA4BECCA1914A1C0F9EFCE6532756F5E9BF68B0E01E8802D91270"); break; //TCH.08.14650915
+    case 146 : return F("b31449344401892903408DFBB7A9B0000200B6E4800004B6E14010042A7D46CBF2CCB086E480000C2086CDF23326CB9BCFFFF046D0E0BD624CF16800B"); break; //QDS.08.90921840
+    case 147 : return F("b314493447236379635085D337A040000200B6E3103004B6E070300420D0E6C7F2CCB086E310300C2086C9E24326C1422FFFF046D240E86250E4E80E2"); break; //QDS.08.96373672
+    case 148 : return F("b3444EE4D774933221608D1BB7AF7000000046D280DBB24036EEB000059A9426CE1F7436E00000002FF2C000002598620C80B0265F10802FD66A000779E80EA"); break; //SON.08.22334977
+    case 149 : return F("b2E44B00971280018540843227A0E0000202F2F036E0000000F1000016F1B8563822A0000E70A812A000000000000AF010000000000FFFF81DD"); break; //BMP.08.18002871
+    case 150 : return F("b2E44B00971280018540843227A0E0000002F2F0F0000000000000000718D00000000000000000000000000000000FFFF0B2A00002FF97381DE"); break; //BMP.08.18002871
+    case 151 : return F("b26446532392238253508D6A07AAF0000000B6E0000004B6E000000422F866C7F2C326CFFFF046D2D0A83279B6780E8"); break; //LSE.08.25382239
+    case 152 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218
+    case 153 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218
+    case 154 : return F("bY2F44C5145935816213087A080000202F2F046D0F2EBB24036E000000426C9F25436E000000317F00346D00200000F1458737"); break; //EFE.08.62813559
+    case 155 : return F("b3E44F536861202000108F54A7A9B0020250A86DF3204D8B7DAFBD62C57159092FFCBB77F612E656C59AD065C96CC24B353B024A6930F800C00010900E10E03005DC1207F1B05074A5380F2"); break; //MWU.08.00021286
+    case 156 : return F("b2E446850844616516180E459A0015C284604600A7200003900341D9A5C477E9ABC873D05000000000000000000019BB4000406456E897887DD"); break; //TCH.80.51164684
+    case 157 : return F("b2E4468506974806164805122A001DE26EB02900D570200000000040D21820D4E301126353030334B44170B0A01004EE40000000000FFFF83D9"); break; //TCH.80.61807469
+    case 158 : return F("b324468500441169269802F7CA0119F272F01600A3B00C8082F09000007730000000500000220140B1807030000007111000000000000000001C29A85D5"); break; //TCH.80.92164104
+    case 159 : return F("b33446850710351129480ABE9A20F9F270000D00E03000128090B0900DA3600000000000000000000000100020000274800000000000000000000FFFF80E3"); break; //TCH.80.12510371
+    case 160 : return F("b4944C51402203571000451A77A090001202F2F046D2E299926040687ED7214000001FD17000413F6670400043B009378000000042B00000000025B1900025F1986B90002610A0003FD0C05000002FD0B3011F52BA393"); break; //EFE.04.71352002
+    case 161 : return F("b2844C5146427807103073D877234626016C5140007D72000202F2F04DD1C6D2F3498260413B96E010001FD1700066080E7"); break; //EFE.07.16606234"); break; //EFE.07.71802764
+    case 162 : return F("bA644C514960080900307DABF7296008090C5140007ED0000202F2F426E8D6C7E2944133C08000001FD1700840113039188130000C40113D611000084021324108D5A0000C40213A70F00008403132C0F00003550C403137B0E0000840413C0080000C404749913650800008405133C080000C40513AD831B07000084061380050000C406138401006999008407133A000000C407133A00000084286B081300000000046D332A8F260413A7137132000003FD0C02032102FD0B01114D8480FA"); break; //EFE.07.90800096"); break; //EFE.07.90800096
+    case 163 : return F("b4806AC1956030015020363917256030015AC19020323000000466D0009780004872B000D7811313136353330303028633531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8030"); break; //FML.03.15000356"); break; //FML.03.15000356
+    case 164 : return F("b3644A511621280223837CE5E7241022436931581038C002005A8F458578136DF07A14CE37F82BA702DF936647F231F18C961CF7A6CE31CFAACE57153A8888B"); break; //ELS.03.36240241"); break; //DME.37.22801262
+    case 165 : return F("b1F44685034025063591B28F57AE100000001FD17000265EF070F6A18DFC253E0F351D0900E8180F9"); break; //TCH.1B.63500234
+    case 166 : return F("b294468501904601473F0AE14A0009F27EDA5B130CA280080770001045667006BA1007CB2008DC3009ED4000FE50096BA80EB"); break; //TCH.F0.14600419
+    case 167 : return F("b294468508220285376F0F19BA0009F27A1280028A128000033000006BC4C006BA1007CB2008DC3009ED4000FE50096BA80DC"); break; //TCH.F0.53282082
+    case 168 : return F("b50446850920420745937163972612600006850FE036B0000200415CC4CB0551F0084041552FC1C0082046C7C228D92D804951F1E72FE000000006F1BF114A212D1B377124C1EA62E83430E5006511443943F9B47F52901FD17002F1F2F800E"); break; //TCH.03.00002661"); break; //TCH.37.74200492
+    case 169 : return F("b3644685005012040714351C0A004372900000060DA4E029B5FBAC4123A84170DBBFBF13D06000000000000000000AE3800000000000000000000000000FFFF94A6"); break; //TCH.43.40200105
+    case 170 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105
+    case 171 : return F("b5E44496A973430000D1AFCF97AF7CB50057AB6BD92ED8A65D4DB8A19DDA1B6D3CD164A0F600C93485BCFC48263B255C4FC57033B6114A4DD1590F6E3B22855F7161BBFB100973B49CDB3593DEC5164ECF04F1CAB0311E89B873ECECCD7FBF0D60D125734B551865D12F7D012748025"); break; //ZRI.1A.00303497
+    case 172 : return F("b4E449344492512971437674F72090328039344141A7510002081027CB4ED034955230F82026C8927C1027C0354466C2C230FC4026D2C0E892781037C034C412307BC3282036C892702FD171000326CFFFF048C536D2809BA22ECC883DC"); break; //QDS.1A.03280309"); break; //QDS.37.97122549
+    case 173 : return F("b4E449344505427971537894372380266079344151A6203002081027C8B4A034955230282026CFFFFC1027C035446B3DE2302C4026D1107632681037C034C41234AE90082036CFFFF02FD170008326CFFFF0469C16D000BB822FEF28024"); break; //QDS.1A.07660238"); break; //QDS.37.97275450
+    case 174 : return F("b3E44934435188745211A19F67801FD080D81027C034955230182026CF8CB932381037C034C41230082036CFFFF0359F5FD17300010326CFFFF046D060A8428027576FDAC7E5F0192628012"); break; //QDS.1A.45871835
+    case 175 : return F("b3E44934455319745221A29607801FD088A81027C034955230F82026CB7439C2381037C034C41230082036CFFFF037174FD17500010326CFFFF046D260B862B02842AFDAC7E8200271080FB"); break; //QDS.1A.45973155
+    case 176 : return F("b3E44934404155047231ABDD57801FD084C81027C034955230082026CCC02FFFF81037C034C41230082036CFFFF03E0CFFD17000000326CFFFF046D080DAC2102EA18FDAC7E11005C3E80E0"); break; //QDS.1A.47501504
+    case 177 : return F("bY60442515485001000C1A7A23005025568AED71E43AF834900BEC738E08C4FA2637B8915FB401FD6296F19C3AEECEEBC3164B967CD5445E6AAFE90F416314191CB1839210B7CD2EFE168911FD465DAB56CCDA9C82862B90F29353AB57532B49E67E"); break; //EIE.1A.00015048
+    case 178 : return F("bY75442515639716000C1A8C208A900F002C25A7000000DDCE55D203E089D07AA800500710760097C0A7F24E9681882D62EFF802EC33146C9B3828FD5B2026432F40E7098DA78C4538579DDE260B2DCCE933093DA312A2C499D4473F150422121279632724B2FCB44A2110D2A2DA87B8C084512CDD698F"); break; //EIE.1A.00169763
+    case 179 : return F("b5E442515954400000C1AA17A7A74005025F6B841CED5F1892796E8217F31F08E864EF5C0BBBDFE640AE3711C34C4B5B8AE3B821F1F0F3FF81C8259CBDABD6D05A0A751305C3399E9450DE8E86BE0BDB2D7AFA79BB10179B7EB1F37983CB8C7F10746888DFA54B4CD95AB8C78018025"); break; //EIE.1A.00004495
+    case 180 : return F("b5E442515695800000C1A452E7AF200502547F361FEDBED8040998A4AF106CE368D0D469CFE69DD1983E5D5A84078AB4F4F2AFD97FD57668A660038C14F4B79CC3CAB703C3C4ADB6B19AE027E25C4E157B23E9A4D5A7CF24F4962BD29591A73F3E3BD13C9F26D15826364C2DFB28051"); break; //EIE.1A.00005869
+    case 181 : return F("b37449344981002451F1AF6837AC418002081027C034955230082026C7CB5FFFF81037C034C41230282036C6D2A027047FD178400326C962A046D080FA623CD2B8AD1"); break; //QDS.1A.45021098
+    case 182 : return F("bY5844972642631092001A727299291897A60030B21340A5C84E203CFF62E039C2A1F61CF679A05B7DA7F31E4F0AAA0F30EFFECF4AC176AC9E173C426799C618D50A4B285CBB9074CDF78FA733C7AEC4F66C45D760FDFEE2327E8016"); break; //ITW.1A.92106342"); break; //ITW.30.18299972
+    case 183 : return F("b3E449344981002451F1A2CED7801FD08FB81027C034955230082026C61FAFFFF81037C034C41230282036C6D2A034D22FD17840018326C962A046D0A0EA523028B4BFDAC7EF0002E078AD5"); break; //QDS.1A.45021098
+    case 184 : return F("bY5044972694376092001A7A3E1340A553D753D0583A78B2BC306DF80BA1DBF6FC88DAFCD83AF7D955EC6196B643A571494B99AE831F8894A4726042E23EA5C474411A585EEEEE8E84A15F54562C31168159804D"); break; //ITW.1A.92603794
+    case 185 : return F("bY5044972625353893071A7AE40040A517031B1A58F312C641E1E45D19F6DD3EA9B61FC929D70F39747F6A8A3BA53D7ED25B2A86C741728467DADC3652D54F40660B397E72F60CE3434006A1D843B3248D9C8016"); break; //ITW.1A.93383525
+    case 186 : return F("b1744242349075722754982E471F202000001FD0C6202FD173000CFB38EF8"); break; //HYD.49.22570749
+    case 187 : return F("b1744242349075722754982E471F602000001FD0C6202FD17300044308CEF"); break; //HYD.49.22570749
+    case 188 : return F("b1744242349075722754982E471FC02000001FD0C6202FD173000B52092F0"); break; //HYD.49.22570749
+    case 189 : return F("b1744242349075722754982E4711402000001FD0C6202FD17300084489CF0"); break; //HYD.49.22570749
+    case 190 : return F("b63442D2C272951803504DCB48C2064900F002C251664000038AA5E8D66325C253B6B7A6400400710EDD4746D6402CF31496EE7AE09E634270E5701ED9E5D16E7A5A22EBC15B0CB6AC0EF980F73E5AD3BF6E658AFC24F614AFBF844AD1EC8CA21C0FCF8FC2E9E64C0B28542EE8C7EA37B444A8036"); break; //KAM.04.80512927
+    case 191 : return F("b5E44A7329022226704041EDE7A7500502599F38B5BB9B53F705A6B158D76D3C33F390AE5F6E48A051680C3B866F317F4DBC781E920051DB2619F768DA54EB632DA8746E483A5569A9D8C0E16905ED61857D1B9A07C6EB6AE501B22E0D55A7DD4AF67DB88D6DDCCA5B78E5B88528014"); break; //LUG.04.67222290
+    case 192 : return F("b60446850090510825937364F8C0039900F002C2584340D0063667A2334810387B25C72989280612423FE048500307DA907106591F67C43C0B36FCA410346B6A06E7CAADE06D06CF2911ED2775E3297F20105876C245C9309EC93EA0F68B3F9FA466371C73B4A39B80FEBAD1F9C40758028"); break; //HYD.04.61809298"); break; //TCH.37.82100509
+    case 193 : return F("b62446850189272149408C4E28C00CF900F002C2564B20800A81E8BCCCA14532696097A6400300710F61F531C03426B9317EAD69912E862AC2017148D8C179D50E133470B7A04CD063D00E6FB1C522DDDC80A3300335E26FE16F9D339A61C0F41BC1ACF4A0A8A29310674536C344CA64D7D"); break; //TCH.08.14729218
+    case 194 : return F("b434468501892721494083F2A8C20CD900F002C255FB20800903D2653461ABE7080E57A5F002007103032214C1915B7A16175B00F90B8EB889A4C280207D9C74F5A4088C584D951BDB20851C3DF9E"); break; //TCH.08.14729218
+    case 195 : return F("b48442423240756341200933E7AA0303A31A074C27AB1B79A4BF71E2818DAA788D064B94612F5AED2C06E10F1A022615C7EA6E4E97D999D450C33C358340DCB3D948F7F9B58E2B922ED1A580BA905B4E34388A184D5"); break; //HYD.00.34560724
+    case 196 : return F("bY5044972670314082001A7A120040A5AE9E0AFE53E0E5216516C41E94CCEA4220BC25A8A5CCF38635E315900BA6BEB0CB5E343BF419524FFED59CD28D106314AC875F9812782700A8267FFCDB4A24251099804A"); break; //ITW.1A.82403170
+    case 197 : return F("bY5044972670314082000A7A7A0040A5CE2F908D1B6C7FAB19CF06F0EE140BB44A84C7FF54BDA05D33D6EE45686D054984A4467283EDA6514E8094E361082D9555BD043A64D3F593BAE29C577984921E3CBC92D7"); break; //ITW.0A.82403170
+    case 198 : return F("b3B44931536000000013721968C30AA900F002C25BBD101005A558A6E08987D124F027212334938931581036C0010657B0710B23E83B33BD1C8F5A3AB0DAFCEFB35C1D44718C68011"); break; //ELS.03.38493312"); break; //ELS.37.00000036
+    case 199 : return F("b2E44B05C95720000021B556C7A500020059C8692AAFADBDBCAA36875B54901F2E32655B735A59AB899223306CD8402A02D189C816E86BB8061"); break; //WEP.1B.00007295
+    case 200 : return F("b3B449526564412004237BD198C20E7900F002C25641C00002B7452AB08B6F3843AA6725644120095264203E700108D660710B78BE5BE2867BD38DCE24619A59D0A6BB9384E3A80E2"); break; //ITU.03.00124456"); break; //ITU.37.00124456
+    case 201 : return F("b2E44A5111863054230037FB57A30002105DE7DCE381F74E06136FEB49A5B3D45B688341DDCF387CC0D6344DF5BF60078C7A596B1A0D3BE8020"); break; //DME.03.42056318
+    case 202 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103
+    case 203 : return F("b2644AC488437000050379BF37201843700AC485003210000002F2F0CFB6E16038161002F2F2F2F2F2F2F2F2C138057"); break; //REL.03.00378401"); break; //REL.37.00003784
+    case 204 : return F("b3644496A855900500537290F7285590000496A0103CC002025CF5138AEB21021CE8339724700D0AF89B3CDDAAE2BAD28479AC27ADBE1E3C3B849269E632772E54C"); break; //ZRI.03.00005985"); break; //ZRI.37.50005985
+    case 205 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103
+    case 206 : return F("b3B449526404107004237CE7B8C2076900F002C25832C020029B8AE8C5A07F337A5EF72046318043041000375001016F10710C9B050BA32F8730DFCC9465A1C3385BA84D8190D801B"); break; //PIP.03.04186304"); break; //ITU.37.00074140
+    case 207 : return F("b3B4493154809000001379A2E8C2028900F002C252907000090080FACAA513D7A7B7B729549120430413A03CB001054A107101672D7C39DD223F7A8D3C4FFF7E9CA294237B1AD83DE"); break; //PIP.03.04124995"); break; //ELS.37.00000948
+    case 208 : return F("b1944C418637901000103C9CE7A690000A00414EE75620202FD08EAF2A1B38039"); break; //FFD.03.00017963
+    case 209 : return F("b3B449526544412004237036C8C2065900F002C25943B00000CC9351967A937062362723991270492262203650810BBE30710483A983059AFE3C21836C683F23BA2B5A04EBAE38021"); break; //ITR.03.04279139"); break; //ITU.37.00124454
+    case 210 : return F("b3B4493154909000001375BA68C20BD900F002C25BE0200004F4E34B29075C9986ACB722009210493150003B10010E2D407103C75563866FAC12EBCE6FC5829323547A3CC9A488057"); break; //ELS.03.04210920"); break; //ELS.37.00000949
+    case 211 : return F("b3B44931558130000013727848C20DD900F002C25DE0C0000ADFB2D6B2E02C73267AF72011713419315250339001037AB0710A68555FAD7A29A1A875EE33FD26D13EAE33A7AF08035"); break; //ELS.03.41131701"); break; //ELS.37.00001358
+    case 212 : return F("b3644A511870350133837198E7280409628931580038F002105F09DD43A485B949F2AB07122E6A6CB3E4BDF9055A351316D145C3EF8522BDF56D9D12495B281D5B0"); break; //ELS.03.28964080"); break; //DME.37.13500387
+    case 213 : return F("b3644A511621280223837CE5E724102243693158103850020050075969BAC2F21DD6DEDFFDBDDCF9F0E9A7E066046DF920DF3BFE4DA1174DE1943F173A93C0E2A5B"); break; //ELS.03.36240241"); break; //DME.37.22801262
+    case 214 : return F("b1B44A5110301808238379BA77241022436931581038F88000002A7183766D900E3278003"); break; //ELS.03.36240241"); break; //DME.37.82800103
+    case 215 : return F("b2E44B05C10130000021B34137A490000002F2F0A6653020AFB1A5105E7D102FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8016"); break; //WEP.1B.00001310
+    case 216 : return F("b2E44B05C77010000041B1E0B7AE60000002F2F0A6641020AFB1A8103251802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F9"); break; //WEP.1B.00000177
+    case 217 : return F("b4E44333081400004032AF18B7A03004005E5AEF57BA739B9789BD9AAFAE16068AFF9FB85A70613B3800B85D28B409B5BDD9607BE2A1450EEE20137C663FB522F4E6B9E914E8E3795C577BFB32D6E0152C21EB0358C41DD6D9F76498020"); break; //LAS.2A.04004081
+    case 218 : return F("b1E443330886102001E1B8E9A7A180000202F2F026584030778281E18ADDC240B0000AB01768017"); break; //LAS.1B.00026188
+    case 219 : return F("b2E443330603903003C1B18AF7A77002025BB29E575E63C4AA6174A82CED44D5497FA0207F44D4518D8CFA53CD7024276A675D4E0621C0F80E5"); break; //LAS.1B.00033960
+    case 220 : return F("b7044B40908794920101B6F8B7A320000000265490A42651F0A8201650BCEB9072265D70912655F0A62659905526576FB4F0A02FB1ABD0142FB1ABB018201FB1A36B6C60122FB1AB90112FB1ABC0162FB1AC24B1C0152FB1ACF01066D3B1D2EAA25000FFFBE95FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCA00FFFFFFFFFFFFFF7B5382DE"); break; //BMT.1B.20497908
+    case 221 : return F("b60449615347810630C1BE5957A8C0000202F2F026505094265F908822DF20165880822658C081265680A62655808B1785265680A02FB1A880142FB1A95018201130DFB1AE00122FB1A660112FB1A100262FB491D1A660152FB1A1F0202FD1B60030DFD0F817605302E302E340F6C04"); break; //ELV.1B.63107834
+    case 222 : return F("bY2944961565181468201B7A140000202F2F0265E20842659A0802FD1B30030DFD0F05302E302E340F5E46"); break; //ELV.1B.68141865
+    case 223 : return F("b1644AF4C40020041011B788C7A0A0000000266F3000266F10026FE800F"); break; //SEO.1B.41000240
+    case 224 : return F("b5E442515695800000C1A452E7A590050252502B45C2E823EF856FABFC4775E28D2904492FB74BA65A843BA7E63BC698D83279AE2BE04B957699517818F7B33F8E58001A7C9CE0E73C5769487247954D0A000E811754BAB1CED0E61567EB1FE504B091E0C7DCF4632E3FAC31618804D"); break; //EIE.1A.00005869
+    case 225 : return F("b5344E2306291001500030F388C30A7900F002C2583AE010032E1E493C32BEF51CDA37A430030071027A19EE14B0BBCAD656D0783516CCB7CFBC6AAFAECDCAD70020FE3DA54FCBC8EC2AED88DFD0972C55CF9336E1683574ABADBD046BB53623F8013"); break; //LGB.03.15009162
+    case 226 : return F("b5344A732806139690404B70A8C2063900F002C25923338000C8BC361CE2EE050FD3B7A6340300710DEC49523134391877289A80A53A505655A833F754F221E619D08FB4DB5AD773EAB16B545B306C69D1493CD851012BBF4624A5DDA556AF07E83E5"); break; //LUG.04.69396180
+    case 227 : return F("b9644A732460335700A043C1F7ACD0000200274DE02046D030D94250C804C0623000000446D3B177F2C4C060000005D610084016D3B179E248C010600000000CCB81D0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980F9"); break; //LUG.04.70350346
+    case 228 : return F("b13440000000000DA00DAA8CF7101FD0C3A02FD171101CB938032"); break; //@@@.DA.DA000000
+    case 229 : return F("b63445A146699750001026CE68C20D7900F002C25D7CE0C000B7C13179B38522166CE7AD700400710CDEF8D2A82F77DD15E367871F1E04261AAFAC430C2B55C1DED4A3148306D4C296CF10D72C9E79310A47DD73FDBFDF2CEA6490B6CA12A30EE5D64621A90B5E71F75D50D24C87B10E2ADDF802E"); break; //EBZ.02.00759966
+    case 230 : return F("b5E44496A3680003888049D2D7A1D0050053FBA7B54810C548AC112ECFC76CE753AF07A625248C05827C843371AB5DC6C6C8D5D457E845B4B67FB4CEFF06720EA7A9112BFD0A96BC7E97D49FB9BBD59155D109433F0C4823DEA7A13E5281C00E4945F5B05D7518CE085EC8BFE738122"); break; //ZRI.04.38008036
+    case 231 : return F("b1B44A5110301808238379BA77241022436931581038A88000002A7184A0AD900E327803A"); break; //ELS.03.36240241"); break; //DME.37.82800103
+    case 232 : return F("b3E44B405399098502E0439DC7A0F70300530C7144B5962760A55DE8BA4E49C37676B0CD702698B5FBCE59E35E33D33F736AE13FB1C31DD43ADDC3FE7FF8E1EDC01D749974884BBF96580FE"); break; //AMT.04.50989039
+    case 233 : return F("b5B4479169014216130377E5B8C2034900F002C256448000029E1A134984E32773DF97289000047791611023400302CBD0710FF4380B4AE49A140E94F319BE97049FDA8DDC96A8DC437F3BFB02ADC86082E9507934C7ED4FC6F4F678D613F25C09A1DDE927D817F5A824A8012"); break; //ESY.02.47000089"); break; //ESY.37.61211490
+    case 234 : return F("b7B445A1415200000023724958C20A0900F002C25B4F60800C0E1D417100AD1BB6EDC72324371005A140102A00050F9B10710112A08DB9869998DE2C0D8614F76213F8682D10A8EF2951413C839461E8E3139EA62193E02B1584E6EC8EDB082AB70C6504F1ADF9E6ABD270E96FE8745AEB93C454FC3C9EAA2D5FCD6679E8A38A3E0818D4B6652993CEE5F8E514867801D"); break; //EBZ.02.00714332"); break; //EBZ.37.00002015
+    case 235 : return F("b49449344100549253508227F780DFF5F3500824600007E0007B06EFF2BE9FF000000007F2C000000009E24000000DAC000008000800080008000800080000000E9F10000000000000000002F046D010E8625249880E6"); break; //QDS.08.25490510
+    case 236 : return F("b9644A732370335700A045D187A440000200274DD02046D071189260C33FD0638010000446D3B177F2C4C06000000DFDD0084016D3B179F258C010641000000CC0DBA0106000000008C020600000000CC0206B190000000008C030600000000CC030600008B9400008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22000000000F0010004FF980E0"); break; //LUG.04.70350337
+    case 237 : return F("b4806AC1956030015020363917256030015AC190203D8000000466D00AC9200118625000D78113131363533303030619C3531343530303030308940FD1A014C933B263A494700004C130546000001FD67030D9A8057"); break; //FML.03.15000356"); break; //FML.03.15000356
+    case 238 : return F("b9E44A815242292070102881F7F4B0098050B0E989C939C3479F8904137236FE582B853DCACDD8DB48A717A6B42935CB977102079E6397B07AAD2A648E5B65E44D97F9A020B2BFAE433FA37FCB2A2C32711A5986B301D2F6E4A424C1144D808CE9D592C316B117C572689AC6C1322CC05E81F590CAAF457390F6B39DACC946FA314F8E8A34268157AC4338781C3EF5807F9221394DD1FAB5165E1261614B8B85758851295334DF52D9A4DCE2E1E17A555A21007D2DA802B"); break; //EMH.02.07922224
+    case 239 : return F("b2E44B05C99010100021B65BE7AC30000002F2F0A6605020AFB1A33041AA002FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE8008"); break; //WEP.1B.00010199
+    case 240 : return F("b2E44B05C75000000041B8EF87A510000002F2F0A6661010AFB1A8905449802FD971D00002F2F2F2F2F2F2F2F2F2FDF772F2F2F2F2F25EE80F8"); break; //WEP.1B.00000075
+    case 241 : return F("b1E44C418EA76010001034ECB7A820010A5597AB1CCF2014088590E64BCAB21DC1CC068DAA08035"); break; //FFD.03.000176EA
+    case 242 : return F("b4E442423245450514A07545F7AC6004005BDB5EDF3750DF41725EE867C3E39750E20B9F5FB092089B6A5DC7AA586101778BCD5EAD4995B102AC639F0FB4D12403EFE3554F72CAE4F5D348CC374F571CCE4A98634318027AAF3E7DD8600"); break; //HYD.07.51505424
+    case 243 : return F("b3C449344454392352337ABFC727972126793442304FF0000200C050321648206004C0530240500426CBF2CCC0805098875630600C2086CDF23326CFFFF046D177FEA11D3242E1F830C"); break; //QDS.04.67127279"); break; //QDS.37.35924345
+    case 244 : return F("bY5E449726900634160004728499181897A60030D30040A5D20022C396367C6A868B05FE2E9F70B6878070839C62E69DDC79CB409EBCAD68E950A6958B9B92FE6D1B700065B8BD52CB3035B93653FFDA3C3EF3EF6BBB1B450C7976874216AD188011"); break; //ITW.04.16340690"); break; //ITW.30.18189984
+    case 245 : return F("b3444A73228188269040439327A370000202F2F0974040970080C06348AF03500000C14040301000B2D6207000B3BDD4A2322000A5A05090A5E0506F29780E8"); break; //LUG.04.69821828
+    case 246 : return F("b9644A732170135700A0476A67A2F00002002747501046D3315952B0C3CF20637000000446D3B177F2C4C06000000F4640084016D3B179F2A8C010636000000CCB85A0106330000008C020633000000CC02067E6A330000008C030633000000CC030600009A1900008C040600000000CC04060000000099348C050600000000CC0506000000008C0673DA0600000000CC0606000000008C070600E0F80000003C22890200000F001000C94A80E8"); break; //LUG.04.70350117
+    case 247 : return F("b5E44496A044550108804533A7A19005005B14A145C0EEC7C6FC18A85582753BE2BD08B6F0F9320809C5F28436A3388BB09145DF69DA51ED00DCCFE7BA2D6F86936D17E654489390BB7511CC4DA27A576A9612B1243151C634899A74D3EBDB2CA578CA4444F5740D74D3EC766628013"); break; //ZRI.04.10504504
+    case 248 : return F("bD6440186669601001637060C7215067071010660047400C0251CE72725D9364386C4BF8A15AF4C722115F38B66DDE9F4B800D05A14F870E74492B8F57CB2282388B864D71416FDE0A74CA84735B59D1806BBBF8483B6B14AB580F0D5257E8CC06889EBA80AFB032BCEECD849E7E6A4639B5649B0BF5CBB57BF0993AE16EF7D88D784ADD9CE09284880D14D1502967798901C9E24ED6A04A3B26AE580B5D2AEC46804DEC1CBEDCB1E0B3FE1F84679620FD59E297FE8E67374EE9C34CA5A7F45B4B3709C6BD4E0B546F706DDC571C6469CFA90602C0E339A054CDE1D0A0E071651FD49FEBD485D8E561BB36AF7BED2741D35BBC88051"); break; //APA.04.71700615"); break; //APA.37.00019666
+    case 249 : return F("bA944C514381355920004A5FC7A3D0090257403AC17355186FF933BAF0D0820041595970BD168489A746DF337AB01D1DBBA3EF7D486D74CCCEE777E57BD12A15D22B89AB4B4266D84B9C5C1DDBC437CD64807AF0B2B232E3A3C1B4DA568C9F8BA39FDF4565C170ACEF1918655B45D82E8BAAD769B88FA917E82542CAD596AB2145433599FFDDD3FCE889F657A8AC07FE5CED3F184244475B59710CFDCA44D7C3AE00CC021FA6EE3F49B77939529B49279FC526803FD0C05010002FD0B01111E248025"); break; //EFE.04.92551338
+    case 250 : return F("b6E44A511485268614004B97F7A86006005B8D6D972C8E06EFB5AEC0FC750DB9DFFE62BB59DA183A5709DFC5029CEB2E44FEAF475DD2F3CF6D90544F29372A5C027DACA08536B2457CDEDEB76E36B281F4A0FD4A203FE9F7DFD337BDEA15D02F53A21CB281A3EF01E15D779B3018A99D0E3EF2FF946A63F419E56644BBDF8FB8010"); break; //DME.04.61685248
+    case 251 : return F("b3E44A51159351969A004761E7A030030050F0020FC830AABCA7CEE4EC8E44524D98802ED5CA2EF3C89774FF800730951E02569BF91C156331EC9C29730FE2F437128D7E574D29C4E808047"); break; //DME.04.69193559
+    case 252 : return F("b7144685009022004593720EB8C00EE900F002C252CA40100F5CDA57367A4436D244E72791670692423FE040600407D7C07108660CA27A4DD53871B12F96FFEF0F84E0CA88A2201BD794859D35CCD051F34327D04EAAE8772675F8E7F28DE2E515FBC9AF3A40E72C078304158D453511F2CF39DBF98DBE1EAD84F0F2CAD975EDDCC4F"); break; //HYD.04.69701679"); break; //TCH.37.04200209
+    case 253 : return F("bA944C514381355920004A5FC7A670090251913406458376E6F9CEB817ABA85D9F26E5867EBA222FDFF496F2C8A312E593C80C0604576D7B5269B4A25BD15B314C0598FE3395611400AA7ADB705EE2120686208A449BDC5EE828FF048F669516585611337839C0A40A308F4DE619997F29D2438CE9EC263574FC60D42197CFA7B47528574792BFFF54AFF9193CA678242653A97DEBCB5E5333687846A875A69C4B7371C762C03A2DB5AB738D92623907896AEBF03FD0C05010002FD0B0111C6E48020"); break; //EFE.04.92551338
+    case 254 : return F("b5E44C51475051512010425417A1C004025EEA5F744D756D853BB606F5DBB33DD7B26896D7370BD9C50990E605CE03F8F16DEE8AA36EF2C87E3902F933DDD87E1D383B7C647E8D2236E5FF5E1BE6742D65511FE1629B5CD3F6F0F800C000109002F680300035E2001020A0901C98008"); break; //EFE.04.12150575
+    case 255 : return F("b5344A5115158536941043C9D8C00CC900F002C25986B0110B13D74311F4D1ACF88797A7E7031071066DEA0D815DFAC31E07763666D4C46C2DEA856FCEC34F2B5CCF3750F72BD8F924544E73C51D7DBC11A1AE31049E5D0E121CE0053AEB8E018805B"); break; //DME.04.69535851
+    case 256 : return F("bA944C514381355920004A5FC7A79009025E37121BAC0ACF1A216281AE7A85DA20DF90613A83417418BD9BA7B0F09C192713A65DECD3ECB2DFC5299683A5B2669EE70851493FC9944D7579F7FCD44CCC839E432D498E40EFDF1614BD9B7E6F1B4AD96CC16939C9D061434790765D3883275B80DE0508393A0F00C1B1590A66F94D920BD5E80125C610F7C0B0346D8D0CFE8DD4E8A14370077D90A7BCFA59345DB13D7134E80D5F2F1D50B3B043075126DF42C2A03FD0C05010002FD0B0111ABC78028"); break; //EFE.04.92551338
+    case 257 : return F("b4E44EE4D557648251B048F777ABE004025D84601014EEA5EDCC08B007BC8BB1EBB266BADDEC127897EDA4296EEB8EB05A57DFCA5190F54B41C05B1BF0F5EF82B719CD27EA6242263DFB94F589F38843A1146916B872141977275BD8035"); break; //SON.04.25487655
+    case 258 : return F("b2644AC488137000050371BF47201813700AC485000D90000002F2F0C32C60E569405002F2F2F2F2F2F2F2FB90E8059"); break; //REL.00.00378101"); break; //REL.37.00003781
+    case 259 : return F("b2644AC488137000050371BF47202813700AC48500CE20000002F2F0C068D16635640002F2F2F2F2F2F2F2F72188058"); break; //REL.0C.00378102"); break; //REL.37.00003781
+    case 260 : return F("b36446850790133512243C21BA1009F29793C0088230200007E0E4FFD476370544E2D33280000000000000000000CC8A2608214298C81CD45160105964E203B372E"); break; //TCH.43.51330179
+    case 261 : return F("b3744685029069372274351B5A2129F27995400305C1200000007400B69792D4C914447BB7D4A2AC86CB37BFAFD59EEC7971B1F070D00000000002C0040004FD085CC"); break; //TCH.43.72930629
+    case 262 : return F("b37446850715221622843FE7BA2129F29F827008800000080800D0000DE060000E00739EF48B3861639BC2044115A10CB0C6283102F1830C0080AD861D24C1E8F80E1"); break; //TCH.43.62215271
+    case 263 : return F("b374468508475676739430ECDA2109F27793500287F15000000D34D95907D550ADB32A24C9AAAA9922ED6A76075D51C1F7937DB8D4B95F20E4C63EDC5997041E280FA"); break; //TCH.43.67677584
+    case 264 : return F("b36446850442620514543A443A1009F279E180038CC0400803680000286DB08F0C00727FCF0C82F92E8128D31165D722873C60C1568910103062C700107EF3F912E"); break; //TCH.43.51202644
+    case 265 : return F("b3744685060478860574395F9A20D9F29820500A00000000100060000E34A00000000000000000000000000000000FFFF0000000000000000000000000000FFFF80D4"); break; //TCH.43.60884760
+    case 266 : return F("b3644685005012040714351C0A0009F298F560290E10101A40000FFF719F04E99E7B2F9E2E512000000000000000042B000000000000000000000000000FFFF95A6"); break; //TCH.43.40200105
+    case 267 : return F("b364468504200545145444FE0A1009F29CA6900B0F00D008023001A68BB4748D28D4F7685755095534E09E187ADE5A7B903D03027041000000100008002CF41D570"); break; //TCH.44.51540042
+    case 268 : return F("b37446850341929625744B280A20D9F27C9350058395A00000169848B3EDF2ECEF8A17F008A875B80FCB947E0914351334E69E4A14D72A8E296375279E286AB9B8008"); break; //TCH.44.62291934
+    case 269 : return F("b36446850060670527144C1AAA0009F29C41F0190DE0F0081A5A1BF2595D38ABA9F512E89A3471D67F8F0C6206308383B6288298BD0A3D68424A228A9907F8B34B2"); break; //TCH.44.52700606
+    case 270 : return F("bY5044972608062002001A7AC90340A517F5A8F83D78281AEFF06FDBDEDEF842336FA663F292D6EEACB0F54CA02FB47C8F587862B352ED30166FEE61753998230C38444F845C9FCC364D3C614095F92D14A28010"); break; //ITW.1A.02200608
+    case 271 : return F("bY5E449726900634160004728499181897A60030BA0040A53FB81E378B33F9E23FEDF6A0B1B381F4972C2842041CC7EC8D74D675CE56222039CE82385073750F2B695CBEC9B0E8A48EC3F09B74C26E4194A06B7974203DDD0C7976874216E0D282DE"); break; //ITW.04.16340690"); break; //ITW.30.18189984
+    case 272 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984
+    case 273 : return F("bY5044972625353893071A7A0B0040A5B12EC2E009C467CAD2AF0A38712684FE764C6181D2969A7F0F7B076CB9719FEB21C32E48161EEA5A1E6E92F354FED894994C368F17E6F68E2E6AA1D7C289E3FD7A908015"); break; //ITW.1A.93383525
+    case 274 : return F("b2E449726606670194107A6AB8CB0D47ABF0000A00413E84B070004FD3442178001C00004933C00000000333B00000A2D00325A0000CD2E801C"); break; //ITW.07.19706660
+    case 275 : return F("bY50449726141331160007728499181897A600307E0030A5500A4109842EF76DD4A2DEDF6722CCB4D0746C8505086D91ED34B41AFD24FED0111715A21E549191B1529EE2AE8229E50E7900000000000070208AD8"); break; //ITW.07.16311314"); break; //ITW.30.18189984
+
+  }
+ // *INDENT-ON*
+  count = 0;
+  return F("");
+}
+
+# endif // if P094_DEBUG_OPTIONS
+
+bool P094_data_struct::loop() {
+  if (!isInitialized()) {
+    return false;
+  }
+  bool fullSentenceReceived = false;
+
+  if (easySerial != nullptr) {
+    int available = easySerial->available();
+
+    unsigned long timeout = millis() + 10;
+
+    while (available > 0 && !fullSentenceReceived) {
+      // Look for end marker
+      char c = easySerial->read();
+      --available;
+
+      if (available == 0) {
+        if (!timeOutReached(timeout)) {
+          available = easySerial->available();
+        }
+        delay(0);
+      }
+
+      switch (c) {
+        case 13:
+        {
+          const size_t length = sentence_part.length();
+          bool valid          = length > 0;
+
+          for (size_t i = 0; i < length && valid; ++i) {
+            if ((sentence_part[i] > 127) || (sentence_part[i] < 32)) {
+              sentence_part = String();
+              ++sentences_received_error;
+              valid = false;
+            }
+          }
+
+          if (valid) {
+            fullSentenceReceived = true;
+          }
+          break;
+        }
+        case 10:
+
+          // Ignore LF
+          break;
+        default:
+
+          if ((c >= 32) && (c < 127)) {
+            sentence_part += c;
+          } else {
+            current_sentence_errored = true;
+          }
+          break;
+      }
+
+      if (max_length_reached()) { fullSentenceReceived = true; }
+    }
+  }
+
+  if (fullSentenceReceived) {
+    ++sentences_received;
+    length_last_received = sentence_part.length();
+  }
+# if P094_DEBUG_OPTIONS
+  else {
+    if (debug_generate_CUL_data && (sentence_part.length() == 0)) {
+      static uint32_t last_test_sentence = 0;
+      static int count                   = 0;
+
+      if (timePassedSince(last_test_sentence) > 1000) {
+        count++;
+
+        //        sentence_part = F("b2644AC48585300005037FAB97201585300AC485003150000202F2F0C0AF314213993002F2F2F2F2F2F2F2FAFCA8046");
+        sentence_part        = getDebugSentences(count);
+        fullSentenceReceived = true;
+        last_test_sentence   = millis();
+      }
+    }
+  }
+# endif // if P094_DEBUG_OPTIONS
+  return fullSentenceReceived;
+}
+
+const String& P094_data_struct::peekSentence() const {
+  return sentence_part;
+}
+
+void P094_data_struct::getSentence(String& string, bool appendSysTime) {
+  string        = std::move(sentence_part);
+  sentence_part = String(); // FIXME TD-er: Should not be needed as move already cleared it.
+
+  if (appendSysTime) {
+    // Unix timestamp = 10 decimals + separator
+    if (string.reserve(sentence_part.length() + 11)) {
+      string += ';';
+      string += node_time.getUnixTime();
+    }
+  }
+  sentence_part.reserve(string.length());
+}
+
+void P094_data_struct::getSentencesReceived(uint32_t& succes, uint32_t& error, uint32_t& length_last) const {
+  succes      = sentences_received;
+  error       = sentences_received_error;
+  length_last = length_last_received;
+}
+
+void P094_data_struct::setMaxLength(uint16_t maxlenght) {
+  max_length = maxlenght;
+}
+
+uint32_t P094_data_struct::getFilterOffWindowTime() const {
+  return filterOffWindowTime;
+}
+
+void P094_data_struct::setDisableFilterWindowTimer() {
+  if (getFilterOffWindowTime() == 0) {
+    disable_filter_window = 0;
+  }
+  else {
+    disable_filter_window = millis() + getFilterOffWindowTime();
+  }
+}
+
+bool P094_data_struct::disableFilterWindowActive() const {
+  if (disable_filter_window != 0) {
+    if (!timeOutReached(disable_filter_window)) {
+      // We're still in the window where filtering is disabled
+      return true;
+    }
+  }
+  return false;
+}
+
+bool P094_data_struct::parsePacket(const String& received, mBusPacket_t& packet) {
+  const size_t strlength = received.length();
+
+  if (strlength == 0) {
+    return false;
+  }
+
+  const char firstChar = received[0];
+
+  if ((firstChar == 'b')) {
+    // Received a data packet in CUL format.
+    if (strlength < 21) {
+      return false;
+    }
+
+    // Decoded packet
+    if (!packet.parse(received)) { return false; }
+
+    const mBusPacket_header_t *header = packet.getDeviceHeader();
+
+    if (header == nullptr) {
+      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+        addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Header "), packet.toString()));
+      }
+
+      return false;
+    }
+
+    if (mute_messages) {
+      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+        addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Muted "), packet.toString()));
+      }
+
+      return false; // Mute all messages
+    }
+
+    if (!interval_filter.enabled || (_filters.size() == 0)) {
+      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+        addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Filter "), packet.toString()));
+      }
+
+      return true; // No filtering
+    }
+
+    for (unsigned int f = 0; f < _filters.size(); ++f) {
+      if (_filters[f].matches(*header)) {
+        const bool res = interval_filter.filter(packet, _filters[f]);
+
+        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+          addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: Match "), _filters[f].toString()));
+          addLogMove(LOG_LEVEL_INFO, concat(res ? F("CUL Filter: Pass ") : F("CUL Filter: Reject "), header->toString()));
+        }
+
+        return res;
+      }
+    }
+
+    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
+      addLogMove(LOG_LEVEL_INFO, concat(F("CUL Filter: NO Match "), header->toString()));
+    }
+
+    // No matching filter, so consider fall-through filter to be:
+    // *.*.*;none
+    return false;
+  } else {
+    switch (firstChar) {
+      case 'C': // CMODE
+      case 'S': // SMODE
+      case 'T': // TMODE
+      case 'O': // OFF
+      case 'V': // Version info
+
+        // FIXME TD-er: Must test the result of the other possible answers.
+        return true;
+    }
+  }
+
+  return false;
+}
+
+void P094_data_struct::interval_filter_purgeExpired() {
+  interval_filter.purgeExpired();
+}
+
+void P094_data_struct::html_show_interval_filter_stats() const
+{
+  if (interval_filter._mBusFilterMap.empty()) { return; }
+
+  addRowLabel(F("Interval Filter Entries"));
+  addHtmlInt(interval_filter._mBusFilterMap.size());
+
+  addFormNote(F("Non expired W-MBus device filters"));
+}
+
+bool P094_data_struct::collect_stats_add(const mBusPacket_t& packet, const String& source) {
+  if (collect_stats) {
+    return mBus_stats[firstStatsIndexActive ? 0 : 1].add(packet, source);
+  }
+  return false;
+}
+
+void P094_data_struct::prepare_dump_stats() {
+  firstStatsIndexActive = !firstStatsIndexActive;
+}
+
+bool P094_data_struct::dump_next_stats(String& str) {
+  const uint8_t dumpStatsIndex = firstStatsIndexActive ? 1 : 0;
+
+  if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return false; }
+
+  str = concat(F("stats;"), mBus_stats[dumpStatsIndex].getFront());
+
+  return true;
+}
+
+void P094_data_struct::html_show_mBus_stats() const
+{
+  const uint8_t dumpStatsIndex = firstStatsIndexActive ? 0 : 1;
+
+  if (mBus_stats[dumpStatsIndex]._mBusStatsMap.empty()) { return; }
+
+  addRowLabel(F("W-MBus Devices"));
+  addHtmlInt(mBus_stats[dumpStatsIndex]._mBusStatsMap.size());
+
+  addFormNote(F("Devices received since last culreader,dumpstats"));
+
+  mBus_stats[dumpStatsIndex].toHtml();
+}
+
+bool P094_data_struct::max_length_reached() const {
+  if (max_length == 0) { return false; }
+  return sentence_part.length() >= max_length;
+}
+
+bool P094_data_struct::isDuplicate(const P094_filter& other) const
+{
+  const String f_str = other.toString();
+
+  for (auto it = _filters.begin(); it != _filters.end(); ++it) {
+    if (f_str.equals(it->toString())) {
+      return true;
+    }
+  }
+  return false;
+}
+
+# if P094_DEBUG_OPTIONS
+uint32_t P094_data_struct::getDebugCounter() {
+  return debug_counter++;
+}
+
+# endif // if P094_DEBUG_OPTIONS
+
+#endif  // USES_P094
\ No newline at end of file
diff --git a/src/src/PluginStructs/P094_data_struct.h b/src/src/PluginStructs/P094_data_struct.h
index 9fdcd9db2..872038631 100644
--- a/src/src/PluginStructs/P094_data_struct.h
+++ b/src/src/PluginStructs/P094_data_struct.h
@@ -1,146 +1,194 @@
-#ifndef PLUGINSTRUCTS_P094_DATA_STRUCT_H
-#define PLUGINSTRUCTS_P094_DATA_STRUCT_H
-
-#include "../../_Plugin_Helper.h"
-#ifdef USES_P094
-
-#include 
-#include 
-
-
-# define P094_REGEX_POS             0
-# define P094_NR_CHAR_USE_POS       1
-# define P094_FILTER_OFF_WINDOW_POS 2
-# define P094_MATCH_TYPE_POS        3
-
-# define P094_FIRST_FILTER_POS   10
-
-# define P094_ITEMS_PER_FILTER   4
-# define P094_AND_FILTER_BLOCK   3
-# define P094_NR_FILTERS         (7 * P094_AND_FILTER_BLOCK)
-# define P94_Nlines              (P094_FIRST_FILTER_POS + (P094_ITEMS_PER_FILTER * (P094_NR_FILTERS)))
-# define P94_Nchars              128
-# define P94_MAX_CAPTURE_INDEX   32
-
-
-enum P094_Match_Type {
-  P094_Regular_Match          = 0,
-  P094_Regular_Match_inverted = 1,
-  P094_Filter_Disabled        = 2
-};
-# define P094_Match_Type_NR_ELEMENTS 3
-
-enum P094_Filter_Value_Type {
-  P094_not_used      = 0,
-  P094_packet_length = 1,
-  P094_unknown1      = 2,
-  P094_manufacturer  = 3,
-  P094_serial_number = 4,
-  P094_unknown2      = 5,
-  P094_meter_type    = 6,
-  P094_rssi          = 7,
-  P094_position      = 8
-};
-# define P094_FILTER_VALUE_Type_NR_ELEMENTS 9
-
-enum P094_Filter_Comp {
-  P094_Equal_OR      = 0,
-  P094_NotEqual_OR   = 1,
-  P094_Equal_MUST    = 2,
-  P094_NotEqual_MUST = 3
-};
-
-# define P094_FILTER_COMP_NR_ELEMENTS 4
-
-
-struct P094_data_struct : public PluginTaskData_base {
-public:
-
-  P094_data_struct();
-
-  virtual ~P094_data_struct();
-
-  void reset();
-
-  bool init(ESPEasySerialPort port, 
-            const int16_t serial_rx,
-            const int16_t serial_tx,
-            unsigned long baudrate);
-
-  void post_init();
-
-  bool isInitialized() const;
-
-  void sendString(const String& data);
-
-  bool loop();
-
-  const String& peekSentence() const;
-
-  void getSentence(String& string, bool appendSysTime);
-
-  void getSentencesReceived(uint32_t& succes,
-                            uint32_t& error,
-                            uint32_t& length_last) const;
-
-  void setMaxLength(uint16_t maxlenght);
-
-  void setLine(uint8_t          varNr,
-               const String& line);
-
-
-  uint32_t        getFilterOffWindowTime() const;
-
-  P094_Match_Type getMatchType() const;
-
-  bool            invertMatch() const;
-
-  bool            filterUsed(uint8_t lineNr) const;
-
-  String          getFilter(uint8_t                 lineNr,
-                            P094_Filter_Value_Type& capture,
-                            uint32_t              & optional,
-                            P094_Filter_Comp      & comparator) const;
-
-  void          setDisableFilterWindowTimer();
-
-  bool          disableFilterWindowActive() const;
-
-  bool          parsePacket(const String& received) const;
-
-  static const __FlashStringHelper * MatchType_toString(P094_Match_Type matchType);
-  static const __FlashStringHelper * P094_FilterValueType_toString(P094_Filter_Value_Type valueType);
-  static const __FlashStringHelper * P094_FilterComp_toString(P094_Filter_Comp comparator);
-
-
-  // Made public so we don't have to copy the values when loading/saving.
-  String _lines[P94_Nlines];
-
-  static size_t P094_Get_filter_base_index(size_t filterLine);
-
-  // Get (and increment) debug counter
-  uint32_t getDebugCounter();
-
-private:
-
-  bool max_length_reached() const;
-
-  ESPeasySerial *easySerial = nullptr;
-  String         sentence_part;
-  uint16_t       max_length               = 550;
-  uint32_t       sentences_received       = 0;
-  uint32_t       sentences_received_error = 0;
-  bool           current_sentence_errored = false;
-  uint32_t       length_last_received     = 0;
-  unsigned long  disable_filter_window    = 0;
-  uint32_t       debug_counter            = 0;
-
-  bool                   valueType_used[P094_FILTER_VALUE_Type_NR_ELEMENTS] = {0};
-  P094_Filter_Value_Type valueType_index[P094_NR_FILTERS];
-  P094_Filter_Comp       filter_comp[P094_NR_FILTERS];
-};
-
-
-#endif // USES_P094
-
-#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H
\ No newline at end of file
+#ifndef PLUGINSTRUCTS_P094_DATA_STRUCT_H
+#define PLUGINSTRUCTS_P094_DATA_STRUCT_H
+
+#include "../../_Plugin_Helper.h"
+#ifdef USES_P094
+
+# include "../Helpers/CUL_interval_filter.h"
+# include "../Helpers/CUL_stats.h"
+
+# include "../PluginStructs/P094_Filter.h"
+
+# include 
+# include 
+
+# ifndef P094_DEBUG_OPTIONS
+#  define P094_DEBUG_OPTIONS 0
+# endif // ifndef P094_DEBUG_OPTIONS
+
+
+# define P094_BAUDRATE           PCONFIG_LONG(0)
+# define P094_BAUDRATE_LABEL     PCONFIG_LABEL(0)
+
+# define P094_DEBUG_SENTENCE_LENGTH  PCONFIG_LONG(1)
+# define P094_DEBUG_SENTENCE_LABEL   PCONFIG_LABEL(1)
+
+# define P094_DISABLE_WINDOW_TIME_MS  PCONFIG_LONG(2)
+
+# define P094_GET_APPEND_RECEIVE_SYSTIME    bitRead(PCONFIG(0), 0)
+# define P094_SET_APPEND_RECEIVE_SYSTIME(X) bitWrite(PCONFIG(0), 0, X)
+
+# if P094_DEBUG_OPTIONS
+#  define P094_GET_GENERATE_DEBUG_CUL_DATA    bitRead(PCONFIG(0), 1)
+#  define P094_SET_GENERATE_DEBUG_CUL_DATA(X) bitWrite(PCONFIG(0), 1, X)
+# endif // if P094_DEBUG_OPTIONS
+
+# define P094_GET_INTERVAL_FILTER    bitRead(PCONFIG(0), 2)
+# define P094_SET_INTERVAL_FILTER(X) bitWrite(PCONFIG(0), 2, X)
+
+# define P094_GET_COLLECT_STATS    bitRead(PCONFIG(0), 3)
+# define P094_SET_COLLECT_STATS(X) bitWrite(PCONFIG(0), 3, X)
+
+# define P094_GET_MUTE_MESSAGES    bitRead(PCONFIG(0), 4)
+# define P094_SET_MUTE_MESSAGES(X) bitWrite(PCONFIG(0), 4, X)
+
+# define P094_NR_FILTERS           PCONFIG(1)
+
+# ifdef ESP8266
+#  define P094_MAX_NR_FILTERS      25
+# endif // ifdef ESP8266
+# ifdef ESP32
+#  define P094_MAX_NR_FILTERS      100
+# endif // ifdef ESP32
+
+
+# ifdef ESP8266
+#  define P094_MAX_MSG_LENGTH      550
+# endif // ifdef ESP8266
+# ifdef ESP32
+#  define P094_MAX_MSG_LENGTH      1024
+# endif // ifdef ESP32
+
+
+# define P094_DEFAULT_BAUDRATE   38400
+
+
+struct P094_data_struct : public PluginTaskData_base {
+public:
+
+  P094_data_struct();
+
+  virtual ~P094_data_struct();
+
+  void reset();
+
+  bool init(ESPEasySerialPort port,
+            const int16_t     serial_rx,
+            const int16_t     serial_tx,
+            unsigned long     baudrate);
+
+  void setFlags(unsigned long filterOffWindowTime_ms,
+                bool          intervalFilterEnabled,
+                bool          mute,
+                bool          collectStats);
+
+
+  void          loadFilters(struct EventStruct *event,
+                            uint8_t             nrFilters);
+
+  String        saveFilters(struct EventStruct *event) const;
+
+
+  void          clearFilters();
+
+  bool          addFilter(struct EventStruct *event, const String& filter);
+
+  String        getFiltersMD5() const;
+
+  void          WebformLoadFilters(uint8_t nrFilters) const;
+
+  void          WebformSaveFilters(struct EventStruct *event,
+                                   uint8_t             nrFilters);
+
+  bool          isInitialized() const;
+
+  void          sendString(const String& data);
+
+  bool          loop();
+
+  const String& peekSentence() const;
+
+  void          getSentence(String& string,
+                            bool    appendSysTime);
+
+  void          getSentencesReceived(uint32_t& succes,
+                                     uint32_t& error,
+                                     uint32_t& length_last) const;
+
+  void     setMaxLength(uint16_t maxlenght);
+
+  void     setLine(uint8_t       varNr,
+                   const String& line);
+
+  uint32_t getFilterOffWindowTime() const;
+
+  bool     filterUsed(uint8_t lineNr) const;
+
+  void     setDisableFilterWindowTimer();
+
+  bool     disableFilterWindowActive() const;
+
+  bool     parsePacket(const String& received,
+                       mBusPacket_t& packet);
+
+
+# if P094_DEBUG_OPTIONS
+
+  // Get (and increment) debug counter
+  uint32_t getDebugCounter();
+
+  void     setGenerate_DebugCulData(bool value) {
+    debug_generate_CUL_data = value;
+  }
+
+# endif // if P094_DEBUG_OPTIONS
+
+  void interval_filter_purgeExpired();
+
+  void html_show_interval_filter_stats() const;
+
+
+  bool collect_stats_add(const mBusPacket_t& packet, const String& source);
+  void prepare_dump_stats();
+  bool dump_next_stats(String& str);
+
+  void html_show_mBus_stats() const;
+
+private:
+
+  bool max_length_reached() const;
+
+  bool isDuplicate(const P094_filter& other) const;
+
+  std::vector_filters;
+
+  ESPeasySerial *easySerial = nullptr;
+  String         sentence_part;
+  uint16_t       max_length = P094_MAX_MSG_LENGTH;
+  uint16_t       nrFilters{};
+  unsigned long  filterOffWindowTime      = 0;
+  uint32_t       sentences_received       = 0;
+  uint32_t       sentences_received_error = 0;
+  bool           current_sentence_errored = false;
+  uint32_t       length_last_received     = 0;
+  unsigned long  disable_filter_window    = 0;
+
+  # if P094_DEBUG_OPTIONS
+  uint32_t debug_counter           = 0;
+  bool     debug_generate_CUL_data = false;
+  # endif // if P094_DEBUG_OPTIONS
+  bool collect_stats = false;
+  bool mute_messages = false;
+
+  bool firstStatsIndexActive = false;
+
+  CUL_interval_filter interval_filter;
+
+  // Alternating stats, one being flushed, the other used to collect new stats
+  CUL_Stats mBus_stats[2];
+};
+
+
+#endif // USES_P094
+
+#endif // PLUGINSTRUCTS_P094_DATA_STRUCT_H
diff --git a/src/src/PluginStructs/P095_data_struct.cpp b/src/src/PluginStructs/P095_data_struct.cpp
index 65fcbb3e2..9ed5125a0 100644
--- a/src/src/PluginStructs/P095_data_struct.cpp
+++ b/src/src/PluginStructs/P095_data_struct.cpp
@@ -17,10 +17,11 @@ const __FlashStringHelper* ILI9xxx_type_toString(const ILI9xxx_type_e& device) {
     case ILI9xxx_type_e::ILI9481_RGB_320x480: return F("ILI9481 320 x 480px (RGB)");
     case ILI9xxx_type_e::ILI9481_CMI7_320x480: return F("ILI9481 320 x 480px (CMI7)");
     case ILI9xxx_type_e::ILI9481_CMI8_320x480: return F("ILI9481 320 x 480px (CMI8)");
-    # ifdef P095_ENABLE_ILI948X
-    case ILI9xxx_type_e::ILI9486_320x480: return F("ILI9486 320 x 480px");
-    case ILI9xxx_type_e::ILI9488_320x480: return F("ILI9488 320 x 480px");
-    # endif // ifdef P095_ENABLE_ILI948X
+    # if P095_ENABLE_ILI948X
+
+    // case ILI9xxx_type_e::ILI9486_320x480: return F("ILI9486 320 x 480px");
+    case ILI9xxx_type_e::ILI9488_320x480: return F("ILI9486/ILI9488 320 x 480px");
+    # endif // if P095_ENABLE_ILI948X
   }
   # ifndef BUILD_NO_DEBUG
   return F("Unsupported type!");
@@ -49,10 +50,11 @@ void ILI9xxx_type_toResolution(const ILI9xxx_type_e& device,
     case ILI9xxx_type_e::ILI9481_RGB_320x480:
     case ILI9xxx_type_e::ILI9481_CMI7_320x480:
     case ILI9xxx_type_e::ILI9481_CMI8_320x480:
-    # ifdef P095_ENABLE_ILI948X
-    case ILI9xxx_type_e::ILI9486_320x480:
+    # if P095_ENABLE_ILI948X
+
+    // case ILI9xxx_type_e::ILI9486_320x480:
     case ILI9xxx_type_e::ILI9488_320x480:
-    # endif // ifdef P095_ENABLE_ILI948X
+    # endif // if P095_ENABLE_ILI948X
       x = 320;
       y = 480;
       break;
@@ -68,10 +70,10 @@ const __FlashStringHelper* P095_CommandTrigger_toString(const P095_CommandTrigge
     case P095_CommandTrigger::ili9341: break;
     case P095_CommandTrigger::ili9342: return F("ili9342");
     case P095_CommandTrigger::ili9481: return F("ili9481");
-    # ifdef P095_ENABLE_ILI948X
+    # if P095_ENABLE_ILI948X
     case P095_CommandTrigger::ili9486: return F("ili9486");
     case P095_CommandTrigger::ili9488: return F("ili9488");
-    # endif // ifdef P095_ENABLE_ILI948X
+    # endif // if P095_ENABLE_ILI948X
   }
   return F("ili9341"); // Default command trigger
 }
@@ -89,11 +91,19 @@ P095_data_struct::P095_data_struct(ILI9xxx_type_e      displayType,
                                    String              commandTrigger,
                                    uint16_t            fgcolor,
                                    uint16_t            bgcolor,
-                                   bool                textBackFill)
+                                   bool                textBackFill
+                                   # if                ADAGFX_FONTS_INCLUDED
+                                   ,
+                                   const uint8_t       defaultFontId
+                                   # endif // if ADAGFX_FONTS_INCLUDED
+                                   )
   : _displayType(displayType), _rotation(rotation), _fontscaling(fontscaling), _textmode(textmode),
   _backlightPin(backlightPin), _backlightPercentage(backlightPercentage), _displayTimer(displayTimer),
   _displayTimeout(displayTimer), _commandTrigger(commandTrigger), _fgcolor(fgcolor), _bgcolor(bgcolor),
   _textBackFill(textBackFill)
+  # if ADAGFX_FONTS_INCLUDED
+  , _defaultFontId(defaultFontId)
+  # endif // if ADAGFX_FONTS_INCLUDED
 {
   _commandTrigger.toLowerCase();
   _commandTriggerCmd  = _commandTrigger;
@@ -106,6 +116,9 @@ P095_data_struct::P095_data_struct(ILI9xxx_type_e      displayType,
 P095_data_struct::~P095_data_struct() {
   delete gfxHelper;
   delete tft;
+  # if P095_ENABLE_ILI948X
+  delete ili9488;
+  # endif // if P095_ENABLE_ILI948X
 }
 
 void P095_data_struct::init() {
@@ -123,35 +136,53 @@ bool P095_data_struct::plugin_init(struct EventStruct *event) {
   init();
   bool success = false;
 
-  if (nullptr == tft) {
+  if (nullptr == tft
+      # if P095_ENABLE_ILI948X
+      && nullptr == ili9488
+      # endif // if P095_ENABLE_ILI948X
+      ) {
     # ifndef BUILD_NO_DEBUG
     addLog(LOG_LEVEL_INFO, F("ILI9341: Init start."));
     # endif // ifndef BUILD_NO_DEBUG
 
-    tft = new (std::nothrow) Adafruit_ILI9341(PIN(0), PIN(1), PIN(2), static_cast(_displayType), _xpix, _ypix);
+    # if P095_ENABLE_ILI948X
 
-    if (nullptr != tft) {
-      tft->begin();
+    if (ILI9xxx_type_e::ILI9488_320x480 == _displayType) {
+      ili9488 = new (std::nothrow) ILI9488(PIN(0), PIN(1), PIN(2));
+
+      if (nullptr != ili9488) {
+        ili9488->begin();
+        useILI9488 = true;
+      }
+    } else
+    # endif // if P095_ENABLE_ILI948X
+    {
+      tft = new (std::nothrow) Adafruit_ILI9341(PIN(0), PIN(1), PIN(2), static_cast(_displayType), _xpix, _ypix);
+
+      if (nullptr != tft) {
+        tft->begin();
+      }
     }
 
+
     # ifndef BUILD_NO_DEBUG
 
     if (loglevelActiveFor(LOG_LEVEL_INFO)) {
       String log;
       log.reserve(65);
-      log += F("ILI9341: Init done, address: 0x");
-      log += String(reinterpret_cast(tft), HEX);
-      log += ' ';
+      log += strformat(F("ILI9341: Init done, address: 0x%x "),
+                       #  if P095_ENABLE_ILI948X
+                       useILI9488 ? reinterpret_cast(ili9488) :
+                       #  endif // if P095_ENABLE_ILI948X
+                       reinterpret_cast(tft));
 
-      if (nullptr == tft) {
+      if (!isInitialized()) {
         log += F("in");
       }
-      log += F("valid, display: ");
-      log += ILI9xxx_type_toString(static_cast(P095_CONFIG_FLAG_GET_TYPE));
-      log += F(", commands: ");
-      log += _commandTrigger;
-      log += '/';
-      log += _commandTriggerCmd;
+      log += strformat(F("valid, display: %s, commands: %s/%s"),
+                       String(ILI9xxx_type_toString(_displayType)).c_str(),
+                       _commandTrigger.c_str(),
+                       _commandTriggerCmd.c_str());
       addLogMove(LOG_LEVEL_INFO, log);
     }
     # endif // ifndef BUILD_NO_DEBUG
@@ -159,18 +190,44 @@ bool P095_data_struct::plugin_init(struct EventStruct *event) {
     addLog(LOG_LEVEL_INFO, F("ILI9341: No init?"));
   }
 
-  if (nullptr != tft) {
-    gfxHelper = new (std::nothrow) AdafruitGFX_helper(tft,
-                                                      _commandTrigger,
-                                                      _xpix,
-                                                      _ypix,
-                                                      AdaGFXColorDepth::FullColor,
-                                                      _textmode,
-                                                      _fontscaling,
-                                                      _fgcolor,
-                                                      _bgcolor,
-                                                      true,
-                                                      _textBackFill);
+  if (isInitialized()) {
+    # if P095_ENABLE_ILI948X
+
+    if (useILI9488) {
+      gfxHelper = new (std::nothrow) AdafruitGFX_helper(ili9488,
+                                                        _commandTrigger,
+                                                        _xpix,
+                                                        _ypix,
+                                                        AdaGFXColorDepth::FullColor,
+                                                        _textmode,
+                                                        _fontscaling,
+                                                        _fgcolor,
+                                                        _bgcolor,
+                                                        true,
+                                                        _textBackFill
+                                                        #  if ADAGFX_FONTS_INCLUDED
+                                                        , _defaultFontId
+                                                        #  endif // if ADAGFX_FONTS_INCLUDED
+                                                        );
+    } else
+    # endif // if P095_ENABLE_ILI948X
+    {
+      gfxHelper = new (std::nothrow) AdafruitGFX_helper(tft,
+                                                        _commandTrigger,
+                                                        _xpix,
+                                                        _ypix,
+                                                        AdaGFXColorDepth::FullColor,
+                                                        _textmode,
+                                                        _fontscaling,
+                                                        _fgcolor,
+                                                        _bgcolor,
+                                                        true,
+                                                        _textBackFill
+                                                        # if ADAGFX_FONTS_INCLUDED
+                                                        , _defaultFontId
+                                                        # endif // if ADAGFX_FONTS_INCLUDED
+                                                        );
+    }
 
     if (nullptr != gfxHelper) {
       gfxHelper->initialize();
@@ -180,10 +237,21 @@ bool P095_data_struct::plugin_init(struct EventStruct *event) {
       gfxHelper->invertDisplay(P095_CONFIG_FLAG_GET_INVERTDISPLAY);
     }
     updateFontMetrics();
-    tft->fillScreen(_bgcolor);             // fill screen with background color
-    tft->setTextColor(_fgcolor, _bgcolor); // set text color to white and configured background
-    tft->setTextSize(_fontscaling);        // Handles 0 properly, text size, default 1 = very small
-    tft->setCursor(0, 0);                  // move cursor to position (0, 0) pixel
+    # if P095_ENABLE_ILI948X
+
+    if (useILI9488) {
+      ili9488->fillScreen(_bgcolor);             // fill screen with background color
+      ili9488->setTextColor(_fgcolor, _bgcolor); // set text color to white and configured background
+      ili9488->setTextSize(_fontscaling);        // Handles 0 properly, text size, default 1 = very small
+      ili9488->setCursor(0, 0);                  // move cursor to position (0, 0) pixel
+    } else
+    # endif // if P095_ENABLE_ILI948X
+    {
+      tft->fillScreen(_bgcolor);             // fill screen with background color
+      tft->setTextColor(_fgcolor, _bgcolor); // set text color to white and configured background
+      tft->setTextSize(_fontscaling);        // Handles 0 properly, text size, default 1 = very small
+      tft->setCursor(0, 0);                  // move cursor to position (0, 0) pixel
+    }
     displayOnOff(true);
     # ifdef P095_SHOW_SPLASH
 
@@ -202,7 +270,7 @@ bool P095_data_struct::plugin_init(struct EventStruct *event) {
     updateFontMetrics();
 
 
-    if (P095_CONFIG_BUTTON_PIN != -1) {
+    if (validGpio(P095_CONFIG_BUTTON_PIN)) {
       pinMode(P095_CONFIG_BUTTON_PIN, INPUT_PULLUP);
     }
 
@@ -242,14 +310,24 @@ bool P095_data_struct::plugin_exit(struct EventStruct *event) {
 
   if ((nullptr != tft) && bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_CLEAR_ON_EXIT)) {
     tft->fillScreen(ADAGFX_BLACK); // fill screen with black color
-    displayOnOff(false);
   }
+  # if P095_ENABLE_ILI948X
+
+  if ((nullptr != ili9488) && bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_CLEAR_ON_EXIT)) {
+    ili9488->fillScreen(ADAGFX_BLACK); // fill screen with black color
+  }
+  # endif // if P095_ENABLE_ILI948X
+  displayOnOff(false);
 
   delete gfxHelper;
   gfxHelper = nullptr;
 
   delete tft;
   tft = nullptr;
+  # if P095_ENABLE_ILI948X
+  delete ili9488;
+  ili9488 = nullptr;
+  # endif // if P095_ENABLE_ILI948X
   return true;
 }
 
@@ -257,7 +335,7 @@ bool P095_data_struct::plugin_exit(struct EventStruct *event) {
  * plugin_read: Re-draw the default content
  ***************************************************************************/
 bool P095_data_struct::plugin_read(struct EventStruct *event) {
-  if ((nullptr != tft) && !_splashState) {
+  if (isInitialized() && !_splashState) {
     if (stringsHasContent) {
       gfxHelper->setColumnRowMode(false); // Turn off column mode
 
@@ -279,7 +357,7 @@ bool P095_data_struct::plugin_read(struct EventStruct *event) {
       gfxHelper->setColumnRowMode(bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_USE_COL_ROW)); // Restore column mode
       int16_t curX, curY;
       gfxHelper->getCursorXY(curX, curY);                                                    // Get current X and Y coordinates,
-      UserVar.setFloat(event->TaskIndex, 0, curX);                                               // and put into Values
+      UserVar.setFloat(event->TaskIndex, 0, curX);                                           // and put into Values
       UserVar.setFloat(event->TaskIndex, 1, curY);
     }
   }
@@ -305,6 +383,12 @@ bool P095_data_struct::plugin_ten_per_second(struct EventStruct *event) {
       if (nullptr != tft) {
         tft->fillScreen(_bgcolor); // fill screen with background color
       }
+      #  if P095_ENABLE_ILI948X
+
+      if (nullptr != ili9488) {
+        ili9488->fillScreen(_bgcolor); // fill screen with background color
+      }
+      #  endif // if P095_ENABLE_ILI948X
 
       // Schedule the surrogate initial PLUGIN_READ that has been suppressed by the splash
       Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10);
@@ -312,7 +396,7 @@ bool P095_data_struct::plugin_ten_per_second(struct EventStruct *event) {
   }
   # endif // ifdef P095_SHOW_SPLASH
 
-  if ((P095_CONFIG_BUTTON_PIN != -1) && (getButtonState()) && (nullptr != tft)) {
+  if (validGpio(P095_CONFIG_BUTTON_PIN) && (getButtonState()) && isInitialized()) {
     displayOnOff(true);
     markButtonStateProcessed();
   }
@@ -326,7 +410,7 @@ bool P095_data_struct::plugin_once_a_second(struct EventStruct *event) {
   if ((_displayTimer > 0) && !_splashState) {
     _displayTimer--;
 
-    if ((nullptr != tft) && (_displayTimer == 0)) {
+    if (isInitialized() && (_displayTimer == 0)) {
       displayOnOff(false);
     }
   }
@@ -340,7 +424,7 @@ bool P095_data_struct::plugin_write(struct EventStruct *event, const String& str
   bool   success = false;
   String cmd     = parseString(string, 1);
 
-  if ((nullptr != tft) && cmd.equals(_commandTriggerCmd) && !_splashState) {
+  if (isInitialized() && cmd.equals(_commandTriggerCmd) && !_splashState) {
     String arg1 = parseString(string, 2);
     success = true;
 
@@ -355,13 +439,29 @@ bool P095_data_struct::plugin_write(struct EventStruct *event, const String& str
       String arg2 = parseString(string, 3);
 
       if (!arg2.isEmpty()) {
-        tft->fillScreen(AdaGFXparseColor(arg2));
+        # if P095_ENABLE_ILI948X
+
+        if (useILI9488) {
+          ili9488->fillScreen(AdaGFXparseColor(arg2));
+        } else
+        # endif // if P095_ENABLE_ILI948X
+        {
+          tft->fillScreen(AdaGFXparseColor(arg2));
+        }
       } else {
-        tft->fillScreen(_bgcolor);
+        # if P095_ENABLE_ILI948X
+
+        if (useILI9488) {
+          ili9488->fillScreen(_bgcolor);
+        } else
+        # endif // if P095_ENABLE_ILI948X
+        {
+          tft->fillScreen(_bgcolor);
+        }
       }
     }
     else if (equals(arg1, F("backlight"))) {
-      if ((P095_CONFIG_BACKLIGHT_PIN != -1) &&       // All is valid?
+      if (validGpio(P095_CONFIG_BACKLIGHT_PIN) &&    // All is valid?
           (event->Par2 > 0) &&
           (event->Par2 <= 100)) {
         P095_CONFIG_BACKLIGHT_PERCENT = event->Par2; // Set but don't store
@@ -373,7 +473,15 @@ bool P095_data_struct::plugin_write(struct EventStruct *event, const String& str
     else if (equals(arg1, F("inv"))) {
       if ((event->Par2 >= 0) &&
           (event->Par2 <= 1)) {
-        tft->invertDisplay(event->Par2);
+        # if P095_ENABLE_ILI948X
+
+        if (useILI9488) {
+          ili9488->invertDisplay(event->Par2);
+        } else
+        # endif // if P095_ENABLE_ILI948X
+        {
+          tft->invertDisplay(event->Par2);
+        }
       } else {
         success = false;
       }
@@ -383,7 +491,15 @@ bool P095_data_struct::plugin_write(struct EventStruct *event, const String& str
         if (nullptr != gfxHelper) {
           gfxHelper->setRotation(event->Par2 % 4);
         } else {
-          tft->setRotation(event->Par2 % 4);
+          # if P095_ENABLE_ILI948X
+
+          if (useILI9488) {
+            ili9488->setRotation(event->Par2 % 4);
+          } else
+          # endif // if P095_ENABLE_ILI948X
+          {
+            tft->setRotation(event->Par2 % 4);
+          }
         }
       } else {
         success = false;
@@ -392,8 +508,8 @@ bool P095_data_struct::plugin_write(struct EventStruct *event, const String& str
       success = false;
     }
   }
-  else if (tft && (cmd.equals(_commandTrigger) ||
-                   (gfxHelper && gfxHelper->isAdaGFXTrigger(cmd))) && !_splashState) {
+  else if (isInitialized() && (cmd.equals(_commandTrigger) ||
+                               (gfxHelper && gfxHelper->isAdaGFXTrigger(cmd))) && !_splashState) {
     success = true;
 
     if (!bitRead(P095_CONFIG_FLAGS, P095_CONFIG_FLAG_NO_WAKE)) { // Wake display?
@@ -440,7 +556,7 @@ bool P095_data_struct::plugin_get_config_value(struct EventStruct *event,
  * displayOnOff: Turn display on or off
  ***************************************************************************/
 void P095_data_struct::displayOnOff(bool state) {
-  if (_backlightPin != -1) {
+  if (validGpio(_backlightPin)) {
     # if defined(ESP8266)
     analogWrite(_backlightPin, state ? ((1024 / 100) * _backlightPercentage) : 0);
     # endif // if defined(ESP8266)
@@ -449,7 +565,15 @@ void P095_data_struct::displayOnOff(bool state) {
     # endif // if defined(ESP32)
   }
 
-  tft->sendCommand(state ? ILI9341_DISPON : ILI9341_DISPOFF);
+  # if P095_ENABLE_ILI948X
+
+  if (useILI9488) {
+    ili9488->writecommand(state ? ILI9341_DISPON : ILI9341_DISPOFF);
+  } else
+  # endif // if P095_ENABLE_ILI948X
+  {
+    tft->sendCommand(state ? ILI9341_DISPON : ILI9341_DISPOFF);
+  }
   _displayTimer = (state ? _displayTimeout : 0);
 }
 
diff --git a/src/src/PluginStructs/P095_data_struct.h b/src/src/PluginStructs/P095_data_struct.h
index 16dd4a261..679d57d34 100644
--- a/src/src/PluginStructs/P095_data_struct.h
+++ b/src/src/PluginStructs/P095_data_struct.h
@@ -14,8 +14,17 @@
 # define P095_Nchars           60
 # define P095_DebounceTreshold  5           // number of 20 msec (fifty per second) ticks before the button has settled
 
-// # define P095_ENABLE_ILI948X                            // Enable or disable support for ILI9486 and ILI9488.
-// MUST reflect similar #define in Adafruit_ILI9341.h !
+# if !defined(P095_ENABLE_ILI948X) && defined(ESP32)
+#  define P095_ENABLE_ILI948X   1           // Enable or disable support for ILI9486 and ILI9488.
+# endif // if !defined(P095_ENABLE_ILI948X) && defined(ESP32)
+# if defined(LIMIT_BUILD_SIZE) && P095_ENABLE_ILI948X && !defined(PLUGIN_BUILD_CUSTOM)
+#  undef P095_ENABLE_ILI948X
+#  define P095_ENABLE_ILI948X   0 // Not enabled for limited buildsizes
+# endif // if defined(LIMIT_BUILD_SIZE) && P095_ENABLE_ILI948X && !defined(PLUGIN_BUILD_CUSTOM)
+
+# if P095_ENABLE_ILI948X
+#  include  // Specific behavior: ILI9488 needs 24 bit colors in SPI mode
+# endif // if P095_ENABLE_ILI948X
 
 # ifndef LIMIT_BUILD_SIZE
 #  define P095_SHOW_SPLASH                              // Enable to show initial splash (text)
@@ -28,6 +37,7 @@
 # define P095_CONFIG_DISPLAY_TIMEOUT    PCONFIG(3)      // Time-out when display-button is enable
 # define P095_CONFIG_BACKLIGHT_PIN      PCONFIG(4)      // Backlight pin
 # define P095_CONFIG_BACKLIGHT_PERCENT  PCONFIG(5)      // Backlight percentage
+# define P095_CONFIG_DEFAULT_FONT       PCONFIG(6)      // Default font
 # define P095_CONFIG_COLORS            PCONFIG_ULONG(3) // 2 Colors fit in 1 long
 
 # define P095_CONFIG_FLAGS             PCONFIG_ULONG(0) // All flags
@@ -82,10 +92,11 @@ enum class ILI9xxx_type_e : uint8_t {
   ILI9481_RGB_320x480    = 7u,
   ILI9481_CMI7_320x480   = 8u,
   ILI9481_CMI8_320x480   = 9u,
-  # ifdef P095_ENABLE_ILI948X
-  ILI9486_320x480 = 10u,
-  ILI9488_320x480 = 11u,
-  # endif // ifdef P095_ENABLE_ILI948X
+  # if P095_ENABLE_ILI948X
+
+  // ILI9486_320x480 = 10u,
+  ILI9488_320x480 = 11u, // Uses a separate library for having a 16 bit data interface
+  # endif // if P095_ENABLE_ILI948X
 };
 
 enum class P095_CommandTrigger : uint8_t {
@@ -93,10 +104,10 @@ enum class P095_CommandTrigger : uint8_t {
   ili9341,
   ili9342,
   ili9481,
-  # ifdef P095_ENABLE_ILI948X
+  # if P095_ENABLE_ILI948X
   ili9486,
   ili9488,
-  # endif // ifdef P095_ENABLE_ILI948X
+  # endif // if P095_ENABLE_ILI948X
 };
 
 const __FlashStringHelper* ILI9xxx_type_toString(const ILI9xxx_type_e& device);
@@ -118,8 +129,13 @@ public:
                    String              commandTrigger,
                    uint16_t            fgcolor      = ADAGFX_WHITE,
                    uint16_t            bgcolor      = ADAGFX_BLACK,
-                   bool                textBackFill = true);
-  P095_data_struct()                                = delete;
+                   bool                textBackFill = true
+                   # if                ADAGFX_FONTS_INCLUDED
+                   ,
+                   const uint8_t       defaultFontId = 0
+                   # endif // if ADAGFX_FONTS_INCLUDED
+                   );
+  P095_data_struct() = delete;
   virtual ~P095_data_struct();
 
   void init();
@@ -145,10 +161,22 @@ public:
 
 private:
 
+  bool isInitialized() {
+    return !(nullptr == tft
+             # if P095_ENABLE_ILI948X
+             && nullptr == ili9488
+             # endif // if P095_ENABLE_ILI948X
+             );
+  }
+
   void displayOnOff(bool state);
   void updateFontMetrics();
 
   Adafruit_ILI9341 *tft = nullptr;
+  # if P095_ENABLE_ILI948X
+  ILI9488 *ili9488    = nullptr;
+  bool     useILI9488 = false;
+  # endif // if P095_ENABLE_ILI948X
 
   AdafruitGFX_helper *gfxHelper = nullptr;
 
@@ -172,6 +200,9 @@ private:
   uint16_t            _fgcolor      = ADAGFX_WHITE;
   uint16_t            _bgcolor      = ADAGFX_BLACK;
   bool                _textBackFill = false;
+  # if ADAGFX_FONTS_INCLUDED
+  uint8_t _defaultFontId;
+  # endif // if ADAGFX_FONTS_INCLUDED
 
   String _commandTriggerCmd;
 
diff --git a/src/src/PluginStructs/P103_data_struct.cpp b/src/src/PluginStructs/P103_data_struct.cpp
index db2617a72..7635e230b 100644
--- a/src/src/PluginStructs/P103_data_struct.cpp
+++ b/src/src/PluginStructs/P103_data_struct.cpp
@@ -23,7 +23,9 @@ bool P103_send_I2C_command(uint8_t I2Caddress, const String& cmd, char *sensorda
   }
   # endif // ifndef BUILD_NO_DEBUG
   Wire.beginTransmission(I2Caddress);
-  Wire.write(cmd.c_str());
+  for (size_t i = 0; i < cmd.length(); ++i)  {
+    Wire.write(static_cast(cmd[i]));
+  }
   error = Wire.endTransmission();
 
   if (error != 0)
diff --git a/src/src/PluginStructs/P104_data_struct.cpp b/src/src/PluginStructs/P104_data_struct.cpp
index 08d8ebafa..8badf5550 100644
--- a/src/src/PluginStructs/P104_data_struct.cpp
+++ b/src/src/PluginStructs/P104_data_struct.cpp
@@ -1,2435 +1,2598 @@
-#include "../PluginStructs/P104_data_struct.h"
-
-#ifdef USES_P104
-
-# include "../Helpers/ESPEasy_Storage.h"
-# include "../Helpers/Numerical.h"
-# include "../WebServer/Markup_Forms.h"
-# include "../WebServer/ESPEasy_WebServer.h"
-# include "../WebServer/Markup.h"
-# include "../WebServer/HTML_wrappers.h"
-# include "../ESPEasyCore/ESPEasyRules.h"
-# include "../Globals/ESPEasy_time.h"
-# include "../Globals/RTC.h"
-
-# include 
-# include 
-# include 
-
-// Needed also here for PlatformIO's library finder as the .h file
-// is in a directory which is excluded in the src_filter
-
-# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-void createHString(String& string); // Forward definition
-# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-void reverseStr(String& str);       // Forward definition
-
-/****************************************************************
- * Constructor
- ***************************************************************/
-P104_data_struct::P104_data_struct(MD_MAX72XX::moduleType_t _mod,
-                                   taskIndex_t              _taskIndex,
-                                   int8_t                   _cs_pin,
-                                   uint8_t                  _modules,
-                                   uint8_t                  _zonesCount)
-  : mod(_mod), taskIndex(_taskIndex), cs_pin(_cs_pin), modules(_modules), expectedZones(_zonesCount) {
-  if (Settings.isSPI_valid()) {
-    P = new (std::nothrow) MD_Parola(mod, cs_pin, modules);
-  } else {
-    addLog(LOG_LEVEL_ERROR, F("DOTMATRIX: Required SPI not enabled. Initialization aborted!"));
-  }
-}
-
-/*******************************
- * Destructor
- ******************************/
-P104_data_struct::~P104_data_struct() {
-  # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-
-  if (nullptr != pM) {
-    pM = nullptr; // Not created here, only reset
-  }
-  # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-
-  if (nullptr != P) {
-    // P->~MD_Parola(); // Call destructor directly, as delete of the object fails miserably
-    // do not: delete P; // Warning: the MD_Parola object doesn't have a virtual destructor, and when changed,
-    // a reboot uccurs when the object is deleted here!
-    P = nullptr; // Reset only
-  }
-}
-
-/*******************************
- * Initializer/starter
- ******************************/
-bool P104_data_struct::begin() {
-  if (!initialized) {
-    loadSettings();
-    initialized = true;
-  }
-
-  if ((P != nullptr) && validGpio(cs_pin)) {
-    # ifdef P104_DEBUG
-    addLog(LOG_LEVEL_INFO, F("dotmatrix: begin() called"));
-    # endif // ifdef P104_DEBUG
-    P->begin(expectedZones);
-    # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-    pM = P->getGraphicObject();
-    # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-    return true;
-  }
-  return false;
-}
-
-# define P104_ZONE_SEP   '\x02'
-# define P104_FIELD_SEP  '\x01'
-# define P104_ZONE_DISP  ';'
-# define P104_FIELD_DISP ','
-
-# define P104_CONFIG_VERSION_V2  0xF000 // Marker in first uint16_t to to indicate second version config settings, anything else if first
-                                        // version.
-                                        // Any third version or later could use 0xE000, etc. The 'version' is stored in the first uint16_t
-                                        // stored in the custom settings
-
-/*
-   Settings layout:
-   Version 1:
-   - uint16_t : size of the next blob holding all settings
-   - char[x]  : Blob with settings, with csv-like strings, using P104_FIELD_SEP and P104_ZONE_SEP separators
-   Version 2:
-   - uint16_t : marker with content P104_CONFIG_VERSION_V2
-   - uint16_t : size of next blob holding 1 zone settings string
-   - char[y]  : Blob holding 1 zone settings string, with csv like string, using P104_FIELD_SEP separators
-   - uint16_t : next size, if 0 then no more blobs
-   - char[x]  : Blob
-   - ...
-   - Max. allowed total custom settings size = 1024
- */
-/**************************************
- * loadSettings
- *************************************/
-void P104_data_struct::loadSettings() {
-  uint16_t bufferSize;
-  char    *settingsBuffer;
-
-  if (taskIndex < TASKS_MAX) {
-    int loadOffset = 0;
-
-    // Read size of the used buffer, could be the settings-version marker
-    LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), loadOffset);
-    bool settingsVersionV2  = (bufferSize == P104_CONFIG_VERSION_V2) || (bufferSize == 0u);
-    uint16_t structDataSize = 0;
-    uint16_t reservedBuffer = 0;
-
-    if (!settingsVersionV2) {
-      reservedBuffer = bufferSize + 1;              // just add 1 for storing a string-terminator
-      addLog(LOG_LEVEL_INFO, F("dotmatrix: Reading Settings V1, will be stored as Settings V2."));
-    } else {
-      reservedBuffer = P104_SETTINGS_BUFFER_V2 + 1; // just add 1 for storing a string-terminator
-    }
-    reservedBuffer++;                               // Add 1 for 0..size use
-    settingsBuffer = new char[reservedBuffer]();    // Allocate buffer and reset to all zeroes
-    loadOffset    += sizeof(bufferSize);
-
-    if (settingsVersionV2) {
-      LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), loadOffset);
-      loadOffset += sizeof(bufferSize); // Skip the size
-    }
-    structDataSize = bufferSize;
-    # ifdef P104_DEBUG_DEV
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      addLogMove(LOG_LEVEL_INFO, strformat(F("P104: loadSettings stored Size: %d taskindex: %d"), structDataSize, taskIndex));
-    }
-    # endif // ifdef P104_DEBUG_DEV
-
-    // Read actual data
-    if (structDataSize > 0) {              // Reading 0 bytes logs an error, so lets avoid that
-      LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)settingsBuffer, structDataSize, loadOffset);
-    }
-    settingsBuffer[bufferSize + 1] = '\0'; // Terminate string
-
-    uint8_t zoneIndex = 0;
-
-    {
-      String buffer(settingsBuffer);
-      # ifdef P104_DEBUG_DEV
-
-      String log;
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        log  = F("P104: loadSettings bufferSize: ");
-        log += bufferSize;
-        log += F(" untrimmed: ");
-        log += buffer.length();
-      }
-      # endif // ifdef P104_DEBUG_DEV
-      buffer.trim();
-      # ifdef P104_DEBUG_DEV
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        log += F(" trimmed: ");
-        log += buffer.length();
-        addLogMove(LOG_LEVEL_INFO, log);
-      }
-      # endif // ifdef P104_DEBUG_DEV
-
-      if (zones.size() > 0) {
-        zones.clear();
-      }
-      zones.reserve(P104_MAX_ZONES);
-      numDevices = 0;
-
-      String   tmp;
-      String   fld;
-      int32_t  tmp_int;
-      uint16_t prev2   = 0;
-      int16_t  offset2 = buffer.indexOf(P104_ZONE_SEP);
-
-      if ((offset2 == -1) && (buffer.length() > 0)) {
-        offset2 = buffer.length();
-      }
-
-      while (offset2 > -1) {
-        tmp = buffer.substring(prev2, offset2);
-        # ifdef P104_DEBUG_DEV
-
-        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          log  = F("P104: reading string: ");
-          log += tmp;
-          log.replace(P104_FIELD_SEP, P104_FIELD_DISP);
-          addLogMove(LOG_LEVEL_INFO, log);
-        }
-        # endif // ifdef P104_DEBUG_DEV
-
-        zones.push_back(P104_zone_struct(zoneIndex + 1));
-
-        tmp_int = 0;
-
-        // WARNING: Order of parsing these values should match the numeric order of P104_OFFSET_* values
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_SIZE, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].size = tmp_int;
-        }
-
-        zones[zoneIndex].text = parseStringKeepCaseNoTrim(tmp, 1 + P104_OFFSET_TEXT, P104_FIELD_SEP);
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_ALIGNMENT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].alignment = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_ANIM_IN, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].animationIn = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_SPEED, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].speed = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_ANIM_OUT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].animationOut = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_PAUSE, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].pause = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_FONT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].font = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_CONTENT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].content = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_LAYOUT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].layout = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_SPEC_EFFECT, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].specialEffect = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_OFFSET, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].offset = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_BRIGHTNESS, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].brightness = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_REPEATDELAY, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].repeatDelay = tmp_int;
-        }
-
-        if (validIntFromString(parseString(tmp, 1 + P104_OFFSET_INVERTED, P104_FIELD_SEP), tmp_int)) {
-          zones[zoneIndex].inverted = tmp_int;
-        }
-
-        delay(0);
-
-        numDevices += zones[zoneIndex].size + zones[zoneIndex].offset;
-
-        if (!settingsVersionV2) {
-          prev2   = offset2 + 1;
-          offset2 = buffer.indexOf(P104_ZONE_SEP, prev2);
-        } else {
-          loadOffset    += bufferSize;
-          structDataSize = sizeof(bufferSize);
-          LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, structDataSize, loadOffset);
-          offset2 = bufferSize;  // Length
-
-          if (bufferSize == 0) { // End of zones reached
-            offset2 = -1;        // fall out of while loop
-          } else {
-            structDataSize = bufferSize;
-            loadOffset    += sizeof(bufferSize);
-            LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)settingsBuffer, structDataSize, loadOffset);
-            settingsBuffer[bufferSize + 1] = '\0'; // Terminate string
-            buffer                         = String(settingsBuffer);
-          }
-        }
-        zoneIndex++;
-
-        # ifdef P104_DEBUG
-
-        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          addLogMove(LOG_LEVEL_INFO, concat(F("dotmatrix: parsed zone: "), zoneIndex));
-        }
-        # endif // ifdef P104_DEBUG
-      }
-
-      buffer = String();     // Free some memory
-    }
-
-    delete[] settingsBuffer; // Release allocated buffer
-    # ifdef P104_DEBUG_DEV
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      addLogMove(LOG_LEVEL_INFO, concat(F("P104: read zones from config: "), zoneIndex));
-    }
-    # endif // ifdef P104_DEBUG_DEV
-
-    if (expectedZones == -1) { expectedZones = zoneIndex; }
-
-    if (expectedZones == 0) { expectedZones++; } // Guarantee at least 1 zone to be displayed
-
-    while (zoneIndex < expectedZones) {
-      zones.push_back(P104_zone_struct(zoneIndex + 1));
-
-      if (equals(zones[zoneIndex].text, F("\"\""))) { // Special case
-        zones[zoneIndex].text.clear();
-      }
-
-      zoneIndex++;
-      delay(0);
-    }
-    # ifdef P104_DEBUG_DEV
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      addLogMove(LOG_LEVEL_INFO, strformat(F("P104: total zones initialized: %d expected: %d"), zoneIndex, expectedZones));
-    }
-    # endif // ifdef P104_DEBUG_DEV
-  }
-}
-
-/****************************************************
- * configureZones: initialize Zones setup
- ***************************************************/
-void P104_data_struct::configureZones() {
-  if (!initialized) {
-    loadSettings();
-    initialized = true;
-  }
-
-  uint8_t currentZone = 0;
-  uint8_t zoneOffset  = 0;
-
-  # ifdef P104_DEBUG_DEV
-
-  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    addLogMove(LOG_LEVEL_INFO, concat(F("P104: configureZones to do: "), zones.size()));
-  }
-  # endif // ifdef P104_DEBUG_DEV
-
-  if (nullptr == P) { return; }
-
-  P->displayClear();
-
-  for (auto it = zones.begin(); it != zones.end(); ++it) {
-    if (it->zone <= expectedZones) {
-      zoneOffset += it->offset;
-      P->setZone(currentZone, zoneOffset, zoneOffset + it->size - 1);
-      # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-      it->_startModule = zoneOffset;
-      P->getDisplayExtent(currentZone, it->_lower, it->_upper);
-      # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-      zoneOffset += it->size;
-
-      switch (it->font) {
-        # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-        case P104_DOUBLE_HEIGHT_FONT_ID: {
-          P->setFont(currentZone, numeric7SegDouble);
-          P->setCharSpacing(currentZone, P->getCharSpacing() * 2); // double spacing as well
-          break;
-        }
-        # endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-        # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-        case P104_FULL_DOUBLEHEIGHT_FONT_ID: {
-          P->setFont(currentZone, BigFont);
-          P->setCharSpacing(currentZone, P->getCharSpacing() * 2); // double spacing as well
-          break;
-        }
-        # endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-        # ifdef P104_USE_VERTICAL_FONT
-        case P104_VERTICAL_FONT_ID: {
-          P->setFont(currentZone, _fontVertical);
-          break;
-        }
-        # endif // ifdef P104_USE_VERTICAL_FONT
-        # ifdef P104_USE_EXT_ASCII_FONT
-        case P104_EXT_ASCII_FONT_ID: {
-          P->setFont(currentZone, ExtASCII);
-          break;
-        }
-        # endif // ifdef P104_USE_EXT_ASCII_FONT
-        # ifdef P104_USE_ARABIC_FONT
-        case P104_ARABIC_FONT_ID: {
-          P->setFont(currentZone, fontArabic);
-          break;
-        }
-        # endif // ifdef P104_USE_ARABIC_FONT
-        # ifdef P104_USE_GREEK_FONT
-        case P104_GREEK_FONT_ID: {
-          P->setFont(currentZone, fontGreek);
-          break;
-        }
-        # endif // ifdef P104_USE_GREEK_FONT
-        # ifdef P104_USE_KATAKANA_FONT
-        case P104_KATAKANA_FONT_ID: {
-          P->setFont(currentZone, fontKatakana);
-          break;
-        }
-        # endif // ifdef P104_USE_KATAKANA_FONT
-
-        // Extend above this comment with more fonts if/when available,
-        // case P104_DEFAULT_FONT_ID: and default: clauses should be the last options.
-        // This should also make sure the default font is set if a no longer available font was selected
-        case P104_DEFAULT_FONT_ID:
-        default: {
-          P->setFont(currentZone, nullptr); // default font
-          break;
-        }
-      }
-
-      // Inverted
-      P->setInvert(currentZone, it->inverted);
-
-      // Special Effects
-      P->setZoneEffect(currentZone, (it->specialEffect & P104_SPECIAL_EFFECT_UP_DOWN) == P104_SPECIAL_EFFECT_UP_DOWN,       PA_FLIP_UD);
-      P->setZoneEffect(currentZone, (it->specialEffect & P104_SPECIAL_EFFECT_LEFT_RIGHT) == P104_SPECIAL_EFFECT_LEFT_RIGHT, PA_FLIP_LR);
-
-      // Brightness
-      P->setIntensity(currentZone, it->brightness);
-
-      # ifdef P104_DEBUG_DEV
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        addLogMove(LOG_LEVEL_INFO, strformat(F("P104: configureZones #%d/%d offset: %d"), currentZone + 1, expectedZones, zoneOffset));
-      }
-      # endif // ifdef P104_DEBUG_DEV
-
-      delay(0);
-
-      // Content == text && text != ""
-      if (((it->content == P104_CONTENT_TEXT) ||
-           (it->content == P104_CONTENT_TEXT_REV))
-          && (!it->text.isEmpty())) {
-        displayOneZoneText(currentZone, *it, it->text);
-      }
-
-      # ifdef P104_USE_BAR_GRAPH
-
-      // Content == Bar-graph && text != ""
-      if ((it->content == P104_CONTENT_BAR_GRAPH)
-          && (!it->text.isEmpty())) {
-        displayBarGraph(currentZone, *it, it->text);
-      }
-      # endif // ifdef P104_USE_BAR_GRAPH
-
-      if (it->repeatDelay > -1) {
-        it->_repeatTimer = millis();
-      }
-      currentZone++;
-      delay(0);
-    }
-  }
-
-  // Synchronize the start
-  P->synchZoneStart();
-}
-
-/**********************************************************
- * Display the text with attributes for a specific zone
- *********************************************************/
-void P104_data_struct::displayOneZoneText(uint8_t                 zone,
-                                          const P104_zone_struct& zstruct,
-                                          const String          & text) {
-  if ((nullptr == P) || (zone >= P104_MAX_ZONES)) { return; } // double check
-  sZoneInitial[zone].reserve(text.length());
-  sZoneInitial[zone] = text; // Keep the original string for future use
-  sZoneBuffers[zone].reserve(text.length());
-  sZoneBuffers[zone] = text; // We explicitly want a copy here so it can be modified by parseTemplate()
-
-  sZoneBuffers[zone] = parseTemplate(sZoneBuffers[zone]);
-
-  # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-
-  if (zstruct.layout == P104_LAYOUT_DOUBLE_UPPER) {
-    createHString(sZoneBuffers[zone]);
-  }
-  # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-
-  if (zstruct.content == P104_CONTENT_TEXT_REV) {
-    reverseStr(sZoneBuffers[zone]);
-  }
-
-  String log;
-
-  if (loglevelActiveFor(LOG_LEVEL_INFO) &&
-      logAllText &&
-      log.reserve(28 + text.length() + sZoneBuffers[zone].length())) {
-    log  = strformat(F("dotmatrix: ZoneText: %d, '"), zone + 1); // UI-number
-    log += text;
-    log += F("' -> '");
-    log += sZoneBuffers[zone];
-    log += '\'';
-    addLogMove(LOG_LEVEL_INFO, log);
-  }
-
-  P->displayZoneText(zone,
-                     sZoneBuffers[zone].c_str(),
-                     static_cast(zstruct.alignment),
-                     zstruct.speed,
-                     zstruct.pause,
-                     static_cast(zstruct.animationIn),
-                     static_cast(zstruct.animationOut));
-}
-
-/*********************************************
- * Update all or the specified zone
- ********************************************/
-void P104_data_struct::updateZone(uint8_t                 zone,
-                                  const P104_zone_struct& zstruct) {
-  if (nullptr == P) { return; }
-
-  if (zone == 0) {
-    for (auto it = zones.begin(); it != zones.end(); ++it) {
-      if ((it->zone > 0) &&
-          ((it->content == P104_CONTENT_TEXT) ||
-           (it->content == P104_CONTENT_TEXT_REV))) {
-        displayOneZoneText(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed text
-        P->displayReset(it->zone - 1);
-      }
-      # ifdef P104_USE_BAR_GRAPH
-
-      if ((it->zone > 0) &&
-          (it->content == P104_CONTENT_BAR_GRAPH)) {
-        displayBarGraph(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed bar graph
-      }
-      # endif // ifdef P104_USE_BAR_GRAPH
-
-      if ((zstruct.content == P104_CONTENT_TEXT)
-          || zstruct.content == P104_CONTENT_TEXT_REV
-          # ifdef P104_USE_BAR_GRAPH
-          || zstruct.content == P104_CONTENT_BAR_GRAPH
-          # endif // ifdef P104_USE_BAR_GRAPH
-          ) {
-        if (it->repeatDelay > -1) { // Restart repeat timer
-          it->_repeatTimer = millis();
-        }
-      }
-    }
-  } else {
-    if ((zstruct.zone > 0) &&
-        ((zstruct.content == P104_CONTENT_TEXT) ||
-         (zstruct.content == P104_CONTENT_TEXT_REV))) {
-      displayOneZoneText(zstruct.zone - 1, zstruct, sZoneInitial[zstruct.zone - 1]); // Re-send last displayed text
-      P->displayReset(zstruct.zone - 1);
-    }
-    # ifdef P104_USE_BAR_GRAPH
-
-    if ((zstruct.zone > 0) &&
-        (zstruct.content == P104_CONTENT_BAR_GRAPH)) {
-      displayBarGraph(zstruct.zone - 1, zstruct, sZoneInitial[zstruct.zone - 1]); // Re-send last displayed bar graph
-    }
-    # endif // ifdef P104_USE_BAR_GRAPH
-
-    // Repeat timer is/should be started elsewhere
-  }
-}
-
-# if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-
-/***********************************************
- * Enable/Disable updating a range of modules
- **********************************************/
-void P104_data_struct::modulesOnOff(uint8_t start, uint8_t end, MD_MAX72XX::controlValue_t on_off) {
-  for (uint8_t m = start; m <= end; m++) {
-    pM->control(m, MD_MAX72XX::UPDATE, on_off);
-  }
-}
-
-# endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET)
-
-# ifdef P104_USE_BAR_GRAPH
-
-/********************************************************
- * draw a single bar-graph, arguments already adjusted for direction
- *******************************************************/
-void P104_data_struct::drawOneBarGraph(uint16_t lower,
-                                       uint16_t upper,
-                                       int16_t  pixBottom,
-                                       int16_t  pixTop,
-                                       uint16_t zeroPoint,
-                                       uint8_t  barWidth,
-                                       uint8_t  barType,
-                                       uint8_t  row) {
-  bool on_off;
-
-  for (uint8_t r = 0; r < barWidth; r++) {
-    for (uint8_t col = lower; col <= upper; col++) {
-      on_off = (col >= pixBottom && col <= pixTop); // valid area
-
-      if ((zeroPoint != 0) &&
-          (barType == P104_BARTYPE_STANDARD) &&
-          (barWidth > 2) &&
-          ((r == 0) || (r == barWidth - 1)) &&
-          (col == lower + zeroPoint)) {
-        on_off = false; // when bar wider than 2, turn off zeropoint top and bottom led
-      }
-
-      if ((barType == P104_BARTYPE_SINGLE) && (r > 0)) {
-        on_off = false; // barType 1 = only a single line is drawn, independent of the width
-      }
-
-      if ((barType == P104_BARTYPE_ALT_DOT) && (barWidth > 1) && on_off) {
-        on_off = ((r % 2) == (col % 2)); // barType 2 = dotted line when bar is wider than 1 pixel
-      }
-      pM->setPoint(row + r, col, on_off);
-
-      if (col % 16 == 0) { delay(0); }
-    }
-    delay(0); // Leave some breathingroom
-  }
-}
-
-/********************************************************************
- * Process a graph-string to display in a zone, format:
- * value,max-value,min-value,direction,bartype|...
- *******************************************************************/
-void P104_data_struct::displayBarGraph(uint8_t                 zone,
-                                       const P104_zone_struct& zstruct,
-                                       const String          & graph) {
-  if ((nullptr == P) || (nullptr == pM) || graph.isEmpty()) { return; }
-  sZoneInitial[zone] = graph; // Keep the original string for future use
-
-  #  define NOT_A_COMMA 0x02  // Something else than a comma, or the parseString function will get confused
-  String parsedGraph(graph);  // Extra copy created so we don't mess up the incoming String
-  parsedGraph = parseTemplate(parsedGraph);
-  parsedGraph.replace(',', NOT_A_COMMA);
-
-  std::vector barGraphs;
-  uint8_t currentBar = 0;
-  bool    loop       = true;
-
-  // Parse the graph-string
-  while (loop && currentBar < 8) { // Maximum 8 valuesets possible
-    String graphpart = parseString(parsedGraph, currentBar + 1, '|');
-    graphpart.trim();
-    graphpart.replace(NOT_A_COMMA, ',');
-
-    if (graphpart.isEmpty()) {
-      loop = false;
-    } else {
-      barGraphs.push_back(P104_bargraph_struct(currentBar));
-    }
-
-    if (loop && validDoubleFromString(parseString(graphpart, 1), barGraphs[currentBar].value)) { // value
-      String datapart = parseString(graphpart, 2);                                               // max (default: 100.0)
-
-      if (datapart.isEmpty()) {
-        barGraphs[currentBar].max = 100.0;
-      } else {
-        validDoubleFromString(datapart, barGraphs[currentBar].max);
-      }
-      datapart = parseString(graphpart, 3); // min (default: 0.0)
-
-      if (datapart.isEmpty()) {
-        barGraphs[currentBar].min = 0.0;
-      } else {
-        validDoubleFromString(datapart, barGraphs[currentBar].min);
-      }
-      datapart = parseString(graphpart, 4); // direction
-
-      if (datapart.isEmpty()) {
-        barGraphs[currentBar].direction = 0;
-      } else {
-        int32_t value = 0;
-        validIntFromString(datapart, value);
-        barGraphs[currentBar].direction = value;
-      }
-      datapart = parseString(graphpart, 5); // barType
-
-      if (datapart.isEmpty()) {
-        barGraphs[currentBar].barType = 0;
-      } else {
-        int32_t value = 0;
-        validIntFromString(datapart, value);
-        barGraphs[currentBar].barType = value;
-      }
-
-      if (definitelyGreaterThan(barGraphs[currentBar].min, barGraphs[currentBar].max)) {
-        std::swap(barGraphs[currentBar].min, barGraphs[currentBar].max);
-      }
-    }
-    #  ifdef P104_DEBUG
-
-    if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) {
-      String log;
-
-      if (log.reserve(70)) {
-        log = F("dotmatrix: Bar-graph: ");
-
-        if (loop) {
-          log += currentBar;
-          log += F(" in: ");
-          log += graphpart;
-          log += F(" value: ");
-          log += barGraphs[currentBar].value;
-          log += F(" max: ");
-          log += barGraphs[currentBar].max;
-          log += F(" min: ");
-          log += barGraphs[currentBar].min;
-          log += F(" dir: ");
-          log += barGraphs[currentBar].direction;
-          log += F(" typ: ");
-          log += barGraphs[currentBar].barType;
-        } else {
-          log += F(" bsize: ");
-          log += barGraphs.size();
-        }
-        addLogMove(LOG_LEVEL_INFO, log);
-      }
-    }
-    #  endif // ifdef P104_DEBUG
-    currentBar++; // next
-    delay(0);     // Leave some breathingroom
-  }
-  #  undef NOT_A_COMMA
-
-  if (barGraphs.size() > 0) {
-    uint8_t  barWidth = 8 / barGraphs.size(); // Divide the 8 pixel width per number of bars to show
-    int16_t  pixTop, pixBottom;
-    uint16_t zeroPoint;
-    #  ifdef P104_DEBUG
-    String log;
-
-    if (logAllText &&
-        loglevelActiveFor(LOG_LEVEL_INFO) &&
-        log.reserve(64)) {
-      log  = F("dotmatrix: bar Width: ");
-      log += barWidth;
-      log += F(" low: ");
-      log += zstruct._lower;
-      log += F(" high: ");
-      log += zstruct._upper;
-    }
-    #  endif // ifdef P104_DEBUG
-    modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_OFF); // Stop updates on modules
-    P->setIntensity(zstruct.zone - 1, zstruct.brightness);                                           // don't forget to set the brightness
-    uint8_t row = 0;
-
-    if ((barGraphs.size() == 3) || (barGraphs.size() == 5) || (barGraphs.size() == 6)) {             // Center within the rows a bit
-      for (; row < (barGraphs.size() == 5 ? 2 : 1); row++) {
-        for (uint8_t col = zstruct._lower; col <= zstruct._upper; col++) {
-          pM->setPoint(row, col, false);                                                             // all off
-
-          if (col % 16 == 0) { delay(0); }
-        }
-        delay(0); // Leave some breathingroom
-      }
-    }
-
-    for (auto it = barGraphs.begin(); it != barGraphs.end(); ++it) {
-      if (essentiallyZero(it->min)) {
-        pixTop    = zstruct._lower - 1 + (((zstruct._upper + 1) - zstruct._lower) / it->max) * it->value;
-        pixBottom = zstruct._lower - 1;
-        zeroPoint = 0;
-      } else {
-        if (definitelyLessThan(it->min, 0.0) &&
-            definitelyGreaterThan(it->max,           0.0) &&
-            definitelyGreaterThan(it->max - it->min, 0.01)) { // Zero-point is used
-          zeroPoint = (it->min * -1.0) / ((it->max - it->min) / (1.0 * ((zstruct._upper + 1) - zstruct._lower)));
-        } else {
-          zeroPoint = 0;
-        }
-        pixTop    = zstruct._lower + zeroPoint + (((zstruct._upper + 1) - zstruct._lower) / (it->max - it->min)) * it->value;
-        pixBottom = zstruct._lower + zeroPoint;
-
-        if (definitelyLessThan(it->value, 0.0)) {
-          std::swap(pixTop, pixBottom);
-        }
-      }
-
-      if (it->direction == 1) { // Left to right display: Flip values within the lower/upper range
-        pixBottom = zstruct._upper - (pixBottom - zstruct._lower);
-        pixTop    = zstruct._lower + (zstruct._upper - pixTop);
-        std::swap(pixBottom, pixTop);
-        zeroPoint = zstruct._upper - zstruct._lower - zeroPoint + (zeroPoint == 0 ? 1 : 0);
-      }
-      #  ifdef P104_DEBUG_DEV
-
-      if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) {
-        log += F(" B: ");
-        log += pixBottom;
-        log += F(" T: ");
-        log += pixTop;
-        log += F(" Z: ");
-        log += zeroPoint;
-      }
-      #  endif // ifdef P104_DEBUG_DEV
-      drawOneBarGraph(zstruct._lower, zstruct._upper, pixBottom, pixTop, zeroPoint, barWidth, it->barType, row);
-      row += barWidth;                 // Next set of rows
-      delay(0);                        // Leave some breathingroom
-    }
-
-    for (; row < 8; row++) {           // Clear unused rows
-      for (uint8_t col = zstruct._lower; col <= zstruct._upper; col++) {
-        pM->setPoint(row, col, false); // all off
-
-        if (col % 16 == 0) { delay(0); }
-      }
-      delay(0); // Leave some breathingroom
-    }
-    #  ifdef P104_DEBUG
-
-    if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) {
-      addLogMove(LOG_LEVEL_INFO, log);
-    }
-    #  endif // ifdef P104_DEBUG
-    modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_ON); // Continue updates on modules
-  }
-}
-
-# endif // ifdef P104_USE_BAR_GRAPH
-
-# ifdef P104_USE_DOT_SET
-void P104_data_struct::displayDots(uint8_t                 zone,
-                                   const P104_zone_struct& zstruct,
-                                   const String          & dots) {
-  if ((nullptr == P) || (nullptr == pM) || dots.isEmpty()) { return; }
-  {
-    uint8_t idx = 0;
-    int32_t row;
-    int32_t col;
-    String  sRow;
-    String  sCol;
-    String  sOn_off;
-    bool    on_off = true;
-    modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_OFF); // Stop updates on modules
-    P->setIntensity(zstruct.zone - 1, zstruct.brightness);                                           // don't forget to set the brightness
-    sRow    = parseString(dots, idx + 1);
-    sCol    = parseString(dots, idx + 2);
-    sOn_off = parseString(dots, idx + 3);
-
-    while (!sRow.isEmpty() && !sCol.isEmpty()) {
-      on_off = true; // Default On
-
-      if (validIntFromString(sRow, row) &&
-          validIntFromString(sCol, col) &&
-          (row > 0) && ((row - 1) < 8) &&
-          (col > 0) && ((col - 1) <= (zstruct._upper - zstruct._lower))) { // Valid coordinates?
-        if (equals(sOn_off, F("0"))) {                                     // Dot On is the default
-          on_off = false;
-          idx++;                                                           // 3rd argument used
-        }
-        pM->setPoint(row - 1, zstruct._upper - (col - 1), on_off);         // Reverse layout
-      }
-      idx += 2;                                                            // Skip to next argument set
-
-      if (idx % 16 == 0) { delay(0); }
-      sRow    = parseString(dots, idx + 1);
-      sCol    = parseString(dots, idx + 2);
-      sOn_off = parseString(dots, idx + 3);
-    }
-
-    modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_ON); // Continue updates on modules
-  }
-}
-
-# endif // ifdef P104_USE_DOT_SET
-
-/**************************************************
- * Check if an animation is available in the current build
- *************************************************/
-bool isAnimationAvailable(uint8_t animation, bool noneIsAllowed = false) {
-  textEffect_t selection = static_cast(animation);
-
-  switch (selection) {
-    case PA_NO_EFFECT:
-    {
-      return noneIsAllowed;
-    }
-    case PA_PRINT:
-    case PA_SCROLL_UP:
-    case PA_SCROLL_DOWN:
-    case PA_SCROLL_LEFT:
-    case PA_SCROLL_RIGHT:
-    {
-      return true;
-    }
-    # if ENA_SPRITE
-    case PA_SPRITE:
-    {
-      return true;
-    }
-    # endif // ENA_SPRITE
-    # if ENA_MISC
-    case PA_SLICE:
-    case PA_MESH:
-    case PA_FADE:
-    case PA_DISSOLVE:
-    case PA_BLINDS:
-    case PA_RANDOM:
-    {
-      return true;
-    }
-    # endif // ENA_MISC
-    # if ENA_WIPE
-    case PA_WIPE:
-    case PA_WIPE_CURSOR:
-    {
-      return true;
-    }
-    # endif // ENA_WIPE
-    # if ENA_SCAN
-    case PA_SCAN_HORIZ:
-    case PA_SCAN_HORIZX:
-    case PA_SCAN_VERT:
-    case PA_SCAN_VERTX:
-    {
-      return true;
-    }
-    # endif // ENA_SCAN
-    # if ENA_OPNCLS
-    case PA_OPENING:
-    case PA_OPENING_CURSOR:
-    case PA_CLOSING:
-    case PA_CLOSING_CURSOR:
-    {
-      return true;
-    }
-    # endif // ENA_OPNCLS
-    # if ENA_SCR_DIA
-    case PA_SCROLL_UP_LEFT:
-    case PA_SCROLL_UP_RIGHT:
-    case PA_SCROLL_DOWN_LEFT:
-    case PA_SCROLL_DOWN_RIGHT:
-    {
-      return true;
-    }
-    # endif // ENA_SCR_DIA
-    # if ENA_GROW
-    case PA_GROW_UP:
-    case PA_GROW_DOWN:
-    {
-      return true;
-    }
-    # endif // ENA_GROW
-    default:
-      return false;
-  }
-}
-
-/*******************************************************
- * handlePluginWrite : process commands
- ******************************************************/
-bool P104_data_struct::handlePluginWrite(taskIndex_t   taskIndex,
-                                         const String& string) {
-  # ifdef P104_USE_COMMANDS
-  bool reconfigure = false;
-  # endif // ifdef P104_USE_COMMANDS
-  bool success         = false;
-  const String command = parseString(string, 1);
-
-  if ((nullptr != P) && equals(command, F("dotmatrix"))) { // main command: dotmatrix
-    const String sub = parseString(string, 2);
-
-    int32_t zoneIndex{};
-    const String string4 = parseStringKeepCaseNoTrim(string, 4);
-    # ifdef P104_USE_COMMANDS
-    int32_t value4{};
-    validIntFromString(string4, value4);
-    # endif // ifdef P104_USE_COMMANDS
-
-    // Global subcommands
-    if (equals(sub, F("clear")) && // subcommand: clear[,all]
-        (string4.isEmpty() ||
-         string4.equalsIgnoreCase(F("all")))) {
-      P->displayClear();
-      success = true;
-    }
-
-    if (equals(sub, F("update")) && // subcommand: update[,all]
-        (string4.isEmpty() ||
-         string4.equalsIgnoreCase(F("all")))) {
-      updateZone(0, P104_zone_struct(0));
-      success = true;
-    }
-
-    // Zone-specific subcommands
-    if (validIntFromString(parseString(string, 3), zoneIndex) &&
-        (zoneIndex > 0) &&
-        (static_cast(zoneIndex) <= zones.size())) {
-      // subcommands are processed in the same order as they are presented in the UI
-      for (auto it = zones.begin(); it != zones.end() && !success; ++it) {
-        if ((it->zone == zoneIndex)) {   // This zone
-          if (equals(sub, F("clear"))) { // subcommand: clear,
-            P->displayClear(zoneIndex - 1);
-            success = true;
-            break;
-          }
-
-          if (equals(sub, F("update"))) { // subcommand: update,
-            updateZone(zoneIndex, *it);
-            success = true;
-            break;
-          }
-
-          # ifdef P104_USE_COMMANDS
-
-          if (equals(sub, F("size")) && // subcommand: size,, (1..)
-              (value4 > 0) &&
-              (value4 <= P104_MAX_MODULES_PER_ZONE)) {
-            reconfigure = (it->size != value4);
-            it->size    = value4;
-            success     = true;
-            break;
-          }
-          # endif // ifdef P104_USE_COMMANDS
-
-          if ((equals(sub, F("txt")) ||                                                         // subcommand: [set]txt,, (only
-               equals(sub, F("settxt"))) &&                                                     // allowed for zones with Text content)
-              ((it->content == P104_CONTENT_TEXT) || (it->content == P104_CONTENT_TEXT_REV))) { // no length check, so longer than the UI
-                                                                                                // allows is made possible
-            if (equals(sub, F("settxt")) &&                                                     // subcommand: settxt,, (stores
-                (string4.length() <= P104_MAX_TEXT_LENGTH_PER_ZONE)) {                          // the text in the settings, is not saved)
-              it->text = string4;                                                               // Only if not too long, could 'blow up' the
-            }                                                                                   // settings when saved
-            displayOneZoneText(zoneIndex - 1, *it, string4);
-            success = true;
-            break;
-          }
-
-          # ifdef P104_USE_COMMANDS
-
-          if (equals(sub, F("content")) && // subcommand: content,, (0..-1)
-              (value4 >= 0) &&
-              (value4 < P104_CONTENT_count)) {
-            reconfigure = (it->content != value4);
-            it->content = value4;
-            success     = true;
-            break;
-          }
-
-          if (equals(sub, F("alignment")) &&                            // subcommand: alignment,, (0..3)
-              (value4 >= 0) &&
-              (value4 <= static_cast(textPosition_t::PA_RIGHT))) { // last item in the enum
-            it->alignment = value4;
-            success       = true;
-            break;
-          }
-
-          if (equals(sub, F("anim.in")) && // subcommand: anim.in,, (1..)
-              isAnimationAvailable(value4)) {
-            it->animationIn = value4;
-            success         = true;
-            break;
-          }
-
-          if (equals(sub, F("speed")) && // subcommand: speed,, (0..P104_MAX_SPEED_PAUSE_VALUE)
-              (value4 >= 0) &&
-              (value4 <= P104_MAX_SPEED_PAUSE_VALUE)) {
-            it->speed = value4;
-            success   = true;
-            break;
-          }
-
-          if (equals(sub, F("anim.out")) && // subcommand: anim.out,, (0..)
-              isAnimationAvailable(value4, true)) {
-            it->animationOut = value4;
-            success          = true;
-            break;
-          }
-
-          if (equals(sub, F("pause")) && // subcommand: pause,, (0..P104_MAX_SPEED_PAUSE_VALUE)
-              (value4 >= 0) &&
-              (value4 <= P104_MAX_SPEED_PAUSE_VALUE)) {
-            it->pause = value4;
-            success   = true;
-            break;
-          }
-
-          if (equals(sub, F("font")) && // subcommand: font,, (only for incuded font id's)
-              (
-                (value4 == 0)
-                #  ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-                || (value4 == P104_DOUBLE_HEIGHT_FONT_ID)
-                #  endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-                #  ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-                || (value4 == P104_FULL_DOUBLEHEIGHT_FONT_ID)
-                #  endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-                #  ifdef P104_USE_VERTICAL_FONT
-                || (value4 == P104_VERTICAL_FONT_ID)
-                #  endif // ifdef P104_USE_VERTICAL_FONT
-                #  ifdef P104_USE_EXT_ASCII_FONT
-                || (value4 == P104_EXT_ASCII_FONT_ID)
-                #  endif // ifdef P104_USE_EXT_ASCII_FONT
-                #  ifdef P104_USE_ARABIC_FONT
-                || (value4 == P104_ARABIC_FONT_ID)
-                #  endif // ifdef P104_USE_ARABIC_FONT
-                #  ifdef P104_USE_GREEK_FONT
-                || (value4 == P104_GREEK_FONT_ID)
-                #  endif // ifdef P104_USE_GREEK_FONT
-                #  ifdef P104_USE_KATAKANA_FONT
-                || (value4 == P104_KATAKANA_FONT_ID)
-                #  endif // ifdef P104_USE_KATAKANA_FONT
-              )
-              ) {
-            reconfigure = (it->font != value4);
-            it->font    = value4;
-            success     = true;
-            break;
-          }
-
-          if (equals(sub, F("inverted")) && // subcommand: inverted,, (disable/enable)
-              (value4 >= 0) &&
-              (value4 <= 1)) {
-            reconfigure  = (it->inverted != value4);
-            it->inverted = value4;
-            success      = true;
-            break;
-          }
-
-          #  if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-
-          if (equals(sub, F("layout")) && // subcommand: layout,, (0..2), only when double-height font is available
-              (value4 >= 0) &&
-              (value4 <= P104_LAYOUT_DOUBLE_LOWER)) {
-            reconfigure = (it->layout != value4);
-            it->layout  = value4;
-            success     = true;
-            break;
-          }
-          #  endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-
-          if (equals(sub, F("specialeffect")) && // subcommand: specialeffect,, (0..3)
-              (value4 >= 0) &&
-              (value4 <= P104_SPECIAL_EFFECT_BOTH)) {
-            reconfigure       = (it->specialEffect != value4);
-            it->specialEffect = value4;
-            success           = true;
-            break;
-          }
-
-          if (equals(sub, F("offset")) && // subcommand: offset,, (0..-1)
-              (value4 >= 0) &&
-              (value4 < P104_MAX_MODULES_PER_ZONE) &&
-              (value4 < it->size)) {
-            reconfigure = (it->offset != value4);
-            it->offset  = value4;
-            success     = true;
-            break;
-          }
-
-          if (equals(sub, F("brightness")) && // subcommand: brightness,, (0..15)
-              (value4 >= 0) &&
-              (value4 <= P104_BRIGHTNESS_MAX)) {
-            it->brightness = value4;
-            P->setIntensity(zoneIndex - 1, it->brightness); // Change brightness immediately
-            success = true;
-            break;
-          }
-
-          if (equals(sub, F("repeat")) && // subcommand: repeat,, (-1..86400 = 24h)
-              (value4 >= -1) &&
-              (value4 <= P104_MAX_REPEATDELAY_VALUE)) {
-            it->repeatDelay = value4;
-            success         = true;
-
-            if (it->repeatDelay > -1) {
-              it->_repeatTimer = millis();
-            }
-            break;
-          }
-          # endif // ifdef P104_USE_COMMANDS
-
-          # ifdef P104_USE_BAR_GRAPH
-
-          if ((equals(sub, F("bar")) ||                                // subcommand: [set]bar,, (only allowed for zones
-               equals(sub, F("setbar"))) &&                            // with Bargraph content) no length check, so longer than the UI
-              (it->content == P104_CONTENT_BAR_GRAPH)) {               // allows is made possible
-            if (equals(sub, F("setbar")) &&                            // subcommand: setbar,, (stores the graph-string
-                (string4.length() <= P104_MAX_TEXT_LENGTH_PER_ZONE)) { // in the settings, is not saved)
-              it->text = string4;                                      // Only if not too long, could 'blow up' the settings when saved
-            }
-            displayBarGraph(zoneIndex - 1, *it, string4);
-            success = true;
-            break;
-          }
-          # endif // ifdef P104_USE_BAR_GRAPH
-
-          # ifdef P104_USE_DOT_SET
-
-          if (equals(sub, F("dot"))) {                                    // subcommand: dot,,,[,0][,,[,0]...] to draw
-            displayDots(zoneIndex - 1, *it, parseStringToEnd(string, 4)); // dots at row/column, add ,0 to turn a dot off
-            success = true;
-            break;
-          }
-          # endif // ifdef P104_USE_DOT_SET
-
-          // FIXME TD-er: success is always false here. Maybe this must be done outside the for-loop?
-          if (success) { // Reset the repeat timer
-            if (it->repeatDelay > -1) {
-              it->_repeatTimer = millis();
-            }
-          }
-        }
-      }
-    }
-  }
-
-  # ifdef P104_USE_COMMANDS
-
-  if (reconfigure) {
-    configureZones(); // Re-initialize
-    success = true;   // Successful
-  }
-  # endif // ifdef P104_USE_COMMANDS
-
-  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    String log;
-
-    if (log.reserve(34 + string.length())) {
-      log = F("dotmatrix: command ");
-
-      if (!success) { log += F("NOT "); }
-      log += F("succesful: ");
-      log += string;
-      addLogMove(LOG_LEVEL_INFO, log);
-    }
-  }
-
-  return success; // Default: unknown command
-}
-
-int8_t P104_data_struct::getTime(char *psz,
-                                 bool  seconds,
-                                 bool  colon,
-                                 bool  time12h,
-                                 bool  timeAmpm) {
-  uint16_t h, M, s;
-  String   ampm;
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-
-  if (time12h) {
-    if (timeAmpm) {
-      ampm = (node_time.hour() >= 12 ? F("p") : F("a"));
-    }
-    h = node_time.hour() % 12;
-
-    if (h == 0) { h = 12; }
-  } else
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-  {
-    h = node_time.hour();
-  }
-  M = node_time.minute();
-
-  if (!seconds) {
-    sprintf_P(psz, PSTR("%02d%c%02d%s"), h, (colon ? ':' : ' '), M, ampm.c_str());
-  } else {
-    s = node_time.second();
-    sprintf_P(psz, PSTR("%02d%c%02d %02d%s"), h, (colon ? ':' : ' '), M, s, ampm.c_str());
-  }
-  return M;
-}
-
-void P104_data_struct::getDate(char           *psz,
-                               bool            showYear,
-                               bool            fourDgt
-                               # ifdef         P104_USE_DATETIME_OPTIONS
-                               , const uint8_t dateFmt
-                               , const uint8_t dateSep
-                               # endif // ifdef P104_USE_DATETIME_OPTIONS
-                               ) {
-  uint16_t d, m, y;
-  const uint16_t year = node_time.year() - (fourDgt ? 0 : 2000);
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-  const String separators = F(" /-.");
-  const char   sep        = separators[dateSep];
-  # else // ifdef P104_USE_DATETIME_OPTIONS
-  const char sep = ' ';
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-
-  d = node_time.day();
-  m = node_time.month();
-  y = year;
-  # ifdef P104_USE_DATETIME_OPTIONS
-
-  if (showYear) {
-    switch (dateFmt) {
-      case P104_DATE_FORMAT_US:
-        d = node_time.month();
-        m = node_time.day();
-        y = year;
-        break;
-      case P104_DATE_FORMAT_JP:
-        d = year;
-        m = node_time.month();
-        y = node_time.day();
-        break;
-    }
-  } else {
-    if ((dateFmt == P104_DATE_FORMAT_US) ||
-        (dateFmt == P104_DATE_FORMAT_JP)) {
-      std::swap(d, m);
-    }
-  }
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-
-  if (showYear) {
-    sprintf_P(psz, PSTR("%02d%c%02d%c%02d"), d, sep, m, sep, y); // %02d will expand to 04 when needed
-  } else {
-    sprintf_P(psz, PSTR("%02d%c%02d"), d, sep, m);
-  }
-}
-
-uint8_t P104_data_struct::getDateTime(char           *psz,
-                                      bool            colon,
-                                      bool            time12h,
-                                      bool            timeAmpm,
-                                      bool            fourDgt
-                                      # ifdef         P104_USE_DATETIME_OPTIONS
-                                      , const uint8_t dateFmt
-                                      , const uint8_t dateSep
-                                      # endif // ifdef P104_USE_DATETIME_OPTIONS
-                                      ) {
-  String   ampm;
-  uint16_t d, M, y;
-  uint8_t  h, m;
-  const uint16_t year = node_time.year() - (fourDgt ? 0 : 2000);
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-  const String separators = F(" /-.");
-  const char   sep        = separators[dateSep];
-  # else // ifdef P104_USE_DATETIME_OPTIONS
-  const char sep = ' ';
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-
-  if (time12h) {
-    if (timeAmpm) {
-      ampm = (node_time.hour() >= 12 ? F("p") : F("a"));
-    }
-    h = node_time.hour() % 12;
-
-    if (h == 0) { h = 12; }
-  } else
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-  {
-    h = node_time.hour();
-  }
-  M = node_time.minute();
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-
-  switch (dateFmt) {
-    case P104_DATE_FORMAT_US:
-      d = node_time.month();
-      m = node_time.day();
-      y = year;
-      break;
-    case P104_DATE_FORMAT_JP:
-      d = year;
-      m = node_time.month();
-      y = node_time.day();
-      break;
-    default:
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-  d = node_time.day();
-  m = node_time.month();
-  y = year;
-  # ifdef P104_USE_DATETIME_OPTIONS
-}
-
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-  sprintf_P(psz, PSTR("%02d%c%02d%c%02d %02d%c%02d%s"), d, sep, m, sep, y, h, (colon ? ':' : ' '), M, ampm.c_str()); // %02d will expand to
-                                                                                                                     // 04 when needed
-  return M;
-}
-
-# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-void P104_data_struct::createHString(String& string) {
-  const uint16_t stringLen = string.length();
-
-  for (uint16_t i = 0; i < stringLen; i++) {
-    string[i] |= 0x80; // use 'high' part of the font, by adding 0x80
-  }
-}
-
-# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-
-void P104_data_struct::reverseStr(String& str) {
-  const uint16_t n = str.length();
-
-  // Swap characters starting from two corners
-  for (uint16_t i = 0; i < n / 2; i++) {
-    std::swap(str[i], str[n - i - 1]);
-  }
-}
-
-/************************************************************************
- * execute all PLUGIN_ONE_PER_SECOND tasks
- ***********************************************************************/
-bool P104_data_struct::handlePluginOncePerSecond(struct EventStruct *event) {
-  if (nullptr == P) { return false; }
-  bool redisplay = false;
-  bool success   = false;
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-  bool useFlasher = !bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FLASH);
-  bool time12h    = bitRead(P104_CONFIG_DATETIME,  P104_CONFIG_DATETIME_12H);
-  bool timeAmpm   = bitRead(P104_CONFIG_DATETIME,  P104_CONFIG_DATETIME_AMPM);
-  bool year4dgt   = bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_YEAR4DGT);
-  # else // ifdef P104_USE_DATETIME_OPTIONS
-  bool useFlasher = true;
-  bool time12h    = false;
-  bool timeAmpm   = false;
-  bool year4dgt   = false;
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-  bool newFlasher = !flasher && useFlasher;
-
-  for (auto it = zones.begin(); it != zones.end(); ++it) {
-    redisplay = false;
-
-    if (P->getZoneStatus(it->zone - 1)) { // Animations done?
-      switch (it->content) {
-        case P104_CONTENT_TIME:           // time
-        case P104_CONTENT_TIME_SEC:       // time sec
-        {
-          bool   useSeconds = (it->content == P104_CONTENT_TIME_SEC);
-          int8_t m          = getTime(szTimeL, useSeconds, flasher || !useFlasher, time12h, timeAmpm);
-          flasher          = newFlasher;
-          redisplay        = useFlasher || useSeconds || (it->_lastChecked != m);
-          it->_lastChecked = m;
-          break;
-        }
-        case P104_CONTENT_DATE4: // date/4
-        case P104_CONTENT_DATE6: // date/6
-        {
-          if (it->_lastChecked != node_time.day()) {
-            getDate(szTimeL,
-                    it->content != P104_CONTENT_DATE4,
-                    year4dgt
-                    # ifdef P104_USE_DATETIME_OPTIONS
-                    , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT)
-                    , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR)
-                    # endif // ifdef P104_USE_DATETIME_OPTIONS
-                    );
-            redisplay        = true;
-            it->_lastChecked = node_time.day();
-          }
-          break;
-        }
-        case P104_CONTENT_DATE_TIME: // date-time/9
-        {
-          int8_t m = getDateTime(szTimeL,
-                                 flasher || !useFlasher,
-                                 time12h,
-                                 timeAmpm,
-                                 year4dgt
-                                 # ifdef P104_USE_DATETIME_OPTIONS
-                                 , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT)
-                                 , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR)
-                                 # endif // ifdef P104_USE_DATETIME_OPTIONS
-                                 );
-          flasher          = newFlasher;
-          redisplay        = useFlasher || (it->_lastChecked != m);
-          it->_lastChecked = m;
-          break;
-        }
-        default:
-          break;
-      }
-
-      if (redisplay) {
-        displayOneZoneText(it->zone - 1, *it, String(szTimeL));
-        P->displayReset(it->zone - 1);
-
-        if (it->repeatDelay > -1) {
-          it->_repeatTimer = millis();
-        }
-      }
-    }
-    delay(0); // Leave some breathingroom
-  }
-
-  if (redisplay) {
-    // synchronise the start
-    P->synchZoneStart();
-  }
-  return redisplay || success;
-}
-
-/***************************************************
- * restart a zone if the repeat delay (if any) has passed
- **************************************************/
-void P104_data_struct::checkRepeatTimer(uint8_t z) {
-  if (nullptr == P) { return; }
-  bool handled = false;
-
-  for (auto it = zones.begin(); it != zones.end() && !handled; ++it) {
-    if (it->zone == z + 1) {
-      handled = true;
-
-      if ((it->repeatDelay > -1) && (timePassedSince(it->_repeatTimer) >= (it->repeatDelay - 1) * 1000)) { // Compensated for the '1' in
-                                                                                                           // PLUGIN_ONE_PER_SECOND
-        # ifdef P104_DEBUG
-
-        if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) {
-          String log;
-          log.reserve(51);
-          log  = F("dotmatrix: Repeat zone: ");
-          log += it->zone;
-          log += F(" delay: ");
-          log += it->repeatDelay;
-          log += F(" (");
-          log += (timePassedSince(it->_repeatTimer) / 1000.0f); // Decimals can be useful here
-          log += ')';
-          addLogMove(LOG_LEVEL_INFO, log);
-        }
-        # endif // ifdef P104_DEBUG
-
-        if ((it->content == P104_CONTENT_TEXT) ||
-            (it->content == P104_CONTENT_TEXT_REV)) {
-          displayOneZoneText(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed text
-          P->displayReset(it->zone - 1);
-        }
-
-        if ((it->content == P104_CONTENT_TIME) ||
-            (it->content == P104_CONTENT_TIME_SEC) ||
-            (it->content == P104_CONTENT_DATE4) ||
-            (it->content == P104_CONTENT_DATE6) ||
-            (it->content == P104_CONTENT_DATE_TIME)) {
-          it->_lastChecked = -1; // Invalidate so next run will re-display the date/time
-        }
-        # ifdef P104_USE_BAR_GRAPH
-
-        if (it->content == P104_CONTENT_BAR_GRAPH) {
-          displayBarGraph(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed bar graph
-        }
-        # endif // ifdef P104_USE_BAR_GRAPH
-        it->_repeatTimer = millis();
-      }
-    }
-    delay(0); // Leave some breathingroom
-  }
-}
-
-/***************************************
- * saveSettings gather the zones data from the UI and store in customsettings
- **************************************/
-bool P104_data_struct::saveSettings() {
-  error = String(); // Clear
-
-  # ifdef P104_DEBUG_DEV
-
-  if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-    addLogMove(LOG_LEVEL_INFO, concat(F("P104: saving zones, count: "), expectedZones));
-  }
-  # endif // ifdef P104_DEBUG_DEV
-
-  uint8_t index      = 0;
-  uint8_t action     = P104_ACTION_NONE;
-  uint8_t zoneIndex  = 0;
-  int8_t  zoneOffset = 0;
-
-  zones.clear(); // Start afresh
-
-  for (uint8_t zCounter = 0; zCounter < expectedZones; zCounter++) {
-    # ifdef P104_USE_ZONE_ACTIONS
-    action = getFormItemIntCustomArgName(index + P104_OFFSET_ACTION);
-
-    if (((action == P104_ACTION_ADD_ABOVE) && (zoneOrder == 0)) ||
-        ((action == P104_ACTION_ADD_BELOW) && (zoneOrder == 1))) {
-      zones.push_back(P104_zone_struct(0));
-      zoneOffset++;
-      #  ifdef P104_DEBUG_DEV
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        addLogMove(LOG_LEVEL_INFO, concat(F("P104: insert before zone: "), zoneIndex + 1));
-      }
-      #  endif // ifdef P104_DEBUG_DEV
-    }
-    # endif    // ifdef P104_USE_ZONE_ACTIONS
-    zoneIndex = zCounter + zoneOffset;
-
-    if (action == P104_ACTION_DELETE) {
-      zoneOffset--;
-    } else {
-      # ifdef P104_DEBUG_DEV
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        addLogMove(LOG_LEVEL_INFO, concat(F("P104: read zone: "), zoneIndex + 1));
-      }
-      # endif // ifdef P104_DEBUG_DEV
-      zones.push_back(P104_zone_struct(zoneIndex + 1));
-
-      zones[zoneIndex].size          = getFormItemIntCustomArgName(index + P104_OFFSET_SIZE);
-      zones[zoneIndex].text          = wrapWithQuotes(webArg(getPluginCustomArgName(index + P104_OFFSET_TEXT)));
-      zones[zoneIndex].content       = getFormItemIntCustomArgName(index + P104_OFFSET_CONTENT);
-      zones[zoneIndex].alignment     = getFormItemIntCustomArgName(index + P104_OFFSET_ALIGNMENT);
-      zones[zoneIndex].animationIn   = getFormItemIntCustomArgName(index + P104_OFFSET_ANIM_IN);
-      zones[zoneIndex].speed         = getFormItemIntCustomArgName(index + P104_OFFSET_SPEED);
-      zones[zoneIndex].animationOut  = getFormItemIntCustomArgName(index + P104_OFFSET_ANIM_OUT);
-      zones[zoneIndex].pause         = getFormItemIntCustomArgName(index + P104_OFFSET_PAUSE);
-      zones[zoneIndex].font          = getFormItemIntCustomArgName(index + P104_OFFSET_FONT);
-      zones[zoneIndex].layout        = getFormItemIntCustomArgName(index + P104_OFFSET_LAYOUT);
-      zones[zoneIndex].specialEffect = getFormItemIntCustomArgName(index + P104_OFFSET_SPEC_EFFECT);
-      zones[zoneIndex].offset        = getFormItemIntCustomArgName(index + P104_OFFSET_OFFSET);
-      zones[zoneIndex].inverted      = getFormItemIntCustomArgName(index + P104_OFFSET_INVERTED);
-
-      if (zones[zoneIndex].size != 0) { // for newly added zone, use defaults
-        zones[zoneIndex].brightness  = getFormItemIntCustomArgName(index + P104_OFFSET_BRIGHTNESS);
-        zones[zoneIndex].repeatDelay = getFormItemIntCustomArgName(index + P104_OFFSET_REPEATDELAY);
-      }
-    }
-    # ifdef P104_DEBUG_DEV
-
-    if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-      addLogMove(LOG_LEVEL_INFO, concat(F("P104: add zone: "), zoneIndex + 1));
-    }
-    # endif // ifdef P104_DEBUG_DEV
-
-    # ifdef P104_USE_ZONE_ACTIONS
-
-    if (((action == P104_ACTION_ADD_BELOW) && (zoneOrder == 0)) ||
-        ((action == P104_ACTION_ADD_ABOVE) && (zoneOrder == 1))) {
-      zones.push_back(P104_zone_struct(0));
-      zoneOffset++;
-      #  ifdef P104_DEBUG_DEV
-
-      if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-        addLogMove(LOG_LEVEL_INFO, concat(F("P104: insert after zone: "), zoneIndex + 2));
-      }
-      #  endif // ifdef P104_DEBUG_DEV
-    }
-    # endif    // ifdef P104_USE_ZONE_ACTIONS
-
-    index += P104_OFFSET_COUNT;
-    delay(0);
-  }
-
-  uint16_t bufferSize;
-  int saveOffset = 0;
-
-  numDevices = 0;                      // Count the number of connected display units
-
-  bufferSize = P104_CONFIG_VERSION_V2; // Save special marker that we're using V2 settings
-  // This write is counting
-  error      += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), saveOffset);
-  saveOffset += sizeof(bufferSize);
-
-  String zbuffer;
-  if (zbuffer.reserve(P104_SETTINGS_BUFFER_V2 + 2)) {
-    for (auto it = zones.begin(); it != zones.end() && error.length() == 0; ++it) {
-      // WARNING: Order of values should match the numeric order of P104_OFFSET_* values
-      zbuffer = strformat(
-        F("%u\x01%s\x01%u\x01%u\x01%u\x01%u\x01%u\x01%u\x01%u\x01%u\x01%u\x01%u\x01%d\x01%d\x01%d\x01"),
-        it->size,          // 2
-        it->text.c_str(),  // 2 + ~15
-        it->content,       // 1
-        it->alignment,     // 1
-        it->animationIn,   // 2
-        it->speed,         // 5
-        it->animationOut,  // 2
-        it->pause,         // 5
-        it->font,          // 1
-        it->layout,        // 1
-        it->specialEffect, // 1
-        it->offset,        // 2
-        it->brightness,    // 2
-        it->repeatDelay,   // 4
-        it->inverted);     // 1
-
-      // 47 total + (max) 100 characters for it->text requires a buffer of ~150 (P104_SETTINGS_BUFFER_V2), but only the required length is
-      // stored with the length prefixed
-
-      numDevices += (it->size != 0 ? it->size : 1) + it->offset;                                // Count corrected for newly added zones
-
-      if (saveOffset + zbuffer.length() + (sizeof(bufferSize) * 2) > (DAT_TASKS_CUSTOM_SIZE)) { // Detect ourselves if we've reached the
-        error.reserve(55);                                                                      // high-water mark
-        error += F("Total combination of Zones & text too long to store.\n");
-        addLogMove(LOG_LEVEL_ERROR, error);
-      } else {
-        // Store length of buffer
-        bufferSize = zbuffer.length();
-
-        // As we write in parts, only count as single write.
-        if (RTC.flashDayCounter > 0) {
-          RTC.flashDayCounter--;
-        }
-        error += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type,
-                            taskIndex,
-                            (uint8_t *)&bufferSize,
-                            sizeof(bufferSize),
-                            saveOffset);
-        saveOffset += sizeof(bufferSize);
-
-        // As we write in parts, only count as single write.
-        if (RTC.flashDayCounter > 0) {
-          RTC.flashDayCounter--;
-        }
-        error += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type,
-                            taskIndex,
-                            (uint8_t *)zbuffer.c_str(),
-                            bufferSize,
-                            saveOffset);
-        saveOffset += bufferSize;
-
-        # ifdef P104_DEBUG_DEV
-
-        if (loglevelActiveFor(LOG_LEVEL_INFO)) {
-          addLogMove(LOG_LEVEL_INFO, strformat(F("P104: saveSettings zone: %d bufferSize: %d offset: %d"),
-                                               it->zone, bufferSize, saveOffset));
-          zbuffer.replace(P104_FIELD_SEP, P104_FIELD_DISP);
-          addLog(LOG_LEVEL_INFO, zbuffer);
-        }
-        # endif // ifdef P104_DEBUG_DEV
-      }
-
-      delay(0);
-    }
-
-    // Store an End-of-settings marker == 0
-    bufferSize = 0u;
-
-    // This write is counting
-    SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), saveOffset);
-
-    if (numDevices > 255) {
-      error += strformat(F("More than 255 modules configured (%u)\n"), numDevices);
-    }
-  } else {
-    addLog(LOG_LEVEL_ERROR, F("DOTMATRIX: Can't allocate string for saving settings, insufficient memory!"));
-    return false; // Don't continue
-  }
-
-  return error.isEmpty();
-}
-
-/**************************************************************
-* webform_load
-**************************************************************/
-bool P104_data_struct::webform_load(struct EventStruct *event) {
-  {                                       // Hardware types
-    # define P104_hardwareTypeCount 8
-    const __FlashStringHelper *hardwareTypes[P104_hardwareTypeCount] = {
-      F("Generic (DR:0, CR:1, RR:0)"),    // 010
-      F("Parola (DR:1, CR:1, RR:0)"),     // 110
-      F("FC16 (DR:1, CR:0, RR:0)"),       // 100
-      F("IC Station (DR:1, CR:1, RR:1)"), // 111
-      F("Other 1 (DR:0, CR:0, RR:0)"),    // 000
-      F("Other 2 (DR:0, CR:0, RR:1)"),    // 001
-      F("Other 3 (DR:0, CR:1, RR:1)"),    // 011
-      F("Other 4 (DR:1, CR:0, RR:1)")     // 101
-    };
-    constexpr int hardwareOptions[P104_hardwareTypeCount] = {
-      static_cast(MD_MAX72XX::moduleType_t::GENERIC_HW),
-      static_cast(MD_MAX72XX::moduleType_t::PAROLA_HW),
-      static_cast(MD_MAX72XX::moduleType_t::FC16_HW),
-      static_cast(MD_MAX72XX::moduleType_t::ICSTATION_HW),
-      static_cast(MD_MAX72XX::moduleType_t::DR0CR0RR0_HW),
-      static_cast(MD_MAX72XX::moduleType_t::DR0CR0RR1_HW),
-      static_cast(MD_MAX72XX::moduleType_t::DR0CR1RR1_HW),
-      static_cast(MD_MAX72XX::moduleType_t::DR1CR0RR1_HW)
-    };
-    addFormSelector(F("Hardware type"),
-                    F("hardware"),
-                    P104_hardwareTypeCount,
-                    hardwareTypes,
-                    hardwareOptions,
-                    P104_CONFIG_HARDWARETYPE);
-    # ifdef P104_ADD_SETTINGS_NOTES
-    addFormNote(F("DR = Digits as Rows, CR = Column Reversed, RR = Row Reversed; 0 = no, 1 = yes."));
-    # endif // ifdef P104_ADD_SETTINGS_NOTES
-  }
-
-  {
-    addFormCheckBox(F("Clear display on disable"), F("clrdsp"),
-                    bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_CLEAR_DISABLE));
-
-    addFormCheckBox(F("Log all displayed text (info)"),
-                    F("logtxt"),
-                    bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_LOG_ALL_TEXT));
-  }
-
-  # ifdef P104_USE_DATETIME_OPTIONS
-  {
-    addFormSubHeader(F("Content options"));
-
-    addFormCheckBox(F("Clock with flashing colon"), F("clkflash"), !bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FLASH));
-    addFormCheckBox(F("Clock 12h display"),         F("clk12h"),   bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_12H));
-    addFormCheckBox(F("Clock 12h AM/PM indicator"), F("clkampm"),  bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_AMPM));
-  }
-  { // Date format
-    const __FlashStringHelper *dateFormats[] = {
-      F("Day Month [Year]"),
-      F("Month Day [Year] (US-style)"),
-      F("[Year] Month Day (Japanese-style)")
-    };
-    constexpr int dateFormatOptions[] = {
-      P104_DATE_FORMAT_EU,
-      P104_DATE_FORMAT_US,
-      P104_DATE_FORMAT_JP
-    };
-    addFormSelector(F("Date format"), F("datefmt"),
-                    3,
-                    dateFormats, dateFormatOptions,
-                    get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT));
-  }
-  { // Date separator
-    const __FlashStringHelper *dateSeparators[] = {
-      F("Space"),
-      F("Slash /"),
-      F("Dash -"),
-      F("Dot .")
-    };
-    constexpr int dateSeparatorOptions[] = {
-      P104_DATE_SEPARATOR_SPACE,
-      P104_DATE_SEPARATOR_SLASH,
-      P104_DATE_SEPARATOR_DASH,
-      P104_DATE_SEPARATOR_DOT
-    };
-    addFormSelector(F("Date separator"), F("datesep"),
-                    4,
-                    dateSeparators, dateSeparatorOptions,
-                    get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR));
-
-    addFormCheckBox(F("Year uses 4 digits"), F("year4dgt"), bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_YEAR4DGT));
-  }
-  # endif // ifdef P104_USE_DATETIME_OPTIONS
-
-  addFormSubHeader(F("Zones"));
-
-  { // Zones
-    String zonesList[P104_MAX_ZONES];
-    int    zonesOptions[P104_MAX_ZONES];
-
-    for (uint8_t i = 0; i < P104_MAX_ZONES; i++) {
-      zonesList[i]    = i + 1;
-      zonesOptions[i] = i + 1; // No 0 needed or wanted
-    }
-    # if defined(P104_USE_TOOLTIPS) || defined(P104_ADD_SETTINGS_NOTES)
-
-    const String zonetip = F("Select between 1 and " STRINGIFY(P104_MAX_ZONES) " zones, changing"
-      #  ifdef P104_USE_ZONE_ORDERING
-      " Zones or Zone order"
-      #  endif // ifdef P104_USE_ZONE_ORDERING
-      " will save and reload the page.");
-    # endif    // if defined(P104_USE_TOOLTIPS) || defined(P104_ADD_SETTINGS_NOTES)
-    addFormSelector(F("Zones"), F("zonecnt"), P104_MAX_ZONES, zonesList, zonesOptions, nullptr, P104_CONFIG_ZONE_COUNT, true
-                    # ifdef P104_USE_TOOLTIPS
-                    , zonetip
-                    # endif // ifdef P104_USE_TOOLTIPS
-                    );
-
-    # ifdef P104_USE_ZONE_ORDERING
-    const String orderTypes[] = {
-      F("Numeric order (1..n)"),
-      F("Display order (n..1)")
-    };
-    const int    orderOptions[] = { 0, 1 };
-    addFormSelector(F("Zone order"), F("zoneorder"), 2, orderTypes, orderOptions, nullptr,
-                    bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER) ? 1 : 0, true
-                    #  ifdef P104_USE_TOOLTIPS
-                    , zonetip
-                    #  endif // ifdef P104_USE_TOOLTIPS
-                    );
-    # endif                  // ifdef P104_USE_ZONE_ORDERING
-    # ifdef P104_ADD_SETTINGS_NOTES
-    addFormNote(zonetip);
-    # endif // ifdef P104_ADD_SETTINGS_NOTES
-  }
-  expectedZones = P104_CONFIG_ZONE_COUNT;
-
-  if (expectedZones == 0) { expectedZones++; } // Minimum of 1 zone
-
-  { // Optionlists and zones table
-    const __FlashStringHelper *alignmentTypes[3] = {
-      F("Left"),
-      F("Center"),
-      F("Right")
-    };
-    const int alignmentOptions[3] = {
-      static_cast(textPosition_t::PA_LEFT),
-      static_cast(textPosition_t::PA_CENTER),
-      static_cast(textPosition_t::PA_RIGHT)
-    };
-
-
-
-    // Append the numeric value as a reference for the 'anim.in' and 'anim.out' subcommands
-    const __FlashStringHelper * animationTypes[] {
-      F("None (0)")
-      , F("Print (1)")
-      , F("Scroll up (2)")
-      , F("Scroll down (3)")
-      , F("Scroll left * (4)")
-      , F("Scroll right * (5)")
-    # if ENA_SPRITE
-      , F("Sprite (6)")
-    # endif // ENA_SPRITE
-    # if ENA_MISC
-      , F("Slice * (7)")
-      , F("Mesh (8)")
-      , F("Fade (9)")
-      , F("Dissolve (10)")
-      , F("Blinds (11)")
-      , F("Random (12)")
-    # endif // ENA_MISC
-    # if ENA_WIPE
-      , F("Wipe (13)")
-      , F("Wipe w. cursor (14)")
-    # endif // ENA_WIPE
-    # if ENA_SCAN
-      , F("Scan horiz. (15)")
-      , F("Scan horiz. cursor (16)")
-      , F("Scan vert. (17)")
-      , F("Scan vert. cursor (18)")
-    # endif // ENA_SCAN
-    # if ENA_OPNCLS
-      , F("Opening (19)")
-      , F("Opening w. cursor (20)")
-      , F("Closing (21)")
-      , F("Closing w. cursor (22)")
-    # endif // ENA_OPNCLS
-    # if ENA_SCR_DIA
-      , F("Scroll up left * (23)")
-      , F("Scroll up right * (24)")
-      , F("Scroll down left * (25)")
-      , F("Scroll down right * (26)")
-    # endif // ENA_SCR_DIA
-    # if ENA_GROW
-      , F("Grow up (27)")
-      , F("Grow down (28)")
-    # endif // ENA_GROW
-    };
-
-    const int animationOptions[] = {
-      static_cast(textEffect_t::PA_NO_EFFECT)
-      , static_cast(textEffect_t::PA_PRINT)
-      , static_cast(textEffect_t::PA_SCROLL_UP)
-      , static_cast(textEffect_t::PA_SCROLL_DOWN)
-      , static_cast(textEffect_t::PA_SCROLL_LEFT)
-      , static_cast(textEffect_t::PA_SCROLL_RIGHT)
-    # if ENA_SPRITE
-      , static_cast(textEffect_t::PA_SPRITE)
-    # endif // ENA_SPRITE
-    # if ENA_MISC
-      , static_cast(textEffect_t::PA_SLICE)
-      , static_cast(textEffect_t::PA_MESH)
-      , static_cast(textEffect_t::PA_FADE)
-      , static_cast(textEffect_t::PA_DISSOLVE)
-      , static_cast(textEffect_t::PA_BLINDS)
-      , static_cast(textEffect_t::PA_RANDOM)
-    # endif // ENA_MISC
-    # if ENA_WIPE
-      , static_cast(textEffect_t::PA_WIPE)
-      , static_cast(textEffect_t::PA_WIPE_CURSOR)
-    # endif // ENA_WIPE
-    # if ENA_SCAN
-      , static_cast(textEffect_t::PA_SCAN_HORIZ)
-      , static_cast(textEffect_t::PA_SCAN_HORIZX)
-      , static_cast(textEffect_t::PA_SCAN_VERT)
-      , static_cast(textEffect_t::PA_SCAN_VERTX)
-    # endif // ENA_SCAN
-    # if ENA_OPNCLS
-      , static_cast(textEffect_t::PA_OPENING)
-      , static_cast(textEffect_t::PA_OPENING_CURSOR)
-      , static_cast(textEffect_t::PA_CLOSING)
-      , static_cast(textEffect_t::PA_CLOSING_CURSOR)
-    # endif // ENA_OPNCLS
-    # if ENA_SCR_DIA
-      , static_cast(textEffect_t::PA_SCROLL_UP_LEFT)
-      , static_cast(textEffect_t::PA_SCROLL_UP_RIGHT)
-      , static_cast(textEffect_t::PA_SCROLL_DOWN_LEFT)
-      , static_cast(textEffect_t::PA_SCROLL_DOWN_RIGHT)
-    # endif // ENA_SCR_DIA
-    # if ENA_GROW
-      , static_cast(textEffect_t::PA_GROW_UP)
-      , static_cast(textEffect_t::PA_GROW_DOWN)
-    # endif // ENA_GROW
-    };
-
-    constexpr int animationCount = NR_ELEMENTS(animationOptions);
-
-    delay(0);
-
-    const __FlashStringHelper *fontTypes[] = {
-      F("Default (0)")
-    # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-      , F("Numeric, double height (1)")
-    # endif   // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-    # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-      , F("Full, double height (2)")
-    # endif   // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-    # ifdef P104_USE_VERTICAL_FONT
-      , F("Vertical (3)")
-    # endif   // ifdef P104_USE_VERTICAL_FONT
-    # ifdef P104_USE_EXT_ASCII_FONT
-      , F("Extended ASCII (4)")
-      # endif // ifdef P104_USE_EXT_ASCII_FONT
-    # ifdef P104_USE_ARABIC_FONT
-      , F("Arabic (5)")
-    # endif   // ifdef P104_USE_ARABIC_FONT
-    # ifdef P104_USE_GREEK_FONT
-      , F("Greek (6)")
-    # endif   // ifdef P104_USE_GREEK_FONT
-    # ifdef P104_USE_KATAKANA_FONT
-      , F("Katakana (7)")
-    # endif   // ifdef P104_USE_KATAKANA_FONT
-    };
-    const int fontOptions[] = {
-      P104_DEFAULT_FONT_ID
-    # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-      , P104_DOUBLE_HEIGHT_FONT_ID
-    # endif   // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT
-    # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-      , P104_FULL_DOUBLEHEIGHT_FONT_ID
-    # endif   // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT
-    # ifdef P104_USE_VERTICAL_FONT
-      , P104_VERTICAL_FONT_ID
-    # endif   // ifdef P104_USE_VERTICAL_FONT
-    # ifdef P104_USE_EXT_ASCII_FONT
-      , P104_EXT_ASCII_FONT_ID
-      # endif // ifdef P104_USE_EXT_ASCII_FONT
-    # ifdef P104_USE_ARABIC_FONT
-      , P104_ARABIC_FONT_ID
-    # endif   // ifdef P104_USE_ARABIC_FONT
-    # ifdef P104_USE_GREEK_FONT
-      , P104_GREEK_FONT_ID
-    # endif   // ifdef P104_USE_GREEK_FONT
-    # ifdef P104_USE_KATAKANA_FONT
-      , P104_KATAKANA_FONT_ID
-    # endif   // ifdef P104_USE_KATAKANA_FONT
-    };
-
-    const __FlashStringHelper *layoutTypes[] = {
-      F("Standard")
-    # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-      , F("Double, upper")
-      , F("Double, lower")
-    # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-    };
-    const int layoutOptions[] = {
-      P104_LAYOUT_STANDARD
-    # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-      , P104_LAYOUT_DOUBLE_UPPER
-      , P104_LAYOUT_DOUBLE_LOWER
-    # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT)
-    };
-
-    const __FlashStringHelper *specialEffectTypes[] = {
-      F("None"),
-      F("Flip up/down"),
-      F("Flip left/right *"),
-      F("Flip u/d & l/r *")
-    };
-    const int specialEffectOptions[] = {
-      P104_SPECIAL_EFFECT_NONE,
-      P104_SPECIAL_EFFECT_UP_DOWN,
-      P104_SPECIAL_EFFECT_LEFT_RIGHT,
-      P104_SPECIAL_EFFECT_BOTH
-    };
-
-    const __FlashStringHelper *contentTypes[] = {
-      F("Text"),
-      F("Text reverse"),
-      F("Clock (4 mod)"),
-      F("Clock sec (6 mod)"),
-      F("Date (4 mod)"),
-      F("Date yr (6/7 mod)"),
-      F("Date/time (9/13 mod)"),
-      # ifdef P104_USE_BAR_GRAPH
-      F("Bar graph"),
-      # endif // ifdef P104_USE_BAR_GRAPH
-    };
-    const int contentOptions[] {
-      P104_CONTENT_TEXT,
-      P104_CONTENT_TEXT_REV,
-      P104_CONTENT_TIME,
-      P104_CONTENT_TIME_SEC,
-      P104_CONTENT_DATE4,
-      P104_CONTENT_DATE6,
-      P104_CONTENT_DATE_TIME,
-      # ifdef P104_USE_BAR_GRAPH
-      P104_CONTENT_BAR_GRAPH,
-      # endif // ifdef P104_USE_BAR_GRAPH
-    };
-    const __FlashStringHelper *invertedTypes[3] = {
-      F("Normal"),
-      F("Inverted")
-    };
-    const int invertedOptions[] = {
-      0,
-      1
-    };
-    # ifdef P104_USE_ZONE_ACTIONS
-    uint8_t actionCount = 0;
-    const __FlashStringHelper *actionTypes[4];
-    int actionOptions[4];
-    actionTypes[actionCount]   = F("None");
-    actionOptions[actionCount] = P104_ACTION_NONE;
-    actionCount++;
-
-    if (zones.size() < P104_MAX_ZONES) {
-      actionTypes[actionCount]   = F("New above");
-      actionOptions[actionCount] = P104_ACTION_ADD_ABOVE;
-      actionCount++;
-      actionTypes[actionCount]   = F("New below");
-      actionOptions[actionCount] = P104_ACTION_ADD_BELOW;
-      actionCount++;
-    }
-    actionTypes[actionCount]   = F("Delete");
-    actionOptions[actionCount] = P104_ACTION_DELETE;
-    actionCount++;
-    # endif // ifdef P104_USE_ZONE_ACTIONS
-
-    delay(0);
-
-    addFormSubHeader(F("Zone configuration"));
-
-    {
-      html_table(EMPTY_STRING); // Sub-table
-      html_table_header(F("Zone # "));
-      html_table_header(F("Modules"));
-      html_table_header(F("Text"), 180);
-      html_table_header(F("Content"));
-      html_table_header(F("Alignment"));
-      html_table_header(F("Animation In/Out"));               // 1st and 2nd row title
-      html_table_header(F("Speed/Pause"));                    // 1st and 2nd row title
-      html_table_header(F("Font/Layout"));                    // 1st and 2nd row title
-      html_table_header(F("Inverted/ Special Effects")); // 1st and 2nd row title
-      html_table_header(F("Offset"));
-      html_table_header(F("Brightness"));
-      html_table_header(F("Repeat (sec)"));
-      # ifdef P104_USE_ZONE_ACTIONS
-      html_table_header(F(""),       15); // Spacer
-      html_table_header(F("Action"), 45);
-      # endif // ifdef P104_USE_ZONE_ACTIONS
-    }
-
-    uint16_t index;
-    int16_t  startZone, endZone;
-    int8_t   incrZone = 1;
-    # ifdef P104_USE_ZONE_ACTIONS
-    uint8_t currentRow = 0;
-    # endif // ifdef P104_USE_ZONE_ACTIONS
-
-    # ifdef P104_USE_ZONE_ORDERING
-
-    if (bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER)) {
-      startZone = zones.size() - 1;
-      endZone   = -1;
-      incrZone  = -1;
-    } else
-    # endif // ifdef P104_USE_ZONE_ORDERING
-    {
-      startZone = 0;
-      endZone   = zones.size();
-    }
-
-    for (int8_t zone = startZone; zone != endZone; zone += incrZone) {
-      if (zones[zone].zone <= expectedZones) {
-        index = (zones[zone].zone - 1) * P104_OFFSET_COUNT;
-
-        html_TR_TD(); // All columns use max. width available
-        addHtml(F(" "));
-        addHtmlInt(zones[zone].zone);
-
-        html_TD(); // Modules
-        addNumericBox(getPluginCustomArgName(index + P104_OFFSET_SIZE), zones[zone].size, 1, P104_MAX_MODULES_PER_ZONE);
-
-        html_TD(); // Text
-        addTextBox(getPluginCustomArgName(index + P104_OFFSET_TEXT),
-                   zones[zone].text,
-                   P104_MAX_TEXT_LENGTH_PER_ZONE,
-                   false,
-                   false,
-                   EMPTY_STRING,
-                   F(""));
-
-        html_TD(); // Content
-        addSelector(getPluginCustomArgName(index + P104_OFFSET_CONTENT),
-                    P104_CONTENT_count,
-                    contentTypes,
-                    contentOptions,
-                    nullptr,
-                    zones[zone].content,
-                    false,
-                    true,
-                    F(""));
-
-        html_TD(); // Alignment
-        addSelector(getPluginCustomArgName(index + P104_OFFSET_ALIGNMENT),
-                    3,
-                    alignmentTypes,
-                    alignmentOptions,
-                    nullptr,
-                    zones[zone].alignment,
-                    false,
-                    true,
-                    F(""));
-
-        {
-          html_TD(); // Animation In (without None by passing the second element index)
-          addSelector(getPluginCustomArgName(index + P104_OFFSET_ANIM_IN),
-                      animationCount - 1,
-                      &animationTypes[1],
-                      &animationOptions[1],
-                      nullptr,
-                      zones[zone].animationIn,
-                      false,
-                      true,
-                      F("")
-                      # ifdef P104_USE_TOOLTIPS
-                      , F("Animation In")
-                      # endif // ifdef P104_USE_TOOLTIPS
-                      );
-        }
-
-        html_TD();                 // Speed In
-        addNumericBox(getPluginCustomArgName(index + P104_OFFSET_SPEED), zones[zone].speed, 0, P104_MAX_SPEED_PAUSE_VALUE
-                      # ifdef P104_USE_TOOLTIPS
-                      , F("")      // classname
-                      , F("Speed") // title
-                      # endif // ifdef P104_USE_TOOLTIPS
-                      );
-
-        html_TD(); // Font
-        addSelector(getPluginCustomArgName(index + P104_OFFSET_FONT),
-                    NR_ELEMENTS(fontOptions),
-                    fontTypes,
-                    fontOptions,
-                    nullptr,
-                    zones[zone].font,
-                    false,
-                    true,
-                    F("")
-                    # ifdef P104_USE_TOOLTIPS
-                    , F("Font") // title
-                    # endif // ifdef P104_USE_TOOLTIPS
-                    );
-
-        html_TD(); // Inverted
-        addSelector(getPluginCustomArgName(index + P104_OFFSET_INVERTED),
-                    NR_ELEMENTS(invertedOptions),
-                    invertedTypes,
-                    invertedOptions,
-                    nullptr,
-                    zones[zone].inverted,
-                    false,
-                    true,
-                    F("")
-                    # ifdef P104_USE_TOOLTIPS
-                    , F("Inverted") // title
-                    # endif // ifdef P104_USE_TOOLTIPS
-                    );
-
-        html_TD(3); // Fill columns
-        # ifdef P104_USE_ZONE_ACTIONS
-
-        html_TD();  // Spacer
-        addHtml('|');
-
-        if (currentRow < 2) {
-          addHtml(F("
")); // Action column, text centered and font-size 90% - } else { - html_TD(); - } - - if (currentRow == 0) { - addHtml(F("(applied immediately!)")); - } else if (currentRow == 1) { - addHtml(F("(Delete can't be undone!)")); - } - currentRow++; - # endif // ifdef P104_USE_ZONE_ACTIONS - - // Split here - html_TR_TD(); // Start new row - html_TD(4); // Start with some blank columns - - { - html_TD(); // Animation Out - addSelector(getPluginCustomArgName(index + P104_OFFSET_ANIM_OUT), - animationCount, - animationTypes, - animationOptions, - nullptr, - zones[zone].animationOut, - false, - true, - F("") - # ifdef P104_USE_TOOLTIPS - , F("Animation Out") - # endif // ifdef P104_USE_TOOLTIPS - ); - } - - html_TD(); // Pause after Animation In - addNumericBox(getPluginCustomArgName(index + P104_OFFSET_PAUSE), zones[zone].pause, 0, P104_MAX_SPEED_PAUSE_VALUE - # ifdef P104_USE_TOOLTIPS - , F("") // classname - , F("Pause") // title - # endif // ifdef P104_USE_TOOLTIPS - ); - - html_TD(); // Layout - addSelector(getPluginCustomArgName(index + P104_OFFSET_LAYOUT), - NR_ELEMENTS(layoutOptions), - layoutTypes, - layoutOptions, - nullptr, - zones[zone].layout, - false, - true, - F("") - # ifdef P104_USE_TOOLTIPS - , F("Layout") // title - # endif // ifdef P104_USE_TOOLTIPS - ); - - html_TD(); // Special effects - addSelector(getPluginCustomArgName(index + P104_OFFSET_SPEC_EFFECT), - NR_ELEMENTS(specialEffectOptions), - specialEffectTypes, - specialEffectOptions, - nullptr, - zones[zone].specialEffect, - false, - true, - F("") - # ifdef P104_USE_TOOLTIPS - , F("Special Effects") // title - # endif // ifdef P104_USE_TOOLTIPS - ); - - html_TD(); // Offset - addNumericBox(getPluginCustomArgName(index + P104_OFFSET_OFFSET), zones[zone].offset, 0, 254); - - html_TD(); // Brightness - - if (zones[zone].brightness == -1) { zones[zone].brightness = P104_BRIGHTNESS_DEFAULT; } - addNumericBox(getPluginCustomArgName(index + P104_OFFSET_BRIGHTNESS), zones[zone].brightness, 0, P104_BRIGHTNESS_MAX); - - html_TD(); // Repeat (sec) - addNumericBox(getPluginCustomArgName(index + P104_OFFSET_REPEATDELAY), - zones[zone].repeatDelay, - -1, - P104_MAX_REPEATDELAY_VALUE // max delay 86400 sec. = 24 hours - # ifdef P104_USE_TOOLTIPS - , F("") // classname - , F("Repeat after this delay (sec), -1 = off") // tooltip - # endif // ifdef P104_USE_TOOLTIPS - ); - - # ifdef P104_USE_ZONE_ACTIONS - html_TD(); // Spacer - addHtml('|'); - - html_TD(); // Action - addSelector(getPluginCustomArgName(index + P104_OFFSET_ACTION), - actionCount, - actionTypes, - actionOptions, - nullptr, - P104_ACTION_NONE, // Always start with None - true, - true, - F("")); - # endif // ifdef P104_USE_ZONE_ACTIONS - - delay(0); - } - } - html_end_table(); - } - - # ifdef P104_ADD_SETTINGS_NOTES - addFormNote(concat(F("- Maximum nr. of modules possible (Zones * Size + Offset) = 255. Last saved: "), numDevices)); - addFormNote(F("- 'Animation In' or 'Animation Out' and 'Special Effects' marked with * should not be combined in a Zone.")); - # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) && !defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) - addFormNote(F("- 'Layout' 'Double upper' and 'Double lower' are only supported for numeric 'Content' types like 'Clock' and 'Date'.")); - # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) && !defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) - # endif // ifdef P104_ADD_SETTINGS_NOTES - - return true; -} - -/************************************************************** -* webform_save -**************************************************************/ -bool P104_data_struct::webform_save(struct EventStruct *event) { - P104_CONFIG_ZONE_COUNT = getFormItemInt(F("zonecnt")); - P104_CONFIG_HARDWARETYPE = getFormItemInt(F("hardware")); - - bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_CLEAR_DISABLE, isFormItemChecked(F("clrdsp"))); - bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_LOG_ALL_TEXT, isFormItemChecked(F("logtxt"))); - - # ifdef P104_USE_ZONE_ORDERING - zoneOrder = getFormItemInt(F("zoneorder")); // Is used in saveSettings() - bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER, zoneOrder == 1); - # endif // ifdef P104_USE_ZONE_ORDERING - - # ifdef P104_USE_DATETIME_OPTIONS - uint32_t ulDateTime = 0; - bitWrite(ulDateTime, P104_CONFIG_DATETIME_FLASH, !isFormItemChecked(F("clkflash"))); // Inverted flag - bitWrite(ulDateTime, P104_CONFIG_DATETIME_12H, isFormItemChecked(F("clk12h"))); - bitWrite(ulDateTime, P104_CONFIG_DATETIME_AMPM, isFormItemChecked(F("clkampm"))); - bitWrite(ulDateTime, P104_CONFIG_DATETIME_YEAR4DGT, isFormItemChecked(F("year4dgt"))); - set4BitToUL(ulDateTime, P104_CONFIG_DATETIME_FORMAT, getFormItemInt(F("datefmt"))); - set4BitToUL(ulDateTime, P104_CONFIG_DATETIME_SEP_CHAR, getFormItemInt(F("datesep"))); - P104_CONFIG_DATETIME = ulDateTime; - # endif // ifdef P104_USE_DATETIME_OPTIONS - - previousZones = expectedZones; - expectedZones = P104_CONFIG_ZONE_COUNT; - - bool result = saveSettings(); // Determines numDevices and re-fills zones list - - P104_CONFIG_ZONE_COUNT = zones.size(); - P104_CONFIG_TOTAL_UNITS = numDevices; // Store counted number of devices - - zones.clear(); // Free some memory (temporarily) - - return result; -} - -#endif // ifdef USES_P104 +#include "../PluginStructs/P104_data_struct.h" + +#ifdef USES_P104 + +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Numerical.h" +# include "../WebServer/Markup_Forms.h" +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/Markup.h" +# include "../WebServer/HTML_wrappers.h" +# include "../ESPEasyCore/ESPEasyRules.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/RTC.h" + +# include +# include +# include + +// Needed also here for PlatformIO's library finder as the .h file +// is in a directory which is excluded in the src_filter + +# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) +void createHString(String& string); // Forward definition +# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) +void reverseStr(String& str); // Forward definition + +/**************************************************************** + * Constructor + ***************************************************************/ +P104_data_struct::P104_data_struct(MD_MAX72XX::moduleType_t _mod, + taskIndex_t _taskIndex, + int8_t _cs_pin, + uint8_t _modules, + uint8_t _zonesCount) + : mod(_mod), taskIndex(_taskIndex), cs_pin(_cs_pin), modules(_modules), expectedZones(_zonesCount) { + if (Settings.isSPI_valid()) { + P = new (std::nothrow) MD_Parola(mod, cs_pin, modules); + } else { + addLog(LOG_LEVEL_ERROR, F("DOTMATRIX: Required SPI not enabled. Initialization aborted!")); + } +} + +/******************************* + * Destructor + ******************************/ +P104_data_struct::~P104_data_struct() { + # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + + if (nullptr != pM) { + pM = nullptr; // Not created here, only reset + } + # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + + if (nullptr != P) { + // P->~MD_Parola(); // Call destructor directly, as delete of the object fails miserably + // do not: delete P; // Warning: the MD_Parola object doesn't have a virtual destructor, and when changed, + // a reboot uccurs when the object is deleted here! + P = nullptr; // Reset only + } +} + +/******************************* + * Initializer/starter + ******************************/ +bool P104_data_struct::begin() { + if (!initialized) { + loadSettings(); + initialized = true; + } + + if ((P != nullptr) && validGpio(cs_pin)) { + # ifdef P104_DEBUG + addLog(LOG_LEVEL_INFO, F("dotmatrix: begin() called")); + # endif // ifdef P104_DEBUG + P->begin(expectedZones); + # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + pM = P->getGraphicObject(); + # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + return true; + } + return false; +} + +# define P104_ZONE_SEP '\x02' +# define P104_FIELD_SEP '\x01' +# define P104_ZONE_DISP ';' +# define P104_FIELD_DISP ',' + +# define P104_CONFIG_VERSION_V2 0xF000 // Marker in first uint16_t to to indicate second version config settings, anything else if first + // version. + // Any third version or later could use 0xE000, etc. The 'version' is stored in the first uint16_t + // stored in the custom settings + +/* + Settings layout: + Version 1: + - uint16_t : size of the next blob holding all settings + - char[x] : Blob with settings, with csv-like strings, using P104_FIELD_SEP and P104_ZONE_SEP separators + Version 2: + - uint16_t : marker with content P104_CONFIG_VERSION_V2 + - uint16_t : size of next blob holding 1 zone settings string + - char[y] : Blob holding 1 zone settings string, with csv like string, using P104_FIELD_SEP separators + - uint16_t : next size, if 0 then no more blobs + - char[x] : Blob + - ... + - Max. allowed total custom settings size = 1024 + */ +/************************************** + * loadSettings + *************************************/ +void P104_data_struct::loadSettings() { + uint16_t bufferSize; + char *settingsBuffer; + + if (taskIndex < TASKS_MAX) { + int loadOffset = 0; + + // Read size of the used buffer, could be the settings-version marker + LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), loadOffset); + bool settingsVersionV2 = (bufferSize == P104_CONFIG_VERSION_V2) || (bufferSize == 0u); + uint16_t structDataSize = 0; + uint16_t reservedBuffer = 0; + + if (!settingsVersionV2) { + reservedBuffer = bufferSize + 1; // just add 1 for storing a string-terminator + addLog(LOG_LEVEL_INFO, F("dotmatrix: Reading Settings V1, will be stored as Settings V2.")); + } else { + reservedBuffer = P104_SETTINGS_BUFFER_V2 + 1; // just add 1 for storing a string-terminator + } + reservedBuffer++; // Add 1 for 0..size use + settingsBuffer = new (std::nothrow) char[reservedBuffer](); // Allocate buffer and reset to all zeroes + loadOffset += sizeof(bufferSize); + + if (settingsVersionV2) { + LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), loadOffset); + loadOffset += sizeof(bufferSize); // Skip the size + } + structDataSize = bufferSize; + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P104: loadSettings stored Size: %d taskindex: %d"), structDataSize, taskIndex)); + } + # endif // ifdef P104_DEBUG_DEV + + // Read actual data + if (structDataSize > 0) { // Reading 0 bytes logs an error, so lets avoid that + LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)settingsBuffer, structDataSize, loadOffset); + } + settingsBuffer[bufferSize + 1] = '\0'; // Terminate string + + uint8_t zoneIndex = 0; + + { + String buffer(settingsBuffer); + # ifdef P104_DEBUG_DEV + + String log; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log = F("P104: loadSettings bufferSize: "); + log += bufferSize; + log += F(" untrimmed: "); + log += buffer.length(); + } + # endif // ifdef P104_DEBUG_DEV + buffer.trim(); + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" trimmed: "); + log += buffer.length(); + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifdef P104_DEBUG_DEV + + if (zones.size() > 0) { + zones.clear(); + } + zones.reserve(P104_MAX_ZONES); + numDevices = 0; + + String tmp; + String fld; + int32_t tmp_int; + uint16_t prev2 = 0; + int16_t offset2 = buffer.indexOf(P104_ZONE_SEP); + + if ((offset2 == -1) && (buffer.length() > 0)) { + offset2 = buffer.length(); + } + + while (offset2 > -1) { + tmp = buffer.substring(prev2, offset2); + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + log = F("P104: reading string: "); + log += tmp; + log.replace(P104_FIELD_SEP, P104_FIELD_DISP); + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifdef P104_DEBUG_DEV + + zones.push_back(P104_zone_struct(zoneIndex + 1)); + + tmp_int = 0; + + // WARNING: Order of parsing these values should match the numeric order of P104_OFFSET_* values + for (uint8_t i = 0; i < P104_OFFSET_COUNT; ++i) { + if (i == P104_OFFSET_TEXT) { + zones[zoneIndex].text = parseStringKeepCaseNoTrim(tmp, 1 + P104_OFFSET_TEXT, P104_FIELD_SEP); + } else { + if (validIntFromString(parseString(tmp, 1 + i, P104_FIELD_SEP), tmp_int)) { + zones[zoneIndex].setIntValue(i, tmp_int); + } + } + } + + delay(0); + + numDevices += zones[zoneIndex].size + zones[zoneIndex].offset; + + if (!settingsVersionV2) { + prev2 = offset2 + 1; + offset2 = buffer.indexOf(P104_ZONE_SEP, prev2); + } else { + loadOffset += bufferSize; + structDataSize = sizeof(bufferSize); + LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, structDataSize, loadOffset); + offset2 = bufferSize; // Length + + if (bufferSize == 0) { // End of zones reached + offset2 = -1; // fall out of while loop + } else { + structDataSize = bufferSize; + loadOffset += sizeof(bufferSize); + LoadFromFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)settingsBuffer, structDataSize, loadOffset); + settingsBuffer[bufferSize + 1] = '\0'; // Terminate string + buffer = String(settingsBuffer); + } + } + zoneIndex++; + + # ifdef P104_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("dotmatrix: parsed zone: "), zoneIndex)); + } + # endif // ifdef P104_DEBUG + } + + buffer = String(); // Free some memory + } + + delete[] settingsBuffer; // Release allocated buffer + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: read zones from config: "), zoneIndex)); + } + # endif // ifdef P104_DEBUG_DEV + + if (expectedZones == -1) { expectedZones = zoneIndex; } + + if (expectedZones == 0) { expectedZones++; } // Guarantee at least 1 zone to be displayed + + while (zoneIndex < expectedZones) { + zones.push_back(P104_zone_struct(zoneIndex + 1)); + + if (equals(zones[zoneIndex].text, F("\"\""))) { // Special case + zones[zoneIndex].text.clear(); + } + + zoneIndex++; + delay(0); + } + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P104: total zones initialized: %d expected: %d"), zoneIndex, expectedZones)); + } + # endif // ifdef P104_DEBUG_DEV + } +} + +/**************************************************** + * configureZones: initialize Zones setup + ***************************************************/ +void P104_data_struct::configureZones() { + if (!initialized) { + loadSettings(); + initialized = true; + } + + uint8_t currentZone = 0; + uint8_t zoneOffset = 0; + + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: configureZones to do: "), zones.size())); + } + # endif // ifdef P104_DEBUG_DEV + + if (nullptr == P) { return; } + + P->displayClear(); + + for (auto it = zones.begin(); it != zones.end(); ++it) { + if (it->zone <= expectedZones) { + zoneOffset += it->offset; + P->setZone(currentZone, zoneOffset, zoneOffset + it->size - 1); + # if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + it->_startModule = zoneOffset; + P->getDisplayExtent(currentZone, it->_lower, it->_upper); + # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + zoneOffset += it->size; + + switch (it->font) { + # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + case P104_DOUBLE_HEIGHT_FONT_ID: { + P->setFont(currentZone, numeric7SegDouble); + P->setCharSpacing(currentZone, P->getCharSpacing() * 2); // double spacing as well + break; + } + # endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + case P104_FULL_DOUBLEHEIGHT_FONT_ID: { + P->setFont(currentZone, BigFont); + P->setCharSpacing(currentZone, P->getCharSpacing() * 2); // double spacing as well + break; + } + # endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + # ifdef P104_USE_VERTICAL_FONT + case P104_VERTICAL_FONT_ID: { + P->setFont(currentZone, _fontVertical); + break; + } + # endif // ifdef P104_USE_VERTICAL_FONT + # ifdef P104_USE_EXT_ASCII_FONT + case P104_EXT_ASCII_FONT_ID: { + P->setFont(currentZone, ExtASCII); + break; + } + # endif // ifdef P104_USE_EXT_ASCII_FONT + # ifdef P104_USE_ARABIC_FONT + case P104_ARABIC_FONT_ID: { + P->setFont(currentZone, fontArabic); + break; + } + # endif // ifdef P104_USE_ARABIC_FONT + # ifdef P104_USE_GREEK_FONT + case P104_GREEK_FONT_ID: { + P->setFont(currentZone, fontGreek); + break; + } + # endif // ifdef P104_USE_GREEK_FONT + # ifdef P104_USE_KATAKANA_FONT + case P104_KATAKANA_FONT_ID: { + P->setFont(currentZone, fontKatakana); + break; + } + # endif // ifdef P104_USE_KATAKANA_FONT + + // Extend above this comment with more fonts if/when available, + // case P104_DEFAULT_FONT_ID: and default: clauses should be the last options. + // This should also make sure the default font is set if a no longer available font was selected + case P104_DEFAULT_FONT_ID: + default: { + P->setFont(currentZone, nullptr); // default font + break; + } + } + + // Inverted + P->setInvert(currentZone, it->inverted); + + // Special Effects + P->setZoneEffect(currentZone, (it->specialEffect & P104_SPECIAL_EFFECT_UP_DOWN) == P104_SPECIAL_EFFECT_UP_DOWN, PA_FLIP_UD); + P->setZoneEffect(currentZone, (it->specialEffect & P104_SPECIAL_EFFECT_LEFT_RIGHT) == P104_SPECIAL_EFFECT_LEFT_RIGHT, PA_FLIP_LR); + + // Brightness + P->setIntensity(currentZone, it->brightness); + + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P104: configureZones #%d/%d offset: %d"), currentZone + 1, expectedZones, zoneOffset)); + } + # endif // ifdef P104_DEBUG_DEV + + delay(0); + + // Content == text && text != "" + if (((it->content == P104_CONTENT_TEXT) || + (it->content == P104_CONTENT_TEXT_REV)) + && (!it->text.isEmpty())) { + displayOneZoneText(currentZone, *it, it->text); + } + + # ifdef P104_USE_BAR_GRAPH + + // Content == Bar-graph && text != "" + if ((it->content == P104_CONTENT_BAR_GRAPH) + && (!it->text.isEmpty())) { + displayBarGraph(currentZone, *it, it->text); + } + # endif // ifdef P104_USE_BAR_GRAPH + + if (it->repeatDelay > -1) { + it->_repeatTimer = millis(); + } + currentZone++; + delay(0); + } + } + + // Synchronize the start + P->synchZoneStart(); +} + +/********************************************************** + * Display the text with attributes for a specific zone + *********************************************************/ +void P104_data_struct::displayOneZoneText(uint8_t zone, + const P104_zone_struct& zstruct, + const String & text) { + if ((nullptr == P) || (zone >= P104_MAX_ZONES)) { return; } // double check + sZoneInitial[zone].reserve(text.length()); + sZoneInitial[zone] = text; // Keep the original string for future use + sZoneBuffers[zone].reserve(text.length()); + sZoneBuffers[zone] = text; // We explicitly want a copy here so it can be modified by parseTemplate() + + sZoneBuffers[zone] = parseTemplate(sZoneBuffers[zone]); + + # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + + if (zstruct.layout == P104_LAYOUT_DOUBLE_UPPER) { + createHString(sZoneBuffers[zone]); + } + # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + + if (zstruct.content == P104_CONTENT_TEXT_REV) { + reverseStr(sZoneBuffers[zone]); + } + + String log; + + if (loglevelActiveFor(LOG_LEVEL_INFO) && + logAllText && + log.reserve(28 + text.length() + sZoneBuffers[zone].length())) { + log = strformat(F("dotmatrix: ZoneText: %d, '"), zone + 1); // UI-number + log += text; + log += F("' -> '"); + log += sZoneBuffers[zone]; + log += '\''; + addLogMove(LOG_LEVEL_INFO, log); + } + + P->displayZoneText(zone, + sZoneBuffers[zone].c_str(), + static_cast(zstruct.alignment), + zstruct.speed, + zstruct.pause, + static_cast(zstruct.animationIn), + static_cast(zstruct.animationOut)); +} + +/********************************************* + * Update all or the specified zone + ********************************************/ +void P104_data_struct::updateZone(uint8_t zone, + const P104_zone_struct& zstruct) { + if (nullptr == P) { return; } + + if (zone == 0) { + for (auto it = zones.begin(); it != zones.end(); ++it) { + if ((it->zone > 0) && + ((it->content == P104_CONTENT_TEXT) || + (it->content == P104_CONTENT_TEXT_REV))) { + displayOneZoneText(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed text + P->displayReset(it->zone - 1); + } + # ifdef P104_USE_BAR_GRAPH + + if ((it->zone > 0) && + (it->content == P104_CONTENT_BAR_GRAPH)) { + displayBarGraph(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed bar graph + } + # endif // ifdef P104_USE_BAR_GRAPH + + if ((zstruct.content == P104_CONTENT_TEXT) + || zstruct.content == P104_CONTENT_TEXT_REV + # ifdef P104_USE_BAR_GRAPH + || zstruct.content == P104_CONTENT_BAR_GRAPH + # endif // ifdef P104_USE_BAR_GRAPH + ) { + if (it->repeatDelay > -1) { // Restart repeat timer + it->_repeatTimer = millis(); + } + } + } + } else { + if ((zstruct.zone > 0) && + ((zstruct.content == P104_CONTENT_TEXT) || + (zstruct.content == P104_CONTENT_TEXT_REV))) { + displayOneZoneText(zstruct.zone - 1, zstruct, sZoneInitial[zstruct.zone - 1]); // Re-send last displayed text + P->displayReset(zstruct.zone - 1); + } + # ifdef P104_USE_BAR_GRAPH + + if ((zstruct.zone > 0) && + (zstruct.content == P104_CONTENT_BAR_GRAPH)) { + displayBarGraph(zstruct.zone - 1, zstruct, sZoneInitial[zstruct.zone - 1]); // Re-send last displayed bar graph + } + # endif // ifdef P104_USE_BAR_GRAPH + + // Repeat timer is/should be started elsewhere + } +} + +# if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + +/*********************************************** + * Enable/Disable updating a range of modules + **********************************************/ +void P104_data_struct::modulesOnOff(uint8_t start, uint8_t end, MD_MAX72XX::controlValue_t on_off) { + for (uint8_t m = start; m <= end; m++) { + pM->control(m, MD_MAX72XX::UPDATE, on_off); + } +} + +# endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + +# ifdef P104_USE_BAR_GRAPH + +/******************************************************** + * draw a single bar-graph, arguments already adjusted for direction + *******************************************************/ +void P104_data_struct::drawOneBarGraph(uint16_t lower, + uint16_t upper, + int16_t pixBottom, + int16_t pixTop, + uint16_t zeroPoint, + uint8_t barWidth, + uint8_t barType, + uint8_t row) { + bool on_off; + + for (uint8_t r = 0; r < barWidth; r++) { + for (uint8_t col = lower; col <= upper; col++) { + on_off = (col >= pixBottom && col <= pixTop); // valid area + + if ((zeroPoint != 0) && + (barType == P104_BARTYPE_STANDARD) && + (barWidth > 2) && + ((r == 0) || (r == barWidth - 1)) && + (col == lower + zeroPoint)) { + on_off = false; // when bar wider than 2, turn off zeropoint top and bottom led + } + + if ((barType == P104_BARTYPE_SINGLE) && (r > 0)) { + on_off = false; // barType 1 = only a single line is drawn, independent of the width + } + + if ((barType == P104_BARTYPE_ALT_DOT) && (barWidth > 1) && on_off) { + on_off = ((r % 2) == (col % 2)); // barType 2 = dotted line when bar is wider than 1 pixel + } + pM->setPoint(row + r, col, on_off); + + if (col % 16 == 0) { delay(0); } + } + delay(0); // Leave some breathingroom + } +} + +/******************************************************************** + * Process a graph-string to display in a zone, format: + * value,max-value,min-value,direction,bartype|... + *******************************************************************/ +void P104_data_struct::displayBarGraph(uint8_t zone, + const P104_zone_struct& zstruct, + const String & graph) { + if ((nullptr == P) || (nullptr == pM) || graph.isEmpty()) { return; } + sZoneInitial[zone] = graph; // Keep the original string for future use + + # define NOT_A_COMMA 0x02 // Something else than a comma, or the parseString function will get confused + String parsedGraph(graph); // Extra copy created so we don't mess up the incoming String + parsedGraph = parseTemplate(parsedGraph); + parsedGraph.replace(',', NOT_A_COMMA); + + std::vector barGraphs; + uint8_t currentBar = 0; + bool loop = true; + + // Parse the graph-string + while (loop && currentBar < 8) { // Maximum 8 valuesets possible + String graphpart = parseString(parsedGraph, currentBar + 1, '|'); + graphpart.trim(); + graphpart.replace(NOT_A_COMMA, ','); + + if (graphpart.isEmpty()) { + loop = false; + } else { + barGraphs.push_back(P104_bargraph_struct(currentBar)); + } + + if (loop && validDoubleFromString(parseString(graphpart, 1), barGraphs[currentBar].value)) { // value + String datapart = parseString(graphpart, 2); // max (default: 100.0) + + if (datapart.isEmpty()) { + barGraphs[currentBar].max = 100.0; + } else { + validDoubleFromString(datapart, barGraphs[currentBar].max); + } + datapart = parseString(graphpart, 3); // min (default: 0.0) + + if (datapart.isEmpty()) { + barGraphs[currentBar].min = 0.0; + } else { + validDoubleFromString(datapart, barGraphs[currentBar].min); + } + datapart = parseString(graphpart, 4); // direction + + if (datapart.isEmpty()) { + barGraphs[currentBar].direction = 0; + } else { + int32_t value = 0; + validIntFromString(datapart, value); + barGraphs[currentBar].direction = value; + } + datapart = parseString(graphpart, 5); // barType + + if (datapart.isEmpty()) { + barGraphs[currentBar].barType = 0; + } else { + int32_t value = 0; + validIntFromString(datapart, value); + barGraphs[currentBar].barType = value; + } + + if (definitelyGreaterThan(barGraphs[currentBar].min, barGraphs[currentBar].max)) { + std::swap(barGraphs[currentBar].min, barGraphs[currentBar].max); + } + } + # ifdef P104_DEBUG + + if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(70)) { + log = F("dotmatrix: Bar-graph: "); + + if (loop) { + log += currentBar; + log += F(" in: "); + log += graphpart; + log += F(" value: "); + log += barGraphs[currentBar].value; + log += F(" max: "); + log += barGraphs[currentBar].max; + log += F(" min: "); + log += barGraphs[currentBar].min; + log += F(" dir: "); + log += barGraphs[currentBar].direction; + log += F(" typ: "); + log += barGraphs[currentBar].barType; + } else { + log += F(" bsize: "); + log += barGraphs.size(); + } + addLogMove(LOG_LEVEL_INFO, log); + } + } + # endif // ifdef P104_DEBUG + currentBar++; // next + delay(0); // Leave some breathingroom + } + # undef NOT_A_COMMA + + if (barGraphs.size() > 0) { + uint8_t barWidth = 8 / barGraphs.size(); // Divide the 8 pixel width per number of bars to show + int16_t pixTop, pixBottom; + uint16_t zeroPoint; + # ifdef P104_DEBUG + String log; + + if (logAllText && + loglevelActiveFor(LOG_LEVEL_INFO) && + log.reserve(64)) { + log = F("dotmatrix: bar Width: "); + log += barWidth; + log += F(" low: "); + log += zstruct._lower; + log += F(" high: "); + log += zstruct._upper; + } + # endif // ifdef P104_DEBUG + modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_OFF); // Stop updates on modules + P->setIntensity(zstruct.zone - 1, zstruct.brightness); // don't forget to set the brightness + uint8_t row = 0; + + if ((barGraphs.size() == 3) || (barGraphs.size() == 5) || (barGraphs.size() == 6)) { // Center within the rows a bit + for (; row < (barGraphs.size() == 5 ? 2 : 1); row++) { + for (uint8_t col = zstruct._lower; col <= zstruct._upper; col++) { + pM->setPoint(row, col, false); // all off + + if (col % 16 == 0) { delay(0); } + } + delay(0); // Leave some breathingroom + } + } + + for (auto it = barGraphs.begin(); it != barGraphs.end(); ++it) { + if (essentiallyZero(it->min)) { + pixTop = zstruct._lower - 1 + (((zstruct._upper + 1) - zstruct._lower) / it->max) * it->value; + pixBottom = zstruct._lower - 1; + zeroPoint = 0; + } else { + if (definitelyLessThan(it->min, 0.0) && + definitelyGreaterThan(it->max, 0.0) && + definitelyGreaterThan(it->max - it->min, 0.01)) { // Zero-point is used + zeroPoint = (it->min * -1.0) / ((it->max - it->min) / (1.0 * ((zstruct._upper + 1) - zstruct._lower))); + } else { + zeroPoint = 0; + } + pixTop = zstruct._lower + zeroPoint + (((zstruct._upper + 1) - zstruct._lower) / (it->max - it->min)) * it->value; + pixBottom = zstruct._lower + zeroPoint; + + if (definitelyLessThan(it->value, 0.0)) { + std::swap(pixTop, pixBottom); + } + } + + if (it->direction == 1) { // Left to right display: Flip values within the lower/upper range + pixBottom = zstruct._upper - (pixBottom - zstruct._lower); + pixTop = zstruct._lower + (zstruct._upper - pixTop); + std::swap(pixBottom, pixTop); + zeroPoint = zstruct._upper - zstruct._lower - zeroPoint + (zeroPoint == 0 ? 1 : 0); + } + # ifdef P104_DEBUG_DEV + + if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) { + log += F(" B: "); + log += pixBottom; + log += F(" T: "); + log += pixTop; + log += F(" Z: "); + log += zeroPoint; + } + # endif // ifdef P104_DEBUG_DEV + drawOneBarGraph(zstruct._lower, zstruct._upper, pixBottom, pixTop, zeroPoint, barWidth, it->barType, row); + row += barWidth; // Next set of rows + delay(0); // Leave some breathingroom + } + + for (; row < 8; row++) { // Clear unused rows + for (uint8_t col = zstruct._lower; col <= zstruct._upper; col++) { + pM->setPoint(row, col, false); // all off + + if (col % 16 == 0) { delay(0); } + } + delay(0); // Leave some breathingroom + } + # ifdef P104_DEBUG + + if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifdef P104_DEBUG + modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_ON); // Continue updates on modules + } +} + +# endif // ifdef P104_USE_BAR_GRAPH + +# ifdef P104_USE_DOT_SET +void P104_data_struct::displayDots(uint8_t zone, + const P104_zone_struct& zstruct, + const String & dots) { + if ((nullptr == P) || (nullptr == pM) || dots.isEmpty()) { return; } + { + uint8_t idx = 0; + String sRow; + String sCol; + String sOn_off; + bool on_off = true; + modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_OFF); // Stop updates on modules + P->setIntensity(zstruct.zone - 1, zstruct.brightness); // don't forget to set the brightness + sRow = parseString(dots, idx + 1); + sCol = parseString(dots, idx + 2); + sOn_off = parseString(dots, idx + 3); + + while (!sRow.isEmpty() && !sCol.isEmpty()) { + on_off = true; // Default On + + int32_t row; + int32_t col; + if (validIntFromString(sRow, row) && + validIntFromString(sCol, col) && + (row > 0) && ((row - 1) < 8) && + (col > 0) && ((col - 1) <= (zstruct._upper - zstruct._lower))) { // Valid coordinates? + if (equals(sOn_off, F("0"))) { // Dot On is the default + on_off = false; + idx++; // 3rd argument used + } + pM->setPoint(row - 1, zstruct._upper - (col - 1), on_off); // Reverse layout + } + idx += 2; // Skip to next argument set + + if (idx % 16 == 0) { delay(0); } + sRow = parseString(dots, idx + 1); + sCol = parseString(dots, idx + 2); + sOn_off = parseString(dots, idx + 3); + } + + modulesOnOff(zstruct._startModule, zstruct._startModule + zstruct.size - 1, MD_MAX72XX::MD_ON); // Continue updates on modules + } +} + +# endif // ifdef P104_USE_DOT_SET + +/************************************************** + * Check if an animation is available in the current build + *************************************************/ +bool isAnimationAvailable(uint8_t animation, bool noneIsAllowed = false) { + textEffect_t selection = static_cast(animation); + + switch (selection) { + case PA_NO_EFFECT: + { + return noneIsAllowed; + } + case PA_PRINT: + case PA_SCROLL_UP: + case PA_SCROLL_DOWN: + case PA_SCROLL_LEFT: + case PA_SCROLL_RIGHT: + { + return true; + } + # if ENA_SPRITE + case PA_SPRITE: + { + return true; + } + # endif // ENA_SPRITE + # if ENA_MISC + case PA_SLICE: + case PA_MESH: + case PA_FADE: + case PA_DISSOLVE: + case PA_BLINDS: + case PA_RANDOM: + { + return true; + } + # endif // ENA_MISC + # if ENA_WIPE + case PA_WIPE: + case PA_WIPE_CURSOR: + { + return true; + } + # endif // ENA_WIPE + # if ENA_SCAN + case PA_SCAN_HORIZ: + case PA_SCAN_HORIZX: + case PA_SCAN_VERT: + case PA_SCAN_VERTX: + { + return true; + } + # endif // ENA_SCAN + # if ENA_OPNCLS + case PA_OPENING: + case PA_OPENING_CURSOR: + case PA_CLOSING: + case PA_CLOSING_CURSOR: + { + return true; + } + # endif // ENA_OPNCLS + # if ENA_SCR_DIA + case PA_SCROLL_UP_LEFT: + case PA_SCROLL_UP_RIGHT: + case PA_SCROLL_DOWN_LEFT: + case PA_SCROLL_DOWN_RIGHT: + { + return true; + } + # endif // ENA_SCR_DIA + # if ENA_GROW + case PA_GROW_UP: + case PA_GROW_DOWN: + { + return true; + } + # endif // ENA_GROW + default: + return false; + } +} + +const char p104_subcommands[] PROGMEM = + "clear" + "|update" + + "|txt" + "|settxt" + +# ifdef P104_USE_BAR_GRAPH + "|bar" + "|setbar" +# endif // ifdef P104_USE_BAR_GRAPH + +# ifdef P104_USE_DOT_SET + "|dot" +# endif // ifdef P104_USE_DOT_SET + +# ifdef P104_USE_COMMANDS + "|alignment" + "|anim.in" + "|anim.out" + "|brightness" + "|content" + "|font" + "|inverted" +# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + "|layout" +# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + "|offset" + "|pause" + "|repeat" + "|size" + "|specialeffect" + "|speed" +# endif // ifdef P104_USE_COMMANDS +; + +// Subcommands prefixed by "dotmatrix," +enum class p104_subcommands_e { + clear, // subcommand: clear, / clear[,all] + update, // subcommand: update, / update[,all] + + txt, // subcommand: [set]txt,, (only + settxt, // subcommand: settxt,, (stores + +# ifdef P104_USE_BAR_GRAPH + bar, // subcommand: [set]bar,, (only allowed for zones + setbar, // subcommand: setbar,, (stores the graph-string +# endif // ifdef P104_USE_BAR_GRAPH + +# ifdef P104_USE_DOT_SET + dot, // subcommand: dot,,,[,0][,,[,0]...] to draw +# endif // ifdef P104_USE_DOT_SET + +# ifdef P104_USE_COMMANDS + alignment, // subcommand: alignment,, (0..3) + anim_in, // subcommand: anim.in,, (1..) + anim_out, // subcommand: anim.out,, (0..) + brightness, // subcommand: brightness,, (0..15) + content, // subcommand: content,, (0..-1) + font, // subcommand: font,, (only for incuded font id's) + inverted, // subcommand: inverted,, (disable/enable) +# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + layout, // subcommand: layout,, (0..2), only when double-height font is available +# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + offset, // subcommand: offset,, (0..-1) + pause, // subcommand: pause,, (0..P104_MAX_SPEED_PAUSE_VALUE) + repeat, // subcommand: repeat,, (-1..86400 = 24h) + size, // subcommand: size,, (1..) + specialeffect, // subcommand: specialeffect,, (0..3) + speed, // subcommand: speed,, (0..P104_MAX_SPEED_PAUSE_VALUE) +# endif // ifdef P104_USE_COMMANDS +}; + +/******************************************************* + * handlePluginWrite : process commands + ******************************************************/ +bool P104_data_struct::handlePluginWrite(taskIndex_t taskIndex, + const String& string) { + # ifdef P104_USE_COMMANDS + bool reconfigure = false; + # endif // ifdef P104_USE_COMMANDS + bool success = false; + const String command = parseString(string, 1); + + if ((nullptr != P) && equals(command, F("dotmatrix"))) { // main command: dotmatrix + const String subCommand = parseString(string, 2); + const int subCommand_i = GetCommandCode(subCommand.c_str(), p104_subcommands); + + if (subCommand_i != -1) { + const p104_subcommands_e subcommands_e = static_cast(subCommand_i); + + int32_t zoneIndex{}; + const String string4 = parseStringKeepCaseNoTrim(string, 4); + # ifdef P104_USE_COMMANDS + int32_t value4{}; + validIntFromString(string4, value4); + # endif // ifdef P104_USE_COMMANDS + + // Global subcommands + + if ((subcommands_e == p104_subcommands_e::clear) && // subcommand: clear[,all] + (string4.isEmpty() || + string4.equalsIgnoreCase(F("all")))) { + P->displayClear(); + success = true; + } else + + if ((subcommands_e == p104_subcommands_e::update) && // subcommand: update[,all] + (string4.isEmpty() || + string4.equalsIgnoreCase(F("all")))) { + updateZone(0, P104_zone_struct(0)); + success = true; + } + + // Zone-specific subcommands + if (validIntFromString(parseString(string, 3), zoneIndex) && + (zoneIndex > 0) && + (static_cast(zoneIndex) <= zones.size())) { + // subcommands are processed in the same order as they are presented in the UI + for (auto it = zones.begin(); it != zones.end() && !success; ++it) { + if ((it->zone == zoneIndex)) { // This zone + switch (subcommands_e) { + case p104_subcommands_e::clear: + // subcommand: clear, + { + P->displayClear(zoneIndex - 1); + success = true; + break; + } + + case p104_subcommands_e::update: + // subcommand: update, + { + updateZone(zoneIndex, *it); + success = true; + break; + } + + # ifdef P104_USE_COMMANDS + + case p104_subcommands_e::size: + // subcommand: size,, (1..) + { + if ((value4 > 0) && + (value4 <= P104_MAX_MODULES_PER_ZONE)) + { + reconfigure = (it->size != value4); + it->size = value4; + success = true; + } + break; + } + # endif // ifdef P104_USE_COMMANDS + + case p104_subcommands_e::txt: // subcommand: [set]txt,, (only + case p104_subcommands_e::settxt: // allowed for zones with Text content) + { + if ((it->content == P104_CONTENT_TEXT) || + (it->content == P104_CONTENT_TEXT_REV)) { // no length check, so longer than the UI allows is made + // possible + if ((subcommands_e == p104_subcommands_e::settxt) && // subcommand: settxt,, (stores + (string4.length() <= P104_MAX_TEXT_LENGTH_PER_ZONE)) { // the text in the settings, is not saved) + it->text = string4; // Only if not too long, could 'blow up' the + } // settings when saved + displayOneZoneText(zoneIndex - 1, *it, string4); + success = true; + } + + break; + } + + # ifdef P104_USE_COMMANDS + + case p104_subcommands_e::content: + // subcommand: content,, (0..-1) + { + if ((value4 >= 0) && + (value4 < P104_CONTENT_count)) + { + reconfigure = (it->content != value4); + it->content = value4; + success = true; + } + break; + } + + case p104_subcommands_e::alignment: + // subcommand: alignment,, (0..3) + { + if ((value4 >= 0) && + (value4 <= static_cast(textPosition_t::PA_RIGHT))) // last item in the enum + { + it->alignment = value4; + success = true; + } + break; + } + + case p104_subcommands_e::anim_in: + // subcommand: anim.in,, (1..) + { + if (isAnimationAvailable(value4)) { + it->animationIn = value4; + success = true; + } + break; + } + + case p104_subcommands_e::speed: + // subcommand: speed,, (0..P104_MAX_SPEED_PAUSE_VALUE) + { + if ((value4 >= 0) && + (value4 <= P104_MAX_SPEED_PAUSE_VALUE)) + { + it->speed = value4; + success = true; + } + break; + } + + case p104_subcommands_e::anim_out: + // subcommand: anim.out,, (0..) + { + if (isAnimationAvailable(value4, true)) + { + it->animationOut = value4; + success = true; + } + break; + } + + case p104_subcommands_e::pause: + // subcommand: pause,, (0..P104_MAX_SPEED_PAUSE_VALUE) + { + if ((value4 >= 0) && + (value4 <= P104_MAX_SPEED_PAUSE_VALUE)) + { + it->pause = value4; + success = true; + } + break; + } + + case p104_subcommands_e::font: + // subcommand: font,, (only for incuded font id's) + { + if ( + (value4 == 0) + # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + || (value4 == P104_DOUBLE_HEIGHT_FONT_ID) + # endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + || (value4 == P104_FULL_DOUBLEHEIGHT_FONT_ID) + # endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + # ifdef P104_USE_VERTICAL_FONT + || (value4 == P104_VERTICAL_FONT_ID) + # endif // ifdef P104_USE_VERTICAL_FONT + # ifdef P104_USE_EXT_ASCII_FONT + || (value4 == P104_EXT_ASCII_FONT_ID) + # endif // ifdef P104_USE_EXT_ASCII_FONT + # ifdef P104_USE_ARABIC_FONT + || (value4 == P104_ARABIC_FONT_ID) + # endif // ifdef P104_USE_ARABIC_FONT + # ifdef P104_USE_GREEK_FONT + || (value4 == P104_GREEK_FONT_ID) + # endif // ifdef P104_USE_GREEK_FONT + # ifdef P104_USE_KATAKANA_FONT + || (value4 == P104_KATAKANA_FONT_ID) + # endif // ifdef P104_USE_KATAKANA_FONT + ) + { + reconfigure = (it->font != value4); + it->font = value4; + success = true; + } + break; + } + + case p104_subcommands_e::inverted: + // subcommand: inverted,, (disable/enable) + { + if ((value4 >= 0) && + (value4 <= 1)) + { + reconfigure = (it->inverted != value4); + it->inverted = value4; + success = true; + } + break; + } + + # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + + case p104_subcommands_e::layout: + // subcommand: layout,, (0..2), only when double-height font is available + { + if ((value4 >= 0) && + (value4 <= P104_LAYOUT_DOUBLE_LOWER)) + { + reconfigure = (it->layout != value4); + it->layout = value4; + success = true; + } + break; + } + # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + + case p104_subcommands_e::specialeffect: + // subcommand: specialeffect,, (0..3) + { + if ((value4 >= 0) && + (value4 <= P104_SPECIAL_EFFECT_BOTH)) + { + reconfigure = (it->specialEffect != value4); + it->specialEffect = value4; + success = true; + } + break; + } + + case p104_subcommands_e::offset: + // subcommand: offset,, (0..-1) + { + if ((value4 >= 0) && + (value4 < P104_MAX_MODULES_PER_ZONE) && + (value4 < it->size)) + { + reconfigure = (it->offset != value4); + it->offset = value4; + success = true; + } + break; + } + + case p104_subcommands_e::brightness: + // subcommand: brightness,, (0..15) + { + if ((value4 >= 0) && + (value4 <= P104_BRIGHTNESS_MAX)) + { + it->brightness = value4; + P->setIntensity(zoneIndex - 1, it->brightness); // Change brightness immediately + success = true; + } + break; + } + + case p104_subcommands_e::repeat: + // subcommand: repeat,, (-1..86400 = 24h) + { + if ((value4 >= -1) && + (value4 <= P104_MAX_REPEATDELAY_VALUE)) + { + it->repeatDelay = value4; + success = true; + + if (it->repeatDelay > -1) { + it->_repeatTimer = millis(); + } + } + break; + } + # endif // ifdef P104_USE_COMMANDS + + # ifdef P104_USE_BAR_GRAPH + + case p104_subcommands_e::bar: // subcommand: [set]bar,, (only allowed for + // zones + case p104_subcommands_e::setbar: // with Bargraph content) no length check, so longer than the + // UI allows is made possible + { + if (it->content == P104_CONTENT_BAR_GRAPH) { + if ((subcommands_e == p104_subcommands_e::setbar) && // subcommand: setbar,, (stores the + // graph-string + (string4.length() <= P104_MAX_TEXT_LENGTH_PER_ZONE)) { // in the settings, is not saved) + it->text = string4; // Only if not too long, could 'blow up' the settings when + // saved + } + displayBarGraph(zoneIndex - 1, *it, string4); + success = true; + } + break; + } + # endif // ifdef P104_USE_BAR_GRAPH + + # ifdef P104_USE_DOT_SET + + case p104_subcommands_e::dot: + // subcommand: dot,,,[,0][,,[,0]...] to draw + { + displayDots(zoneIndex - 1, *it, parseStringToEnd(string, 4)); // dots at row/column, add ,0 to turn a dot off + success = true; + break; + } + # endif // ifdef P104_USE_DOT_SET + } + + // FIXME TD-er: success is always false here. Maybe this must be done outside the for-loop? + if (success) { // Reset the repeat timer + if (it->repeatDelay > -1) { + it->_repeatTimer = millis(); + } + } + } + } + } + } + } + + # ifdef P104_USE_COMMANDS + + if (reconfigure) { + configureZones(); // Re-initialize + success = true; // Successful + } + # endif // ifdef P104_USE_COMMANDS + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + + if (log.reserve(34 + string.length())) { + log = F("dotmatrix: command "); + + if (!success) { log += F("NOT "); } + log += F("succesful: "); + log += string; + addLogMove(LOG_LEVEL_INFO, log); + } + } + + return success; // Default: unknown command +} + +int8_t P104_data_struct::getTime(char *psz, + bool seconds, + bool colon, + bool time12h, + bool timeAmpm) { + uint16_t h, M, s; + String ampm; + + # ifdef P104_USE_DATETIME_OPTIONS + + if (time12h) { + if (timeAmpm) { + ampm = (node_time.hour() >= 12 ? F("p") : F("a")); + } + h = node_time.hour() % 12; + + if (h == 0) { h = 12; } + } else + # endif // ifdef P104_USE_DATETIME_OPTIONS + { + h = node_time.hour(); + } + M = node_time.minute(); + + if (!seconds) { + sprintf_P(psz, PSTR("%02d%c%02d%s"), h, (colon ? ':' : ' '), M, ampm.c_str()); + } else { + s = node_time.second(); + sprintf_P(psz, PSTR("%02d%c%02d %02d%s"), h, (colon ? ':' : ' '), M, s, ampm.c_str()); + } + return M; +} + +void P104_data_struct::getDate(char *psz, + bool showYear, + bool fourDgt + # ifdef P104_USE_DATETIME_OPTIONS + , const uint8_t dateFmt + , const uint8_t dateSep + # endif // ifdef P104_USE_DATETIME_OPTIONS + ) { + uint16_t d, m, y; + const uint16_t year = node_time.year() - (fourDgt ? 0 : 2000); + + # ifdef P104_USE_DATETIME_OPTIONS + const String separators = F(" /-."); + const char sep = separators[dateSep]; + # else // ifdef P104_USE_DATETIME_OPTIONS + const char sep = ' '; + # endif // ifdef P104_USE_DATETIME_OPTIONS + + d = node_time.day(); + m = node_time.month(); + y = year; + # ifdef P104_USE_DATETIME_OPTIONS + + if (showYear) { + switch (dateFmt) { + case P104_DATE_FORMAT_US: + d = node_time.month(); + m = node_time.day(); + y = year; + break; + case P104_DATE_FORMAT_JP: + d = year; + m = node_time.month(); + y = node_time.day(); + break; + } + } else { + if ((dateFmt == P104_DATE_FORMAT_US) || + (dateFmt == P104_DATE_FORMAT_JP)) { + std::swap(d, m); + } + } + # endif // ifdef P104_USE_DATETIME_OPTIONS + + if (showYear) { + sprintf_P(psz, PSTR("%02d%c%02d%c%02d"), d, sep, m, sep, y); // %02d will expand to 04 when needed + } else { + sprintf_P(psz, PSTR("%02d%c%02d"), d, sep, m); + } +} + +uint8_t P104_data_struct::getDateTime(char *psz, + bool colon, + bool time12h, + bool timeAmpm, + bool fourDgt + # ifdef P104_USE_DATETIME_OPTIONS + , const uint8_t dateFmt + , const uint8_t dateSep + # endif // ifdef P104_USE_DATETIME_OPTIONS + ) { + String ampm; + uint16_t d, M, y; + uint8_t h, m; + const uint16_t year = node_time.year() - (fourDgt ? 0 : 2000); + + # ifdef P104_USE_DATETIME_OPTIONS + const String separators = F(" /-."); + const char sep = separators[dateSep]; + # else // ifdef P104_USE_DATETIME_OPTIONS + const char sep = ' '; + # endif // ifdef P104_USE_DATETIME_OPTIONS + + # ifdef P104_USE_DATETIME_OPTIONS + + if (time12h) { + if (timeAmpm) { + ampm = (node_time.hour() >= 12 ? F("p") : F("a")); + } + h = node_time.hour() % 12; + + if (h == 0) { h = 12; } + } else + # endif // ifdef P104_USE_DATETIME_OPTIONS + { + h = node_time.hour(); + } + M = node_time.minute(); + + # ifdef P104_USE_DATETIME_OPTIONS + + switch (dateFmt) { + case P104_DATE_FORMAT_US: + d = node_time.month(); + m = node_time.day(); + y = year; + break; + case P104_DATE_FORMAT_JP: + d = year; + m = node_time.month(); + y = node_time.day(); + break; + default: + # endif // ifdef P104_USE_DATETIME_OPTIONS + d = node_time.day(); + m = node_time.month(); + y = year; + # ifdef P104_USE_DATETIME_OPTIONS +} + + # endif // ifdef P104_USE_DATETIME_OPTIONS + sprintf_P(psz, PSTR("%02d%c%02d%c%02d %02d%c%02d%s"), d, sep, m, sep, y, h, (colon ? ':' : ' '), M, ampm.c_str()); // %02d will expand to + // 04 when needed + return M; +} + +# if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) +void P104_data_struct::createHString(String& string) { + const uint16_t stringLen = string.length(); + + for (uint16_t i = 0; i < stringLen; i++) { + string[i] |= 0x80; // use 'high' part of the font, by adding 0x80 + } +} + +# endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + +void P104_data_struct::reverseStr(String& str) { + const uint16_t n = str.length(); + + // Swap characters starting from two corners + for (uint16_t i = 0; i < n / 2; i++) { + std::swap(str[i], str[n - i - 1]); + } +} + +/************************************************************************ + * execute all PLUGIN_ONE_PER_SECOND tasks + ***********************************************************************/ +bool P104_data_struct::handlePluginOncePerSecond(struct EventStruct *event) { + if (nullptr == P) { return false; } + bool redisplay = false; + bool success = false; + + # ifdef P104_USE_DATETIME_OPTIONS + bool useFlasher = !bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FLASH); + bool time12h = bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_12H); + bool timeAmpm = bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_AMPM); + bool year4dgt = bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_YEAR4DGT); + # else // ifdef P104_USE_DATETIME_OPTIONS + bool useFlasher = true; + bool time12h = false; + bool timeAmpm = false; + bool year4dgt = false; + # endif // ifdef P104_USE_DATETIME_OPTIONS + bool newFlasher = !flasher && useFlasher; + + for (auto it = zones.begin(); it != zones.end(); ++it) { + redisplay = false; + + if (P->getZoneStatus(it->zone - 1)) { // Animations done? + switch (it->content) { + case P104_CONTENT_TIME: // time + case P104_CONTENT_TIME_SEC: // time sec + { + bool useSeconds = (it->content == P104_CONTENT_TIME_SEC); + int8_t m = getTime(szTimeL, useSeconds, flasher || !useFlasher, time12h, timeAmpm); + flasher = newFlasher; + redisplay = useFlasher || useSeconds || (it->_lastChecked != m); + it->_lastChecked = m; + break; + } + case P104_CONTENT_DATE4: // date/4 + case P104_CONTENT_DATE6: // date/6 + { + if (it->_lastChecked != node_time.day()) { + getDate(szTimeL, + it->content != P104_CONTENT_DATE4, + year4dgt + # ifdef P104_USE_DATETIME_OPTIONS + , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT) + , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR) + # endif // ifdef P104_USE_DATETIME_OPTIONS + ); + redisplay = true; + it->_lastChecked = node_time.day(); + } + break; + } + case P104_CONTENT_DATE_TIME: // date-time/9 + { + int8_t m = getDateTime(szTimeL, + flasher || !useFlasher, + time12h, + timeAmpm, + year4dgt + # ifdef P104_USE_DATETIME_OPTIONS + , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT) + , get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR) + # endif // ifdef P104_USE_DATETIME_OPTIONS + ); + flasher = newFlasher; + redisplay = useFlasher || (it->_lastChecked != m); + it->_lastChecked = m; + break; + } + default: + break; + } + + if (redisplay) { + displayOneZoneText(it->zone - 1, *it, String(szTimeL)); + P->displayReset(it->zone - 1); + + if (it->repeatDelay > -1) { + it->_repeatTimer = millis(); + } + } + } + delay(0); // Leave some breathingroom + } + + if (redisplay) { + // synchronise the start + P->synchZoneStart(); + } + return redisplay || success; +} + +/*************************************************** + * restart a zone if the repeat delay (if any) has passed + **************************************************/ +void P104_data_struct::checkRepeatTimer(uint8_t z) { + if (nullptr == P) { return; } + bool handled = false; + + for (auto it = zones.begin(); it != zones.end() && !handled; ++it) { + if (it->zone == z + 1) { + handled = true; + + if ((it->repeatDelay > -1) && (timePassedSince(it->_repeatTimer) >= (it->repeatDelay - 1) * 1000)) { // Compensated for the '1' in + // PLUGIN_ONE_PER_SECOND + # ifdef P104_DEBUG + + if (logAllText && loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(51); + log = F("dotmatrix: Repeat zone: "); + log += it->zone; + log += F(" delay: "); + log += it->repeatDelay; + log += F(" ("); + log += (timePassedSince(it->_repeatTimer) / 1000.0f); // Decimals can be useful here + log += ')'; + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifdef P104_DEBUG + + if ((it->content == P104_CONTENT_TEXT) || + (it->content == P104_CONTENT_TEXT_REV)) { + displayOneZoneText(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed text + P->displayReset(it->zone - 1); + } + + if ((it->content == P104_CONTENT_TIME) || + (it->content == P104_CONTENT_TIME_SEC) || + (it->content == P104_CONTENT_DATE4) || + (it->content == P104_CONTENT_DATE6) || + (it->content == P104_CONTENT_DATE_TIME)) { + it->_lastChecked = -1; // Invalidate so next run will re-display the date/time + } + # ifdef P104_USE_BAR_GRAPH + + if (it->content == P104_CONTENT_BAR_GRAPH) { + displayBarGraph(it->zone - 1, *it, sZoneInitial[it->zone - 1]); // Re-send last displayed bar graph + } + # endif // ifdef P104_USE_BAR_GRAPH + it->_repeatTimer = millis(); + } + } + delay(0); // Leave some breathingroom + } +} + +/*************************************** + * saveSettings gather the zones data from the UI and store in customsettings + **************************************/ +bool P104_data_struct::saveSettings() { + error = String(); // Clear + + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: saving zones, count: "), expectedZones)); + } + # endif // ifdef P104_DEBUG_DEV + + uint8_t index = 0; + uint8_t action = P104_ACTION_NONE; + uint8_t zoneIndex = 0; + int8_t zoneOffset = 0; + + zones.clear(); // Start afresh + + for (uint8_t zCounter = 0; zCounter < expectedZones; zCounter++) { + # ifdef P104_USE_ZONE_ACTIONS + action = getFormItemIntCustomArgName(index + P104_OFFSET_ACTION); + + if (((action == P104_ACTION_ADD_ABOVE) && (zoneOrder == 0)) || + ((action == P104_ACTION_ADD_BELOW) && (zoneOrder == 1))) { + zones.push_back(P104_zone_struct(0)); + zoneOffset++; + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: insert before zone: "), zoneIndex + 1)); + } + # endif // ifdef P104_DEBUG_DEV + } + # endif // ifdef P104_USE_ZONE_ACTIONS + zoneIndex = zCounter + zoneOffset; + + if (action == P104_ACTION_DELETE) { + zoneOffset--; + } else { + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: read zone: "), zoneIndex + 1)); + } + # endif // ifdef P104_DEBUG_DEV + zones.push_back(P104_zone_struct(zoneIndex + 1)); + + for (uint8_t i = 0; i < P104_OFFSET_COUNT; ++i) { + // for newly added zone, use defaults + const bool mustCheckSize = + (i == P104_OFFSET_BRIGHTNESS) || + (i == P104_OFFSET_REPEATDELAY); + if (!mustCheckSize || zones[zoneIndex].size != 0) { + if (i == P104_OFFSET_TEXT) { + zones[zoneIndex].text = wrapWithQuotes(webArg(getPluginCustomArgName(index + P104_OFFSET_TEXT))); + } else { + zones[zoneIndex].setIntValue(i, getFormItemIntCustomArgName(index + i)); + } + } + } + } + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: add zone: "), zoneIndex + 1)); + } + # endif // ifdef P104_DEBUG_DEV + + # ifdef P104_USE_ZONE_ACTIONS + + if (((action == P104_ACTION_ADD_BELOW) && (zoneOrder == 0)) || + ((action == P104_ACTION_ADD_ABOVE) && (zoneOrder == 1))) { + zones.push_back(P104_zone_struct(0)); + zoneOffset++; + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P104: insert after zone: "), zoneIndex + 2)); + } + # endif // ifdef P104_DEBUG_DEV + } + # endif // ifdef P104_USE_ZONE_ACTIONS + + index += P104_OFFSET_COUNT; + delay(0); + } + + uint16_t bufferSize; + int saveOffset = 0; + + numDevices = 0; // Count the number of connected display units + + bufferSize = P104_CONFIG_VERSION_V2; // Save special marker that we're using V2 settings + // This write is counting + error += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), saveOffset); + saveOffset += sizeof(bufferSize); + + String zbuffer; + + // 47 total + (max) 100 characters for it->text requires a buffer of ~150 (P104_SETTINGS_BUFFER_V2), but only the required length is + // stored with the length prefixed + if (zbuffer.reserve(P104_SETTINGS_BUFFER_V2 + 2)) { + for (auto it = zones.begin(); it != zones.end() && error.length() == 0; ++it) { + // WARNING: Order of values should match the numeric order of P104_OFFSET_* values + zbuffer.clear(); + for (uint8_t i = 0; i < P104_OFFSET_COUNT; ++i) { + if (i == P104_OFFSET_TEXT) { + zbuffer += it->text; + zbuffer += '\x01'; + } else { + int32_t value{}; + if (it->getIntValue(i, value)) { + zbuffer += value; + zbuffer += '\x01'; + } + } + } + + numDevices += (it->size != 0 ? it->size : 1) + it->offset; // Count corrected for newly added zones + + if (saveOffset + zbuffer.length() + (sizeof(bufferSize) * 2) > (DAT_TASKS_CUSTOM_SIZE)) { // Detect ourselves if we've reached the + error.reserve(55); // high-water mark + error += F("Total combination of Zones & text too long to store.\n"); + addLogMove(LOG_LEVEL_ERROR, error); + } else { + // Store length of buffer + bufferSize = zbuffer.length(); + + // As we write in parts, only count as single write. + if (RTC.flashDayCounter > 0) { + RTC.flashDayCounter--; + } + error += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, + taskIndex, + (uint8_t *)&bufferSize, + sizeof(bufferSize), + saveOffset); + saveOffset += sizeof(bufferSize); + + // As we write in parts, only count as single write. + if (RTC.flashDayCounter > 0) { + RTC.flashDayCounter--; + } + error += SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, + taskIndex, + (uint8_t *)zbuffer.c_str(), + bufferSize, + saveOffset); + saveOffset += bufferSize; + + # ifdef P104_DEBUG_DEV + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("P104: saveSettings zone: %d bufferSize: %d offset: %d"), + it->zone, bufferSize, saveOffset)); + zbuffer.replace(P104_FIELD_SEP, P104_FIELD_DISP); + addLog(LOG_LEVEL_INFO, zbuffer); + } + # endif // ifdef P104_DEBUG_DEV + } + + delay(0); + } + + // Store an End-of-settings marker == 0 + bufferSize = 0u; + + // This write is counting + SaveToFile(SettingsType::Enum::CustomTaskSettings_Type, taskIndex, (uint8_t *)&bufferSize, sizeof(bufferSize), saveOffset); + + if (numDevices > 255) { + error += strformat(F("More than 255 modules configured (%u)\n"), numDevices); + } + } else { + addLog(LOG_LEVEL_ERROR, F("DOTMATRIX: Can't allocate string for saving settings, insufficient memory!")); + return false; // Don't continue + } + + return error.isEmpty(); +} + +/************************************************************** +* webform_load +**************************************************************/ +bool P104_data_struct::webform_load(struct EventStruct *event) { + { // Hardware types + # define P104_hardwareTypeCount 8 + const __FlashStringHelper *hardwareTypes[P104_hardwareTypeCount] = { + F("Generic (DR:0, CR:1, RR:0)"), // 010 + F("Parola (DR:1, CR:1, RR:0)"), // 110 + F("FC16 (DR:1, CR:0, RR:0)"), // 100 + F("IC Station (DR:1, CR:1, RR:1)"), // 111 + F("Other 1 (DR:0, CR:0, RR:0)"), // 000 + F("Other 2 (DR:0, CR:0, RR:1)"), // 001 + F("Other 3 (DR:0, CR:1, RR:1)"), // 011 + F("Other 4 (DR:1, CR:0, RR:1)") // 101 + }; + constexpr int hardwareOptions[P104_hardwareTypeCount] = { + static_cast(MD_MAX72XX::moduleType_t::GENERIC_HW), + static_cast(MD_MAX72XX::moduleType_t::PAROLA_HW), + static_cast(MD_MAX72XX::moduleType_t::FC16_HW), + static_cast(MD_MAX72XX::moduleType_t::ICSTATION_HW), + static_cast(MD_MAX72XX::moduleType_t::DR0CR0RR0_HW), + static_cast(MD_MAX72XX::moduleType_t::DR0CR0RR1_HW), + static_cast(MD_MAX72XX::moduleType_t::DR0CR1RR1_HW), + static_cast(MD_MAX72XX::moduleType_t::DR1CR0RR1_HW) + }; + addFormSelector(F("Hardware type"), + F("hardware"), + P104_hardwareTypeCount, + hardwareTypes, + hardwareOptions, + P104_CONFIG_HARDWARETYPE); + # ifdef P104_ADD_SETTINGS_NOTES + addFormNote(F("DR = Digits as Rows, CR = Column Reversed, RR = Row Reversed; 0 = no, 1 = yes.")); + # endif // ifdef P104_ADD_SETTINGS_NOTES + } + + { + addFormCheckBox(F("Clear display on disable"), F("clrdsp"), + bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_CLEAR_DISABLE)); + + addFormCheckBox(F("Log all displayed text (info)"), + F("logtxt"), + bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_LOG_ALL_TEXT)); + } + + # ifdef P104_USE_DATETIME_OPTIONS + { + addFormSubHeader(F("Content options")); + + addFormCheckBox(F("Clock with flashing colon"), F("clkflash"), !bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FLASH)); + addFormCheckBox(F("Clock 12h display"), F("clk12h"), bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_12H)); + addFormCheckBox(F("Clock 12h AM/PM indicator"), F("clkampm"), bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_AMPM)); + } + { // Date format + const __FlashStringHelper *dateFormats[] = { + F("Day Month [Year]"), + F("Month Day [Year] (US-style)"), + F("[Year] Month Day (Japanese-style)") + }; + constexpr int dateFormatOptions[] = { + P104_DATE_FORMAT_EU, + P104_DATE_FORMAT_US, + P104_DATE_FORMAT_JP + }; + addFormSelector(F("Date format"), F("datefmt"), + 3, + dateFormats, dateFormatOptions, + get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_FORMAT)); + } + { // Date separator + const __FlashStringHelper *dateSeparators[] = { + F("Space"), + F("Slash /"), + F("Dash -"), + F("Dot .") + }; + constexpr int dateSeparatorOptions[] = { + P104_DATE_SEPARATOR_SPACE, + P104_DATE_SEPARATOR_SLASH, + P104_DATE_SEPARATOR_DASH, + P104_DATE_SEPARATOR_DOT + }; + addFormSelector(F("Date separator"), F("datesep"), + 4, + dateSeparators, dateSeparatorOptions, + get4BitFromUL(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_SEP_CHAR)); + + addFormCheckBox(F("Year uses 4 digits"), F("year4dgt"), bitRead(P104_CONFIG_DATETIME, P104_CONFIG_DATETIME_YEAR4DGT)); + } + # endif // ifdef P104_USE_DATETIME_OPTIONS + + addFormSubHeader(F("Zones")); + + { // Zones + String zonesList[P104_MAX_ZONES]; + int zonesOptions[P104_MAX_ZONES]; + + for (uint8_t i = 0; i < P104_MAX_ZONES; i++) { + zonesList[i] = i + 1; + zonesOptions[i] = i + 1; // No 0 needed or wanted + } + # if defined(P104_USE_TOOLTIPS) || defined(P104_ADD_SETTINGS_NOTES) + + const String zonetip = F("Select between 1 and " STRINGIFY(P104_MAX_ZONES) " zones, changing" + # ifdef P104_USE_ZONE_ORDERING + " Zones or Zone order" + # endif // ifdef P104_USE_ZONE_ORDERING + " will save and reload the page."); + # endif // if defined(P104_USE_TOOLTIPS) || defined(P104_ADD_SETTINGS_NOTES) + addFormSelector(F("Zones"), F("zonecnt"), P104_MAX_ZONES, zonesList, zonesOptions, nullptr, P104_CONFIG_ZONE_COUNT, true + # ifdef P104_USE_TOOLTIPS + , zonetip + # endif // ifdef P104_USE_TOOLTIPS + ); + + # ifdef P104_USE_ZONE_ORDERING + const String orderTypes[] = { + F("Numeric order (1..n)"), + F("Display order (n..1)") + }; + const int orderOptions[] = { 0, 1 }; + addFormSelector(F("Zone order"), F("zoneorder"), 2, orderTypes, orderOptions, nullptr, + bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER) ? 1 : 0, true + # ifdef P104_USE_TOOLTIPS + , zonetip + # endif // ifdef P104_USE_TOOLTIPS + ); + # endif // ifdef P104_USE_ZONE_ORDERING + # ifdef P104_ADD_SETTINGS_NOTES + addFormNote(zonetip); + # endif // ifdef P104_ADD_SETTINGS_NOTES + } + expectedZones = P104_CONFIG_ZONE_COUNT; + + if (expectedZones == 0) { expectedZones++; } // Minimum of 1 zone + + { // Optionlists and zones table + const __FlashStringHelper *alignmentTypes[3] = { + F("Left"), + F("Center"), + F("Right") + }; + const int alignmentOptions[3] = { + static_cast(textPosition_t::PA_LEFT), + static_cast(textPosition_t::PA_CENTER), + static_cast(textPosition_t::PA_RIGHT) + }; + + + // Append the numeric value as a reference for the 'anim.in' and 'anim.out' subcommands + const __FlashStringHelper *animationTypes[] { + F("None (0)") + , F("Print (1)") + , F("Scroll up (2)") + , F("Scroll down (3)") + , F("Scroll left * (4)") + , F("Scroll right * (5)") + # if ENA_SPRITE + , F("Sprite (6)") + # endif // ENA_SPRITE + # if ENA_MISC + , F("Slice * (7)") + , F("Mesh (8)") + , F("Fade (9)") + , F("Dissolve (10)") + , F("Blinds (11)") + , F("Random (12)") + # endif // ENA_MISC + # if ENA_WIPE + , F("Wipe (13)") + , F("Wipe w. cursor (14)") + # endif // ENA_WIPE + # if ENA_SCAN + , F("Scan horiz. (15)") + , F("Scan horiz. cursor (16)") + , F("Scan vert. (17)") + , F("Scan vert. cursor (18)") + # endif // ENA_SCAN + # if ENA_OPNCLS + , F("Opening (19)") + , F("Opening w. cursor (20)") + , F("Closing (21)") + , F("Closing w. cursor (22)") + # endif // ENA_OPNCLS + # if ENA_SCR_DIA + , F("Scroll up left * (23)") + , F("Scroll up right * (24)") + , F("Scroll down left * (25)") + , F("Scroll down right * (26)") + # endif // ENA_SCR_DIA + # if ENA_GROW + , F("Grow up (27)") + , F("Grow down (28)") + # endif // ENA_GROW + }; + + const int animationOptions[] = { + static_cast(textEffect_t::PA_NO_EFFECT) + , static_cast(textEffect_t::PA_PRINT) + , static_cast(textEffect_t::PA_SCROLL_UP) + , static_cast(textEffect_t::PA_SCROLL_DOWN) + , static_cast(textEffect_t::PA_SCROLL_LEFT) + , static_cast(textEffect_t::PA_SCROLL_RIGHT) + # if ENA_SPRITE + , static_cast(textEffect_t::PA_SPRITE) + # endif // ENA_SPRITE + # if ENA_MISC + , static_cast(textEffect_t::PA_SLICE) + , static_cast(textEffect_t::PA_MESH) + , static_cast(textEffect_t::PA_FADE) + , static_cast(textEffect_t::PA_DISSOLVE) + , static_cast(textEffect_t::PA_BLINDS) + , static_cast(textEffect_t::PA_RANDOM) + # endif // ENA_MISC + # if ENA_WIPE + , static_cast(textEffect_t::PA_WIPE) + , static_cast(textEffect_t::PA_WIPE_CURSOR) + # endif // ENA_WIPE + # if ENA_SCAN + , static_cast(textEffect_t::PA_SCAN_HORIZ) + , static_cast(textEffect_t::PA_SCAN_HORIZX) + , static_cast(textEffect_t::PA_SCAN_VERT) + , static_cast(textEffect_t::PA_SCAN_VERTX) + # endif // ENA_SCAN + # if ENA_OPNCLS + , static_cast(textEffect_t::PA_OPENING) + , static_cast(textEffect_t::PA_OPENING_CURSOR) + , static_cast(textEffect_t::PA_CLOSING) + , static_cast(textEffect_t::PA_CLOSING_CURSOR) + # endif // ENA_OPNCLS + # if ENA_SCR_DIA + , static_cast(textEffect_t::PA_SCROLL_UP_LEFT) + , static_cast(textEffect_t::PA_SCROLL_UP_RIGHT) + , static_cast(textEffect_t::PA_SCROLL_DOWN_LEFT) + , static_cast(textEffect_t::PA_SCROLL_DOWN_RIGHT) + # endif // ENA_SCR_DIA + # if ENA_GROW + , static_cast(textEffect_t::PA_GROW_UP) + , static_cast(textEffect_t::PA_GROW_DOWN) + # endif // ENA_GROW + }; + + constexpr int animationCount = NR_ELEMENTS(animationOptions); + + delay(0); + + const __FlashStringHelper *fontTypes[] = { + F("Default (0)") + # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + , F("Numeric, double height (1)") + # endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + , F("Full, double height (2)") + # endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + # ifdef P104_USE_VERTICAL_FONT + , F("Vertical (3)") + # endif // ifdef P104_USE_VERTICAL_FONT + # ifdef P104_USE_EXT_ASCII_FONT + , F("Extended ASCII (4)") + # endif // ifdef P104_USE_EXT_ASCII_FONT + # ifdef P104_USE_ARABIC_FONT + , F("Arabic (5)") + # endif // ifdef P104_USE_ARABIC_FONT + # ifdef P104_USE_GREEK_FONT + , F("Greek (6)") + # endif // ifdef P104_USE_GREEK_FONT + # ifdef P104_USE_KATAKANA_FONT + , F("Katakana (7)") + # endif // ifdef P104_USE_KATAKANA_FONT + }; + const int fontOptions[] = { + P104_DEFAULT_FONT_ID + # ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + , P104_DOUBLE_HEIGHT_FONT_ID + # endif // ifdef P104_USE_NUMERIC_DOUBLEHEIGHT_FONT + # ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + , P104_FULL_DOUBLEHEIGHT_FONT_ID + # endif // ifdef P104_USE_FULL_DOUBLEHEIGHT_FONT + # ifdef P104_USE_VERTICAL_FONT + , P104_VERTICAL_FONT_ID + # endif // ifdef P104_USE_VERTICAL_FONT + # ifdef P104_USE_EXT_ASCII_FONT + , P104_EXT_ASCII_FONT_ID + # endif // ifdef P104_USE_EXT_ASCII_FONT + # ifdef P104_USE_ARABIC_FONT + , P104_ARABIC_FONT_ID + # endif // ifdef P104_USE_ARABIC_FONT + # ifdef P104_USE_GREEK_FONT + , P104_GREEK_FONT_ID + # endif // ifdef P104_USE_GREEK_FONT + # ifdef P104_USE_KATAKANA_FONT + , P104_KATAKANA_FONT_ID + # endif // ifdef P104_USE_KATAKANA_FONT + }; + + const __FlashStringHelper *layoutTypes[] = { + F("Standard") + # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + , F("Double, upper") + , F("Double, lower") + # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + }; + const int layoutOptions[] = { + P104_LAYOUT_STANDARD + # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + , P104_LAYOUT_DOUBLE_UPPER + , P104_LAYOUT_DOUBLE_LOWER + # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) || defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + }; + + const __FlashStringHelper *specialEffectTypes[] = { + F("None"), + F("Flip up/down"), + F("Flip left/right *"), + F("Flip u/d & l/r *") + }; + const int specialEffectOptions[] = { + P104_SPECIAL_EFFECT_NONE, + P104_SPECIAL_EFFECT_UP_DOWN, + P104_SPECIAL_EFFECT_LEFT_RIGHT, + P104_SPECIAL_EFFECT_BOTH + }; + + const __FlashStringHelper *contentTypes[] = { + F("Text"), + F("Text reverse"), + F("Clock (4 mod)"), + F("Clock sec (6 mod)"), + F("Date (4 mod)"), + F("Date yr (6/7 mod)"), + F("Date/time (9/13 mod)"), + # ifdef P104_USE_BAR_GRAPH + F("Bar graph"), + # endif // ifdef P104_USE_BAR_GRAPH + }; + const int contentOptions[] { + P104_CONTENT_TEXT, + P104_CONTENT_TEXT_REV, + P104_CONTENT_TIME, + P104_CONTENT_TIME_SEC, + P104_CONTENT_DATE4, + P104_CONTENT_DATE6, + P104_CONTENT_DATE_TIME, + # ifdef P104_USE_BAR_GRAPH + P104_CONTENT_BAR_GRAPH, + # endif // ifdef P104_USE_BAR_GRAPH + }; + const __FlashStringHelper *invertedTypes[3] = { + F("Normal"), + F("Inverted") + }; + const int invertedOptions[] = { + 0, + 1 + }; + # ifdef P104_USE_ZONE_ACTIONS + uint8_t actionCount = 0; + const __FlashStringHelper *actionTypes[4]; + int actionOptions[4]; + actionTypes[actionCount] = F("None"); + actionOptions[actionCount] = P104_ACTION_NONE; + actionCount++; + + if (zones.size() < P104_MAX_ZONES) { + actionTypes[actionCount] = F("New above"); + actionOptions[actionCount] = P104_ACTION_ADD_ABOVE; + actionCount++; + actionTypes[actionCount] = F("New below"); + actionOptions[actionCount] = P104_ACTION_ADD_BELOW; + actionCount++; + } + actionTypes[actionCount] = F("Delete"); + actionOptions[actionCount] = P104_ACTION_DELETE; + actionCount++; + # endif // ifdef P104_USE_ZONE_ACTIONS + + delay(0); + + addFormSubHeader(F("Zone configuration")); + + { + html_table(EMPTY_STRING); // Sub-table + + const __FlashStringHelper *headers[] = { + F("Zone # "), + F("Modules"), + F("Text"), + F("Content"), + F("Alignment"), + F("Animation In/Out"), // 1st and 2nd row title + F("Speed/Pause"), // 1st and 2nd row title + F("Font/Layout"), // 1st and 2nd row title + F("Inverted/ Special Effects"), // 1st and 2nd row title + F("Offset"), + F("Brightness"), + F("Repeat (sec)") + }; + + constexpr unsigned nrHeaders = NR_ELEMENTS(headers); + for (unsigned i = 0; i < nrHeaders; ++i) { + int width = 0; + if (i == 2) { + // "Text" needs a width + width = 180; + } + html_table_header(headers[i], width); + } + # ifdef P104_USE_ZONE_ACTIONS + html_table_header(F(""), 15); // Spacer + html_table_header(F("Action"), 45); + # endif // ifdef P104_USE_ZONE_ACTIONS + } + + uint16_t index; + int16_t startZone, endZone; + int8_t incrZone = 1; + # ifdef P104_USE_ZONE_ACTIONS + uint8_t currentRow = 0; + # endif // ifdef P104_USE_ZONE_ACTIONS + + # ifdef P104_USE_ZONE_ORDERING + + if (bitRead(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER)) { + startZone = zones.size() - 1; + endZone = -1; + incrZone = -1; + } else + # endif // ifdef P104_USE_ZONE_ORDERING + { + startZone = 0; + endZone = zones.size(); + } + + for (int8_t zone = startZone; zone != endZone; zone += incrZone) { + if (zones[zone].zone <= expectedZones) { + index = (zones[zone].zone - 1) * P104_OFFSET_COUNT; + + html_TR_TD(); // All columns use max. width available + addHtml(F(" ")); + addHtmlInt(zones[zone].zone); + + html_TD(); // Modules + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_SIZE), zones[zone].size, 1, P104_MAX_MODULES_PER_ZONE); + + html_TD(); // Text + addTextBox(getPluginCustomArgName(index + P104_OFFSET_TEXT), + zones[zone].text, + P104_MAX_TEXT_LENGTH_PER_ZONE, + false, + false, + EMPTY_STRING, + F("")); + + html_TD(); // Content + addSelector(getPluginCustomArgName(index + P104_OFFSET_CONTENT), + P104_CONTENT_count, + contentTypes, + contentOptions, + nullptr, + zones[zone].content, + false, + true, + F("")); + + html_TD(); // Alignment + addSelector(getPluginCustomArgName(index + P104_OFFSET_ALIGNMENT), + 3, + alignmentTypes, + alignmentOptions, + nullptr, + zones[zone].alignment, + false, + true, + F("")); + + { + html_TD(); // Animation In (without None by passing the second element index) + addSelector(getPluginCustomArgName(index + P104_OFFSET_ANIM_IN), + animationCount - 1, + &animationTypes[1], + &animationOptions[1], + nullptr, + zones[zone].animationIn, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Animation In") + # endif // ifdef P104_USE_TOOLTIPS + ); + } + + html_TD(); // Speed In + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_SPEED), zones[zone].speed, 0, P104_MAX_SPEED_PAUSE_VALUE + # ifdef P104_USE_TOOLTIPS + , F("") // classname + , F("Speed") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(); // Font + addSelector(getPluginCustomArgName(index + P104_OFFSET_FONT), + NR_ELEMENTS(fontOptions), + fontTypes, + fontOptions, + nullptr, + zones[zone].font, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Font") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(); // Inverted + addSelector(getPluginCustomArgName(index + P104_OFFSET_INVERTED), + NR_ELEMENTS(invertedOptions), + invertedTypes, + invertedOptions, + nullptr, + zones[zone].inverted, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Inverted") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(3); // Fill columns + # ifdef P104_USE_ZONE_ACTIONS + + html_TD(); // Spacer + addHtml('|'); + + if (currentRow < 2) { + addHtml(F("")); // Action column, text centered and font-size 90% + } else { + html_TD(); + } + + if (currentRow == 0) { + addHtml(F("(applied immediately!)")); + } else if (currentRow == 1) { + addHtml(F("(Delete can't be undone!)")); + } + currentRow++; + # endif // ifdef P104_USE_ZONE_ACTIONS + + // Split here + html_TR_TD(); // Start new row + html_TD(4); // Start with some blank columns + + { + html_TD(); // Animation Out + addSelector(getPluginCustomArgName(index + P104_OFFSET_ANIM_OUT), + animationCount, + animationTypes, + animationOptions, + nullptr, + zones[zone].animationOut, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Animation Out") + # endif // ifdef P104_USE_TOOLTIPS + ); + } + + html_TD(); // Pause after Animation In + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_PAUSE), zones[zone].pause, 0, P104_MAX_SPEED_PAUSE_VALUE + # ifdef P104_USE_TOOLTIPS + , F("") // classname + , F("Pause") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(); // Layout + addSelector(getPluginCustomArgName(index + P104_OFFSET_LAYOUT), + NR_ELEMENTS(layoutOptions), + layoutTypes, + layoutOptions, + nullptr, + zones[zone].layout, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Layout") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(); // Special effects + addSelector(getPluginCustomArgName(index + P104_OFFSET_SPEC_EFFECT), + NR_ELEMENTS(specialEffectOptions), + specialEffectTypes, + specialEffectOptions, + nullptr, + zones[zone].specialEffect, + false, + true, + F("") + # ifdef P104_USE_TOOLTIPS + , F("Special Effects") // title + # endif // ifdef P104_USE_TOOLTIPS + ); + + html_TD(); // Offset + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_OFFSET), zones[zone].offset, 0, 254); + + html_TD(); // Brightness + + if (zones[zone].brightness == -1) { zones[zone].brightness = P104_BRIGHTNESS_DEFAULT; } + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_BRIGHTNESS), zones[zone].brightness, 0, P104_BRIGHTNESS_MAX); + + html_TD(); // Repeat (sec) + addNumericBox(getPluginCustomArgName(index + P104_OFFSET_REPEATDELAY), + zones[zone].repeatDelay, + -1, + P104_MAX_REPEATDELAY_VALUE // max delay 86400 sec. = 24 hours + # ifdef P104_USE_TOOLTIPS + , F("") // classname + , F("Repeat after this delay (sec), -1 = off") // tooltip + # endif // ifdef P104_USE_TOOLTIPS + ); + + # ifdef P104_USE_ZONE_ACTIONS + html_TD(); // Spacer + addHtml('|'); + + html_TD(); // Action + addSelector(getPluginCustomArgName(index + P104_OFFSET_ACTION), + actionCount, + actionTypes, + actionOptions, + nullptr, + P104_ACTION_NONE, // Always start with None + true, + true, + F("")); + # endif // ifdef P104_USE_ZONE_ACTIONS + + delay(0); + } + } + html_end_table(); + } + + # ifdef P104_ADD_SETTINGS_NOTES + addFormNote(concat(F("- Maximum nr. of modules possible (Zones * Size + Offset) = 255. Last saved: "), numDevices)); + addFormNote(F("- 'Animation In' or 'Animation Out' and 'Special Effects' marked with * should not be combined in a Zone.")); + # if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) && !defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + addFormNote(F("- 'Layout' 'Double upper' and 'Double lower' are only supported for numeric 'Content' types like 'Clock' and 'Date'.")); + # endif // if defined(P104_USE_NUMERIC_DOUBLEHEIGHT_FONT) && !defined(P104_USE_FULL_DOUBLEHEIGHT_FONT) + # endif // ifdef P104_ADD_SETTINGS_NOTES + + return true; +} + +/************************************************************** +* webform_save +**************************************************************/ +bool P104_data_struct::webform_save(struct EventStruct *event) { + P104_CONFIG_ZONE_COUNT = getFormItemInt(F("zonecnt")); + P104_CONFIG_HARDWARETYPE = getFormItemInt(F("hardware")); + + bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_CLEAR_DISABLE, isFormItemChecked(F("clrdsp"))); + bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_LOG_ALL_TEXT, isFormItemChecked(F("logtxt"))); + + # ifdef P104_USE_ZONE_ORDERING + zoneOrder = getFormItemInt(F("zoneorder")); // Is used in saveSettings() + bitWrite(P104_CONFIG_FLAGS, P104_CONFIG_FLAG_ZONE_ORDER, zoneOrder == 1); + # endif // ifdef P104_USE_ZONE_ORDERING + + # ifdef P104_USE_DATETIME_OPTIONS + uint32_t ulDateTime = 0; + bitWrite(ulDateTime, P104_CONFIG_DATETIME_FLASH, !isFormItemChecked(F("clkflash"))); // Inverted flag + bitWrite(ulDateTime, P104_CONFIG_DATETIME_12H, isFormItemChecked(F("clk12h"))); + bitWrite(ulDateTime, P104_CONFIG_DATETIME_AMPM, isFormItemChecked(F("clkampm"))); + bitWrite(ulDateTime, P104_CONFIG_DATETIME_YEAR4DGT, isFormItemChecked(F("year4dgt"))); + set4BitToUL(ulDateTime, P104_CONFIG_DATETIME_FORMAT, getFormItemInt(F("datefmt"))); + set4BitToUL(ulDateTime, P104_CONFIG_DATETIME_SEP_CHAR, getFormItemInt(F("datesep"))); + P104_CONFIG_DATETIME = ulDateTime; + # endif // ifdef P104_USE_DATETIME_OPTIONS + + previousZones = expectedZones; + expectedZones = P104_CONFIG_ZONE_COUNT; + + bool result = saveSettings(); // Determines numDevices and re-fills zones list + + P104_CONFIG_ZONE_COUNT = zones.size(); + P104_CONFIG_TOTAL_UNITS = numDevices; // Store counted number of devices + + zones.clear(); // Free some memory (temporarily) + + return result; +} + + + + +P104_zone_struct::P104_zone_struct(uint8_t _zone) + : text(F("\"\"")), zone(_zone) {} + + +bool P104_zone_struct::getIntValue(uint8_t offset, int32_t& value) const +{ + switch (offset) { + case P104_OFFSET_SIZE: value = size; break; + case P104_OFFSET_TEXT: return false; + case P104_OFFSET_CONTENT: value = content; break; + case P104_OFFSET_ALIGNMENT: value = alignment; break; + case P104_OFFSET_ANIM_IN: value = animationIn; break; + case P104_OFFSET_SPEED: value = speed; break; + case P104_OFFSET_ANIM_OUT: value = animationOut; break; + case P104_OFFSET_PAUSE: value = pause; break; + case P104_OFFSET_FONT: value = font; break; + case P104_OFFSET_LAYOUT: value = layout; break; + case P104_OFFSET_SPEC_EFFECT: value = specialEffect; break; + case P104_OFFSET_OFFSET: value = offset; break; + case P104_OFFSET_BRIGHTNESS: value = brightness; break; + case P104_OFFSET_REPEATDELAY: value = repeatDelay; break; + case P104_OFFSET_INVERTED: value = inverted; break; + + default: + return false; + } + return true; +} + +bool P104_zone_struct::setIntValue(uint8_t offset, int32_t value) +{ + switch (offset) { + case P104_OFFSET_SIZE: size = value; break; + case P104_OFFSET_TEXT: return false; + case P104_OFFSET_CONTENT: content = value; break; + case P104_OFFSET_ALIGNMENT: alignment = value; break; + case P104_OFFSET_ANIM_IN: animationIn = value; break; + case P104_OFFSET_SPEED: speed = value; break; + case P104_OFFSET_ANIM_OUT: animationOut = value; break; + case P104_OFFSET_PAUSE: pause = value; break; + case P104_OFFSET_FONT: font = value; break; + case P104_OFFSET_LAYOUT: layout = value; break; + case P104_OFFSET_SPEC_EFFECT: specialEffect = value; break; + case P104_OFFSET_OFFSET: offset = value; break; + case P104_OFFSET_BRIGHTNESS: brightness = value; break; + case P104_OFFSET_REPEATDELAY: repeatDelay = value; break; + case P104_OFFSET_INVERTED: inverted = value; break; + + default: + return false; + } + return true; +} + + +#endif // ifdef USES_P104 diff --git a/src/src/PluginStructs/P104_data_struct.h b/src/src/PluginStructs/P104_data_struct.h index b36ea6a58..78c7b9a3e 100644 --- a/src/src/PluginStructs/P104_data_struct.h +++ b/src/src/PluginStructs/P104_data_struct.h @@ -306,7 +306,7 @@ struct P104_zone_struct { P104_zone_struct() = delete; // Not used, so leave out explicitly - P104_zone_struct(uint8_t _zone) : text(F("\"\"")), zone(_zone) {} + P104_zone_struct(uint8_t _zone); String text; int32_t repeatDelay = -1; @@ -331,6 +331,10 @@ struct P104_zone_struct { uint16_t _upper = 0u; // lower and upper pixel numbers uint8_t _startModule = 0u; // starting module, end module is _startModule + size - 1 # endif // if defined(P104_USE_BAR_GRAPH) || defined(P104_USE_DOT_SET) + + // Used to loop over member values + bool getIntValue(uint8_t offset, int32_t& value) const; + bool setIntValue(uint8_t offset, int32_t value); }; # ifdef P104_USE_BAR_GRAPH diff --git a/src/src/PluginStructs/P105_data_struct.cpp b/src/src/PluginStructs/P105_data_struct.cpp index 0ab601bba..04f653108 100644 --- a/src/src/PluginStructs/P105_data_struct.cpp +++ b/src/src/PluginStructs/P105_data_struct.cpp @@ -111,18 +111,11 @@ bool P105_data_struct::updateMeasurements(taskIndex_t task_index) { const unsigned long current_time = millis(); if (!initialized()) { - String log; - log.reserve(30); - if (!device.initialize()) { - log += getDeviceName(); - log += F(" : unable to initialize"); - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s : unable to initialize"), getDeviceName().c_str())); return false; } - log = getDeviceName(); - log += F(" : initialized"); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("%s : initialized"), getDeviceName().c_str())); trigger_time = current_time; state = AHTx_state::AHTx_Trigger_measurement; @@ -158,30 +151,22 @@ bool P105_data_struct::updateMeasurements(taskIndex_t task_index) { last_measurement = current_time; state = AHTx_state::AHTx_New_values; - #ifndef BUILD_NO_DEBUG + # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { // Log raw measuerd values only on level DEBUG - String log; - log.reserve(50); // Prevent re-allocation - log += getDeviceName(); - log += F(" : humidity "); - log += device.getHumidity(); - log += F("% temperature "); - log += device.getTemperature(); - log += 'C'; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("%s : humidity %.2f%% temperature %.2fC"), + getDeviceName().c_str(), + device.getHumidity(), + device.getTemperature())); } - #endif + # endif // ifndef BUILD_NO_DEBUG return true; } if (timePassedSince(trigger_time) > 1000) { // should not happen - String log; - log.reserve(15); // Prevent re-allocation - log += getDeviceName(); - log += F(" : reset"); - addLogMove(LOG_LEVEL_ERROR, log); + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s : reset"), getDeviceName().c_str())); device.softReset(); state = AHTx_state::AHTx_Uninitialized; diff --git a/src/src/PluginStructs/P109_data_struct.cpp b/src/src/PluginStructs/P109_data_struct.cpp index ba069e7b9..b43220bad 100644 --- a/src/src/PluginStructs/P109_data_struct.cpp +++ b/src/src/PluginStructs/P109_data_struct.cpp @@ -34,7 +34,7 @@ bool P109_data_struct::plugin_webform_load(struct EventStruct *event) { LoadCustomTaskSettings(event->TaskIndex, reinterpret_cast(&_deviceTemplate), sizeof(_deviceTemplate)); - for (int varNr = 0; varNr < P109_Nlines; varNr++) { + for (int varNr = 0; varNr < P109_Nlines; ++varNr) { addFormTextBox(concat(varNr == 0 ? F("Temperature source ") : F("Line "), varNr + 1), getPluginCustomArgName(varNr + 1), _deviceTemplate[varNr], @@ -50,7 +50,7 @@ bool P109_data_struct::plugin_webform_load(struct EventStruct *event) { bool P109_data_struct::plugin_webform_save(struct EventStruct *event) { bool success = false; - for (uint8_t varNr = 0; varNr < P109_Nlines; varNr++) { + for (uint8_t varNr = 0; varNr < P109_Nlines; ++varNr) { strncpy(_deviceTemplate[varNr], web_server.arg(getPluginCustomArgName(varNr + 1)).c_str(), sizeof(_deviceTemplate[varNr]) - 1); @@ -112,15 +112,13 @@ bool P109_data_struct::plugin_init(struct EventStruct *event) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log += concat(F("Thermo : Btn L:"), static_cast(CONFIG_PIN1)); - log += concat(F(", R:"), static_cast(CONFIG_PIN2)); - log += concat(F(", M:"), static_cast(CONFIG_PIN3)); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("Thermo : Btn L:%d, R:%d, M:%d"), + CONFIG_PIN1, CONFIG_PIN2, CONFIG_PIN3)); } # endif // ifndef BUILD_NO_DEBUG - for (uint8_t pin = 0; pin < 3; pin++) { + for (uint8_t pin = 0; pin < 3; ++pin) { if (validGpio(PIN(pin))) { pinMode(PIN(pin), INPUT_PULLUP); } @@ -129,11 +127,11 @@ bool P109_data_struct::plugin_init(struct EventStruct *event) { _prev_temp = P109_TEMP_STATE_UNSET; String fileName = strformat( - F("thermo%d.dat"), - static_cast(_taskIndex + 1)); // Settings per task index + F("thermo%d.dat"), + _taskIndex + 1); // Settings per task index fs::File f = tryOpenFile(fileName, String('r')); - if (!f) { // Not found? Then open previous default filename + if (!f) { // Not found? Then open previous default filename fileName = F("thermo.dat"); f = tryOpenFile(fileName, String('r')); } @@ -148,17 +146,17 @@ bool P109_data_struct::plugin_init(struct EventStruct *event) { if (UserVar[event->BaseVarIndex] < 1) { UserVar.setFloat(event->TaskIndex, 0, P109_SETPOINT_STATE_INITIAL); // setpoint } - UserVar.setFloat(event->TaskIndex, 1, 0.5f); // Unitialize relay state - UserVar.setFloat(event->TaskIndex, 2, P109_MODE_STATE_INITIAL); // mode (X=0,A=1,M=2) - UserVar.setFloat(event->TaskIndex, 3, 0); // Reset + UserVar.setFloat(event->TaskIndex, 1, 0.5f); // Unitialize relay state + UserVar.setFloat(event->TaskIndex, 2, P109_MODE_STATE_INITIAL); // mode (X=0,A=1,M=2) + UserVar.setFloat(event->TaskIndex, 3, 0); // Reset # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { addLogMove(LOG_LEVEL_INFO, strformat( - F("Thermo : Starting status S:%s, R:%d"), - formatUserVarNoCheck(event, 0).c_str(), - static_cast(UserVar[event->BaseVarIndex + 1]))); + F("Thermo : Starting status S:%s, R:%d"), + formatUserVarNoCheck(event, 0).c_str(), + static_cast(UserVar[event->BaseVarIndex + 1]))); } # endif // ifndef BUILD_NO_DEBUG @@ -288,7 +286,7 @@ bool P109_data_struct::plugin_once_a_second(struct EventStruct *event) { *************************************************************************/ void P109_data_struct::saveThermoSettings(struct EventStruct *event) { const String fileName(strformat(F("thermo%d.dat"), static_cast(event->TaskIndex + 1))); - fs::File f = tryOpenFile(fileName, F("w")); + fs::File f = tryOpenFile(fileName, F("w")); if (f) { f.write(reinterpret_cast(UserVar.getRawTaskValues_Data(event->TaskIndex)), 16); @@ -297,7 +295,7 @@ void P109_data_struct::saveThermoSettings(struct EventStruct *event) { } # ifndef BUILD_NO_DEBUG addLogMove(LOG_LEVEL_INFO, strformat( - F("Thermo : (delayed) Save UserVars to %s"), fileName.c_str())); + F("Thermo : (delayed) Save UserVars to %s"), fileName.c_str())); # endif // ifndef BUILD_NO_DEBUG } @@ -559,10 +557,10 @@ bool P109_data_struct::display_wifibars() { _display->setColor(WHITE); if (WiFiEventData.WiFiServicesInitialized()) { - for (uint8_t ibar = 0; ibar < nbars; ibar++) { - int16_t height = size_y * (ibar + 1) / nbars; - int16_t xpos = x + ibar * width; - int16_t ypos = y + size_y - height; + for (uint8_t ibar = 0; ibar < nbars; ++ibar) { + const int16_t height = size_y * (ibar + 1) / nbars; + const int16_t xpos = x + ibar * width; + const int16_t ypos = y + size_y - height; if (ibar <= nbars_filled) { // Fill complete bar @@ -606,8 +604,8 @@ void P109_data_struct::display_current_temp() { */ void P109_data_struct::display_setpoint_temp(const uint8_t& force) { if (UserVar.getFloat(_taskIndex, 2) == 1) { - float stemp = (roundf(UserVar[_varIndex] * 10.0f)) / 10.0f; - bool isDif = !essentiallyEqual(_prev_setpoint, stemp); + const float stemp = (roundf(UserVar[_varIndex] * 10.0f)) / 10.0f; + const bool isDif = !essentiallyEqual(_prev_setpoint, stemp); if (isDif || (force == 1)) { String tmpString = toString(stemp, 1); @@ -629,7 +627,7 @@ void P109_data_struct::display_setpoint_temp(const uint8_t& force) { void P109_data_struct::display_timeout() { if (UserVar.getFloat(_taskIndex, 2) == 2) { if (_prev_timeout >= (UserVar.getFloat(_taskIndex, 3) + 60.0f)) { - String thour = minutesToHourColonMinute(static_cast(UserVar.getFloat(_taskIndex, 3) / 60.0f)); + const String thour = minutesToHourColonMinute(static_cast(UserVar.getFloat(_taskIndex, 3) / 60.0f)); displayBigText(86, 35, 41, 21, getDialog_plain_18(), 89, 35, thour.substring(1, 5)); _prev_timeout = UserVar.getFloat(_taskIndex, 3); @@ -642,8 +640,8 @@ void P109_data_struct::display_timeout() { */ void P109_data_struct::display_mode() { if (_prev_mode != UserVar.getFloat(_taskIndex, 2)) { - String tmpString = F("XAM"); - uint16_t xamIdx = min(static_cast(UserVar.getFloat(_taskIndex, 2)), 2); + const String tmpString = F("XAM"); + const uint16_t xamIdx = min(static_cast(UserVar.getFloat(_taskIndex, 2)), 2); displayBigText(61, 49, 12, 17, getArialMT_Plain_16(), 61, 49, tmpString.substring(xamIdx, xamIdx + 1)); @@ -721,7 +719,7 @@ void P109_data_struct::setSetpoint(const String& sptemp) { } else { stemp = sptemp.toFloat(); } - UserVar.setFloat(_taskIndex, 0, stemp); + UserVar.setFloat(_taskIndex, 0, stemp); display_setpoint_temp(); } @@ -733,12 +731,10 @@ void P109_data_struct::setHeatRelay(const uint8_t& state) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - log += concat(F("Thermo : Set Relay"), static_cast(_relaypin)); - log += '='; - log += _relayInverted ? !state : state; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("Thermo : Set Relay%d=%d"), + _relaypin, + _relayInverted ? !state : state)); } # endif // ifndef BUILD_NO_DEBUG @@ -754,10 +750,10 @@ void P109_data_struct::setHeater(const String& heater) { if (_setpointDelay == 0) { if ((heater.charAt(0) == '1') || (equals(heater, F("on"))) || ((heater.length() == 0) && (UserVar.getFloat(_taskIndex, 1) == 0))) { - UserVar.setFloat(_taskIndex, 1, 1); + UserVar.setFloat(_taskIndex, 1, 1); setHeatRelay(HIGH); } else { - UserVar.setFloat(_taskIndex, 1, 0); + UserVar.setFloat(_taskIndex, 1, 0); setHeatRelay(LOW); } display_heat(); @@ -770,25 +766,25 @@ void P109_data_struct::setHeater(const String& heater) { */ void P109_data_struct::setMode(const String& amode, const String& atimeout) { - UserVar.setFloat(_taskIndex, 3, 0.0f); // Reset timeout + UserVar.setFloat(_taskIndex, 3, 0.0f); // Reset timeout if ((amode[0] == '0') || (amode[0] == 'x')) { - UserVar.setFloat(_taskIndex, 2, 0); + UserVar.setFloat(_taskIndex, 2, 0); setHeater(F("0")); _display->setColor(BLACK); _display->fillRect(86, 35, 41, 21); _prev_setpoint = P109_SETPOINT_STATE_UNSET; } else if ((amode[0] == '1') || (amode[0] == 'a')) { - UserVar.setFloat(_taskIndex, 2, 1); + UserVar.setFloat(_taskIndex, 2, 1); display_setpoint_temp(1); } else if ((amode[0] == '2') || (amode[0] == 'm')) { - UserVar.setFloat(_taskIndex, 2, 2); - UserVar.setFloat(_taskIndex, 3, (atimeout.toFloat() * 60.0f)); - _prev_timeout = P109_TIMEOUT_STATE_UNSET; + UserVar.setFloat(_taskIndex, 2, 2); + UserVar.setFloat(_taskIndex, 3, (atimeout.toFloat() * 60.0f)); + _prev_timeout = P109_TIMEOUT_STATE_UNSET; display_timeout(); setHeater(F("1")); } else { - UserVar.setFloat(_taskIndex, 2, 0); + UserVar.setFloat(_taskIndex, 2, 0); } // _changed = 1; diff --git a/src/src/PluginStructs/P110_data_struct.cpp b/src/src/PluginStructs/P110_data_struct.cpp index ee3fcdf82..7b908aeec 100644 --- a/src/src/PluginStructs/P110_data_struct.cpp +++ b/src/src/PluginStructs/P110_data_struct.cpp @@ -1,118 +1,182 @@ -#include "../PluginStructs/P110_data_struct.h" - -#ifdef USES_P110 - -P110_data_struct::P110_data_struct(uint8_t i2c_addr, int timing, bool range) : i2cAddress(i2c_addr), timing(timing), range(range) {} - -// **************************************************************************/ -// Initialize VL53L0X -// **************************************************************************/ -bool P110_data_struct::begin() { - initState = true; - - sensor.setAddress(i2cAddress); // Initialize for configured address - - if (!sensor.init()) { - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("VL53L0X: Sensor not found, init failed for 0x"); - log += String(i2cAddress, HEX); - addLogMove(LOG_LEVEL_INFO, log); - addLog(LOG_LEVEL_INFO, sensor.getInitResult()); - } - initState = false; - return initState; - } - - sensor.setTimeout(500); - - if (range) { - // lower the return signal rate limit (default is 0.25 MCPS) - sensor.setSignalRateLimit(0.1); - - // increase laser pulse periods (defaults are 14 and 10 PCLKs) - sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18); - sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); - } - - sensor.setMeasurementTimingBudget(timing * 1000); - - initPhase = P110_initPhases::InitDelay; - timeToWait = timing + 50; - - return initState; -} - -bool P110_data_struct::plugin_fifty_per_second() { - if (initPhase == P110_initPhases::InitDelay) { - timeToWait -= 20; // milliseconds - - // String log = F("VL53L0X: remaining wait: "); - // log += timeToWait; - // addLogMove(LOG_LEVEL_INFO, log); - - if (timeToWait <= 0) { - timeToWait = 0; - initPhase = P110_initPhases::Ready; - } - } - return true; -} - -long P110_data_struct::readDistance() { - long dist = -1; // Invalid - - if (initPhase != P110_initPhases::Ready) { return dist; } - - # if defined(P110_INFO_LOG) || defined(P110_DEBUG_LOG) - String log; - # endif // if defined(P110_INFO_LOG) || defined(P110_DEBUG_LOG) - # ifdef P110_DEBUG_LOG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - log = F("VL53L0X : idx: 0x"); - log += String(i2cAddress, HEX); - log += F(" init: "); - log += String(initState, BIN); - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // P110_DEBUG_LOG - - if (initState) { - success = true; - dist = sensor.readRangeSingleMillimeters(); - - if (sensor.timeoutOccurred()) { - # ifdef P110_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, F("VL53L0X: TIMEOUT")); - # endif // P110_DEBUG_LOG - success = false; - } else if (dist >= 8190) { - # ifdef P110_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, F("VL53L0X: NO MEASUREMENT")); - # endif // P110_DEBUG_LOG - success = false; - } - - # ifdef P110_INFO_LOG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - log = F("VL53L0X: Address: 0x"); - log += String(i2cAddress, HEX); - log += F(" / Timing: "); - log += timing; - log += F(" / Long Range: "); - log += String(range, BIN); - log += F(" / Distance: "); - log += dist; - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // P110_INFO_LOG - } - return dist; -} - -bool P110_data_struct::isReadSuccessful() { - return success; -} - -#endif // ifdef USES_P110 +#include "../PluginStructs/P110_data_struct.h" + +#ifdef USES_P110 + +P110_data_struct::P110_data_struct(uint8_t i2c_addr, int timing, bool range) : + _i2cAddress(i2c_addr), + _timing(timing), + _range(range) {} + +// **************************************************************************/ +// Initialize VL53L0X +// **************************************************************************/ +bool P110_data_struct::begin(uint32_t interval_ms) { + _timeToWait = 0; + _initPhase = P110_initPhases::Undefined; + sensor.setAddress(_i2cAddress); // Initialize for configured address + + if (!sensor.init()) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L0X: Sensor not found, init failed for 0x%02x"), _i2cAddress)); + addLog(LOG_LEVEL_INFO, sensor.getInitResult()); + } + return false; + } + + sensor.setTimeout(500); + + if (_range) { + // lower the return signal rate limit (default is 0.25 MCPS) + sensor.setSignalRateLimit(0.1); + + // increase laser pulse periods (defaults are 14 and 10 PCLKs) + sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodPreRange, 18); + sensor.setVcselPulsePeriod(VL53L0X::VcselPeriodFinalRange, 14); + } + + sensor.setMeasurementTimingBudget(_timing * 1000); + + _initPhase = P110_initPhases::InitDelay; + _timeToWait = millis() + _timing + 50; + + sensor.startContinuous(interval_ms); + + return true; +} + +bool P110_data_struct::check_reading_ready(struct EventStruct *event) { + if (_initPhase == P110_initPhases::InitDelay) { + if ((_timeToWait != 0) && timeOutReached(_timeToWait)) { + _timeToWait = 0; + _initPhase = P110_initPhases::Ready; + } + } else { + if (readDistance() >= 0) { + Scheduler.schedule_task_device_timer(event->TaskIndex, millis()); + } + } + return true; +} + +bool P110_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isReadSuccessful()) { + const float new_distance = getDistance(); + const float prev_distance = _prev_distance; + + const bool first_sample = (prev_distance < 0.0f); + + const float estimator = (_filtered < 0.0f) + ? new_distance + : _filtered; + + const float ratio_filtered = 16; + const float ratio_newVal = _prev_newval_ratio; + _prev_newval_ratio = std::abs(estimator - new_distance); + + _filtered = + ((ratio_filtered * estimator) + (ratio_newVal * new_distance)) + / (ratio_filtered + ratio_newVal); + + const float dist = _filtered; + const float p_dist = prev_distance; + + // Check trend: + // 0 = equal + // -1 = move closer + // 1 = move away + + const int16_t displacement = first_sample ? 0 : roundf(dist - p_dist); + const int16_t disp_dir = (displacement == 0) + ? 0 + : (displacement > 0) ? 1 : -1; + + //const bool direction_changed = disp_dir != static_cast(UserVar.getFloat(event->TaskIndex, 1)); + const bool triggered = + // direction_changed || + (std::abs(displacement) > P110_DELTA); + + # ifdef P110_INFO_LOG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("VL53L0x: Perform read: trig: %d, prev: %d, dist: %d"), triggered, p_dist, dist)); + } + # endif // ifdef P110_INFO_LOG + // Value is classified as invalid when > 8190, so no conversion or 'split' needed + UserVar.setFloat(event->TaskIndex, 0, _filtered); + UserVar.setFloat(event->TaskIndex, 1, disp_dir); // Trend of value + + if (first_sample || triggered || (P110_SEND_ALWAYS == 1)) { + // Update the "previous" distance. + _prev_distance = _filtered; + success = true; + } + } + return success; +} + +int16_t P110_data_struct::getDistance() { + const int res = _distance; + + _distance = P110_DISTANCE_WAITING; + return res; +} + +int16_t P110_data_struct::readDistance() { + if (_initPhase != P110_initPhases::Ready) { + return P110_DISTANCE_UNINITIALIZED; + } + + int16_t dist{}; + + if (sensor.asyncReadRangeContinuousMillimeters(dist)) { + if ((dist >= 0) && (dist < 8192)) { + // Only keep a copy of valid distance readings. + // Since the distance reading is later called from PLUGIN_READ, + // we might have had a new reading inbetween which could be a "still waiting" + // value and then we lost the actual reading. + + _distance = dist; + return _distance; + } + } + + if (dist == VL53L0X_WAITING) { + // Just waiting + // No need to keep sending many logs per second + return P110_DISTANCE_WAITING; + } + + +# ifdef P110_DEBUG_LOG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("VL53L0X: idx: 0x%02x init: %u"), + _i2cAddress, static_cast(_initPhase))); + } +# endif // P110_DEBUG_LOG + + if (sensor.timeoutOccurred()) { +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("VL53L0X: TIMEOUT")); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_READ_TIMEOUT; + } else if (dist >= 8190u) { +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, concat(F("VL53L0X: NO MEASUREMENT: "), dist)); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_OUT_OF_RANGE; + } + +# ifdef P110_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("VL53L0X: NO MEASUREMENT: 0xFFFF")); +# endif // P110_DEBUG_LOG + return P110_DISTANCE_READ_ERROR; +} + +bool P110_data_struct::isReadSuccessful() const { + return _distance >= 0; +} + +#endif // ifdef USES_P110 diff --git a/src/src/PluginStructs/P110_data_struct.h b/src/src/PluginStructs/P110_data_struct.h index 84cc1dc57..d59d02dd2 100644 --- a/src/src/PluginStructs/P110_data_struct.h +++ b/src/src/PluginStructs/P110_data_struct.h @@ -1,57 +1,75 @@ -#ifndef PLUGINSTRUCTS_P110_DATA_STRUCT_H -#define PLUGINSTRUCTS_P110_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P110 - -# define P110_INFO_LOG // Enable debugging output (INFO loglevel) -# define P110_DEBUG_LOG // Enable extended debugging output (DEBUG loglevel) - -# if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) - # ifdef P110_DEBUG_LOG - # undef P110_DEBUG_LOG - # endif // ifdef P110_DEBUG_LOG -# endif // if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) - -# include -# include - -# define P110_I2C_ADDRESS PCONFIG(0) -# define P110_TIMING PCONFIG(1) -# define P110_RANGE PCONFIG(2) - -enum class P110_initPhases : uint8_t { - Ready = 0x00, - InitDelay = 0x01, - Undefined = 0xFF -}; - -struct P110_data_struct : public PluginTaskData_base { -public: - - P110_data_struct(uint8_t i2c_addr, - int timing, - bool range); - P110_data_struct() = delete; - virtual ~P110_data_struct() = default; - - bool begin(); - long readDistance(); - bool isReadSuccessful(); - bool plugin_fifty_per_second(); - -private: - - VL53L0X sensor; - - uint8_t i2cAddress; - int timing; - bool range; - - int32_t timeToWait = 0; - P110_initPhases initPhase = P110_initPhases::Undefined; - bool initState = false; - bool success = false; -}; -#endif // ifdef USES_P110 -#endif // ifndef PLUGINSTRUCTS_P110_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P110_DATA_STRUCT_H +#define PLUGINSTRUCTS_P110_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P110 + +// # define P110_INFO_LOG // Enable debugging output (INFO loglevel) +# define P110_DEBUG_LOG // Enable extended debugging output (DEBUG loglevel) + +# if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) +# ifdef P110_DEBUG_LOG +# undef P110_DEBUG_LOG +# endif // ifdef P110_DEBUG_LOG +# endif // if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) + +# include + +# define P110_I2C_ADDRESS PCONFIG(0) +# define P110_TIMING PCONFIG(1) +# define P110_RANGE PCONFIG(2) +# define P110_SEND_ALWAYS PCONFIG(3) +# define P110_DELTA PCONFIG(4) + +# define P110_DISTANCE_UNINITIALIZED -1 +# define P110_DISTANCE_READ_TIMEOUT -2 +# define P110_DISTANCE_READ_ERROR -3 +# define P110_DISTANCE_OUT_OF_RANGE -4 +# define P110_DISTANCE_WAITING -5 + + +enum class P110_initPhases : uint8_t { + Undefined = 0xFF, + InitDelay = 0x00, + Ready = 0x01, + WaitMeasurement = 0x02 +}; + +struct P110_data_struct : public PluginTaskData_base { +public: + + P110_data_struct(uint8_t i2c_addr, + int timing, + bool range); + P110_data_struct() = delete; + virtual ~P110_data_struct() = default; + + bool begin(uint32_t interval_ms); + int16_t readDistance(); + + // Return last reading and clear the cached _distance value + // This way we know if there was a new successful reading since last call of getDistance() + int16_t getDistance(); + bool isReadSuccessful() const; + bool check_reading_ready(struct EventStruct *event); + + bool plugin_read(struct EventStruct *event); + +private: + + VL53L0X sensor; + + float _prev_distance = -1.0f; + float _filtered = -1.0f; + float _prev_newval_ratio = 1.0f; + + const uint8_t _i2cAddress; + const int _timing; + const bool _range; + + int16_t _distance = P110_DISTANCE_UNINITIALIZED; + int32_t _timeToWait = 0; + P110_initPhases _initPhase = P110_initPhases::Undefined; +}; +#endif // ifdef USES_P110 +#endif // ifndef PLUGINSTRUCTS_P110_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P113_data_struct.cpp b/src/src/PluginStructs/P113_data_struct.cpp index d26ef04ea..262e44a1a 100644 --- a/src/src/PluginStructs/P113_data_struct.cpp +++ b/src/src/PluginStructs/P113_data_struct.cpp @@ -1,121 +1,117 @@ -#include "../PluginStructs/P113_data_struct.h" - -#ifdef USES_P113 - -P113_data_struct::P113_data_struct(uint8_t i2c_addr, int timing, bool range) : i2cAddress(i2c_addr), timing(timing), range(range) { - sensor = new (std::nothrow) SFEVL53L1X(); -} - -P113_data_struct::~P113_data_struct() { - if (nullptr != sensor) { - delete sensor; - } -} - -// **************************************************************************/ -// Initialize VL53L1X -// **************************************************************************/ -bool P113_data_struct::begin() { - initState = nullptr != sensor; - - if (initState) { - uint16_t id = sensor->getID(); - - // FIXME 2023-08-11 tonhuisman: Disabled, as it seems to mess up the sensor - // sensor->setI2CAddress(i2cAddress); // Initialize for configured address - - uint8_t res = sensor->begin(); - - if (res) { // 0/false is NO-ERROR - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat(F("VL53L1X: Sensor not found, init failed for 0x%02x, id: 0x%04X status: %d"), - i2cAddress, id, res)); - } - initState = false; - return initState; - } - - sensor->setTimingBudgetInMs(timing); - - if (range) { - sensor->setDistanceModeLong(); - } else { - sensor->setDistanceModeShort(); - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L1X: Sensor initialized at address 0x%02x, id: 0x%04x"), i2cAddress, id)); - } - } - - return initState; -} - -bool P113_data_struct::startRead() { - if (initState && !readActive && (nullptr != sensor)) { - sensor->startRanging(); - readActive = true; - distance = -1; - } - return readActive; -} - -bool P113_data_struct::readAvailable() { - bool ready = (nullptr != sensor) && sensor->checkForDataReady(); - - if (ready) { - distance = sensor->getDistance(); - sensor->clearInterrupt(); - sensor->stopRanging(); - - // readActive = false; - } - return ready; -} - -uint16_t P113_data_struct::readDistance() { - success = false; - - # if defined(P113_DEBUG) || defined(P113_DEBUG_DEBUG) - String log; - # endif // if defined(P113_DEBUG) || defined(P113_DEBUG_DEBUG) - # ifdef P113_DEBUG_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - addLogMove(LOG_LEVEL_DEBUG, strformat(F("VL53L1X : idx: 0x%x init: %d"), i2cAddress, initState ? 1 : 0)); - } - # endif // P113_DEBUG_DEBUG - - success = true; - readActive = false; - - if (distance >= 8190) { - # ifdef P113_DEBUG_DEBUG - addLog(LOG_LEVEL_DEBUG, "VL53L1X: NO MEASUREMENT"); - # endif // P113_DEBUG_DEBUG - success = false; - } - - # ifdef P113_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L1X: Address: 0x%02x / Timing: %d / Long Range: %d / Distance: %d"), - i2cAddress, timing, range ? 1 : 0, distance)); - } - # endif // P113_DEBUG - - return distance; -} - -uint16_t P113_data_struct::readAmbient() { - if (nullptr == sensor) { - return 0u; - } - return sensor->getAmbientRate(); -} - -bool P113_data_struct::isReadSuccessful() { - return success; -} - -#endif // ifdef USES_P113 +#include "../PluginStructs/P113_data_struct.h" + +#ifdef USES_P113 + +P113_data_struct::P113_data_struct(uint8_t i2c_addr, int timing, bool range) : i2cAddress(i2c_addr), timing(timing), range(range) { + sensor = new (std::nothrow) SFEVL53L1X(); +} + +P113_data_struct::~P113_data_struct() { + delete sensor; +} + +// **************************************************************************/ +// Initialize VL53L1X +// **************************************************************************/ +bool P113_data_struct::begin() { + initState = nullptr != sensor; + + if (initState) { + const uint16_t id = sensor->getID(); + + // FIXME 2023-08-11 tonhuisman: Disabled, as it seems to mess up the sensor + // sensor->setI2CAddress(i2cAddress); // Initialize for configured address + + const bool res = sensor->begin(); + + if (res) { // 0/false is NO-ERROR + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("VL53L1X: Sensor not found, init failed for 0x%02x, id: 0x%04X status: %d"), + i2cAddress, id, res)); + } + initState = false; + return initState; + } + + sensor->setTimingBudgetInMs(timing); + + if (range) { + sensor->setDistanceModeLong(); + } else { + sensor->setDistanceModeShort(); + } + + # ifdef P113_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L1X: Sensor initialized at address 0x%02x, id: 0x%04x"), i2cAddress, id)); + } + # endif // ifdef P113_DEBUG + } + + return initState; +} + +bool P113_data_struct::startRead() { + if (initState && !readActive && (nullptr != sensor)) { + sensor->startRanging(); + readActive = true; + distance = -1; + } + return readActive; +} + +bool P113_data_struct::readAvailable() { + bool ready = (nullptr != sensor) && sensor->checkForDataReady(); + + if (ready) { + distance = sensor->getDistance(); + sensor->clearInterrupt(); + sensor->stopRanging(); + + // readActive = false; + } + return ready; +} + +uint16_t P113_data_struct::readDistance() { + # ifdef P113_DEBUG_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("VL53L1X: idx: 0x%02x init: %d"), i2cAddress, initState)); + } + # endif // P113_DEBUG_DEBUG + + success = true; + readActive = false; + + if (distance >= 8190u) { + # ifdef P113_DEBUG_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("VL53L1X: NO MEASUREMENT"), distance)); + # endif // P113_DEBUG_DEBUG + success = false; + } + + # ifdef P113_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat(F("VL53L1X: Address: 0x%02x / Timing: %d / Long Range: %d / Distance: %d"), + i2cAddress, timing, range, distance)); + } + # endif // P113_DEBUG + + return distance; +} + +uint16_t P113_data_struct::readAmbient() { + if (nullptr == sensor) { + return 0u; + } + return sensor->getAmbientRate(); +} + +bool P113_data_struct::isReadSuccessful() { + return success; +} + +#endif // ifdef USES_P113 diff --git a/src/src/PluginStructs/P113_data_struct.h b/src/src/PluginStructs/P113_data_struct.h index 583cdb30b..a43582152 100644 --- a/src/src/PluginStructs/P113_data_struct.h +++ b/src/src/PluginStructs/P113_data_struct.h @@ -1,50 +1,55 @@ -#ifndef PLUGINSTRUCTS_P113_DATA_STRUCT_H -#define PLUGINSTRUCTS_P113_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P113 - -# define P113_DEBUG // Enable debugging output (INFO loglevel) -# ifndef BUILD_NO_DEBUG -# define P113_DEBUG_DEBUG // Enable extended debugging output (DEBUG loglevel) -# endif // ifndef BUILD_NO_DEBUG - -# ifdef LIMIT_BUILD_SIZE - # ifdef P113_DEBUG_DEBUG - # undef P113_DEBUG_DEBUG - # endif // ifdef P113_DEBUG_DEBUG -# endif // ifdef LIMIT_BUILD_SIZE - -# include -# include - -struct P113_data_struct : public PluginTaskData_base { -public: - - P113_data_struct(uint8_t i2c_addr, - int timing, - bool range); - P113_data_struct() = delete; - virtual ~P113_data_struct(); - - bool begin(); - bool startRead(); - bool readAvailable(); - uint16_t readDistance(); - uint16_t readAmbient(); - bool isReadSuccessful(); - -private: - - SFEVL53L1X *sensor = nullptr; - - const uint8_t i2cAddress; - bool initState = false; - const int timing; - const bool range; - bool success = false; - bool readActive = false; - uint16_t distance = 0u; -}; -#endif // ifdef USES_P113 -#endif // ifndef PLUGINSTRUCTS_P113_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P113_DATA_STRUCT_H +#define PLUGINSTRUCTS_P113_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P113 + +# define P113_DEBUG // Enable debugging output (INFO loglevel) +# ifndef BUILD_NO_DEBUG +# define P113_DEBUG_DEBUG // Enable extended debugging output (DEBUG loglevel) +# endif // ifndef BUILD_NO_DEBUG + +# if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) +# ifdef P113_DEBUG_DEBUG +# undef P113_DEBUG_DEBUG +# endif // ifdef P113_DEBUG_DEBUG +# endif // if defined(LIMIT_BUILD_SIZE) || defined(BUILD_NO_DEBUG) + +# include + +# define P113_I2C_ADDRESS PCONFIG(0) +# define P113_TIMING PCONFIG(1) +# define P113_RANGE PCONFIG(2) +# define P113_SEND_ALWAYS PCONFIG(3) +# define P113_DELTA PCONFIG(4) + +struct P113_data_struct : public PluginTaskData_base { +public: + + P113_data_struct(uint8_t i2c_addr, + int timing, + bool range); + P113_data_struct() = delete; + virtual ~P113_data_struct(); + + bool begin(); + bool startRead(); + bool readAvailable(); + uint16_t readDistance(); + uint16_t readAmbient(); + bool isReadSuccessful(); + +private: + + SFEVL53L1X *sensor = nullptr; + + const uint8_t i2cAddress; + bool initState = false; + const int timing; + const bool range; + bool success = false; + bool readActive = false; + uint16_t distance = 0u; +}; +#endif // ifdef USES_P113 +#endif // ifndef PLUGINSTRUCTS_P113_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P114_data_struct.cpp b/src/src/PluginStructs/P114_data_struct.cpp index f12d34693..6aa6b5a74 100644 --- a/src/src/PluginStructs/P114_data_struct.cpp +++ b/src/src/PluginStructs/P114_data_struct.cpp @@ -21,18 +21,15 @@ bool P114_data_struct::read_sensor(float& _UVA, float& _UVB, float& _UVIndex) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - log.reserve(48); - log = F("VEML6075: i2caddress: 0x"); - log += String(i2cAddress, HEX); - log += F(", initialized: "); - log += String(initialised ? F("true") : F("false")); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("VEML6075: i2caddress: 0x%02x, initialized: %s"), + i2cAddress, + String(initialised ? F("true") : F("false")).c_str())); } # endif // ifndef BUILD_NO_DEBUG if (initialised) { - for (int j = 0; j < 5; j++) { + for (int j = 0; j < 5; ++j) { UVData[j] = I2C_read16_LE_reg(i2cAddress, VEML6075_UVA_DATA + j); } @@ -51,9 +48,7 @@ bool P114_data_struct::read_sensor(float& _UVA, float& _UVB, float& _UVIndex) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("VEML6075: IT raw: 0x"); - log += String(IT + 1, HEX); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("VEML6075: IT raw: 0x%02x"), IT + 1)); } # endif // ifndef BUILD_NO_DEBUG return true; diff --git a/src/src/PluginStructs/P116_data_struct.cpp b/src/src/PluginStructs/P116_data_struct.cpp index 3e88fcf49..83db80e7b 100644 --- a/src/src/PluginStructs/P116_data_struct.cpp +++ b/src/src/PluginStructs/P116_data_struct.cpp @@ -1,492 +1,538 @@ -#include "../PluginStructs/P116_data_struct.h" - -#ifdef USES_P116 - -/**************************************************************************** - * ST77xx_type_toString: Display-value for the device selected - ***************************************************************************/ -const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device) { - switch (device) { - case ST77xx_type_e::ST7735s_128x128: return F("ST7735 128 x 128px"); - case ST77xx_type_e::ST7735s_128x160: return F("ST7735 128 x 160px"); - case ST77xx_type_e::ST7735s_80x160: return F("ST7735 80 x 160px"); - case ST77xx_type_e::ST7735s_80x160_M5: return F("ST7735 80 x 160px (Color inverted)"); - case ST77xx_type_e::ST7789vw_240x320: return F("ST7789 240 x 320px"); - case ST77xx_type_e::ST7789vw_240x240: return F("ST7789 240 x 240px"); - case ST77xx_type_e::ST7789vw_240x280: return F("ST7789 240 x 280px"); - case ST77xx_type_e::ST7789vw_135x240: return F("ST7789 135 x 240px"); - case ST77xx_type_e::ST7796s_320x480: return F("ST7796 320 x 480px"); - } - return F("Unsupported type!"); -} - -/**************************************************************************** - * ST77xx_type_toResolution: X and Y resolution for the selected type - ***************************************************************************/ -void ST77xx_type_toResolution(const ST77xx_type_e& device, - uint16_t & x, - uint16_t & y) { - switch (device) { - case ST77xx_type_e::ST7735s_128x128: - x = 128; - y = 128; - break; - case ST77xx_type_e::ST7735s_128x160: - x = 128; - y = 160; - break; - case ST77xx_type_e::ST7735s_80x160_M5: - case ST77xx_type_e::ST7735s_80x160: - x = 80; - y = 160; - break; - case ST77xx_type_e::ST7789vw_240x320: - x = 240; - y = 320; - break; - case ST77xx_type_e::ST7789vw_240x240: - x = 240; - y = 240; - break; - case ST77xx_type_e::ST7789vw_240x280: - x = 240; - y = 280; - break; - case ST77xx_type_e::ST7789vw_135x240: - x = 135; - y = 240; - break; - case ST77xx_type_e::ST7796s_320x480: - x = 320; - y = 480; - break; - } -} - -/**************************************************************************** - * P116_CommandTrigger_toString: return the command string selected - ***************************************************************************/ -const __FlashStringHelper* P116_CommandTrigger_toString(const P116_CommandTrigger& cmd) { - switch (cmd) { - case P116_CommandTrigger::tft: return F("tft"); - case P116_CommandTrigger::st7735: return F("st7735"); - case P116_CommandTrigger::st7789: return F("st7789"); - case P116_CommandTrigger::st7796: return F("st7796"); - case P116_CommandTrigger::st77xx: break; - } - return F("st77xx"); // Default command trigger -} - -/**************************************************************************** - * Constructor - ***************************************************************************/ -P116_data_struct::P116_data_struct(ST77xx_type_e device, - uint8_t rotation, - uint8_t fontscaling, - AdaGFXTextPrintMode textmode, - int8_t backlightPin, - uint8_t backlightPercentage, - uint32_t displayTimer, - String commandTrigger, - uint16_t fgcolor, - uint16_t bgcolor, - bool textBackFill) - : _device(device), _rotation(rotation), _fontscaling(fontscaling), _textmode(textmode), _backlightPin(backlightPin), - _backlightPercentage(backlightPercentage), _displayTimer(displayTimer), _displayTimeout(displayTimer), - _commandTrigger(commandTrigger), _fgcolor(fgcolor), _bgcolor(bgcolor), _textBackFill(textBackFill) -{ - _commandTrigger.toLowerCase(); - _commandTriggerCmd = _commandTrigger; - _commandTriggerCmd += F("cmd"); -} - -/**************************************************************************** - * Destructor - ***************************************************************************/ -P116_data_struct::~P116_data_struct() { - cleanup(); -} - -/**************************************************************************** - * plugin_init: Initialize display - ***************************************************************************/ -bool P116_data_struct::plugin_init(struct EventStruct *event) { - ST77xx_type_toResolution(_device, _xpix, _ypix); - - updateFontMetrics(); - - bool success = false; - - ButtonState = false; // button not touched - ButtonLastState = 0xFF; // Last state checked (debouncing in progress) - DebounceCounter = 0; // debounce counter - - if (nullptr == st77xx) { - addLog(LOG_LEVEL_INFO, F("ST77xx: Init start.")); - uint8_t initRoptions = 0xFF; - - switch (_device) { - case ST77xx_type_e::ST7735s_128x128: - - initRoptions = INITR_144GREENTAB; // 128x128px - - // fall through - case ST77xx_type_e::ST7735s_128x160: - - if (initRoptions == 0xFF) { - initRoptions = INITR_BLACKTAB; // 128x160px - } - - // fall through - case ST77xx_type_e::ST7735s_80x160_M5: - - if (initRoptions == 0xFF) { - initRoptions = INITR_GREENTAB160x80; // 80x160px ST7735sv, inverted (M5Stack StickC) - } - - // fall through - case ST77xx_type_e::ST7735s_80x160: - { - if (initRoptions == 0xFF) { - initRoptions = INITR_MINI160x80; // 80x160px - } - - st7735 = new (std::nothrow) Adafruit_ST7735(PIN(0), PIN(1), PIN(2)); - - if (nullptr != st7735) { - st7735->initR(initRoptions); // initialize a ST7735s chip - st77xx = st7735; // pass pointer after initialization - } - break; - } - case ST77xx_type_e::ST7789vw_240x320: // Fall through - case ST77xx_type_e::ST7789vw_240x240: - case ST77xx_type_e::ST7789vw_240x280: - case ST77xx_type_e::ST7789vw_135x240: - { - st7789 = new (std::nothrow) Adafruit_ST7789(PIN(0), PIN(1), PIN(2)); - - if (nullptr != st7789) { - st7789->init(_xpix, _ypix, SPI_MODE2); - st77xx = st7789; - } - break; - } - case ST77xx_type_e::ST7796s_320x480: - { - st7796 = new (std::nothrow) Adafruit_ST7796S_kbv(PIN(0), PIN(1), PIN(2)); - - if (nullptr != st7796) { - st7796->begin(); - st77xx = st7796; - } - break; - } - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(90); - log += F("ST77xx: Init done, address: 0x"); - log += String(reinterpret_cast(st77xx), HEX); - log += ' '; - - if (nullptr == st77xx) { - log += F("in"); - } - log += F("valid, commands: "); - log += _commandTrigger; - log += F(", display: "); - log += ST77xx_type_toString(_device); - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // ifndef BUILD_NO_DEBUG - } else { - addLog(LOG_LEVEL_INFO, F("ST77xx: No init?")); - } - - if (nullptr != st77xx) { - gfxHelper = new (std::nothrow) AdafruitGFX_helper(st77xx, - _commandTrigger, - _xpix, - _ypix, - AdaGFXColorDepth::FullColor, - _textmode, - _fontscaling, - _fgcolor, - _bgcolor, - true, - _textBackFill); - - if (nullptr != gfxHelper) { - displayOnOff(true); - - gfxHelper->initialize(); - gfxHelper->setRotation(_rotation); - st77xx->fillScreen(_bgcolor); // fill screen with black color - st77xx->setTextColor(_fgcolor, _bgcolor); // set text color to white and black background - - # ifdef P116_SHOW_SPLASH - uint16_t yPos = 0; - gfxHelper->printText(String(F("ESPEasy")).c_str(), 0, yPos, 3, ST77XX_WHITE, ST77XX_BLUE); - yPos += (3 * _fontheight); - gfxHelper->printText(String(F("ST77xx")).c_str(), 0, yPos, 2, ST77XX_BLUE, ST77XX_WHITE); - delay(100); // Splash - # endif // ifdef P116_SHOW_SPLASH - - gfxHelper->setColumnRowMode(bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW)); - st77xx->setTextSize(_fontscaling); // Handles 0 properly, text size, default 1 = very small - st77xx->setCursor(0, 0); // move cursor to position (0, 0) pixel - updateFontMetrics(); - - - if (P116_CONFIG_BUTTON_PIN != -1) { - pinMode(P116_CONFIG_BUTTON_PIN, INPUT_PULLUP); - } - - if (!stringsLoaded) { - LoadCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); - stringsLoaded = true; - - for (uint8_t x = 0; x < P116_Nlines && !stringsHasContent; x++) { - stringsHasContent = !strings[x].isEmpty(); - } - } - success = true; - } - } - return success; -} - -/**************************************************************************** - * updateFontMetrics: recalculate x and y columns, based on font size and font scale - ***************************************************************************/ -void P116_data_struct::updateFontMetrics() { - if (nullptr != gfxHelper) { - gfxHelper->getTextMetrics(_textcols, _textrows, _fontwidth, _fontheight, _fontscaling, _heightOffset, _xpix, _ypix); - gfxHelper->getColors(_fgcolor, _bgcolor); - } else { - _textcols = _xpix / (_fontwidth * _fontscaling); - _textrows = _ypix / (_fontheight * _fontscaling); - } -} - -/**************************************************************************** - * plugin_exit: De-initialize before destruction - ***************************************************************************/ -bool P116_data_struct::plugin_exit(struct EventStruct *event) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_INFO, F("ST77xx: Exit.")); - # endif // ifndef BUILD_NO_DEBUG - - if ((nullptr != st77xx) && bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_CLEAR_ON_EXIT)) { - st77xx->fillScreen(ADAGFX_BLACK); // fill screen with black color - displayOnOff(false); - } - cleanup(); - return true; -} - -/**************************************************************************** - * cleanup: De-initialize pointers - ***************************************************************************/ -void P116_data_struct::cleanup() { - delete gfxHelper; - gfxHelper = nullptr; - - delete st7735; - st7735 = nullptr; - - delete st7789; - st7789 = nullptr; - st77xx = nullptr; // Only used as a proxy -} - -/**************************************************************************** - * plugin_read: Re-draw the default content - ***************************************************************************/ -bool P116_data_struct::plugin_read(struct EventStruct *event) { - if (nullptr != st77xx) { - if (stringsHasContent) { - gfxHelper->setColumnRowMode(false); // Turn off column mode - - int yPos = 0; - - for (uint8_t x = 0; x < P116_Nlines; x++) { - String newString = AdaGFXparseTemplate(strings[x], _textcols, gfxHelper); - - # if ADAGFX_PARSE_SUBCOMMAND - updateFontMetrics(); - # endif // if ADAGFX_PARSE_SUBCOMMAND - - if (yPos < _ypix) { - gfxHelper->printText(newString.c_str(), 0, yPos, _fontscaling, _fgcolor, _bgcolor); - } - delay(0); - yPos += (_fontheight * _fontscaling); - } - gfxHelper->setColumnRowMode(bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW)); // Restore column mode - int16_t curX, curY; - gfxHelper->getCursorXY(curX, curY); // Get current X and Y coordinates, - UserVar.setFloat(event->TaskIndex, 0, curX); // and put into Values - UserVar.setFloat(event->TaskIndex, 1, curY); - } - } - return false; // Always return false, so no attempt to send to - // Controllers or generate events is started -} - -/**************************************************************************** - * plugin_ten_per_second: check button, if any, that wakes up the display - ***************************************************************************/ -bool P116_data_struct::plugin_ten_per_second(struct EventStruct *event) { - if ((P116_CONFIG_BUTTON_PIN != -1) && (getButtonState()) && (nullptr != st77xx)) { - displayOnOff(true); - markButtonStateProcessed(); - } - return true; -} - -/**************************************************************************** - * plugin_once_a_second: Count down display timer, if any, and turn display off if countdown reached - ***************************************************************************/ -bool P116_data_struct::plugin_once_a_second(struct EventStruct *event) { - if (_displayTimer > 0) { - _displayTimer--; - - if ((nullptr != st77xx) && (_displayTimer == 0)) { - displayOnOff(false); - } - } - return true; -} - -/**************************************************************************** - * plugin_write: Handle commands - ***************************************************************************/ -bool P116_data_struct::plugin_write(struct EventStruct *event, - const String & string) { - bool success = false; - String cmd = parseString(string, 1); - - if ((nullptr != st77xx) && cmd.equals(_commandTriggerCmd)) { - String arg1 = parseString(string, 2); - success = true; - - if (equals(arg1, F("off"))) { - displayOnOff(false); - } - else if (equals(arg1, F("on"))) { - displayOnOff(true); - } - else if (equals(arg1, F("clear"))) { - st77xx->fillScreen(_bgcolor); - } - else if (equals(arg1, F("backlight"))) { - String arg2 = parseString(string, 3); - int32_t nArg2{}; - - if ((P116_CONFIG_BACKLIGHT_PIN != -1) && // All is valid? - validIntFromString(arg2, nArg2) && - (nArg2 > 0) && - (nArg2 <= 100)) { - P116_CONFIG_BACKLIGHT_PERCENT = nArg2; // Set but don't store - displayOnOff(true); - } else { - success = false; - } - } else { - success = false; - } - } - else if (st77xx && (cmd.equals(_commandTrigger) || - (gfxHelper && gfxHelper->isAdaGFXTrigger(cmd)))) { - success = true; - - if (!bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_NO_WAKE)) { // Wake display? - displayOnOff(true); - } - - if (nullptr != gfxHelper) { - String tmp = string; - - // Hand it over after replacing variables - success = gfxHelper->processCommand(AdaGFXparseTemplate(tmp, _textcols, gfxHelper)); - - updateFontMetrics(); // Font or color may have changed - - if (success) { - int16_t curX, curY; - gfxHelper->getCursorXY(curX, curY); // Get current X and Y coordinates, and put into Values - UserVar.setFloat(event->TaskIndex, 0, curX); - UserVar.setFloat(event->TaskIndex, 1, curY); - } - } - } - return success; -} - -# if ADAGFX_ENABLE_GET_CONFIG_VALUE - -/**************************************************************************** - * plugin_get_config_value: Retrieve values like [#] - ***************************************************************************/ -bool P116_data_struct::plugin_get_config_value(struct EventStruct *event, - String & string) { - bool success = false; - - if (gfxHelper != nullptr) { - success = gfxHelper->pluginGetConfigValue(string); - } - return success; -} - -# endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE - -/**************************************************************************** - * displayOnOff: Turn display on or off - ***************************************************************************/ -void P116_data_struct::displayOnOff(bool state) { - if (_backlightPin != -1) { - # if defined(ESP8266) - analogWrite(_backlightPin, state ? ((1024 / 100) * _backlightPercentage) : 0); - # endif // if defined(ESP8266) - # if defined(ESP32) - analogWriteESP32(_backlightPin, state ? ((1024 / 100) * _backlightPercentage) : 0, 0); - # endif // if defined(ESP32) - } - st77xx->enableDisplay(state); // Display on - _displayTimer = (state ? _displayTimeout : 0); -} - -/**************************************************************************** - * registerButtonState: the button has been pressed, apply some debouncing - ***************************************************************************/ -void P116_data_struct::registerButtonState(uint8_t newButtonState, - bool bPin3Invers) { - if ((ButtonLastState == 0xFF) || (bPin3Invers != (!!newButtonState))) { - ButtonLastState = newButtonState; - DebounceCounter++; - } else { - ButtonLastState = 0xFF; // Reset - DebounceCounter = 0; - ButtonState = false; - } - - if ((ButtonLastState == newButtonState) && - (DebounceCounter >= P116_DebounceTreshold)) { - ButtonState = true; - } -} - -/**************************************************************************** - * markButtonStateProcessed: reset the button state - ***************************************************************************/ -void P116_data_struct::markButtonStateProcessed() { - ButtonState = false; - DebounceCounter = 0; -} - -#endif // ifdef USES_P116 +#include "../PluginStructs/P116_data_struct.h" + +#ifdef USES_P116 + +/**************************************************************************** + * ST77xx_type_toString: Display-value for the device selected + ***************************************************************************/ +const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device) { + switch (device) { + case ST77xx_type_e::ST7735s_128x128: return F("ST7735 128 x 128px"); + case ST77xx_type_e::ST7735s_128x160: return F("ST7735 128 x 160px"); + case ST77xx_type_e::ST7735s_80x160: return F("ST7735 80 x 160px"); + case ST77xx_type_e::ST7735s_80x160_M5: return F("ST7735 80 x 160px (Color inverted)"); + # if P116_EXTRA_ST7735 + case ST77xx_type_e::ST7735s_135x240: return F("ST7735 135 x 240px"); + # 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"); + case ST77xx_type_e::ST7789vw_240x280: return F("ST7789 240 x 280px"); + case ST77xx_type_e::ST7789vw_135x240: return F("ST7789 135 x 240px"); + # if P116_EXTRA_ST7789 + case ST77xx_type_e::ST7789vw1_135x240: return F("ST7789 135 x 240px (alt1)"); + case ST77xx_type_e::ST7789vw2_135x240: return F("ST7789 135 x 240px (alt2)"); + case ST77xx_type_e::ST7789vw3_135x240: return F("ST7789 135 x 240px (alt3)"); + # endif // if P116_EXTRA_ST7789 + case ST77xx_type_e::ST7796s_320x480: return F("ST7796 320 x 480px"); + } + return F("Unsupported type!"); +} + +/**************************************************************************** + * ST77xx_type_toResolution: X and Y resolution for the selected type + ***************************************************************************/ +void ST77xx_type_toResolution(const ST77xx_type_e& device, + uint16_t & x, + uint16_t & y) { + switch (device) { + case ST77xx_type_e::ST7735s_128x128: + x = 128; + y = 128; + break; + case ST77xx_type_e::ST7735s_128x160: + x = 128; + y = 160; + break; + case ST77xx_type_e::ST7735s_80x160_M5: + case ST77xx_type_e::ST7735s_80x160: + x = 80; + y = 160; + break; + case ST77xx_type_e::ST7789vw_240x320: + x = 240; + y = 320; + break; + case ST77xx_type_e::ST7789vw_240x240: + x = 240; + y = 240; + break; + case ST77xx_type_e::ST7789vw_240x280: + x = 240; + y = 280; + break; + case ST77xx_type_e::ST7789vw_135x240: + # if P116_EXTRA_ST7789 + case ST77xx_type_e::ST7789vw1_135x240: + case ST77xx_type_e::ST7789vw2_135x240: + case ST77xx_type_e::ST7789vw3_135x240: + # endif // if P116_EXTRA_ST7789 + # if P116_EXTRA_ST7735 + case ST77xx_type_e::ST7735s_135x240: + # endif // if P116_EXTRA_ST7735 + x = 135; + y = 240; + break; + case ST77xx_type_e::ST7796s_320x480: + x = 320; + y = 480; + break; + } +} + +/**************************************************************************** + * P116_CommandTrigger_toString: return the command string selected + ***************************************************************************/ +const __FlashStringHelper* P116_CommandTrigger_toString(const P116_CommandTrigger& cmd) { + switch (cmd) { + case P116_CommandTrigger::tft: return F("tft"); + case P116_CommandTrigger::st7735: return F("st7735"); + case P116_CommandTrigger::st7789: return F("st7789"); + case P116_CommandTrigger::st7796: return F("st7796"); + case P116_CommandTrigger::st77xx: break; + } + return F("st77xx"); // Default command trigger +} + +/**************************************************************************** + * Constructor + ***************************************************************************/ +P116_data_struct::P116_data_struct(ST77xx_type_e device, + uint8_t rotation, + uint8_t fontscaling, + AdaGFXTextPrintMode textmode, + int8_t backlightPin, + uint8_t backlightPercentage, + uint32_t displayTimer, + String commandTrigger, + uint16_t fgcolor, + uint16_t bgcolor, + bool textBackFill + # if ADAGFX_FONTS_INCLUDED + , + const uint8_t defaultFontId + # endif // if ADAGFX_FONTS_INCLUDED + ) + : _device(device), _rotation(rotation), _fontscaling(fontscaling), _textmode(textmode), _backlightPin(backlightPin), + _backlightPercentage(backlightPercentage), _displayTimer(displayTimer), _displayTimeout(displayTimer), + _commandTrigger(commandTrigger), _fgcolor(fgcolor), _bgcolor(bgcolor), _textBackFill(textBackFill) + # if ADAGFX_FONTS_INCLUDED + , _defaultFontId(defaultFontId) + # endif // if ADAGFX_FONTS_INCLUDED +{ + _commandTrigger.toLowerCase(); + _commandTriggerCmd = concat(_commandTrigger, F("cmd")); +} + +/**************************************************************************** + * Destructor + ***************************************************************************/ +P116_data_struct::~P116_data_struct() { + cleanup(); +} + +/**************************************************************************** + * plugin_init: Initialize display + ***************************************************************************/ +bool P116_data_struct::plugin_init(struct EventStruct *event) { + ST77xx_type_toResolution(_device, _xpix, _ypix); + + updateFontMetrics(); + + bool success = false; + + ButtonState = false; // button not touched + ButtonLastState = 0xFF; // Last state checked (debouncing in progress) + DebounceCounter = 0; // debounce counter + + if (nullptr == st77xx) { + addLog(LOG_LEVEL_INFO, F("ST77xx: Init start.")); + uint8_t initRoptions = 0xFF; + + switch (_device) { + case ST77xx_type_e::ST7735s_128x128: + + initRoptions = INITR_144GREENTAB; // 128x128px + + // fall through + case ST77xx_type_e::ST7735s_128x160: + + if (initRoptions == 0xFF) { + initRoptions = INITR_BLACKTAB; // 128x160px + } + + // fall through + case ST77xx_type_e::ST7735s_80x160_M5: + + if (initRoptions == 0xFF) { + initRoptions = INITR_GREENTAB160x80; // 80x160px ST7735sv, inverted (M5Stack StickC) + } + + // fall through + # if P116_EXTRA_ST7735 + case ST77xx_type_e::ST7735s_135x240: + + if (initRoptions == 0xFF) { + initRoptions = INITR_BLACKTAB135x240; // 135x240px + } + + // fall through + # endif // if P116_EXTRA_ST7735 + case ST77xx_type_e::ST7735s_80x160: + { + if (initRoptions == 0xFF) { + initRoptions = INITR_MINI160x80; // 80x160px + } + + st7735 = new (std::nothrow) Adafruit_ST7735(PIN(0), PIN(1), PIN(2)); + + if (nullptr != st7735) { + st7735->initR(initRoptions); // initialize a ST7735s chip + st77xx = st7735; // pass pointer after initialization + } + break; + } + case ST77xx_type_e::ST7789vw_240x320: // Fall through + case ST77xx_type_e::ST7789vw_240x240: + case ST77xx_type_e::ST7789vw_240x280: + case ST77xx_type_e::ST7789vw_135x240: + # if P116_EXTRA_ST7789 + case ST77xx_type_e::ST7789vw1_135x240: + case ST77xx_type_e::ST7789vw2_135x240: + case ST77xx_type_e::ST7789vw3_135x240: + # endif // if P116_EXTRA_ST7789 + { + st7789 = new (std::nothrow) Adafruit_ST7789(PIN(0), PIN(1), PIN(2)); + + if (nullptr != st7789) { + uint8_t init_seq = 0; // Default/original initialisation + + # if P116_EXTRA_ST7789 + + if (ST77xx_type_e::ST7789vw1_135x240 == _device) { + init_seq = 1; + } else if (ST77xx_type_e::ST7789vw2_135x240 == _device) { + init_seq = 2; + } else if (ST77xx_type_e::ST7789vw3_135x240 == _device) { + init_seq = 3; + } + # endif // if P116_EXTRA_ST7789 + st7789->init(_xpix, _ypix, SPI_MODE2, init_seq); + st77xx = st7789; + } + break; + } + case ST77xx_type_e::ST7796s_320x480: + { + st7796 = new (std::nothrow) Adafruit_ST7796S_kbv(PIN(0), PIN(1), PIN(2)); + + if (nullptr != st7796) { + st7796->begin(); + st77xx = st7796; + } + break; + } + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log.reserve(90); + log += strformat(F("ST77xx: Init done, address: 0x%x "), reinterpret_cast(st77xx)); + + if (nullptr == st77xx) { + log += F("in"); + } + log += strformat(F("valid, commands: %s, display: "), _commandTrigger.c_str()); + log += ST77xx_type_toString(_device); + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // ifndef BUILD_NO_DEBUG + } else { + addLog(LOG_LEVEL_INFO, F("ST77xx: No init?")); + } + + if (nullptr != st77xx) { + gfxHelper = new (std::nothrow) AdafruitGFX_helper(st77xx, + _commandTrigger, + _xpix, + _ypix, + AdaGFXColorDepth::FullColor, + _textmode, + _fontscaling, + _fgcolor, + _bgcolor, + true, + _textBackFill + # if ADAGFX_FONTS_INCLUDED + , _defaultFontId + # endif // if ADAGFX_FONTS_INCLUDED + ); + + if (nullptr != gfxHelper) { + displayOnOff(true); + + gfxHelper->initialize(); + gfxHelper->setRotation(_rotation); + st77xx->fillScreen(_bgcolor); // fill screen with black color + st77xx->setTextColor(_fgcolor, _bgcolor); // set text color to white and black background + + # ifdef P116_SHOW_SPLASH + uint16_t yPos = 0; + gfxHelper->printText(String(F("ESPEasy")).c_str(), 0, yPos, 3, ST77XX_WHITE, ST77XX_BLUE); + yPos += (3 * _fontheight); + gfxHelper->printText(String(F("ST77xx")).c_str(), 0, yPos, 2, ST77XX_BLUE, ST77XX_WHITE); + delay(100); // Splash + # endif // ifdef P116_SHOW_SPLASH + + gfxHelper->setColumnRowMode(bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW)); + st77xx->setTextSize(_fontscaling); // Handles 0 properly, text size, default 1 = very small + st77xx->setCursor(0, 0); // move cursor to position (0, 0) pixel + updateFontMetrics(); + + + if (P116_CONFIG_BUTTON_PIN != -1) { + pinMode(P116_CONFIG_BUTTON_PIN, INPUT_PULLUP); + } + + if (!stringsLoaded) { + LoadCustomTaskSettings(event->TaskIndex, strings, P116_Nlines, 0); + stringsLoaded = true; + + for (uint8_t x = 0; x < P116_Nlines && !stringsHasContent; ++x) { + stringsHasContent = !strings[x].isEmpty(); + } + } + success = true; + } + } + return success; +} + +/**************************************************************************** + * updateFontMetrics: recalculate x and y columns, based on font size and font scale + ***************************************************************************/ +void P116_data_struct::updateFontMetrics() { + if (nullptr != gfxHelper) { + gfxHelper->getTextMetrics(_textcols, _textrows, _fontwidth, _fontheight, _fontscaling, _heightOffset, _xpix, _ypix); + gfxHelper->getColors(_fgcolor, _bgcolor); + } else { + _textcols = _xpix / (_fontwidth * _fontscaling); + _textrows = _ypix / (_fontheight * _fontscaling); + } +} + +/**************************************************************************** + * plugin_exit: De-initialize before destruction + ***************************************************************************/ +bool P116_data_struct::plugin_exit(struct EventStruct *event) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("ST77xx: Exit.")); + # endif // ifndef BUILD_NO_DEBUG + + if ((nullptr != st77xx) && bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_CLEAR_ON_EXIT)) { + st77xx->fillScreen(ADAGFX_BLACK); // fill screen with black color + displayOnOff(false); + } + cleanup(); + return true; +} + +/**************************************************************************** + * cleanup: De-initialize pointers + ***************************************************************************/ +void P116_data_struct::cleanup() { + delete gfxHelper; + gfxHelper = nullptr; + + delete st7735; + st7735 = nullptr; + + delete st7789; + st7789 = nullptr; + st77xx = nullptr; // Only used as a proxy +} + +/**************************************************************************** + * plugin_read: Re-draw the default content + ***************************************************************************/ +bool P116_data_struct::plugin_read(struct EventStruct *event) { + if (nullptr != st77xx) { + if (stringsHasContent) { + gfxHelper->setColumnRowMode(false); // Turn off column mode + + int yPos = 0; + + for (uint8_t x = 0; x < P116_Nlines; ++x) { + const String newString = AdaGFXparseTemplate(strings[x], _textcols, gfxHelper); + + # if ADAGFX_PARSE_SUBCOMMAND + updateFontMetrics(); + # endif // if ADAGFX_PARSE_SUBCOMMAND + + if (yPos < _ypix) { + gfxHelper->printText(newString.c_str(), 0, yPos, _fontscaling, _fgcolor, _bgcolor); + } + delay(0); + yPos += (_fontheight * _fontscaling); + } + gfxHelper->setColumnRowMode(bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_USE_COL_ROW)); // Restore column mode + int16_t curX, curY; + gfxHelper->getCursorXY(curX, curY); // Get current X and Y coordinates, + UserVar.setFloat(event->TaskIndex, 0, curX); // and put into Values + UserVar.setFloat(event->TaskIndex, 1, curY); + } + } + return false; // Always return false, so no attempt to send to + // Controllers or generate events is started +} + +/**************************************************************************** + * plugin_ten_per_second: check button, if any, that wakes up the display + ***************************************************************************/ +bool P116_data_struct::plugin_ten_per_second(struct EventStruct *event) { + if ((P116_CONFIG_BUTTON_PIN != -1) && getButtonState() && (nullptr != st77xx)) { + displayOnOff(true); + markButtonStateProcessed(); + } + return true; +} + +/**************************************************************************** + * plugin_once_a_second: Count down display timer, if any, and turn display off if countdown reached + ***************************************************************************/ +bool P116_data_struct::plugin_once_a_second(struct EventStruct *event) { + if (_displayTimer > 0) { + _displayTimer--; + + if ((nullptr != st77xx) && (_displayTimer == 0)) { + displayOnOff(false); + } + } + return true; +} + +/**************************************************************************** + * plugin_write: Handle commands + ***************************************************************************/ +bool P116_data_struct::plugin_write(struct EventStruct *event, + const String & string) { + bool success = false; + const String cmd = parseString(string, 1); + + if ((nullptr != st77xx) && cmd.equals(_commandTriggerCmd)) { + const String arg1 = parseString(string, 2); + success = true; + + if (equals(arg1, F("off"))) { + displayOnOff(false); + } + else if (equals(arg1, F("on"))) { + displayOnOff(true); + } + else if (equals(arg1, F("clear"))) { + st77xx->fillScreen(_bgcolor); + } + else if (equals(arg1, F("backlight"))) { + if ((P116_CONFIG_BACKLIGHT_PIN != -1) && // All is valid? + (event->Par2 >= 0) && + (event->Par2 <= 100)) { + P116_CONFIG_BACKLIGHT_PERCENT = event->Par2; // Set but don't store + _backlightPercentage = event->Par2; // Also set to current + displayOnOff(true); + } else { + success = false; + } + } else { + success = false; + } + } + else if (st77xx && (cmd.equals(_commandTrigger) || + (gfxHelper && gfxHelper->isAdaGFXTrigger(cmd)))) { + success = true; + + if (!bitRead(P116_CONFIG_FLAGS, P116_CONFIG_FLAG_NO_WAKE)) { // Wake display? + displayOnOff(true); + } + + if (nullptr != gfxHelper) { + String tmp = string; + + // Hand it over after replacing variables + success = gfxHelper->processCommand(AdaGFXparseTemplate(tmp, _textcols, gfxHelper)); + + updateFontMetrics(); // Font or color may have changed + + if (success) { + int16_t curX, curY; + gfxHelper->getCursorXY(curX, curY); // Get current X and Y coordinates, and put into Values + UserVar.setFloat(event->TaskIndex, 0, curX); + UserVar.setFloat(event->TaskIndex, 1, curY); + } + } + } + return success; +} + +# if ADAGFX_ENABLE_GET_CONFIG_VALUE + +/**************************************************************************** + * plugin_get_config_value: Retrieve values like [#] + ***************************************************************************/ +bool P116_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + + if (gfxHelper != nullptr) { + success = gfxHelper->pluginGetConfigValue(string); + } + return success; +} + +# endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE + +/**************************************************************************** + * displayOnOff: Turn display on or off + ***************************************************************************/ +void P116_data_struct::displayOnOff(bool state) { + if (_backlightPin != -1) { + # if defined(ESP8266) + analogWrite(_backlightPin, state ? ((1024 / 100) * _backlightPercentage) : 0); + # endif // if defined(ESP8266) + # if defined(ESP32) + analogWriteESP32(_backlightPin, state ? ((1024 / 100) * _backlightPercentage) : 0, 0); + # endif // if defined(ESP32) + } + st77xx->enableDisplay(state); // Display on + _displayTimer = (state ? _displayTimeout : 0); +} + +/**************************************************************************** + * registerButtonState: the button has been pressed, apply some debouncing + ***************************************************************************/ +void P116_data_struct::registerButtonState(uint8_t newButtonState, + bool bPin3Invers) { + if ((ButtonLastState == 0xFF) || (bPin3Invers != (!!newButtonState))) { + ButtonLastState = newButtonState; + DebounceCounter++; + } else { + ButtonLastState = 0xFF; // Reset + DebounceCounter = 0; + ButtonState = false; + } + + if ((ButtonLastState == newButtonState) && + (DebounceCounter >= P116_DebounceTreshold)) { + ButtonState = true; + } +} + +/**************************************************************************** + * markButtonStateProcessed: reset the button state + ***************************************************************************/ +void P116_data_struct::markButtonStateProcessed() { + ButtonState = false; + DebounceCounter = 0; +} + +#endif // ifdef USES_P116 diff --git a/src/src/PluginStructs/P116_data_struct.h b/src/src/PluginStructs/P116_data_struct.h index 2144ebd8f..b1c511a8a 100644 --- a/src/src/PluginStructs/P116_data_struct.h +++ b/src/src/PluginStructs/P116_data_struct.h @@ -1,179 +1,215 @@ -#ifndef PLUGINSTRUCTS_P116_DATA_STRUCT_H -#define PLUGINSTRUCTS_P116_DATA_STRUCT_H - -#include "../../_Plugin_Helper.h" -#ifdef USES_P116 - -# include // include Adafruit graphics library -# include // include Adafruit ST77xx TFT library -# include // include Adafruit ST7735 TFT library -# include // include Adafruit ST7789 TFT library -# include // include Adafruit ST7796 TFT library - -# include "../Helpers/AdafruitGFX_helper.h" // Use Adafruit graphics helper object -# include "../CustomBuild/StorageLayout.h" - -# define P116_Nlines 24 // The number of different lines which can be displayed -# define P116_Nchars 60 -# define P116_DebounceTreshold 5 // number of 20 msec (fifty per second) ticks before the button has settled - -// # define P116_SHOW_SPLASH // Enable to show splash (text) - -# define P116_CONFIG_BUTTON_PIN PCONFIG(0) // Pin for display-button -# define P116_CONFIG_DISPLAY_TIMEOUT PCONFIG(1) // Time-out when display-button is enable -# define P116_CONFIG_TYPE PCONFIG(2) // Type of device -# define P116_CONFIG_BACKLIGHT_PIN PCONFIG(3) // Backlight pin -# define P116_CONFIG_BACKLIGHT_PERCENT PCONFIG(4) // Backlight percentage -# define P116_CONFIG_COLORS PCONFIG_ULONG(3) // 2 Colors fit in 1 long - -# define P116_CONFIG_FLAGS PCONFIG_ULONG(0) // All flags -# define P116_CONFIG_FLAG_NO_WAKE 0 // Flag: Don't wake display -# define P116_CONFIG_FLAG_INVERT_BUTTON 1 // Flag: Inverted button state -# define P116_CONFIG_FLAG_CLEAR_ON_EXIT 2 // Flag: Clear display on exit -# define P116_CONFIG_FLAG_USE_COL_ROW 3 // Flag: Use Col/Row text addressing in commands -# define P116_CONFIG_FLAG_MODE 4 // Flag-offset to store 4 bits for Mode, uses bits 4, 5, 6 and 7 -# define P116_CONFIG_FLAG_ROTATION 8 // Flag-offset to store 4 bits for Rotation, uses bits 8, 9, 10 and 11 -# define P116_CONFIG_FLAG_FONTSCALE 12 // Flag-offset to store 4 bits for Font scaling, uses bits 12, 13, 14 and 15 -# 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 - -// 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_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) - -# ifdef ESP32 - -// for D32 Pro with TFT connector - # define P116_TFT_CS 14 - # define P116_TFT_CS_HSPI 26 // when connected to Hardware-SPI GPIO-14 is already used - # define P116_TFT_DC 27 - # define P116_TFT_RST -1 // 33 - # define P116_BACKLIGHT_PIN -1 // 15 // D8 -# else // ifdef ESP32 - -// Was: for D1 Mini with shield connection - # define P116_TFT_CS 0 // D3 - # define P116_TFT_DC 4 // D2 - # define P116_TFT_RST -1 // D4 // -1 - # define P116_BACKLIGHT_PIN -1 // D6 // 15 // D8 -> Blocks Wemos -# endif // ifdef ESP32 - -enum class ST77xx_type_e : uint8_t { - ST7735s_128x128 = 0, - ST7735s_128x160 = 1u, - ST7735s_80x160 = 2u, - ST7789vw_240x320 = 3u, - ST7789vw_240x240 = 4u, - ST7789vw_240x280 = 5u, - ST7789vw_135x240 = 6u, - ST7796s_320x480 = 7u, - ST7735s_80x160_M5 = 8u, -}; - -enum class P116_CommandTrigger : uint8_t { - tft = 0u, - st77xx = 1u, - st7735 = 2u, - st7789 = 3u, - st7796 = 4u, -}; - -const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device); -const __FlashStringHelper* P116_CommandTrigger_toString(const P116_CommandTrigger& cmd); -void ST77xx_type_toResolution(const ST77xx_type_e& device, - uint16_t & x, - uint16_t & y); - -struct P116_data_struct : public PluginTaskData_base { -public: - - P116_data_struct(ST77xx_type_e device, - uint8_t rotation, - uint8_t fontscaling, - AdaGFXTextPrintMode textmode, - int8_t backlightPin, - uint8_t backlightPercentage, - uint32_t displayTimer, - String commandTrigger, - uint16_t fgcolor = ADAGFX_WHITE, - uint16_t bgcolor = ADAGFX_BLACK, - bool textBackFill = true); - P116_data_struct() = delete; - virtual ~P116_data_struct(); - - bool plugin_init(struct EventStruct *event); - bool plugin_exit(struct EventStruct *event); - bool plugin_read(struct EventStruct *event); - bool plugin_write(struct EventStruct *event, - const String & string); - # if ADAGFX_ENABLE_GET_CONFIG_VALUE - bool plugin_get_config_value(struct EventStruct *event, - String & string); - # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE - bool plugin_ten_per_second(struct EventStruct *event); - bool plugin_once_a_second(struct EventStruct *event); - - void registerButtonState(uint8_t newButtonState, - bool bPin3Invers); - void markButtonStateProcessed(); - bool getButtonState() { - return ButtonState; - } - -private: - - void displayOnOff(bool state); - void updateFontMetrics(); - void cleanup(); - - Adafruit_ST77xx *st77xx = nullptr; - Adafruit_ST7735 *st7735 = nullptr; - Adafruit_ST7789 *st7789 = nullptr; - Adafruit_ST7796S_kbv *st7796 = nullptr; - AdafruitGFX_helper *gfxHelper = nullptr; - enum ST77xx_type_e _device; - - uint16_t _xpix = 0; - uint16_t _ypix = 0; - uint16_t _textcols = 0; - uint16_t _textrows = 0; - uint8_t _fontwidth = 6; // Default font characteristics - uint8_t _fontheight = 10; - uint8_t _heightOffset = 0; - - uint8_t _rotation; - uint8_t _fontscaling; - AdaGFXTextPrintMode _textmode; - int8_t _backlightPin; - uint8_t _backlightPercentage; - uint32_t _displayTimer; - uint32_t _displayTimeout; - String _commandTrigger; - uint16_t _fgcolor; - uint16_t _bgcolor; - bool _textBackFill; - - String _commandTriggerCmd; - - // Display button - bool ButtonState = false; // button not touched - uint8_t ButtonLastState = 0; // Last state checked (debouncing in progress) - uint8_t DebounceCounter = 0; // debounce counter - - int8_t _leftMarginCompensation = 0; // Not settable yet - int8_t _topMarginCompensation = 0; - - String strings[P116_Nlines]; - bool stringsLoaded = false; - bool stringsHasContent = false; -}; - - -#endif // ifdef USES_P116 -#endif // ifndef PLUGINSTRUCTS_P116_DATA_STRUCT_H +#ifndef PLUGINSTRUCTS_P116_DATA_STRUCT_H +#define PLUGINSTRUCTS_P116_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P116 + +# include // include Adafruit graphics library +# include // include Adafruit ST77xx TFT library +# include // include Adafruit ST7735 TFT library +# include // include Adafruit ST7789 TFT library +# include // include Adafruit ST7796 TFT library + +# if defined(ST7789_EXTRA_INIT) && !ST7789_EXTRA_INIT +# define P116_EXTRA_ST7789 0 // This will get disabled for ESP8266 in Adafruit_ST7789.h +# endif // if defined(ST7789_EXTRA_INIT) && !ST7789_EXTRA_INIT +# if defined(LIMIT_BUILD_SIZE) and !defined(P116_EXTRA_ST7789) +# define P116_EXTRA_ST7789 0 +# endif // if defined(LIMIT_BUILD_SIZE) and !defined(P116_EXTRA_ST7789) +# ifndef P116_EXTRA_ST7789 +# define P116_EXTRA_ST7789 0 // Disabled by default (not verified on any hardware yet) +# endif // ifndef P116_EXTRA_ST7789 +# if defined(ST7735_EXTRA_INIT) && !ST7735_EXTRA_INIT +# define P116_EXTRA_ST7735 0 // This will get disabled for ESP8266 in Adafruit_ST7735.h +# endif // if defined(ST7735_EXTRA_INIT) && !ST7735_EXTRA_INIT +# if defined(LIMIT_BUILD_SIZE) and !defined(P116_EXTRA_ST7789) +# define P116_EXTRA_ST7735 0 +# endif // if defined(LIMIT_BUILD_SIZE) and !defined(P116_EXTRA_ST7735) +# ifndef P116_EXTRA_ST7735 +# define P116_EXTRA_ST7735 1 +# endif // ifndef P116_EXTRA_ST7735 + +# include "../Helpers/AdafruitGFX_helper.h" // Use Adafruit graphics helper object +# include "../CustomBuild/StorageLayout.h" + +# define P116_Nlines 24 // The number of different lines which can be displayed +# define P116_Nchars 60 +# define P116_DebounceTreshold 5 // number of 20 msec (fifty per second) ticks before the button has settled + +// # define P116_SHOW_SPLASH // Enable to show splash (text) + +# define P116_CONFIG_BUTTON_PIN PCONFIG(0) // Pin for display-button +# define P116_CONFIG_DISPLAY_TIMEOUT PCONFIG(1) // Time-out when display-button is enable +# define P116_CONFIG_TYPE PCONFIG(2) // Type of device +# define P116_CONFIG_BACKLIGHT_PIN PCONFIG(3) // Backlight pin +# define P116_CONFIG_BACKLIGHT_PERCENT PCONFIG(4) // Backlight percentage +# define P116_CONFIG_DEFAULT_FONT PCONFIG(5) // Default font +# define P116_CONFIG_COLORS PCONFIG_ULONG(3) // 2 Colors fit in 1 long + +# define P116_CONFIG_FLAGS PCONFIG_ULONG(0) // All flags +# define P116_CONFIG_FLAG_NO_WAKE 0 // Flag: Don't wake display +# define P116_CONFIG_FLAG_INVERT_BUTTON 1 // Flag: Inverted button state +# define P116_CONFIG_FLAG_CLEAR_ON_EXIT 2 // Flag: Clear display on exit +# define P116_CONFIG_FLAG_USE_COL_ROW 3 // Flag: Use Col/Row text addressing in commands +# define P116_CONFIG_FLAG_MODE 4 // Flag-offset to store 4 bits for Mode, uses bits 4, 5, 6 and 7 +# define P116_CONFIG_FLAG_ROTATION 8 // Flag-offset to store 4 bits for Rotation, uses bits 8, 9, 10 and 11 +# define P116_CONFIG_FLAG_FONTSCALE 12 // Flag-offset to store 4 bits for Font scaling, uses bits 12, 13, 14 and 15 +# 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 + +// 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_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) + +# ifdef ESP32 + +// for D32 Pro with TFT connector + # define P116_TFT_CS 14 + # define P116_TFT_CS_HSPI 26 // when connected to Hardware-SPI GPIO-14 is already used + # define P116_TFT_DC 27 + # define P116_TFT_RST -1 // 33 + # define P116_BACKLIGHT_PIN -1 // 15 // D8 +# else // ifdef ESP32 + +// Was: for D1 Mini with shield connection + # define P116_TFT_CS 0 // D3 + # define P116_TFT_DC 4 // D2 + # define P116_TFT_RST -1 // D4 // -1 + # define P116_BACKLIGHT_PIN -1 // D6 // 15 // D8 -> Blocks Wemos +# endif // ifdef ESP32 + +enum class ST77xx_type_e : uint8_t { + ST7735s_128x128 = 0, + ST7735s_128x160 = 1u, + ST7735s_80x160 = 2u, + ST7789vw_240x320 = 3u, + ST7789vw_240x240 = 4u, + ST7789vw_240x280 = 5u, + ST7789vw_135x240 = 6u, + ST7796s_320x480 = 7u, + ST7735s_80x160_M5 = 8u, + # if P116_EXTRA_ST7789 + ST7789vw1_135x240 = 9u, + ST7789vw2_135x240 = 10u, + ST7789vw3_135x240 = 11u, + # endif // if P116_EXTRA_ST7789 + # if P116_EXTRA_ST7735 + ST7735s_135x240 = 12u, + # endif // if P116_EXTRA_ST7735 +}; + +enum class P116_CommandTrigger : uint8_t { + tft = 0u, + st77xx = 1u, + st7735 = 2u, + st7789 = 3u, + st7796 = 4u, +}; + +const __FlashStringHelper* ST77xx_type_toString(const ST77xx_type_e& device); +const __FlashStringHelper* P116_CommandTrigger_toString(const P116_CommandTrigger& cmd); +void ST77xx_type_toResolution(const ST77xx_type_e& device, + uint16_t & x, + uint16_t & y); + +struct P116_data_struct : public PluginTaskData_base { +public: + + P116_data_struct(ST77xx_type_e device, + uint8_t rotation, + uint8_t fontscaling, + AdaGFXTextPrintMode textmode, + int8_t backlightPin, + uint8_t backlightPercentage, + uint32_t displayTimer, + String commandTrigger, + uint16_t fgcolor = ADAGFX_WHITE, + uint16_t bgcolor = ADAGFX_BLACK, + bool textBackFill = true + # if ADAGFX_FONTS_INCLUDED + , + const uint8_t defaultFontId = 0 + # endif // if ADAGFX_FONTS_INCLUDED + ); + P116_data_struct() = delete; + virtual ~P116_data_struct(); + + bool plugin_init(struct EventStruct *event); + bool plugin_exit(struct EventStruct *event); + bool plugin_read(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + const String & string); + # if ADAGFX_ENABLE_GET_CONFIG_VALUE + bool plugin_get_config_value(struct EventStruct *event, + String & string); + # endif // if ADAGFX_ENABLE_GET_CONFIG_VALUE + bool plugin_ten_per_second(struct EventStruct *event); + bool plugin_once_a_second(struct EventStruct *event); + + void registerButtonState(uint8_t newButtonState, + bool bPin3Invers); + void markButtonStateProcessed(); + bool getButtonState() { + return ButtonState; + } + +private: + + void displayOnOff(bool state); + void updateFontMetrics(); + void cleanup(); + + Adafruit_ST77xx *st77xx = nullptr; + Adafruit_ST7735 *st7735 = nullptr; + Adafruit_ST7789 *st7789 = nullptr; + Adafruit_ST7796S_kbv *st7796 = nullptr; + AdafruitGFX_helper *gfxHelper = nullptr; + enum ST77xx_type_e _device; + + uint16_t _xpix = 0; + uint16_t _ypix = 0; + uint16_t _textcols = 0; + uint16_t _textrows = 0; + uint8_t _fontwidth = 6; // Default font characteristics + uint8_t _fontheight = 10; + uint8_t _heightOffset = 0; + + uint8_t _rotation; + uint8_t _fontscaling; + AdaGFXTextPrintMode _textmode; + int8_t _backlightPin; + uint8_t _backlightPercentage; + uint32_t _displayTimer; + uint32_t _displayTimeout; + String _commandTrigger; + uint16_t _fgcolor; + uint16_t _bgcolor; + bool _textBackFill; + # if ADAGFX_FONTS_INCLUDED + uint8_t _defaultFontId; + # endif // if ADAGFX_FONTS_INCLUDED + + String _commandTriggerCmd; + + // Display button + bool ButtonState = false; // button not touched + uint8_t ButtonLastState = 0; // Last state checked (debouncing in progress) + uint8_t DebounceCounter = 0; // debounce counter + + int8_t _leftMarginCompensation = 0; // Not settable yet + int8_t _topMarginCompensation = 0; + + String strings[P116_Nlines]; + bool stringsLoaded = false; + bool stringsHasContent = false; +}; + + +#endif // ifdef USES_P116 +#endif // ifndef PLUGINSTRUCTS_P116_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P118_data_struct.cpp b/src/src/PluginStructs/P118_data_struct.cpp index ce759471d..a33615c8d 100644 --- a/src/src/PluginStructs/P118_data_struct.cpp +++ b/src/src/PluginStructs/P118_data_struct.cpp @@ -1,726 +1,716 @@ -#include "../PluginStructs/P118_data_struct.h" - -#ifdef USES_P118 - -// **************************************************************************/ -// Constructor -// **************************************************************************/ -P118_data_struct::P118_data_struct(int8_t csPin, - int8_t irqPin, - bool logData, - bool rfLog) - : _csPin(csPin), _irqPin(irqPin), _log(logData), _rfLog(rfLog) {} - -// **************************************************************************/ -// Destructor -// **************************************************************************/ -P118_data_struct::~P118_data_struct() { - delete _rf; - _rf = nullptr; -} - -bool P118_data_struct::plugin_init(struct EventStruct *event) { - bool success = false; - - LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&_ExtraSettings, sizeof(_ExtraSettings)); - # ifdef P118_DEBUG_LOG - addLog(LOG_LEVEL_INFO, F("ITHO: Extra Settings PLUGIN_118 loaded")); - # endif // ifdef P118_DEBUG_LOG - - int8_t spi_pins[3]; - uint32_t startInit = 0; - - if (Settings.getSPI_pins(spi_pins) && validGpio(spi_pins[1])) { - startInit = millis(); - _rf = new (std::nothrow) IthoCC1101(_csPin, spi_pins[1]); // Pass CS and MISO - } else { - addLog(LOG_LEVEL_ERROR, F("ITHO: SPI configuration not correct!")); - } - - if (nullptr != _rf) { - success = true; - # if P118_FEATURE_ORCON - _rf->enableOrcon(P118_CONFIG_ORCON == 1); // Enabled? - # endif // if P118_FEATURE_ORCON - - // DeviceID used to send commands, can also be changed on the fly for multi itho control, 10,87,81 corresponds with old library - _rf->setDeviceID(P118_CONFIG_DEVID1, P118_CONFIG_DEVID2, P118_CONFIG_DEVID3); - _rf->init(); - - const long duration = timePassedSince(startInit); - if (duration > P118_TIMEOUT_LIMIT) { - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - addLogMove(LOG_LEVEL_ERROR, strformat( - F("ITHO: Init duration was: %d msec. suggesting that the CC1101 board is not (correctly) connected."), - duration)); - } - success = false; - } - - if (success) { - if (validGpio(_irqPin)) { - attachInterruptArg(digitalPinToInterrupt(_irqPin), - reinterpret_cast(ISR_ithoCheck), - this, - FALLING); - addLog(LOG_LEVEL_INFO, F("ITHO: Interrupts enabled.")); - } else { - addLog(LOG_LEVEL_ERROR, F("ITHO: Interrupt pin disabled, sending is OK, not receiving data!")); - } - _rf->initReceive(); - _InitRunned = true; - } - } - return success; -} - -bool P118_data_struct::plugin_exit(struct EventStruct *event) { - // remove interupt when plugin is removed - if (validGpio(_irqPin)) { - detachInterrupt(digitalPinToInterrupt(_irqPin)); - } - - return true; -} - -bool P118_data_struct::plugin_once_a_second(struct EventStruct *event) { - // decrement timer when timermode is running - if ((_State >= 10) && (_Timer > 0)) { - _Timer--; - - if (_Timer == 0) { _Timer--; } - } - - // if timer has elapsed set Fan state to low - if ((_State >= 10) && (_Timer < 0)) - { - if (_State < 100) { - _State = 1; // Itho low - } else { - _State = 101; // Orcon low - } - _Timer = 0; // Avoid doing this again - } - - // Publish new data when vars are changed or init has runned or timer is running (update every 2 sec) - if ((_OldState != _State) || ((_Timer > 0) && (_Timer % 2 == 0)) || - (_OldLastIDindex != _LastIDindex) || _InitRunned) - { - # ifdef P118_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, F("ITHO: UPDATE by PLUGIN_ONCE_A_SECOND")); - # endif // ifdef P118_DEBUG_LOG - PublishData(event); - sendData(event); - - // reset flag set by init - _InitRunned = false; - } - - // Remeber current state for next cycle - _OldState = _State; - _OldLastIDindex = _LastIDindex; - return true; -} - -bool P118_data_struct::plugin_fifty_per_second(struct EventStruct *event) { - if (_Int) { - ITHOcheck(); - _Int = false; // reset flag - } - - return true; -} - -bool P118_data_struct::plugin_read(struct EventStruct *event) { - // This ensures that even when Values are not changing, data is send at the configured interval for aquisition - # ifdef P118_DEBUG_LOG - addLog(LOG_LEVEL_DEBUG, F("ITHO: UPDATE by PLUGIN_READ")); - # endif // ifdef P118_DEBUG_LOG - PublishData(event); - - return true; -} - -bool P118_data_struct::plugin_write(struct EventStruct *event, const String& string) { - bool success = false; - String cmd = parseString(string, 1); - - bool stateCmd = equals(cmd, F("state")); - - 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.")); } - # endif // ifndef BUILD_NO_DEBUG - success = true; - - switch (event->Par1) { - case 1111: // Join command - { - _rf->sendCommand(IthoJoin); - _rf->initReceive(); - PluginWriteLog(F("join")); - break; - } - case 9999: // Leave command - { - _rf->sendCommand(IthoLeave); - _rf->initReceive(); - PluginWriteLog(F("leave")); - break; - } - case 0: // Off command - { - _rf->sendCommand(IthoStandby); - _State = 0; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("standby")); - break; - } - case 1: // Fan low - { - _rf->sendCommand(IthoLow); - _State = 1; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("low speed")); - break; - } - case 2: // Fan medium - { - _rf->sendCommand(IthoMedium); - _State = 2; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("medium speed")); - break; - } - case 3: // Fan high - { - _rf->sendCommand(IthoHigh); - _State = 3; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("high speed")); - break; - } - case 4: // Fan full - { - _rf->sendCommand(IthoFull); - _State = 4; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("full speed")); - break; - } - case 13: // Timer1 - 10 min - { - _rf->sendCommand(IthoTimer1); - _State = 13; - _Timer = PLUGIN_118_Time1; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("timer 1")); - break; - } - case 23: // Timer2 - 20 min - { - _rf->sendCommand(IthoTimer2); - _State = 23; - _Timer = PLUGIN_118_Time2; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("timer 2")); - break; - } - case 33: // Timer3 - 30 min - { - _rf->sendCommand(IthoTimer3); - _State = 33; - _Timer = PLUGIN_118_Time3; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("timer 3")); - break; - } - # if P118_FEATURE_ORCON - case 100: // Fan StandBy - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconStandBy, srcID, destID); - _State = 100; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon standBy")); - break; - } - case 101: // Fan low - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconLow, srcID, destID); - _State = 101; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon low speed")); - break; - } - case 102: // Fan medium - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconMedium, srcID, destID); - _State = 102; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon medium speed")); - break; - } - case 103: // Fan high - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconHigh, srcID, destID); - _State = 103; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon high speed")); - break; - } - case 104: // Fan auto - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconAuto, srcID, destID); - _State = 104; - _Timer = 0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon auto speed")); - break; - } - case 110: // Timer 12*60 minutes @ speed 0 - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconTimer0, srcID, destID); - _State = 110; - _Timer = PLUGIN_118_OrconTime0; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon Timer 0")); - break; - } - case 111: // Timer 60 minutes @ speed 1 - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconTimer1, srcID, destID); - _State = 111; - _Timer = PLUGIN_118_OrconTime1; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon Timer 1")); - break; - } - case 112: // Timer 13*60 minutes @ speed 2 - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconTimer2, srcID, destID); - _State = 112; - _Timer = PLUGIN_118_OrconTime2; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon Timer 2")); - break; - } - case 113: // Timer 60 minutes @ speed 3 - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconTimer3, srcID, destID); - _State = 113; - _Timer = PLUGIN_118_OrconTime3; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon Timer 3")); - break; - } - case 114: - { - uint8_t srcID[3], destID[3]; - SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); - _rf->sendCommand(OrconAutoCO2, srcID, destID); - _State = 114; - _Timer = PLUGIN_118_OrconTime3; - _LastIDindex = 0; - _rf->initReceive(); - PluginWriteLog(F("Orcon Auto CO2")); - break; - } - # else // if P118_FEATURE_ORCON - case 100: - case 101: - case 102: - case 103: - case 104: - case 110: - case 111: - case 112: - case 113: - case 114: - PluginWriteLog(F("Orcon support not included!")); - success = false; - break; - # endif // if P118_FEATURE_ORCON - default: - { - PluginWriteLog(F("INVALID")); - success = false; - break; - } - } - } - return success; -} - -void P118_data_struct::ITHOcheck() { - bool _dbgLog = _log - # ifndef BUILD_NO_DEBUG - && loglevelActiveFor(LOG_LEVEL_DEBUG) - # endif // ifndef BUILD_NO_DEBUG - ; - - # ifndef BUILD_NO_DEBUG - - if (_dbgLog) { - addLog(LOG_LEVEL_DEBUG, "ITHO: RF signal received"); // All logs statements contain if-statement to disable logging to - } // reduce log clutter when many RF sources are present - # endif // ifndef BUILD_NO_DEBUG - - if (_rf->checkForNewPacket()) { - IthoCommand cmd = _rf->getLastCommand(); - String Id = _rf->getLastIDstr(); - - if (_rfLog && loglevelActiveFor(LOG_LEVEL_INFO)) { - addLogMove(LOG_LEVEL_INFO, strformat( - F("ITHO: Received from ID: %s ; raw cmd: %d"), - Id.c_str(), cmd)); - } - - // Move check here to prevent function calling within ISR - byte index = 0; - - if (Id == _ExtraSettings.ID1) { - index = 1; - } - else if (Id == _ExtraSettings.ID2) { - index = 2; - } - else if (Id == _ExtraSettings.ID3) { - index = 3; - } - - String log; - - if (index > 0) { - if (_dbgLog) { - log += strformat(F("Command received from remote-ID: %s , command: "), Id.c_str()); - } - - switch (cmd) { - case IthoUnknown: - - if (_dbgLog) { log += F("unknown"); } - break; - case IthoStandby: - case DucoStandby: - - if (_dbgLog) { log += F("standby"); } - _State = 0; - _Timer = 0; - _LastIDindex = index; - break; - case IthoLow: - case DucoLow: - - if (_dbgLog) { log += F("low"); } - _State = 1; - _Timer = 0; - _LastIDindex = index; - break; - case IthoMedium: - case DucoMedium: - - if (_dbgLog) { log += F("medium"); } - _State = 2; - _Timer = 0; - _LastIDindex = index; - break; - case IthoHigh: - case DucoHigh: - - if (_dbgLog) { log += F("high"); } - _State = 3; - _Timer = 0; - _LastIDindex = index; - break; - case IthoFull: - - if (_dbgLog) { log += F("full"); } - _State = 4; - _Timer = 0; - _LastIDindex = index; - break; - case IthoTimer1: - - if (_dbgLog) { log += F("timer1"); } - _State = 13; - _Timer = PLUGIN_118_Time1; - _LastIDindex = index; - break; - case IthoTimer2: - - if (_dbgLog) { log += F("timer2"); } - _State = 23; - _Timer = PLUGIN_118_Time2; - _LastIDindex = index; - break; - case IthoTimer3: - - if (_dbgLog) { log += F("timer3"); } - _State = 33; - _Timer = PLUGIN_118_Time3; - _LastIDindex = index; - break; - case IthoJoin: - - if (_dbgLog) { log += F("join"); } - break; - case IthoLeave: - - if (_dbgLog) { log += F("leave"); } - break; - # if P118_FEATURE_ORCON - case OrconStandBy: - - if (_dbgLog) { log += F("Orcon standby"); } - _State = 100; - _Timer = 0; - _LastIDindex = index; - break; - case OrconLow: - - if (_dbgLog) { log += F("Orcon low"); } - _State = 101; - _Timer = 0; - _LastIDindex = index; - break; - case OrconMedium: - - if (_dbgLog) { log += F("Orcon medium"); } - _State = 102; - _Timer = 0; - _LastIDindex = index; - break; - case OrconHigh: - - if (_dbgLog) { log += F("Orcon high"); } - _State = 103; - _Timer = 0; - _LastIDindex = index; - break; - case OrconAuto: - - if (_dbgLog) { log += F("Orcon auto"); } - _State = 104; - _Timer = 0; - _LastIDindex = index; - break; - case OrconTimer0: - - if (_dbgLog) { log += F("Orcon Timer0"); } - _State = 110; - _Timer = PLUGIN_118_OrconTime0; - _LastIDindex = index; - break; - case OrconTimer1: - - if (_dbgLog) { log += F("Orcon Timer1"); } - _State = 111; - _Timer = PLUGIN_118_OrconTime1; - _LastIDindex = index; - break; - case OrconTimer2: - - if (_dbgLog) { log += F("Orcon Timer2"); } - _State = 112; - _Timer = PLUGIN_118_OrconTime2; - _LastIDindex = index; - break; - case OrconTimer3: - - if (_dbgLog) { log += F("Orcon Timer3"); } - _State = 113; - _Timer = PLUGIN_118_OrconTime3; - _LastIDindex = index; - break; - case OrconAutoCO2: - - if (_dbgLog) { log += F("Orcon AutoCO2"); } - _State = 114; - _Timer = 0; - _LastIDindex = index; - break; - # else // if P118_FEATURE_ORCON - case OrconStandBy: - case OrconLow: - case OrconMedium: - case OrconHigh: - case OrconAuto: - case OrconTimer0: - case OrconTimer1: - case OrconTimer2: - case OrconTimer3: - case OrconAutoCO2: - break; - # endif // if P118_FEATURE_ORCON - } - } else { - if (_dbgLog) { - log += F("Device-ID: "); - log += Id; - log += F(" IGNORED"); - } - } - - # ifndef BUILD_NO_DEBUG - - if (_dbgLog) { - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG - } -} - -void P118_data_struct::PublishData(struct EventStruct *event) { - UserVar.setFloat(event->TaskIndex, 0, _State); - UserVar.setFloat(event->TaskIndex, 1, _Timer); - UserVar.setFloat(event->TaskIndex, 2, _LastIDindex); - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("State: "); - - log += UserVar[event->BaseVarIndex]; - addLog(LOG_LEVEL_DEBUG, log); - log.clear(); - log += F("Timer: "); - log += UserVar[event->BaseVarIndex + 1]; - addLog(LOG_LEVEL_DEBUG, log); - log.clear(); - log += F("LastIDindex: "); - log += UserVar[event->BaseVarIndex + 2]; - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG -} - -void P118_data_struct::PluginWriteLog(const String& command) { - String log = F("Send Itho" - # if P118_FEATURE_ORCON - "/Orcon" - # endif // if P118_FEATURE_ORCON - " command for: "); - - log += command; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - addLog(LOG_LEVEL_INFO, log); - } - printWebString += log; -} - -// **************************************************************************/ -// Interrupt handler -// **************************************************************************/ -void P118_data_struct::ISR_ithoCheck(P118_data_struct *self) { - self->_Int = true; -} - -# if P118_FEATURE_ORCON - -/** - * Orcon specific: Set Destination ID - */ -void P118_data_struct::SetDestIDSrcID(struct EventStruct *event, uint8_t (& srcID)[3], uint8_t (& destID)[3], char (& tmpTmpID)[9]) -{ - destID[0] = PCONFIG(1) - 0; - destID[1] = PCONFIG(2) - 0; - destID[2] = PCONFIG(3) - 0; - - const char *delimiter = ","; - char *token; - - // char tmpID[9] = PLUGIN_118_ExtraSettings.ID1; // copy needed otherwise we modify PLUGIN_118_ExtraSettings.ID1 itself - char tmpID[9]; - - memcpy(tmpID, tmpTmpID, 9); - token = strtok(tmpID, delimiter); // select the first part - - if (token) { - srcID[0] = strtol(token, NULL, 16); // convert first string part (hex) to int - } else { - srcID[0] = 0; - } - token = strtok(NULL, delimiter); - - if (token) { - srcID[1] = strtol(token, NULL, 16); // convert first string part (hex) to int - } else { - srcID[1] = 0; - } - token = strtok(NULL, delimiter); - - if (token) { - srcID[2] = strtol(token, NULL, 16); // convert first string part (hex) to int - } else { - srcID[2] = 0; - } - - # ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("srcID: "); - log += static_cast(srcID[0]); - log += ','; - log += static_cast(srcID[1]); - log += ','; - log += static_cast(srcID[2]); - log += F(" destID: "); - log += static_cast(destID[0]); - log += ','; - log += static_cast(destID[1]); - log += ','; - log += static_cast(destID[2]); - addLogMove(LOG_LEVEL_DEBUG, log); - } - # endif // ifndef BUILD_NO_DEBUG -} - -# endif // if P118_FEATURE_ORCON - -#endif // ifdef USES_P118 +#include "../PluginStructs/P118_data_struct.h" + +#ifdef USES_P118 + +// **************************************************************************/ +// Constructor +// **************************************************************************/ +P118_data_struct::P118_data_struct(int8_t csPin, + int8_t irqPin, + bool logData, + bool rfLog) + : _csPin(csPin), _irqPin(irqPin), _log(logData), _rfLog(rfLog) {} + +// **************************************************************************/ +// Destructor +// **************************************************************************/ +P118_data_struct::~P118_data_struct() { + delete _rf; + _rf = nullptr; +} + +bool P118_data_struct::plugin_init(struct EventStruct *event) { + bool success = false; + + LoadCustomTaskSettings(event->TaskIndex, (uint8_t *)&_ExtraSettings, sizeof(_ExtraSettings)); + # ifdef P118_DEBUG_LOG + addLog(LOG_LEVEL_INFO, F("ITHO: Extra Settings PLUGIN_118 loaded")); + # endif // ifdef P118_DEBUG_LOG + + int8_t spi_pins[3]; + uint32_t startInit = 0; + + if (Settings.getSPI_pins(spi_pins) && validGpio(spi_pins[1])) { + startInit = millis(); + _rf = new (std::nothrow) IthoCC1101(_csPin, spi_pins[1]); // Pass CS and MISO + } else { + addLog(LOG_LEVEL_ERROR, F("ITHO: SPI configuration not correct!")); + } + + if (nullptr != _rf) { + success = true; + # if P118_FEATURE_ORCON + _rf->enableOrcon(P118_CONFIG_ORCON == 1); // Enabled? + # endif // if P118_FEATURE_ORCON + + // DeviceID used to send commands, can also be changed on the fly for multi itho control, 10,87,81 corresponds with old library + _rf->setDeviceID(P118_CONFIG_DEVID1, P118_CONFIG_DEVID2, P118_CONFIG_DEVID3); + _rf->init(); + + const long duration = timePassedSince(startInit); + + if (duration > P118_TIMEOUT_LIMIT) { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat( + F("ITHO: Init duration was: %d msec. suggesting that the CC1101 board is not (correctly) connected."), + duration)); + } + success = false; + } + + if (success) { + if (validGpio(_irqPin)) { + attachInterruptArg(digitalPinToInterrupt(_irqPin), + reinterpret_cast(ISR_ithoCheck), + this, + FALLING); + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, F("ITHO: Interrupts enabled.")); + # endif // ifndef BUILD_NO_DEBUG + } else { + addLog(LOG_LEVEL_ERROR, F("ITHO: Interrupt pin disabled, sending is OK, not receiving data!")); + } + _rf->initReceive(); + _InitRunned = true; + } + } + return success; +} + +bool P118_data_struct::plugin_exit(struct EventStruct *event) { + // remove interupt when plugin is removed + if (validGpio(_irqPin)) { + detachInterrupt(digitalPinToInterrupt(_irqPin)); + } + + return true; +} + +bool P118_data_struct::plugin_once_a_second(struct EventStruct *event) { + // decrement timer when timermode is running + if ((_State >= 10) && (_Timer > 0)) { + _Timer--; + + if (_Timer == 0) { _Timer--; } + } + + // if timer has elapsed set Fan state to low + if ((_State >= 10) && (_Timer < 0)) + { + if (_State < 100) { + _State = 1; // Itho low + } else { + _State = 101; // Orcon low + } + _Timer = 0; // Avoid doing this again + } + + // Publish new data when vars are changed or init has runned or timer is running (update every 2 sec) + if ((_OldState != _State) || ((_Timer > 0) && (_Timer % 2 == 0)) || + (_OldLastIDindex != _LastIDindex) || _InitRunned) + { + # ifdef P118_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("ITHO: UPDATE by PLUGIN_ONCE_A_SECOND")); + # endif // ifdef P118_DEBUG_LOG + PublishData(event); + sendData(event); + + // reset flag set by init + _InitRunned = false; + } + + // Remeber current state for next cycle + _OldState = _State; + _OldLastIDindex = _LastIDindex; + return true; +} + +bool P118_data_struct::plugin_fifty_per_second(struct EventStruct *event) { + if (_Int) { + ITHOcheck(); + _Int = false; // reset flag + } + + return true; +} + +bool P118_data_struct::plugin_read(struct EventStruct *event) { + // This ensures that even when Values are not changing, data is send at the configured interval for aquisition + # ifdef P118_DEBUG_LOG + addLog(LOG_LEVEL_DEBUG, F("ITHO: UPDATE by PLUGIN_READ")); + # endif // ifdef P118_DEBUG_LOG + PublishData(event); + + return true; +} + +bool P118_data_struct::plugin_write(struct EventStruct *event, const String& string) { + bool success = false; + const String cmd = parseString(string, 1); + + const bool stateCmd = equals(cmd, F("state")); + + 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.")); } + # endif // ifndef BUILD_NO_DEBUG + success = true; + + switch (event->Par1) { + case 1111: // Join command + { + _rf->sendCommand(IthoJoin); + _rf->initReceive(); + PluginWriteLog(F("join")); + break; + } + case 9999: // Leave command + { + _rf->sendCommand(IthoLeave); + _rf->initReceive(); + PluginWriteLog(F("leave")); + break; + } + case 0: // Off command + { + _rf->sendCommand(IthoStandby); + _State = 0; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("standby")); + break; + } + case 1: // Fan low + { + _rf->sendCommand(IthoLow); + _State = 1; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("low speed")); + break; + } + case 2: // Fan medium + { + _rf->sendCommand(IthoMedium); + _State = 2; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("medium speed")); + break; + } + case 3: // Fan high + { + _rf->sendCommand(IthoHigh); + _State = 3; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("high speed")); + break; + } + case 4: // Fan full + { + _rf->sendCommand(IthoFull); + _State = 4; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("full speed")); + break; + } + case 13: // Timer1 - 10 min + { + _rf->sendCommand(IthoTimer1); + _State = 13; + _Timer = PLUGIN_118_Time1; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("timer 1")); + break; + } + case 23: // Timer2 - 20 min + { + _rf->sendCommand(IthoTimer2); + _State = 23; + _Timer = PLUGIN_118_Time2; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("timer 2")); + break; + } + case 33: // Timer3 - 30 min + { + _rf->sendCommand(IthoTimer3); + _State = 33; + _Timer = PLUGIN_118_Time3; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("timer 3")); + break; + } + # if P118_FEATURE_ORCON + case 100: // Fan StandBy + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconStandBy, srcID, destID); + _State = 100; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon standBy")); + break; + } + case 101: // Fan low + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconLow, srcID, destID); + _State = 101; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon low speed")); + break; + } + case 102: // Fan medium + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconMedium, srcID, destID); + _State = 102; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon medium speed")); + break; + } + case 103: // Fan high + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconHigh, srcID, destID); + _State = 103; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon high speed")); + break; + } + case 104: // Fan auto + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconAuto, srcID, destID); + _State = 104; + _Timer = 0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon auto speed")); + break; + } + case 110: // Timer 12*60 minutes @ speed 0 + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconTimer0, srcID, destID); + _State = 110; + _Timer = PLUGIN_118_OrconTime0; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon Timer 0")); + break; + } + case 111: // Timer 60 minutes @ speed 1 + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconTimer1, srcID, destID); + _State = 111; + _Timer = PLUGIN_118_OrconTime1; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon Timer 1")); + break; + } + case 112: // Timer 13*60 minutes @ speed 2 + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconTimer2, srcID, destID); + _State = 112; + _Timer = PLUGIN_118_OrconTime2; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon Timer 2")); + break; + } + case 113: // Timer 60 minutes @ speed 3 + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconTimer3, srcID, destID); + _State = 113; + _Timer = PLUGIN_118_OrconTime3; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon Timer 3")); + break; + } + case 114: + { + uint8_t srcID[3], destID[3]; + SetDestIDSrcID(event, srcID, destID, _ExtraSettings.ID1); + _rf->sendCommand(OrconAutoCO2, srcID, destID); + _State = 114; + _Timer = PLUGIN_118_OrconTime3; + _LastIDindex = 0; + _rf->initReceive(); + PluginWriteLog(F("Orcon Auto CO2")); + break; + } + # else // if P118_FEATURE_ORCON + case 100: + case 101: + case 102: + case 103: + case 104: + case 110: + case 111: + case 112: + case 113: + case 114: + PluginWriteLog(F("Orcon support not included!")); + success = false; + break; + # endif // if P118_FEATURE_ORCON + default: + { + PluginWriteLog(F("INVALID")); + success = false; + break; + } + } + } + return success; +} + +void P118_data_struct::ITHOcheck() { + bool _dbgLog = _log + # ifndef BUILD_NO_DEBUG + && loglevelActiveFor(LOG_LEVEL_DEBUG) + # endif // ifndef BUILD_NO_DEBUG + ; + + # ifndef BUILD_NO_DEBUG + + if (_dbgLog) { + addLog(LOG_LEVEL_DEBUG, "ITHO: RF signal received"); // All logs statements contain if-statement to disable logging to + } // reduce log clutter when many RF sources are present + # endif // ifndef BUILD_NO_DEBUG + + if (_rf->checkForNewPacket()) { + const IthoCommand cmd = _rf->getLastCommand(); + const String Id = _rf->getLastIDstr(); + + if (_rfLog && loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, strformat( + F("ITHO: Received from ID: %s ; raw cmd: %d"), + Id.c_str(), cmd)); + } + + // Move check here to prevent function calling within ISR + byte index = 0; + + if (Id == _ExtraSettings.ID1) { + index = 1; + } + else if (Id == _ExtraSettings.ID2) { + index = 2; + } + else if (Id == _ExtraSettings.ID3) { + index = 3; + } + + String log; + + if (index > 0) { + if (_dbgLog) { + log += strformat(F("Command received from remote-ID: %s , command: "), Id.c_str()); + } + + switch (cmd) { + case IthoUnknown: + + if (_dbgLog) { log += F("unknown"); } + break; + case IthoStandby: + case DucoStandby: + + if (_dbgLog) { log += F("standby"); } + _State = 0; + _Timer = 0; + _LastIDindex = index; + break; + case IthoLow: + case DucoLow: + + if (_dbgLog) { log += F("low"); } + _State = 1; + _Timer = 0; + _LastIDindex = index; + break; + case IthoMedium: + case DucoMedium: + + if (_dbgLog) { log += F("medium"); } + _State = 2; + _Timer = 0; + _LastIDindex = index; + break; + case IthoHigh: + case DucoHigh: + + if (_dbgLog) { log += F("high"); } + _State = 3; + _Timer = 0; + _LastIDindex = index; + break; + case IthoFull: + + if (_dbgLog) { log += F("full"); } + _State = 4; + _Timer = 0; + _LastIDindex = index; + break; + case IthoTimer1: + + if (_dbgLog) { log += F("timer1"); } + _State = 13; + _Timer = PLUGIN_118_Time1; + _LastIDindex = index; + break; + case IthoTimer2: + + if (_dbgLog) { log += F("timer2"); } + _State = 23; + _Timer = PLUGIN_118_Time2; + _LastIDindex = index; + break; + case IthoTimer3: + + if (_dbgLog) { log += F("timer3"); } + _State = 33; + _Timer = PLUGIN_118_Time3; + _LastIDindex = index; + break; + case IthoJoin: + + if (_dbgLog) { log += F("join"); } + break; + case IthoLeave: + + if (_dbgLog) { log += F("leave"); } + break; + # if P118_FEATURE_ORCON + case OrconStandBy: + + if (_dbgLog) { log += F("Orcon standby"); } + _State = 100; + _Timer = 0; + _LastIDindex = index; + break; + case OrconLow: + + if (_dbgLog) { log += F("Orcon low"); } + _State = 101; + _Timer = 0; + _LastIDindex = index; + break; + case OrconMedium: + + if (_dbgLog) { log += F("Orcon medium"); } + _State = 102; + _Timer = 0; + _LastIDindex = index; + break; + case OrconHigh: + + if (_dbgLog) { log += F("Orcon high"); } + _State = 103; + _Timer = 0; + _LastIDindex = index; + break; + case OrconAuto: + + if (_dbgLog) { log += F("Orcon auto"); } + _State = 104; + _Timer = 0; + _LastIDindex = index; + break; + case OrconTimer0: + + if (_dbgLog) { log += F("Orcon Timer0"); } + _State = 110; + _Timer = PLUGIN_118_OrconTime0; + _LastIDindex = index; + break; + case OrconTimer1: + + if (_dbgLog) { log += F("Orcon Timer1"); } + _State = 111; + _Timer = PLUGIN_118_OrconTime1; + _LastIDindex = index; + break; + case OrconTimer2: + + if (_dbgLog) { log += F("Orcon Timer2"); } + _State = 112; + _Timer = PLUGIN_118_OrconTime2; + _LastIDindex = index; + break; + case OrconTimer3: + + if (_dbgLog) { log += F("Orcon Timer3"); } + _State = 113; + _Timer = PLUGIN_118_OrconTime3; + _LastIDindex = index; + break; + case OrconAutoCO2: + + if (_dbgLog) { log += F("Orcon AutoCO2"); } + _State = 114; + _Timer = 0; + _LastIDindex = index; + break; + # else // if P118_FEATURE_ORCON + case OrconStandBy: + case OrconLow: + case OrconMedium: + case OrconHigh: + case OrconAuto: + case OrconTimer0: + case OrconTimer1: + case OrconTimer2: + case OrconTimer3: + case OrconAutoCO2: + break; + # endif // if P118_FEATURE_ORCON + } + } else { + if (_dbgLog) { + log += strformat(F("Device-ID: %s IGNORED"), Id.c_str()); + } + } + + # ifndef BUILD_NO_DEBUG + + if (_dbgLog) { + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG + } +} + +void P118_data_struct::PublishData(struct EventStruct *event) { + UserVar.setFloat(event->TaskIndex, 0, _State); + UserVar.setFloat(event->TaskIndex, 1, _Timer); + UserVar.setFloat(event->TaskIndex, 2, _LastIDindex); + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLog(LOG_LEVEL_DEBUG, concat(F("State: "), formatUserVarNoCheck(event, 0))); + addLog(LOG_LEVEL_DEBUG, concat(F("Timer: "), formatUserVarNoCheck(event, 1))); + addLog(LOG_LEVEL_DEBUG, concat(F("LastIDindex: "), formatUserVarNoCheck(event, 2))); + } + # endif // ifndef BUILD_NO_DEBUG +} + +void P118_data_struct::PluginWriteLog(const String& command) { + String log = concat(F("Send Itho" + # if P118_FEATURE_ORCON + "/Orcon" + # endif // if P118_FEATURE_ORCON + " command for: "), command); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, log); + } + printWebString += log; +} + +// **************************************************************************/ +// Interrupt handler +// **************************************************************************/ +void P118_data_struct::ISR_ithoCheck(P118_data_struct *self) { + self->_Int = true; +} + +# if P118_FEATURE_ORCON + +/** + * Orcon specific: Set Destination ID + */ +void P118_data_struct::SetDestIDSrcID(struct EventStruct *event, uint8_t (& srcID)[3], uint8_t (& destID)[3], char (& tmpTmpID)[9]) +{ + destID[0] = PCONFIG(1) - 0; + destID[1] = PCONFIG(2) - 0; + destID[2] = PCONFIG(3) - 0; + + const char *delimiter = ","; + char *token; + + // char tmpID[9] = PLUGIN_118_ExtraSettings.ID1; // copy needed otherwise we modify PLUGIN_118_ExtraSettings.ID1 itself + char tmpID[9]; + + memcpy(tmpID, tmpTmpID, 9); + token = strtok(tmpID, delimiter); // select the first part + + if (token) { + srcID[0] = strtol(token, NULL, 16); // convert first string part (hex) to int + } else { + srcID[0] = 0; + } + token = strtok(NULL, delimiter); + + if (token) { + srcID[1] = strtol(token, NULL, 16); // convert first string part (hex) to int + } else { + srcID[1] = 0; + } + token = strtok(NULL, delimiter); + + if (token) { + srcID[2] = strtol(token, NULL, 16); // convert first string part (hex) to int + } else { + srcID[2] = 0; + } + + # ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("srcID: "); + log += static_cast(srcID[0]); + log += ','; + log += static_cast(srcID[1]); + log += ','; + log += static_cast(srcID[2]); + log += F(" destID: "); + log += static_cast(destID[0]); + log += ','; + log += static_cast(destID[1]); + log += ','; + log += static_cast(destID[2]); + addLogMove(LOG_LEVEL_DEBUG, log); + } + # endif // ifndef BUILD_NO_DEBUG +} + +# endif // if P118_FEATURE_ORCON + +#endif // ifdef USES_P118 diff --git a/src/src/PluginStructs/P119_data_struct.cpp b/src/src/PluginStructs/P119_data_struct.cpp index 996903427..06ca40131 100644 --- a/src/src/PluginStructs/P119_data_struct.cpp +++ b/src/src/PluginStructs/P119_data_struct.cpp @@ -29,24 +29,19 @@ P119_data_struct::~P119_data_struct() { // Initialize sensor and read data from ITG3205 // **************************************************************************/ bool P119_data_struct::read_sensor() { - #ifdef PLUGIN_119_DEBUG + # ifdef PLUGIN_119_DEBUG String log; # endif // if PLUGIN_119_DEBUG if (!initialized()) { init_sensor(); - #ifdef PLUGIN_119_DEBUG + # ifdef PLUGIN_119_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG) && - log.reserve(55)) { - log = F("ITG3205: i2caddress: 0x"); - log += String(_i2cAddress, HEX); - log += F(", initialized: "); - log += initialized() ? F("true") : F("false"); - log += F(", ID=0x"); - log += String(itg3205->readWhoAmI(), HEX); - addLogMove(LOG_LEVEL_DEBUG, log); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("ITG3205: i2caddress: 0x%02x, initialized: %d, ID=0x%02x"), + _i2cAddress, initialized(), itg3205->readWhoAmI())); } # endif // if PLUGIN_119_DEBUG } @@ -71,19 +66,12 @@ bool P119_data_struct::read_sensor() { _aUsed = 0; } - #ifdef PLUGIN_119_DEBUG + # ifdef PLUGIN_119_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG) && - log.reserve(40)) { - log = F("ITG3205: "); - log += _rawData ? F("raw ") : F(""); - log += F(", X: "); - log += itg3205->g.x; - log += F(", Y: "); - log += itg3205->g.y; - log += F(", Z: "); - log += itg3205->g.z; - addLogMove(LOG_LEVEL_DEBUG, log); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("ITG3205: %s, X: %d, Y: %d, Z: %d"), + String(_rawData ? F("raw ") : F("")).c_str(), + itg3205->g.x, itg3205->g.y, itg3205->g.z)); } # endif // if PLUGIN_119_DEBUG return true; @@ -100,7 +88,7 @@ bool P119_data_struct::read_data(int& X, int& Y, int& Z) { Z = 0; if (initialized()) { - for (uint8_t n = 0; n <= _aMax; n++) { + for (uint8_t n = 0; n <= _aMax; ++n) { X += _XA[n]; Y += _YA[n]; Z += _ZA[n]; @@ -110,20 +98,10 @@ bool P119_data_struct::read_data(int& X, int& Y, int& Z) { Y /= _aMax; Z /= _aMax; - #ifdef PLUGIN_119_DEBUG + # ifdef PLUGIN_119_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - - if (log.reserve(40)) { - log = F("ITG3205: averages, X: "); - log += X; - log += F(", Y: "); - log += Y; - log += F(", Z: "); - log += Z; - addLogMove(LOG_LEVEL_DEBUG, log); - } + addLogMove(LOG_LEVEL_DEBUG, strformat(F("ITG3205: averages, X: %d, Y: %d, Z: %d"), X, Y, Z)); } # endif // if PLUGIN_119_DEBUG } @@ -147,17 +125,12 @@ bool P119_data_struct::init_sensor() { return false; } - #ifdef PLUGIN_119_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; + # ifdef PLUGIN_119_DEBUG - if (log.reserve(25)) { - log = F("ITG3205: Address: 0x"); - log += String(_i2cAddress, HEX); - addLogMove(LOG_LEVEL_DEBUG, log); - } + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("ITG3205: Address: 0x%02x"), _i2cAddress)); } - #endif + # endif // ifdef PLUGIN_119_DEBUG return true; } diff --git a/src/src/PluginStructs/P120_data_struct.cpp b/src/src/PluginStructs/P120_data_struct.cpp index 8837a7dbd..b1ae66dea 100644 --- a/src/src/PluginStructs/P120_data_struct.cpp +++ b/src/src/PluginStructs/P120_data_struct.cpp @@ -54,19 +54,16 @@ bool P120_data_struct::read_sensor(struct EventStruct *event) { log.reserve(55)) { if (i2c_mode) { # ifdef USES_P120 - log = F("ADXL345: i2caddress: 0x"); - log += String(_i2cAddress, HEX); + log = strformat(F("ADXL345: i2caddress: 0x%02x"), _i2cAddress); # endif // ifdef USES_P120 } else { # ifdef USES_P125 - log = F("ADXL345: CS-pin: "); - log += _cs_pin; + log = concat(F("ADXL345: CS-pin: "), _cs_pin); # endif // ifdef USES_P125 } - log += F(", initialized: "); - log += String(initialized() ? F("true") : F("false")); - log += F(", ID=0x"); - log += String(adxl345->getDevID(), HEX); + log += strformat(F(", initialized: %s, ID=0x%02x"), + String(initialized() ? F("true") : F("false")), + adxl345->getDevID()); addLogMove(LOG_LEVEL_DEBUG, log); } # endif // if PLUGIN_120_DEBUG @@ -91,15 +88,8 @@ bool P120_data_struct::read_sensor(struct EventStruct *event) { # if PLUGIN_120_DEBUG - if (loglevelActiveFor(LOG_LEVEL_DEBUG) && - log.reserve(40)) { - log = F("ADXL345: X: "); - log += _x; - log += F(", Y: "); - log += _y; - log += F(", Z: "); - log += _z; - addLogMove(LOG_LEVEL_DEBUG, log); + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, strformat(F("ADXL345: X: %d, Y: %d, Z: %d"), _x, _y, _z)); } # endif // if PLUGIN_120_DEBUG @@ -173,7 +163,7 @@ bool P120_data_struct::read_data(struct EventStruct *event) const case valueType::NR_ValueTypes: break; } - UserVar.setFloat(event->TaskIndex, i, value); + UserVar.setFloat(event->TaskIndex, i, value); } } return true; @@ -513,8 +503,9 @@ bool P120_data_struct::plugin_webform_loadOutputSelector(struct EventStruct *eve for (uint8_t i = 0; i < P120_NR_OUTPUT_OPTIONS; ++i) { options[i] = P120_data_struct::valuename(i, true); } - + const uint8_t valueCount = P120_NR_OUTPUT_VALUES; + for (uint8_t i = 0; i < valueCount; ++i) { const uint8_t pconfigIndex = i + P120_QUERY1_CONFIG_POS; sensorTypeHelper_loadOutputSelector(event, pconfigIndex, i, P120_NR_OUTPUT_OPTIONS, options); @@ -649,6 +640,7 @@ bool P120_data_struct::plugin_webform_load(struct EventStruct *event) { // ******************************************************************* bool P120_data_struct::plugin_webform_save(struct EventStruct *event) { const uint8_t valueCount = P120_NR_OUTPUT_VALUES; + for (uint8_t i = 0; i < valueCount; ++i) { const uint8_t pconfigIndex = i + P120_QUERY1_CONFIG_POS; const uint8_t choice = PCONFIG(pconfigIndex); @@ -766,6 +758,7 @@ bool P120_data_struct::plugin_get_config_value(struct EventStruct *event, String void P120_data_struct::plugin_get_device_value_names(struct EventStruct *event) { const uint8_t valueCount = P120_NR_OUTPUT_VALUES; + for (uint8_t i = 0; i < VARS_PER_TASK; ++i) { if (i < valueCount) { const uint8_t pconfigIndex = i + P120_QUERY1_CONFIG_POS; diff --git a/src/src/PluginStructs/P121_data_struct.cpp b/src/src/PluginStructs/P121_data_struct.cpp index 72613268a..3608e7abf 100644 --- a/src/src/PluginStructs/P121_data_struct.cpp +++ b/src/src/PluginStructs/P121_data_struct.cpp @@ -12,6 +12,7 @@ bool P121_data_struct::begin(int taskid) if (!initialized) { mag = Adafruit_HMC5883_Unified(taskid); initialized = mag.begin(); + if (initialized) { // Set up oversampling and filter initialization sensor_t sensor; @@ -19,19 +20,14 @@ bool P121_data_struct::begin(int taskid) # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("HMC5883_U: Sensor: "); - log += String(sensor.name); - log += F(", Driver Ver: "); - log += sensor.version; - log += F(", Unique ID: "); - log += sensor.sensor_id; - log += F(", Max Value: "); - log += toString(sensor.max_value); - log += F(", Min Value: "); - log += toString(sensor.min_value); - log += F(", Resolution: "); - log += toString(sensor.resolution); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("HMC5883_U: Sensor: %s, Driver Ver: %d, Unique ID: %d, Max Value: %.2f, Min Value: %.2f, Resolution: %.2f"), + sensor.name, + sensor.version, + sensor.sensor_id, + sensor.max_value, + sensor.min_value, + sensor.resolution)); } # endif // ifndef BUILD_NO_DEBUG } diff --git a/src/src/PluginStructs/P122_data_struct.cpp b/src/src/PluginStructs/P122_data_struct.cpp index 3df3130bc..7e2f10883 100644 --- a/src/src/PluginStructs/P122_data_struct.cpp +++ b/src/src/PluginStructs/P122_data_struct.cpp @@ -54,11 +54,10 @@ bool P122_data_struct::setupDevice(uint8_t i2caddr, uint8_t resolution) if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SHT2x : Setup Device with address= "); - log += formatToHex(_i2caddr); - log += F(" resolution= "); - log += String(resolution); - addLog(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("SHT2x : Setup Device with address= %x resolution= %d"), + _i2caddr, + resolution)); } # endif // ifdef PLUGIN_122_DEBUG return true; @@ -89,9 +88,7 @@ bool P122_data_struct::update() else if (I2C_wakeup(_i2caddr) != 0) // Try to access the I2C device { if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("SHT2x : Not found at I2C address: "); - log += String(_i2caddr, HEX); - addLog(LOG_LEVEL_ERROR, log); + addLog(LOG_LEVEL_ERROR, strformat(F("SHT2x : Not found at I2C address: %x"), _i2caddr)); } _errCount++; } @@ -209,11 +206,7 @@ bool P122_data_struct::update() { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SHT2x : ***state transition "); - log += String((int)oldState); - log += F("-->"); - log += String((int)_state); - addLog(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("SHT2x : ***state transition %d-->%d"), static_cast(oldState), static_cast(_state))); } } # endif // ifdef PLUGIN_122_DEBUG diff --git a/src/src/PluginStructs/P123_data_struct.cpp b/src/src/PluginStructs/P123_data_struct.cpp new file mode 100644 index 000000000..dd9daf867 --- /dev/null +++ b/src/src/PluginStructs/P123_data_struct.cpp @@ -0,0 +1,528 @@ +#include "../PluginStructs/P123_data_struct.h" + +#ifdef USES_P123 + +# include "../Helpers/AdafruitGFX_helper.h" + +const __FlashStringHelper* toString(P123_TouchType_e tType) { + switch (tType) { + case P123_TouchType_e::FT62x6: return F("FT62x6 (0x38)"); + case P123_TouchType_e::GT911_1: return F("GT911 (0x5D)"); + case P123_TouchType_e::GT911_2: return F("GT911 (0x14)"); + case P123_TouchType_e::CST820: return F("CST820 (0x15)"); + case P123_TouchType_e::CST226: return F("CST226 (0x5A)"); + case P123_TouchType_e::AXS15231: return F("AXS15231 (0x3B)"); + case P123_TouchType_e::CHSC5816: return F("CHSC5816 (0x2E)"); + case P123_TouchType_e::Automatic: return F("Auto-detect"); + } + return F(""); +} + +/** + * Order must match enum P123_TouchType_e, with Automatic (-1) ignored + */ +const uint8_t P123_i2cAddressValues[] = { FT6X36_ADDR, GT911_ADDR1, GT911_ADDR2, CST820_ADDR, CST226_ADDR, AXS15231_ADDR, CHSC5816_ADDR }; + +bool P123_data_struct::plugin_i2c_has_address(const int Par1) { + return intArrayContains(NR_ELEMENTS(P123_i2cAddressValues), P123_i2cAddressValues, Par1); +} + +uint8_t P123_data_struct::plugin_i2c_address(P123_TouchType_e touchType) { + const int tType = static_cast(touchType); + + if ((tType >= 0) && (tType < NR_ELEMENTS(P123_i2cAddressValues))) { + return P123_i2cAddressValues[tType]; + } + return 0u; +} + +P123_TouchType_e P123_data_struct::getTouchType() { + if (nullptr != touchscreen) { + const int stype = touchscreen->sensorType(); + + switch (stype) { + case CT_TYPE_FT6X36: return P123_TouchType_e::FT62x6; + case CT_TYPE_GT911: + + if (GT911_ADDR1 == touchscreen->getI2CAddress()) { + return P123_TouchType_e::GT911_1; + } else { + return P123_TouchType_e::GT911_2; + } + case CT_TYPE_CST820: return P123_TouchType_e::CST820; + case CT_TYPE_CST226: return P123_TouchType_e::CST226; + case CT_TYPE_AXS15231: return P123_TouchType_e::AXS15231; + case CT_TYPE_CHSC5816: return P123_TouchType_e::CHSC5816; + } + } + return P123_TouchType_e::Automatic; +} + +int P123_data_struct::getBBCapTouchType(P123_TouchType_e touchType) { + switch (touchType) { + case P123_TouchType_e::Automatic: return CT_TYPE_UNKNOWN; + case P123_TouchType_e::FT62x6: return CT_TYPE_FT6X36; + case P123_TouchType_e::GT911_1: // Fall through + case P123_TouchType_e::GT911_2: return CT_TYPE_GT911; + case P123_TouchType_e::CST820: return CT_TYPE_CST820; + case P123_TouchType_e::CST226: return CT_TYPE_CST226; + case P123_TouchType_e::AXS15231: return CT_TYPE_AXS15231; + case P123_TouchType_e::CHSC5816: return CT_TYPE_CHSC5816; + } + return CT_TYPE_UNKNOWN; +} + +/** + * Constructor + */ +P123_data_struct::P123_data_struct(P123_TouchType_e touchType) + : _touchType(touchType) { + touchHandler = new (std::nothrow) ESPEasy_TouchHandler(); // Temporary object to be able to call loadTouchObjects + _i2caddr = plugin_i2c_address(_touchType); +} + +/** + * Destructor + */ +P123_data_struct::~P123_data_struct() { + reset(); +} + +/** + * Proper reset and cleanup. + */ +void P123_data_struct::reset() { + # ifdef PLUGIN_123_DEBUG + addLog(LOG_LEVEL_INFO, F("P123 DEBUG Touchscreen reset.")); + # endif // PLUGIN_123_DEBUG + + delete touchscreen; + touchscreen = nullptr; + delete touchHandler; + touchHandler = nullptr; +} + +/** + * Initialize data and set up the touchscreen. + */ +bool P123_data_struct::init(struct EventStruct *event) { + _rotation = P123_CONFIG_ROTATION; + _ts_x_res = P123_CONFIG_X_RES; + _ts_y_res = P123_CONFIG_Y_RES; + + reset(); + + touchHandler = new (std::nothrow) ESPEasy_TouchHandler(static_cast(P123_CONFIG_DISPLAY_TASK), + static_cast(P123_COLOR_DEPTH)); + + if (nullptr != touchHandler) { + touchHandler->init(event); + + if (touchHandler->touchEnabled()) { + touchscreen = new (std::nothrow) BBCapTouch(); + + if (nullptr != touchscreen) { + touchscreen->setThreshold(P123_CONFIG_THRESHOLD); + + P123_TouchType_e touchType = static_cast(P123_GET_TOUCH_TYPE); + + if (P123_TouchType_e::Automatic != touchType) { // Manual override + touchscreen->sensorType(getBBCapTouchType(touchType)); + touchscreen->setI2CAddress(plugin_i2c_address(touchType)); + } + + if (touchscreen->init(-1, -1, P123_RESETPIN, P123_INTERRUPTPIN) != CT_SUCCESS) { + delete touchscreen; + touchscreen = nullptr; + } else { + setRotation(_rotation); + } + } + } + + # ifdef PLUGIN_123_DEBUG + 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.")); + # endif // PLUGIN_123_DEBUG + } + return isInitialized(); +} + +/** + * mode: -2 = clear buttons in group, -3 = clear all buttongroups, -1 = draw buttons in group, 0 = initialize buttons + */ +void P123_data_struct::displayButtonGroup(struct EventStruct *event, + int16_t buttonGroup, + int8_t mode) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + touchHandler->displayButtonGroup(event, buttonGroup, mode); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH +} + +/** + * (Re)Display a button + */ +bool P123_data_struct::displayButton(struct EventStruct *event, + const int8_t & buttonNr, + int16_t buttonGroup, + int8_t mode) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->displayButton(event, buttonNr, buttonGroup, mode); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Properly initialized? then true + */ +bool P123_data_struct::isInitialized() const { + return touchHandler != nullptr && (!touchHandler->touchEnabled() || touchscreen != nullptr); +} + +/** + * Load the settings onto the webpage + */ +bool P123_data_struct::plugin_webform_load(struct EventStruct *event) { + if (nullptr != touchHandler) { + return touchHandler->plugin_webform_load(event); + } + return false; +} + +/** + * Save the settings from the web page to flash + */ +bool P123_data_struct::plugin_webform_save(struct EventStruct *event) { + if (nullptr != touchHandler) { + const bool result = touchHandler->plugin_webform_save(event); + P123_CONFIG_VTYPE = touchHandler->get_device_valuecount(event); // Store 'locally' + return result; + } + return false; +} + +/** + * Parse and execute the plugin commands, delegated to ESPEasy_TouchHandler + */ +bool P123_data_struct::plugin_write(struct EventStruct *event, + const String & string) { + bool success = false; + String command; + String subcommand; + + command = parseString(string, 1); + subcommand = parseString(string, 2); + + if (isInitialized() && equals(command, F("touch"))) { + # ifdef PLUGIN_123_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("P123 WRITE arguments Par1: %d, 2: %d, 3: %d, 4: %d"), + event->Par1, event->Par2, event->Par3, event->Par4)); + } + # endif // ifdef PLUGIN_123_DEBUG + + if (equals(subcommand, F("rot"))) { // touch,rot,<0..3> : Set rotation to 0, 90, 180, 270 degrees + setRotation(static_cast(event->Par2 % 4)); + success = true; + } else if (equals(subcommand, F("flip"))) { // touch,flip,<0|1> : Flip rotation by 0 or 180 degrees + setRotationFlipped(event->Par2 > 0); + success = true; + } else { // Rest of the commands handled by ESPEasy_TouchHandler + success = touchHandler->plugin_write(event, string); + } + } + return success; +} + +/** + * Every 1/50th second we check if the screen is touched + */ +bool P123_data_struct::plugin_fifty_per_second(struct EventStruct *event) { + if (isInitialized() && touchHandler->touchEnabled()) { + if (touched()) { + int16_t x = 0; + int16_t y = 0; + int16_t z = 0; + int16_t ox = 0; + int16_t oy = 0; + readData(x, y, z, ox, oy); + + int16_t rx = x; // Keep raw values + int16_t ry = y; + scaleRawToCalibrated(x, y); // Map to screen coordinates if so configured + + return touchHandler->plugin_fifty_per_second(event, x, y, ox, oy, rx, ry, z); + } else { + touchHandler->releaseTouch(event); + } + } + return false; +} + +/** + * Handle getting config values, delegated to ESPEasy_TouchHandler + */ +bool P123_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + if (nullptr != touchHandler) { + return touchHandler->plugin_get_config_value(event, string); + } + return false; +} + +/** + * Load the touch objects from the settings, and initialize then properly where needed. + */ +void P123_data_struct::loadTouchObjects(struct EventStruct *event) { + if (nullptr != touchHandler) { + touchHandler->loadTouchObjects(event); + } +} + +/** + * Check if the screen is touched. + */ +bool P123_data_struct::touched() { + if (isInitialized()) { + return touchscreen->getSamples(&touchInfo) > 0; // 1 or more points touched + } + return false; +} + +/** + * Read the raw data if the touchscreen is initialized. + */ +void P123_data_struct::readData(int16_t& x, + int16_t& y, + int16_t& z, + int16_t& ox, + int16_t& oy) { + if (isInitialized()) { + x = touchInfo.x[0]; // Only 1 point used for now. + y = touchInfo.y[0]; + z = touchInfo.pressure[0]; + ox = touchInfo.x[0]; // Change of touch driver has made these arguments obsolete, but in use for touchHandler... + oy = touchInfo.y[0]; + } +} + +/** + * Set rotation + */ +void P123_data_struct::setRotation(uint8_t n) { + _rotation = n; + + if (isInitialized()) { + const bool fl = touchHandler->_flipped; + + switch (_rotation) { // Rotation is handled by touch driver, flipped handled here + case 0: touchscreen->setOrientation(fl ? 180 : 0, _ts_x_res, _ts_y_res); break; + case 1: touchscreen->setOrientation(fl ? 270 : 90, _ts_x_res, _ts_y_res); break; + case 2: touchscreen->setOrientation(fl ? 0 : 180, _ts_x_res, _ts_y_res); break; + case 3: touchscreen->setOrientation(fl ? 90 : 270, _ts_x_res, _ts_y_res); break; + } + } + # ifdef PLUGIN_123_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P123 DEBUG Rotation set: "), _rotation)); + } + # endif // PLUGIN_123_DEBUG +} + +/** + * Set rotationFlipped + */ +void P123_data_struct::setRotationFlipped(bool flipped) { + touchHandler->_flipped = flipped; + # ifdef PLUGIN_123_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLogMove(LOG_LEVEL_INFO, concat(F("P123 DEBUG RotationFlipped set: "), boolToString(flipped))); + } + # endif // PLUGIN_123_DEBUG +} + +/** + * Check within the list of defined objects if we touched one of them. + * The smallest matching surface is selected if multiple objects overlap. + * Returns state, and sets selectedObjectName to the best matching object + */ +bool P123_data_struct::isValidAndTouchedTouchObject(const int16_t& x, + const int16_t& y, + String & selectedObjectName, + int8_t & selectedObjectIndex) { + if (nullptr != touchHandler) { + return touchHandler->isValidAndTouchedTouchObject(x, y, selectedObjectName, selectedObjectIndex); + } + return false; +} + +/** + * Get the index of a touch object by name or number + */ +int8_t P123_data_struct::getTouchObjectIndex(struct EventStruct *event, + const String & touchObject, + bool isButton) { + if (nullptr != touchHandler) { + return touchHandler->getTouchObjectIndex(event, touchObject, isButton); + } + return -1; +} + +/** + * Set the enabled/disabled state of an object. + */ +bool P123_data_struct::setTouchObjectState(struct EventStruct *event, + const String & touchObject, + bool state) { + if (nullptr != touchHandler) { + return touchHandler->setTouchObjectState(event, touchObject, state); + } + return false; +} + +/** + * Set the on/off state of a touch-button object. + */ +bool P123_data_struct::setTouchButtonOnOff(struct EventStruct *event, + const String & touchObject, + bool state) { + if (nullptr != touchHandler) { + return touchHandler->setTouchButtonOnOff(event, touchObject, state); + } + return false; +} + +/** + * Scale the provided raw coordinates to screen-resolution coordinates if calibration is enabled/configured + */ +void P123_data_struct::scaleRawToCalibrated(int16_t& x, + int16_t& y) { + if ((nullptr != touchHandler) && touchHandler->isCalibrationActive()) { + int16_t lx = x - touchHandler->Touch_Settings.top_left.x; + + if (lx <= 0) { + x = 0; + } else { + if (lx > touchHandler->Touch_Settings.bottom_right.x) { + lx = touchHandler->Touch_Settings.bottom_right.x; + } + float x_fact = static_cast(touchHandler->Touch_Settings.bottom_right.x - touchHandler->Touch_Settings.top_left.x) / + static_cast(_ts_x_res); + x = static_cast(round(lx / x_fact)); + } + int16_t ly = y - touchHandler->Touch_Settings.top_left.y; + + if (ly <= 0) { + y = 0; + } else { + if (ly > touchHandler->Touch_Settings.bottom_right.y) { + ly = touchHandler->Touch_Settings.bottom_right.y; + } + float y_fact = (touchHandler->Touch_Settings.bottom_right.y - touchHandler->Touch_Settings.top_left.y) / _ts_y_res; + y = static_cast(round(ly / y_fact)); + } + } +} + +/** + * Get the current button group + */ +int16_t P123_data_struct::getButtonGroup() const { + if (nullptr != touchHandler) { + return touchHandler->getButtonGroup(); + } + return 0; +} + +/** + * Check if a valid button group, optionally ignoring group 0 + */ +bool P123_data_struct::validButtonGroup(int16_t buttonGroup, + bool ignoreZero) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->validButtonGroup(buttonGroup, ignoreZero); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Set the desired button group, must be between the minimum and maximum found values + */ +bool P123_data_struct::setButtonGroup(struct EventStruct *event, + int16_t buttonGroup) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->setButtonGroup(event, buttonGroup); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Increment button group, if max. group > 0 then min. group = 1 + */ +bool P123_data_struct::nextButtonGroup(struct EventStruct *event) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->nextButtonGroup(event); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Decrement button group, if max. group > 0 then min. group = 1 + */ +bool P123_data_struct::prevButtonGroup(struct EventStruct *event) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->prevButtonGroup(event); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Increment button group page (+10), if max. group > 0 then min. group page (+10) = 1 + */ +bool P123_data_struct::nextButtonPage(struct EventStruct *event) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->nextButtonPage(event); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +/** + * Decrement button group page (-10), if max. group > 0 then min. group = 1 + */ +bool P123_data_struct::prevButtonPage(struct EventStruct *event) { + # if TOUCH_FEATURE_EXTENDED_TOUCH + + if (nullptr != touchHandler) { + return touchHandler->prevButtonPage(event); + } + # endif // if TOUCH_FEATURE_EXTENDED_TOUCH + return false; +} + +#endif // ifdef USES_P123 diff --git a/src/src/PluginStructs/P123_data_struct.h b/src/src/PluginStructs/P123_data_struct.h new file mode 100644 index 000000000..bb9b5512f --- /dev/null +++ b/src/src/PluginStructs/P123_data_struct.h @@ -0,0 +1,159 @@ +#ifndef PLUGINSTRUCTS_P123_DATA_STRUCT_H +#define PLUGINSTRUCTS_P123_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" + +#ifdef USES_P123 + +# include "../Helpers/ESPEasy_TouchHandler.h" + +# include + +# ifndef LIMIT_BUILD_SIZE +# define PLUGIN_123_DEBUG // Additional debugging information +# else // ifndef LIMIT_BUILD_SIZE +# ifndef P123_LIMIT_BUILD_SIZE // Can be set from elsewhere +# define P123_LIMIT_BUILD_SIZE +# endif // ifndef P123_LIMIT_BUILD_SIZE +# endif // ifndef LIMIT_BUILD_SIZE + +# if defined(BUILD_NO_DEBUG) && defined(PLUGIN_123_DEBUG) +# undef PLUGIN_123_DEBUG +# endif // if defined(BUILD_NO_DEBUG) && defined(PLUGIN_123_DEBUG) + +# define P123_I2C_ADDRESS PCONFIG(6) +# define P123_CONFIG_DISPLAY_TASK PCONFIG(0) + +# define P123_CONFIG_FLAGS PCONFIG_ULONG(0) // All flags +# define P123_CONFIG_FLAG_TOUCHTYPE 0 // Flag indexes + +// We're storing an int8_t in range -128..127 in an uin8_t +# define P123_GET_TOUCH_TYPE (get8BitFromUL(P123_CONFIG_FLAGS, P123_CONFIG_FLAG_TOUCHTYPE) - 128) +# define P123_SET_TOUCH_TYPE(T) (set8BitToUL(P123_CONFIG_FLAGS, P123_CONFIG_FLAG_TOUCHTYPE, T + 128)) + +# define P123_INTERRUPTPIN (CONFIG_PIN1) +# define P123_RESETPIN (CONFIG_PIN2) + +# define P123_COLOR_DEPTH PCONFIG_LONG(1) +# define P123_CONFIG_THRESHOLD PCONFIG(1) +# define P123_CONFIG_ROTATION PCONFIG(2) +# define P123_CONFIG_X_RES PCONFIG(3) +# define P123_CONFIG_Y_RES PCONFIG(4) +# define P123_CONFIG_VTYPE PCONFIG(5) + +# define P123_CONFIG_DISPLAY_PREV PCONFIG(7) + +// Default settings values +# define P123_TS_THRESHOLD 40 // Threshold before the value is registered as a proper touch +# define P123_TS_ROTATION 0 // Rotation 0-3 = 0/90/180/270 degrees +# define P123_TS_X_RES 320 // Pixels, should match with the screen it is mounted on +# define P123_TS_Y_RES 480 + +# define P123_TOUCH_X_NATIVE P123_TS_X_RES // Native touchscreen resolution, same as default display resolution +# define P123_TOUCH_Y_NATIVE P123_TS_Y_RES + +# define P123_ROTATION_0 0 +# define P123_ROTATION_90 1 +# define P123_ROTATION_180 2 +# define P123_ROTATION_270 3 + +enum class P123_TouchType_e : int8_t { + Automatic = -1, + FT62x6 = 0, // Also used as offset in I2C address array + GT911_1 = 1, + GT911_2 = 2, + CST820 = 3, + CST226 = 4, + AXS15231 = 5, + CHSC5816 = 6, +}; + +const __FlashStringHelper* toString(P123_TouchType_e tType); + +// Data structure +struct P123_data_struct : public PluginTaskData_base +{ + P123_data_struct(P123_TouchType_e touchType); + ~P123_data_struct(); + + static bool plugin_i2c_has_address(int Par1); + static uint8_t plugin_i2c_address(P123_TouchType_e touchType); + + P123_TouchType_e getTouchType(); + int getBBCapTouchType(P123_TouchType_e touchType); + + void reset(); + bool init(struct EventStruct *event); + bool isInitialized() const; + + bool plugin_webform_load(struct EventStruct *event); + bool plugin_webform_save(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + const String & string); + bool plugin_fifty_per_second(struct EventStruct *event); + bool plugin_get_config_value(struct EventStruct *event, + String & string); + + void loadTouchObjects(struct EventStruct *event); + bool touched(); + void readData(int16_t& x, + int16_t& y, + int16_t& z, + int16_t& ox, + int16_t& oy); + + void setRotation(uint8_t n); + void setRotationFlipped(bool _flipped); + bool isValidAndTouchedTouchObject(const int16_t& x, + const int16_t& y, + String & selectedObjectName, + int8_t & selectedObjectIndex); + int8_t getTouchObjectIndex(struct EventStruct *event, + const String & touchObject, + bool isButton = false); + bool setTouchObjectState(struct EventStruct *event, + const String & touchObject, + bool state); + bool setTouchButtonOnOff(struct EventStruct *event, + const String & touchObject, + bool state); + void scaleRawToCalibrated(int16_t& x, + int16_t& y); + + int16_t getButtonGroup() const; + bool validButtonGroup(int16_t buttonGroup, + bool ignoreZero = false); + bool setButtonGroup(struct EventStruct *event, + int16_t buttonGroup); + bool nextButtonGroup(struct EventStruct *event); + bool prevButtonGroup(struct EventStruct *event); + bool nextButtonPage(struct EventStruct *event); + bool prevButtonPage(struct EventStruct *event); + void displayButtonGroup(struct EventStruct *event, + int16_t buttonGroup, + int8_t mode = 0); + bool displayButton(struct EventStruct *event, + const int8_t & buttonNr, + int16_t buttonGroup = -1, + int8_t mode = 0); + +private: + + // This is initialized by calling init() + BBCapTouch *touchscreen = nullptr; + uint8_t _rotation = 0u; + uint16_t _ts_x_res = 0u; + uint16_t _ts_y_res = 0u; + + int16_t _i2caddr{}; + int16_t _resetPin = -1; + int16_t _interruptPin = -1; + P123_TouchType_e _touchType; + + TOUCHINFO touchInfo; + + ESPEasy_TouchHandler *touchHandler = nullptr; +}; + +#endif // ifdef USES_P123 +#endif // ifndef PLUGINSTRUCTS_P123_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P124_data_struct.cpp b/src/src/PluginStructs/P124_data_struct.cpp index f7f73c852..627f24f05 100644 --- a/src/src/PluginStructs/P124_data_struct.cpp +++ b/src/src/PluginStructs/P124_data_struct.cpp @@ -8,7 +8,7 @@ P124_data_struct::P124_data_struct(int8_t i2c_address, uint8_t relayCount, bool changeAddress) - : _i2c_address(i2c_address), _relayCount(relayCount), _changeAddress(changeAddress) + : _i2c_address(i2c_address), _relayCount(relayCount), _changeAddress(changeAddress) {} // **************************************************************************/ @@ -29,15 +29,12 @@ bool P124_data_struct::init() { if (_changeAddress) { // This increment shpould match with the range of addresses in _P124_MultiRelay.ino PLUGIN_I2C_HAS_ADDRESS - uint8_t _new_address = _i2c_address == 0x18 ? 0x11 : _i2c_address + 1; // Set to next address + const uint8_t _new_address = _i2c_address == 0x18 ? 0x11 : _i2c_address + 1; // Set to next address relay->changeI2CAddress(_new_address, _i2c_address); # ifndef BUILD_NO_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("MultiRelay: Change I2C address 0x"); - log += String(_i2c_address, HEX); - log += F(" to 0x"); - log += String(_new_address, HEX); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat(F("MultiRelay: Change I2C address 0x%02x to 0x%02x"), _i2c_address, _new_address)); } # endif // ifndef BUILD_NO_DEBUG } diff --git a/src/src/PluginStructs/P126_data_struct.cpp b/src/src/PluginStructs/P126_data_struct.cpp index a3cfacf82..3aae12ad2 100644 --- a/src/src/PluginStructs/P126_data_struct.cpp +++ b/src/src/PluginStructs/P126_data_struct.cpp @@ -17,10 +17,8 @@ P126_data_struct::P126_data_struct(int8_t dataPin, // Destructor // **************************************************************************/ P126_data_struct::~P126_data_struct() { - if (nullptr != shift) { - delete shift; - shift = nullptr; - } + delete shift; + shift = nullptr; } bool P126_data_struct::plugin_init(struct EventStruct *event) { @@ -32,7 +30,7 @@ bool P126_data_struct::plugin_init(struct EventStruct *event) { const uint8_t *pvalue = shift->getAll(); // Get current state - for (uint8_t i = 0; i < _chipCount; i++) { + for (uint8_t i = 0; i < _chipCount; ++i) { value[i] = pvalue[i]; } @@ -40,30 +38,17 @@ bool P126_data_struct::plugin_init(struct EventStruct *event) { static_cast(ceil((P126_CONFIG_CHIP_COUNT - P126_CONFIG_SHOW_OFFSET) / 4.0))); uint32_t par; - for (uint16_t varNr = 0; varNr < maxVar; varNr++) { + for (uint16_t varNr = 0; varNr < maxVar; ++varNr) { par = UserVar.getUint32(event->TaskIndex, varNr); - for (uint8_t n = 0; n < 4 && idx < _chipCount; n++, idx++) { + for (uint8_t n = 0; n < 4 && idx < _chipCount; ++n, ++idx) { value[idx] = ((par >> (n * 8)) & 0xff); # ifdef P126_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log; - log.reserve(64); - log += F("SHIFTOUT: plugin_init: value["); - log += idx; - log += F("] : "); - log += value[idx]; - log += F("/0x"); - log += String(value[idx], HEX); - log += F(", n * 8: "); - log += n; - log += '/'; - log += n * 8; - log += F(", varNr: "); - log += varNr; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("SHIFTOUT: plugin_init: value[%d] : %d/0x%02x, n * 8: %d/%d, varNr: %d"), + idx, value[idx], value[idx], n, n * 8, varNr)); } # endif // ifdef P126_DEBUG_LOG } @@ -81,22 +66,15 @@ const uint32_t P126_data_struct::getChannelState(uint8_t offset, uint8_t size) c if (nullptr != pvalue) { uint16_t sft = 0u; - for (uint8_t ofs = offset; ofs < last; ofs++, sft++) { + for (uint8_t ofs = offset; ofs < last; ++ofs, ++sft) { result += (pvalue[ofs] << (8 * sft)); } # ifdef P126_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("SHIFTOUT: getChannelState offset: "); - log += offset; - log += F(", size: "); - log += size; - log += F(", result: "); - log += result; - log += F("/0x"); - log += String(result, HEX); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("SHIFTOUT: getChannelState offset: %d, size: %d, result: %d/0x%x"), + offset, size, result, result)); } # endif // ifdef P126_DEBUG_LOG } @@ -107,8 +85,8 @@ bool P126_data_struct::plugin_read(struct EventStruct *event) { const uint16_t last = P126_CONFIG_SHOW_OFFSET + (VARS_PER_TASK * 4); uint8_t varNr = 0; - for (uint16_t index = P126_CONFIG_SHOW_OFFSET; index < _chipCount && index < last && varNr < VARS_PER_TASK; index += 4, varNr++) { - uint32_t result = getChannelState(index, min(VARS_PER_TASK, _chipCount - index)); + for (uint16_t index = P126_CONFIG_SHOW_OFFSET; index < _chipCount && index < last && varNr < VARS_PER_TASK; index += 4, ++varNr) { + const uint32_t result = getChannelState(index, min(VARS_PER_TASK, _chipCount - index)); UserVar.setUint32(event->TaskIndex, varNr, result); } return true; @@ -120,8 +98,8 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, String command = parseString(string, 1); if (equals(command, F("shiftout"))) { - const String subcommand = parseString(string,2); - const bool hc_update = subcommand.indexOf(F("noupdate")) == -1; + const String subcommand = parseString(string, 2); + const bool hc_update = subcommand.indexOf(F("noupdate")) == -1; if (equals(subcommand, F("set")) || equals(subcommand, F("setnoupdate"))) { const uint8_t pin = event->Par2; @@ -133,12 +111,7 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, # ifdef P126_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = command; - log += F(", pin: "); - log += pin; - log += F(", value: "); - log += value; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, strformat(F("%s, pin: %d, value: %d"), command.c_str(), pin, value)); } # endif // ifdef P126_DEBUG_LOG } @@ -152,7 +125,7 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, const uint8_t *pvalue = shift->getAll(); // Get current state - for (uint8_t i = 0; i < _chipCount; i++) { + for (uint8_t i = 0; i < _chipCount; ++i) { value[i] = pvalue[i]; } @@ -163,11 +136,11 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, String arg = parseString(string, param); while (!arg.isEmpty() && idx < _chipCount && success) { - int colon = arg.indexOf(':'); // First colon: Chip-index, range 1.._chipCount - int32_t itmp = 0; + int colon = arg.indexOf(':'); // First colon: Chip-index, range 1.._chipCount + int32_t itmp = 0; if (colon != -1) { - String cis = arg.substring(0, colon); + const String cis = arg.substring(0, colon); arg = arg.substring(colon + 1); if (!cis.isEmpty() && validIntFromString(cis, itmp) && (itmp > 0) && (itmp <= _chipCount)) { @@ -180,7 +153,7 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, width = 4; // Set default data width to 4 = 32 bits if (colon != -1) { - String lis = arg.substring(0, colon); + const String lis = arg.substring(0, colon); arg = arg.substring(colon + 1); if (!lis.isEmpty() && validIntFromString(lis, itmp) && (itmp > 0) && (itmp <= 4)) { @@ -199,46 +172,24 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, # ifdef P126_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = command; - log += F(": arg: "); - log += arg; - log += F(", tmp: "); - log += ull2String(tmp); - log += F("/0x"); - log += ull2String(tmp, HEX); - log += F(", par: "); - log += par; - log += F("/0x"); - log += ull2String(par, HEX); - log += F(", chip:"); - log += idx; - log += F(", width:"); - log += width; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("%s: arg: %s, tmp: %s/0x%x, par: %d/0x%x, chip:%d, width:%d"), + command.c_str(), arg.c_str(), ull2String(tmp).c_str(), tmp, par, par, idx, width)); } # endif // ifdef P126_DEBUG_LOG param++; // Process next argument arg = parseString(string, param); - for (uint8_t n = 0; n < width && idx < _chipCount; n++, idx++) { + for (uint8_t n = 0; n < width && idx < _chipCount; ++n, ++idx) { value[idx] = ((par >> (n * 8)) & 0xff); # ifdef P126_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = command; - log += F(": value["); - log += idx; - log += F("] : "); - log += value[idx]; - log += F("/0x"); - log += String(value[idx], HEX); - log += F(", n * 8: "); - log += n; - log += '/'; - log += n * 8; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("%s: value[%d] : %d/0x%x, n * 8: %d/%d"), + command.c_str(), idx, value[idx], value[idx], n, n * 8)); } # endif // ifdef P126_DEBUG_LOG } @@ -271,7 +222,7 @@ bool P126_data_struct::plugin_write(struct EventStruct *event, // Reset State_A..D values when changing the offset if ((previousOffset != P126_CONFIG_SHOW_OFFSET) && P126_CONFIG_FLAGS_GET_VALUES_RESTORE) { - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) { + for (uint8_t varNr = 0; varNr < VARS_PER_TASK; ++varNr) { UserVar.setUint32(event->TaskIndex, varNr, 0u); } # ifdef P126_DEBUG_LOG diff --git a/src/src/PluginStructs/P128_data_struct.cpp b/src/src/PluginStructs/P128_data_struct.cpp index b9533fd24..65bc1c9c1 100644 --- a/src/src/PluginStructs/P128_data_struct.cpp +++ b/src/src/PluginStructs/P128_data_struct.cpp @@ -43,22 +43,98 @@ bool P128_data_struct::plugin_read(struct EventStruct *event) { # ifndef LIMIT_BUILD_SIZE if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - log.reserve(64); - log = F("Lights: mode: "); - log += P128_modeType_toString(mode); - log += F(" lastmode: "); - log += P128_modeType_toString(savemode); - log += F(" fadetime: "); - log += (int)UserVar[event->BaseVarIndex + 2]; - log += F(" fadedelay: "); - log += (int)UserVar[event->BaseVarIndex + 3]; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, strformat( + F("Lights: mode: %s lastmode: %s fadetime: %d fadedelay: %d"), + String(P128_modeType_toString(mode)).c_str(), + String(P128_modeType_toString(savemode)).c_str(), + (int)UserVar[event->BaseVarIndex + 2], + (int)UserVar[event->BaseVarIndex + 3])); } # endif // ifndef LIMIT_BUILD_SIZE return true; } +const char neopixelfx_subcommands[] PROGMEM = + "all" + "|bgcolor" + "|colorfade" + "|comet" + "|count" + "|dim" + "|dualscan" + "|dualwipe" + "|fade" + "|fadedelay" + "|fadetime" +# if P128_ENABLE_FAKETV + "|faketv" +# endif // if P128_ENABLE_FAKETV + "|fire" + "|fireflicker" + "|hsv" + "|hsvline" + "|hsvone" + "|kitt" + "|line" + "|off" + "|on" + "|one" + "|rainbow" + "|rgb" + "|scan" + "|simpleclock" + "|sparkle" + "|speed" + "|statusrequest" + "|stop" + "|theatre" + "|tick" + "|twinkle" + "|twinklefade" + "|wipe"; + + +enum class neopixelfx_subcommands_e { + all, + bgcolor, + colorfade, + comet, + count, + dim, + dualscan, + dualwipe, + fade, + fadedelay, + fadetime, +# if P128_ENABLE_FAKETV + faketv, +# endif // if P128_ENABLE_FAKETV + fire, + fireflicker, + hsv, + hsvline, + hsvone, + kitt, + line, + off, + on, + one, + rainbow, + rgb, + scan, + simpleclock, + sparkle, + speed, + statusrequest, + stop, + theatre, + tick, + twinkle, + twinklefade, + wipe +}; + + bool P128_data_struct::plugin_write(struct EventStruct *event, const String & string) { bool success = false; @@ -68,632 +144,609 @@ bool P128_data_struct::plugin_write(struct EventStruct *event, if ((equals(command, F("neopixelfx"))) || (equals(command, F("nfx")))) { const String subCommand = parseString(string, 2); - const String str3 = parseString(string, 3); - const int32_t str3i = event->Par2; - const String str4 = parseString(string, 4); - const int32_t str4i = event->Par3; - const String str5 = parseString(string, 5); - const int32_t str5i = event->Par4; - const String str6 = parseString(string, 6); - const int32_t str6i = event->Par5; - const String str7 = parseString(string, 7); - const int32_t str7i = str7.toInt(); + const int subCommand_i = GetCommandCode(subCommand.c_str(), neopixelfx_subcommands); - const bool command_is_on = (equals(subCommand, F("on"))); - const bool command_is_off = (equals(subCommand, F("off"))); + if (subCommand_i != -1) { + const String str3 = parseString(string, 3); + const int32_t str3i = event->Par2; + const String str4 = parseString(string, 4); + const int32_t str4i = event->Par3; + const String str5 = parseString(string, 5); + const int32_t str5i = event->Par4; + const String str6 = parseString(string, 6); + const int32_t str6i = event->Par5; + const String str7 = parseString(string, 7); + const int32_t str7i = str7.toInt(); - if (equals(subCommand, F("fadetime"))) { - success = true; - fadetime = str3i; - } - else if (equals(subCommand, F("fadedelay"))) { - success = true; - fadedelay = str3i; - } + const neopixelfx_subcommands_e subcommands_e = static_cast(subCommand_i); - else if (equals(subCommand, F("speed"))) { - success = true; - defaultspeed = str3i; - speed = defaultspeed; - } - - else if (equals(subCommand, F("bgcolor"))) { success = true; - hex2rrggbb(str3); - } - else if (equals(subCommand, F("count"))) { - success = true; - count = str3i; - } + switch (subcommands_e) { + case neopixelfx_subcommands_e::fadetime: + { + fadetime = str3i; + break; + } - else if (command_is_on || command_is_off) { - success = true; - fadetime = str3.isEmpty() + case neopixelfx_subcommands_e::fadedelay: + { + fadedelay = str3i; + break; + } + + case neopixelfx_subcommands_e::speed: + { + defaultspeed = str3i; + speed = defaultspeed; + break; + } + + case neopixelfx_subcommands_e::bgcolor: + { + hex2rrggbb(str3); + break; + } + + case neopixelfx_subcommands_e::count: + { + count = str3i; + break; + } + + case neopixelfx_subcommands_e::on: + case neopixelfx_subcommands_e::off: + { + fadetime = str3.isEmpty() ? 1000 : str3i; - fadedelay = str4.isEmpty() + fadedelay = str4.isEmpty() ? 0 : str4i; - for (int pixel = 0; pixel < pixelCount; pixel++) { - r_pixel = (fadedelay < 0) + for (int pixel = 0; pixel < pixelCount; pixel++) { + r_pixel = (fadedelay < 0) ? pixelCount - pixel - 1 : pixel; - starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); + starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); - if ((command_is_on) && (mode == P128_modetype::Off)) { // switch on - rgb_target[pixel] = rgb_old[pixel]; - rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); - } else if (command_is_off) { // switch off - rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); - rgb_target[pixel] = RgbColor(0); + if (subcommands_e == neopixelfx_subcommands_e::off) { + rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); + rgb_target[pixel] = RgbColor(0); + } else if (mode == P128_modetype::Off) { // switch on + rgb_target[pixel] = rgb_old[pixel]; + rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); + } + } + + if (subcommands_e == neopixelfx_subcommands_e::off) { + savemode = mode; + mode = P128_modetype::Fade; + } else if (mode == P128_modetype::Off) { + // switch on + mode = (savemode == P128_modetype::On) ? P128_modetype::Fade : savemode; + } + + maxtime = starttime[r_pixel] + (fadetime / 20); + break; } - } - if ((command_is_on) && (mode == P128_modetype::Off)) { // switch on - mode = (savemode == P128_modetype::On) ? P128_modetype::Fade : savemode; - } else if (command_is_off) { // switch off - savemode = mode; - mode = P128_modetype::Fade; - } + case neopixelfx_subcommands_e::dim: + { + if ((str3i >= 0) && (str3i <= maxBright)) { // Safety check + Plugin_128_pixels->SetBrightness(str3i); + } else { success = false; } + break; + } - maxtime = starttime[r_pixel] + (fadetime / 20); - } + case neopixelfx_subcommands_e::line: + { + mode = P128_modetype::On; - else if (equals(subCommand, F("dim"))) { - if ((str3i >= 0) && (str3i <= maxBright)) { // Safety check - success = true; - Plugin_128_pixels->SetBrightness(str3i); - } - } + hex2rgb(str5); - else if (equals(subCommand, F("line"))) { - success = true; - mode = P128_modetype::On; + const int lastPixelIndex = (str4i - str3i + pixelCount) % pixelCount; - hex2rgb(str5); + for (int i = 0; i <= lastPixelIndex; ++i) { + Plugin_128_pixels->SetPixelColor((i + str3i - 1) % pixelCount, rgb); + } + break; + } - for (int i = 0; i <= (str4i - str3i + pixelCount) % pixelCount; i++) { - Plugin_128_pixels->SetPixelColor((i + str3i - 1) % pixelCount, rgb); - } - } + case neopixelfx_subcommands_e::tick: + { + mode = P128_modetype::On; - else if (equals(subCommand, F("tick"))) { - success = true; - mode = P128_modetype::On; + hex2rgb(str4); - hex2rgb(str4); + // for (int i = 0; i < pixelCount ; i = i + (pixelCount / parseString(string, 3).toInt())) { + for (int i = 0; i < str3i; i++) { + Plugin_128_pixels->SetPixelColor(i * pixelCount / str3i, rgb); + } + break; + } - // for (int i = 0; i < pixelCount ; i = i + (pixelCount / parseString(string, 3).toInt())) { - for (int i = 0; i < str3i; i++) { - Plugin_128_pixels->SetPixelColor(i * pixelCount / str3i, rgb); - } - } + case neopixelfx_subcommands_e::one: + { + mode = P128_modetype::On; - else if (equals(subCommand, F("one"))) { - success = true; - mode = P128_modetype::On; + const uint16_t pixnum = str3i - 1; + hex2rgb(str4); - const uint16_t pixnum = str3i - 1; - hex2rgb(str4); + Plugin_128_pixels->SetPixelColor(pixnum, rgb); + break; + } - Plugin_128_pixels->SetPixelColor(pixnum, rgb); - } + case neopixelfx_subcommands_e::fade: + case neopixelfx_subcommands_e::all: + case neopixelfx_subcommands_e::rgb: + { + mode = P128_modetype::Fade; - else if ((equals(subCommand, F("fade"))) || (equals(subCommand, F("all"))) || (equals(subCommand, F("rgb")))) { - success = true; - mode = P128_modetype::Fade; + if ((subcommands_e == neopixelfx_subcommands_e::all) || + (subcommands_e == neopixelfx_subcommands_e::rgb)) { + fadedelay = 0; + } - if ((equals(subCommand, F("all"))) || (equals(subCommand, F("rgb")))) { - fadedelay = 0; - } + hex2rgb(str3); + hex2rgb_pixel(str3); - hex2rgb(str3); - hex2rgb_pixel(str3); - - fadetime = str4.isEmpty() + fadetime = str4.isEmpty() ? fadetime : str4i; - fadedelay = str5.isEmpty() + fadedelay = str5.isEmpty() ? fadedelay : str5i; - for (int pixel = 0; pixel < pixelCount; pixel++) { - r_pixel = (fadedelay < 0) + for (int pixel = 0; pixel < pixelCount; pixel++) { + r_pixel = (fadedelay < 0) ? pixelCount - pixel - 1 : pixel; - starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); + starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); - rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); - } - maxtime = starttime[r_pixel] + (fadetime / 20); - } + rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); + } + maxtime = starttime[r_pixel] + (fadetime / 20); + break; + } - else if (equals(subCommand, F("hsv"))) { - success = true; - mode = P128_modetype::Fade; - fadedelay = 0; - rgb = - RgbColor(HsbColor(str3.toFloat() / 360.0f, str4.toFloat() / 100.0f, - str5.toFloat() / 100.0f)); + case neopixelfx_subcommands_e::hsv: + { + mode = P128_modetype::Fade; + fadedelay = 0; + rgb = + RgbColor(HsbColor(str3.toFloat() / 360.0f, + str4.toFloat() / 100.0f, + str5.toFloat() / 100.0f)); - rgb2colorStr(); + rgb2colorStr(); - hex2rgb_pixel(colorStr); + hex2rgb_pixel(colorStr); - fadetime = str6.isEmpty() + fadetime = str6.isEmpty() ? fadetime : str6i; - fadedelay = str7.isEmpty() + fadedelay = str7.isEmpty() ? fadedelay : str7i; - for (int pixel = 0; pixel < pixelCount; pixel++) { - r_pixel = (fadedelay < 0) + for (int pixel = 0; pixel < pixelCount; pixel++) { + r_pixel = (fadedelay < 0) ? pixelCount - pixel - 1 : pixel; - starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); + starttime[r_pixel] = counter20ms + (pixel * abs(fadedelay) / 20); - rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); - } - maxtime = starttime[r_pixel] + (fadetime / 20); - } + rgb_old[pixel] = Plugin_128_pixels->GetPixelColor(pixel); + } + maxtime = starttime[r_pixel] + (fadetime / 20); + break; + } - else if (equals(subCommand, F("hsvone"))) { - success = true; - mode = P128_modetype::On; - rgb = - RgbColor(HsbColor(str4.toFloat() / 360.0f, str5.toFloat() / 100.0f, - str6.toFloat() / 100.0f)); + case neopixelfx_subcommands_e::hsvone: + { + mode = P128_modetype::On; + rgb = + RgbColor(HsbColor(str4.toFloat() / 360.0f, + str5.toFloat() / 100.0f, + str6.toFloat() / 100.0f)); - rgb2colorStr(); + rgb2colorStr(); - hex2rgb(colorStr); - const uint16_t pixnum = str3i - 1; - Plugin_128_pixels->SetPixelColor(pixnum, rgb); - } + hex2rgb(colorStr); + const uint16_t pixnum = str3i - 1; + Plugin_128_pixels->SetPixelColor(pixnum, rgb); + break; + } - else if (equals(subCommand, F("hsvline"))) { - success = true; - mode = P128_modetype::On; + case neopixelfx_subcommands_e::hsvline: + { + mode = P128_modetype::On; - rgb = - RgbColor(HsbColor(str5.toFloat() / 360.0f, str6.toFloat() / 100.0f, - str7.toFloat() / 100.0f)); + rgb = + RgbColor(HsbColor(str5.toFloat() / 360.0f, + str6.toFloat() / 100.0f, + str7.toFloat() / 100.0f)); - rgb2colorStr(); + rgb2colorStr(); - hex2rgb(colorStr); + hex2rgb(colorStr); - for (int i = 0; i <= (str4i - str3i + pixelCount) % pixelCount; i++) { - Plugin_128_pixels->SetPixelColor((i + str3i - 1) % pixelCount, rgb); - } - } + const int lastPixelIndex = (str4i - str3i + pixelCount) % pixelCount; - else if (equals(subCommand, F("rainbow"))) { - success = true; - fadeIn = (mode == P128_modetype::Off) ? true : false; - mode = P128_modetype::Rainbow; - starttimerb = counter20ms; + for (int i = 0; i <= lastPixelIndex; ++i) { + Plugin_128_pixels->SetPixelColor((i + str3i - 1) % pixelCount, rgb); + } + break; + } - rainbowspeed = str3.isEmpty() + case neopixelfx_subcommands_e::rainbow: + { + fadeIn = (mode == P128_modetype::Off) ? true : false; + mode = P128_modetype::Rainbow; + starttimerb = counter20ms; + + rainbowspeed = str3.isEmpty() ? speed : str3i; - fadetime = str4.isEmpty() + fadetime = str4.isEmpty() ? fadetime : str4i; - } + break; + } - else if (equals(subCommand, F("colorfade"))) { - success = true; - mode = P128_modetype::ColorFade; + case neopixelfx_subcommands_e::colorfade: + { + mode = P128_modetype::ColorFade; - hex2rgb(str3); + hex2rgb(str3); - if (!str4.isEmpty()) { hex2rrggbb(str4); } + if (!str4.isEmpty()) { hex2rrggbb(str4); } - startpixel = str5.isEmpty() + startpixel = str5.isEmpty() ? 0 : str5i - 1; - endpixel = str6.isEmpty() + endpixel = str6.isEmpty() ? pixelCount - 1 : str6i - 1; - } - - else if (equals(subCommand, F("kitt"))) { - success = true; - mode = P128_modetype::Kitt; - - _counter_mode_step = 0; - - hex2rgb(str3); - - speed = str4.isEmpty() - ? defaultspeed - : str4i; - } - - else if (equals(subCommand, F("comet"))) { - success = true; - mode = P128_modetype::Comet; - - _counter_mode_step = 0; - - hex2rgb(str3); - - speed = str4.isEmpty() - ? defaultspeed - : str4i; - } - - else if (equals(subCommand, F("theatre"))) { - success = true; - mode = P128_modetype::Theatre; - - hex2rgb(str3); - - if (!str4.isEmpty()) { hex2rrggbb(str4); } - - count = str5.isEmpty() - ? count - : str5i; - - speed = str6.isEmpty() - ? defaultspeed - : str6i; - - for (int i = 0; i < pixelCount; i++) { - if ((i / count) % 2 == 0) { - Plugin_128_pixels->SetPixelColor(i, rgb); - } else { - Plugin_128_pixels->SetPixelColor(i, rrggbb); + break; } - } - } - else if (equals(subCommand, F("scan"))) { - success = true; - mode = P128_modetype::Scan; + case neopixelfx_subcommands_e::kitt: + { + mode = P128_modetype::Kitt; - _counter_mode_step = 0; + _counter_mode_step = 0; - hex2rrggbb(F("000000")); + hex2rgb(str3); - /*CLEAN ALL PIXELS */ - for (int i = 0; i < pixelCount; i++) { - Plugin_128_pixels->SetPixelColor(i, rrggbb); - } - hex2rgb(str3); + speed = str4.isEmpty() + ? defaultspeed + : str4i; + break; + } - if (!str4.isEmpty()) { hex2rrggbb(str4); } + case neopixelfx_subcommands_e::comet: + { + mode = P128_modetype::Comet; - speed = str5.isEmpty() + _counter_mode_step = 0; + + hex2rgb(str3); + + speed = str4.isEmpty() + ? defaultspeed + : str4i; + break; + } + + case neopixelfx_subcommands_e::theatre: + { + mode = P128_modetype::Theatre; + + hex2rgb(str3); + + if (!str4.isEmpty()) { hex2rrggbb(str4); } + + count = str5.isEmpty() + ? count + : str5i; + + speed = str6.isEmpty() + ? defaultspeed + : str6i; + + for (int i = 0; i < pixelCount; i++) { + if ((i / count) % 2 == 0) { + Plugin_128_pixels->SetPixelColor(i, rgb); + } else { + Plugin_128_pixels->SetPixelColor(i, rrggbb); + } + } + break; + } + + case neopixelfx_subcommands_e::scan: + { + mode = P128_modetype::Scan; + + _counter_mode_step = 0; + + hex2rrggbb(F("000000")); + + /*CLEAN ALL PIXELS */ + for (int i = 0; i < pixelCount; i++) { + Plugin_128_pixels->SetPixelColor(i, rrggbb); + } + hex2rgb(str3); + + if (!str4.isEmpty()) { hex2rrggbb(str4); } + + speed = str5.isEmpty() ? defaultspeed : str5i; - ledi = str6.isEmpty() + ledi = str6.isEmpty() ? 1 : str6i; - ledf = str7.isEmpty() + ledf = str7.isEmpty() ? pixelCount : str7i + 1; - } + break; + } - else if (equals(subCommand, F("dualscan"))) { - success = true; - mode = P128_modetype::Dualscan; + case neopixelfx_subcommands_e::dualscan: + { + mode = P128_modetype::Dualscan; - _counter_mode_step = 0; - hex2rrggbb(F("000000")); + _counter_mode_step = 0; + hex2rrggbb(F("000000")); - /*CLEAN ALL PIXELS */ - for (int i = 0; i < pixelCount; i++) { - Plugin_128_pixels->SetPixelColor(i, rrggbb); - } - hex2rgb(str3); + /*CLEAN ALL PIXELS */ + for (int i = 0; i < pixelCount; i++) { + Plugin_128_pixels->SetPixelColor(i, rrggbb); + } + hex2rgb(str3); - if (!str4.isEmpty()) { hex2rrggbb(str4); } + if (!str4.isEmpty()) { hex2rrggbb(str4); } - speed = str5.isEmpty() + speed = str5.isEmpty() ? defaultspeed : str5i; - ledi = str6.isEmpty() + ledi = str6.isEmpty() ? 1 : str6i; - ledf = str7.isEmpty() + ledf = str7.isEmpty() ? pixelCount : str7i + 1; - } + break; + } - else if (equals(subCommand, F("twinkle"))) { - success = true; - mode = P128_modetype::Twinkle; + case neopixelfx_subcommands_e::twinkle: + { + // FIXME TD-er: code duplication in: Twinkle, twinklefade, sparkle + mode = P128_modetype::Twinkle; - _counter_mode_step = 0; + _counter_mode_step = 0; - hex2rgb(str3); + hex2rgb(str3); - if (!str4.isEmpty()) { hex2rrggbb(str4); } + if (!str4.isEmpty()) { hex2rrggbb(str4); } - speed = str5.isEmpty() + speed = str5.isEmpty() ? defaultspeed : str5i; - } + break; + } - else if (equals(subCommand, F("twinklefade"))) { - success = true; - mode = P128_modetype::TwinkleFade; + case neopixelfx_subcommands_e::twinklefade: + { + mode = P128_modetype::TwinkleFade; - hex2rgb(str3); + hex2rgb(str3); - count = str4.isEmpty() + count = str4.isEmpty() ? count : str4i; - speed = str5.isEmpty() + speed = str5.isEmpty() ? defaultspeed : str5i; - } + break; + } - else if (equals(subCommand, F("sparkle"))) { - success = true; - mode = P128_modetype::Sparkle; + case neopixelfx_subcommands_e::sparkle: + { + mode = P128_modetype::Sparkle; - _counter_mode_step = 0; + _counter_mode_step = 0; - hex2rgb(str3); - hex2rrggbb(str4); + hex2rgb(str3); + hex2rrggbb(str4); - speed = str5.isEmpty() + speed = str5.isEmpty() ? defaultspeed : str5i; - } + break; + } - else if (equals(subCommand, F("wipe"))) { - success = true; - mode = P128_modetype::Wipe; + case neopixelfx_subcommands_e::wipe: + case neopixelfx_subcommands_e::dualwipe: + { + mode = (subcommands_e == neopixelfx_subcommands_e::wipe) + ? P128_modetype::Wipe + : P128_modetype::Dualwipe; - _counter_mode_step = 0; + _counter_mode_step = 0; - hex2rgb(str3); + hex2rgb(str3); - if (!str4.isEmpty()) { - hex2rrggbb(str4); - } else { - hex2rrggbb(F("000000")); - } + if (!str4.isEmpty()) { + hex2rrggbb(str4); + } else { + hex2rrggbb(F("000000")); + } - speed = str5.isEmpty() + speed = str5.isEmpty() ? defaultspeed : str5i; - } - - else if (equals(subCommand, F("dualwipe"))) { - success = true; - mode = P128_modetype::Dualwipe; - - _counter_mode_step = 0; - - hex2rgb(str3); - - if (!str4.isEmpty()) { - hex2rrggbb(str4); - } else { - hex2rrggbb(F("000000")); - } - - speed = str5.isEmpty() - ? defaultspeed - : str5i; - } + break; + } # if P128_ENABLE_FAKETV - else if (equals(subCommand, F("faketv"))) { - success = true; - mode = P128_modetype::FakeTV; - _counter_mode_step = 0; + case neopixelfx_subcommands_e::faketv: + { + mode = P128_modetype::FakeTV; + _counter_mode_step = 0; - randomSeed(analogRead(A0)); - pixelNum = HwRandom(NUMPixels); // Begin at random point + randomSeed(analogRead(A0)); + pixelNum = HwRandom(NUMPixels); // Begin at random point - startpixel = str3.isEmpty() + startpixel = str3.isEmpty() ? 0 : str3i - 1; - endpixel = str4.isEmpty() + endpixel = str4.isEmpty() ? pixelCount : str4i; - } + break; + } # endif // if P128_ENABLE_FAKETV - else if (equals(subCommand, F("fire"))) { - success = true; - mode = P128_modetype::Fire; + case neopixelfx_subcommands_e::fire: + { + mode = P128_modetype::Fire; - fps = str3.isEmpty() + fps = str3.isEmpty() ? fps : str3i; - fps = (fps == 0 || fps > 50) ? 50 : fps; + fps = (fps == 0 || fps > 50) ? 50 : fps; - brightness = str4.isEmpty() + brightness = str4.isEmpty() ? brightness : str4.toFloat(); - cooling = str5.isEmpty() + cooling = str5.isEmpty() ? cooling : str5.toFloat(); - sparking = str6.isEmpty() + sparking = str6.isEmpty() ? sparking : str6.toFloat(); - } + break; + } - else if (equals(subCommand, F("fireflicker"))) { - success = true; - mode = P128_modetype::FireFlicker; + case neopixelfx_subcommands_e::fireflicker: + { + mode = P128_modetype::FireFlicker; - rev_intensity = str3.isEmpty() + rev_intensity = str3.isEmpty() ? rev_intensity : str3i; - speed = str4.isEmpty() + speed = str4.isEmpty() ? defaultspeed : str4i; - } + break; + } - else if (equals(subCommand, F("simpleclock"))) { - success = true; - mode = P128_modetype::SimpleClock; + case neopixelfx_subcommands_e::simpleclock: + { + mode = P128_modetype::SimpleClock; # if defined(RGBW) || defined(GRBW) - if (!str3.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str3); + if (!str3.isEmpty()) { + rgb_tick_s = rgbStr2RgbWColor(str3); + } - if (str3.length() <= 6) { - rgb_tick_s = RgbwColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } else { - rgb_tick_s = RgbwColor(hcolorui >> 24, - hcolorui >> 16, - hcolorui >> 8, - hcolorui); - } - } + if (!str4.isEmpty()) { + rgb_tick_b = rgbStr2RgbWColor(str4); + } - if (!str4.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str4); + if (!str5.isEmpty()) { + rgb_h = rgbStr2RgbWColor(str5); + } - if (str4.length() <= 6) { - rgb_tick_b = RgbwColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } else { - rgb_tick_b = RgbwColor(hcolorui >> 24, - hcolorui >> 16, - hcolorui >> 8, - hcolorui); - } - } + if (!str6.isEmpty()) { + rgb_m = rgbStr2RgbWColor(str6); + } - if (!str5.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str5); - - if (str5.length() <= 6) { - rgb_h = RgbwColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } else { - rgb_h = RgbwColor(hcolorui >> 24, - hcolorui >> 16, - hcolorui >> 8, - hcolorui); - } - } - - if (!str6.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str6); - - if (str6.length() <= 6) { - rgb_m = RgbwColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } else { - rgb_m = RgbwColor(hcolorui >> 24, - hcolorui >> 16, - hcolorui >> 8, - hcolorui); - } - } - - if (!str7.isEmpty()) { - if (equals(str7, F("off"))) { - rgb_s_off = true; - } else if (str7.length() <= 6) { - const uint32_t hcolorui = rgbStr2Num(str7); - rgb_s_off = false; - rgb_s = RgbwColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } else { - const uint32_t hcolorui = rgbStr2Num(str7); - rgb_s_off = false; - rgb_s = RgbwColor(hcolorui >> 24, - hcolorui >> 16, - hcolorui >> 8, - hcolorui); - } - } + if (!str7.isEmpty()) { + if (equals(str7, F("off"))) { + rgb_s_off = true; + } else { + rgb_s = rgbStr2RgbWColor(str7); + rgb_s_off = false; + } + } # else // if defined(RGBW) || defined(GRBW) - if (!str3.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str3); - rgb_tick_s = RgbColor(hcolorui >> 16, hcolorui >> 8, hcolorui); - } + if (!str3.isEmpty()) { + rgb_tick_s = rgbStr2RgbColor(str3); + } - if (!str4.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str4); - rgb_tick_b = RgbColor(hcolorui >> 16, hcolorui >> 8, hcolorui); - } + if (!str4.isEmpty()) { + rgb_tick_b = rgbStr2RgbColor(str4); + } - if (!str5.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str5); - rgb_h = RgbColor(hcolorui >> 16, hcolorui >> 8, hcolorui); - } + if (!str5.isEmpty()) { + rgb_h = rgbStr2RgbColor(str5); + } - if (!str6.isEmpty()) { - const uint32_t hcolorui = rgbStr2Num(str6); - rgb_m = RgbColor(hcolorui >> 16, hcolorui >> 8, hcolorui); - } + if (!str6.isEmpty()) { + rgb_m = rgbStr2RgbColor(str6); + } - if (!str7.isEmpty()) { - if (equals(str7, F("off"))) { - rgb_s_off = true; - } else { - const uint32_t hcolorui = rgbStr2Num(str7); - rgb_s_off = false; - rgb_s = RgbColor(hcolorui >> 16, hcolorui >> 8, - hcolorui); - } - } + if (!str7.isEmpty()) { + if (equals(str7, F("off"))) { + rgb_s_off = true; + } else { + rgb_s = rgbStr2RgbColor(str7); + rgb_s_off = false; + } + } # endif // if defined(RGBW) || defined(GRBW) - if (!parseString(string, 8).isEmpty()) { - hex2rrggbb(parseString(string, 8)); + if (!parseString(string, 8).isEmpty()) { + hex2rrggbb(parseString(string, 8)); + } + break; + } + + case neopixelfx_subcommands_e::stop: + { + mode = P128_modetype::On; + break; + } + + case neopixelfx_subcommands_e::statusrequest: + { + break; + } } } - else if (equals(subCommand, F("stop"))) { - success = true; - mode = P128_modetype::On; - } - - else if (equals(subCommand, F("statusrequest"))) { - success = true; - } - if (!success) { success = true; // Fake the command to be successful, to get this custom error message out - String log = F("NeoPixelBus: unknown subcommand: "); - log += subCommand; - addLogMove(LOG_LEVEL_INFO, log); - String json; + String error(concat(F("NeoPixelBus: unknown subcommand: "), subCommand)); + printToWebJSON = true; - json += '{'; json += '\n'; - json += to_json_object_value(F("plugin"), F("128")); - json += ','; json += '\n'; - String subjson = F("NeoPixelBus: unknown command: "); - subjson.reserve(subCommand.length() + 30); - subjson += subCommand; - json += to_json_object_value(F("log"), subjson); - json += '\n'; json += '}'; json += '\n'; - // event->Source=EventValueSource::Enum::VALUE_SOURCE_HTTP; - SendStatus(event, json); // send http response to controller (JSON format) + SendStatus( + event, + strformat( + F("{\n\"plugin\":128,\n\"log\":\"%s\"\n}\n"), + error.c_str()) + ); // send http response to controller (JSON format) printToWeb = false; + + addLogMove(LOG_LEVEL_INFO, error); } NeoPixelSendStatus(event); @@ -715,10 +768,9 @@ bool P128_data_struct::plugin_write(struct EventStruct *event, } void P128_data_struct::rgb2colorStr() { - colorStr.clear(); - colorStr += formatToHex_no_prefix(rgb.R, 2); - colorStr += formatToHex_no_prefix(rgb.G, 2); - colorStr += formatToHex_no_prefix(rgb.B, 2); + colorStr = formatToHex_no_prefix( + (rgb.R << 16) | (rgb.G << 8) | rgb.B, + 6); } bool P128_data_struct::plugin_fifty_per_second(struct EventStruct *event) { @@ -804,9 +856,9 @@ bool P128_data_struct::plugin_fifty_per_second(struct EventStruct *event) { if (mode != lastmode) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("NeoPixelBus: Mode Change: "); - log += P128_modeType_toString(mode); - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat( + F("NeoPixelBus: Mode Change: "), + P128_modeType_toString(mode))); } NeoPixelSendStatus(event); } @@ -817,8 +869,7 @@ void P128_data_struct::fade(void) { for (int pixel = 0; pixel < pixelCount; pixel++) { long counter = 20 * (counter20ms - starttime[pixel]); float progress = (float)counter / (float)fadetime; - progress = (progress < 0) ? 0 : progress; - progress = (progress > 1) ? 1 : progress; + progress = constrain(progress, 0.0f, 1.0f); # if defined(RGBW) || defined(GRBW) RgbwColor updatedColor = RgbwColor::LinearBlend( @@ -848,8 +899,7 @@ void P128_data_struct::colorfade(void) { for (uint16_t i = 0; i <= difference; i++) { progress = (float)i / (difference - 1); - progress = (progress >= 1) ? 1 : progress; - progress = (progress <= 0) ? 0 : progress; + progress = constrain(progress, 0.0f, 1.0f); # if defined(RGBW) || defined(GRBW) RgbwColor updatedColor = RgbwColor::LinearBlend( @@ -886,8 +936,7 @@ void P128_data_struct::wipe(void) { void P128_data_struct::dualwipe(void) { if (counter20ms % (unsigned long)(SPEED_MAX / abs(speed)) == 0) { if (speed > 0) { - int i = _counter_mode_step - pixelCount; - i = abs(i); + const int i = abs(static_cast(_counter_mode_step - pixelCount)); Plugin_128_pixels->SetPixelColor(_counter_mode_step, rrggbb); Plugin_128_pixels->SetPixelColor(i, rgb); @@ -896,8 +945,7 @@ void P128_data_struct::dualwipe(void) { Plugin_128_pixels->SetPixelColor(i - 1, rrggbb); } } else { - int i = (pixelCount / 2) - _counter_mode_step; - i = abs(i); + const int i = abs(static_cast((pixelCount / 2) - _counter_mode_step)); Plugin_128_pixels->SetPixelColor(_counter_mode_step + (pixelCount / 2), rrggbb); Plugin_128_pixels->SetPixelColor(i, rgb); @@ -984,8 +1032,8 @@ void P128_data_struct::faketv(void) { * Cycles a rainbow over the entire string of LEDs. */ void P128_data_struct::rainbow(void) { - long counter = 20 * (counter20ms - starttimerb); - float progress = (float)counter / (float)fadetime; + const long counter = 20 * (counter20ms - starttimerb); + const float progress = (float)counter / (float)fadetime; if (fadeIn == true) { Plugin_128_pixels->SetBrightness(progress * maxBright); // Safety check @@ -993,10 +1041,12 @@ void P128_data_struct::rainbow(void) { } for (int i = 0; i < pixelCount; i++) { - uint8_t r1 = (Wheel(((i * 256 / pixelCount) + counter20ms * rainbowspeed / 10) & 255) >> 16); - uint8_t g1 = (Wheel(((i * 256 / pixelCount) + counter20ms * rainbowspeed / 10) & 255) >> 8); - uint8_t b1 = (Wheel(((i * 256 / pixelCount) + counter20ms * rainbowspeed / 10) & 255)); - Plugin_128_pixels->SetPixelColor(i, RgbColor(r1, g1, b1)); + const uint32_t color = Wheel(((i * 256 / pixelCount) + counter20ms * rainbowspeed / 10) & 255); + Plugin_128_pixels->SetPixelColor(i, + RgbColor( + (color >> 16), // r + (color >> 8), // g + (color))); // b } mode = (rainbowspeed == 0) ? P128_modetype::On : P128_modetype::Rainbow; } @@ -1028,19 +1078,19 @@ void P128_data_struct::kitt(void) { RgbwColor px_rgb = Plugin_128_pixels->GetPixelColor(i); // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; - px_rgb.W = px_rgb.W >> 1; + px_rgb.R >>= 1; + px_rgb.G >>= 1; + px_rgb.B >>= 1; + px_rgb.W >>= 1; # else // if defined(RGBW) || defined(GRBW) RgbColor px_rgb = Plugin_128_pixels->GetPixelColor(i); // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; + px_rgb.R >>= 1; + px_rgb.G >>= 1; + px_rgb.B >>= 1; # endif // if defined(RGBW) || defined(GRBW) Plugin_128_pixels->SetPixelColor(i, px_rgb); @@ -1064,55 +1114,32 @@ void P128_data_struct::kitt(void) { void P128_data_struct::comet(void) { if (counter20ms % (unsigned long)(SPEED_MAX / abs(speed)) == 0) { for (uint16_t i = 0; i < pixelCount; i++) { - if (speed > 0) { - # if defined(RGBW) || defined(GRBW) - RgbwColor px_rgb = Plugin_128_pixels->GetPixelColor(i); + const uint16_t pixelIndex = (speed > 0) ? i : pixelCount - i - 1; + # if defined(RGBW) || defined(GRBW) + RgbwColor px_rgb = Plugin_128_pixels->GetPixelColor(pixelIndex); - // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; - px_rgb.W = px_rgb.W >> 1; + // fade out (divide by 2) + px_rgb.R >>= 1; + px_rgb.G >>= 1; + px_rgb.B >>= 1; + px_rgb.W >>= 1; - # else // if defined(RGBW) || defined(GRBW) + # else // if defined(RGBW) || defined(GRBW) - RgbColor px_rgb = Plugin_128_pixels->GetPixelColor(i); + RgbColor px_rgb = Plugin_128_pixels->GetPixelColor(pixelIndex); - // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; - # endif // if defined(RGBW) || defined(GRBW) + // fade out (divide by 2) + px_rgb.R >>= 1; + px_rgb.G >>= 1; + px_rgb.B >>= 1; + # endif // if defined(RGBW) || defined(GRBW) - Plugin_128_pixels->SetPixelColor(i, px_rgb); - } else { - # if defined(RGBW) || defined(GRBW) - RgbwColor px_rgb = Plugin_128_pixels->GetPixelColor(pixelCount - i - 1); - - // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; - px_rgb.W = px_rgb.W >> 1; - - # else // if defined(RGBW) || defined(GRBW) - - RgbColor px_rgb = Plugin_128_pixels->GetPixelColor(pixelCount - i - 1); - - // fade out (divide by 2) - px_rgb.R = px_rgb.R >> 1; - px_rgb.G = px_rgb.G >> 1; - px_rgb.B = px_rgb.B >> 1; - # endif // if defined(RGBW) || defined(GRBW) - - Plugin_128_pixels->SetPixelColor(pixelCount - i - 1, px_rgb); - } + Plugin_128_pixels->SetPixelColor(pixelIndex, px_rgb); } - if (speed > 0) { - Plugin_128_pixels->SetPixelColor(_counter_mode_step, rgb); - } else { - Plugin_128_pixels->SetPixelColor(pixelCount - _counter_mode_step - 1, rgb); + { + const uint16_t pixelIndex = (speed > 0) ? _counter_mode_step : pixelCount - _counter_mode_step - 1; + Plugin_128_pixels->SetPixelColor(pixelIndex, rgb); } _counter_mode_step = (_counter_mode_step + 1) % pixelCount; @@ -1414,24 +1441,21 @@ void P128_data_struct::Plugin_128_simpleclock() { Plugin_128_pixels->ClearTo(rrggbb); for (int i = 0; i < (60 / small_tick); i++) { - if (i % (big_tick / small_tick) == 0) { - Plugin_128_pixels->SetPixelColor((i * pixelCount * small_tick / 60) % pixelCount, rgb_tick_b); - } else { - Plugin_128_pixels->SetPixelColor((i * pixelCount * small_tick / 60) % pixelCount, rgb_tick_s); - } + const bool use_big_tick = i % (big_tick / small_tick) == 0; + Plugin_128_pixels->SetPixelColor((i * pixelCount * small_tick / 60) % pixelCount, use_big_tick ? rgb_tick_b : rgb_tick_s); } for (int i = 0; i < pixelCount; i++) { - if (lround((((float)Seconds + ((float)counter20ms - (float)maxtime) / 50.0) * (float)pixelCount) / 60.0) == i) { + if (lround((((float)Seconds + ((float)counter20ms - (float)maxtime) / 50.0f) * (float)pixelCount) / 60.0f) == i) { if (rgb_s_off == false) { Plugin_128_pixels->SetPixelColor(i, rgb_s); } } - else if (lround((((float)Minutes * 60.0) + (float)Seconds) / 60.0 * (float)pixelCount / 60.0) == i) { + else if (lround((((float)Minutes * 60.0f) + (float)Seconds) / 60.0f * (float)pixelCount / 60.0f) == i) { Plugin_128_pixels->SetPixelColor(i, rgb_m); } - else if (lround(((float)Hours + (float)Minutes / 60) * (float)pixelCount / 12.0) == i) { + else if (lround(((float)Hours + (float)Minutes / 60) * (float)pixelCount / 12.0f) == i) { Plugin_128_pixels->SetPixelColor(i, rgb_h); Plugin_128_pixels->SetPixelColor((i + 1) % pixelCount, rgb_h); Plugin_128_pixels->SetPixelColor((i - 1 + pixelCount) % pixelCount, rgb_h); @@ -1439,10 +1463,32 @@ void P128_data_struct::Plugin_128_simpleclock() { } } -uint32_t P128_data_struct::rgbStr2Num(String rgbStr) { - uint32_t rgbDec = static_cast(strtoul(&rgbStr[0], NULL, 16)); +uint32_t P128_data_struct::rgbStr2Num(const String& rgbStr) { + return static_cast(strtoul(rgbStr.c_str(), NULL, 16)); +} - return rgbDec; +RgbColor P128_data_struct::rgbStr2RgbColor(const String& str) +{ + const uint32_t hcolorui = rgbStr2Num(str); + + return RgbColor(hcolorui >> 16, hcolorui >> 8, hcolorui); +} + +RgbwColor P128_data_struct::rgbStr2RgbWColor(const String& str) +{ + const uint32_t hcolorui = rgbStr2Num(str); + + if (str.length() <= 6) { + // w = 0 + return RgbwColor(hcolorui >> 16, + hcolorui >> 8, + hcolorui); + } + + return RgbwColor(hcolorui >> 24, + hcolorui >> 16, + hcolorui >> 8, + hcolorui); } void P128_data_struct::hex2rgb(const String& hexcolor) { @@ -1490,56 +1536,49 @@ void P128_data_struct::hex2rgb_pixel(const String& hexcolor) { // ------------------------------ JsonResponse ------------------------------------- // --------------------------------------------------------------------------------- void P128_data_struct::NeoPixelSendStatus(struct EventStruct *eventSource) { - String log = F("NeoPixelBusFX: Set "); - - log += rgb.R; - log += '/'; - log += rgb.G; - log += '/'; - log += rgb.B; - - addLogMove(LOG_LEVEL_INFO, log); - - String json; - - json.reserve(285); // Awfully long string :-| + addLogMove(LOG_LEVEL_INFO, strformat( + F("NeoPixelBusFX: Set %u/%u/%u"), + rgb.R, + rgb.G, + rgb.B)); printToWebJSON = true; - json += '{'; json += '\n'; // 2 - json += to_json_object_value(F("plugin"), F("128")); // 12 - json += ','; json += '\n'; - json += to_json_object_value(F("mode"), P128_modeType_toString(mode)); // 14..23 - json += ','; json += '\n'; - json += to_json_object_value(F("lastmode"), P128_modeType_toString(savemode)); // 18..27 - json += ','; json += '\n'; - json += to_json_object_value(F("fadetime"), toString(fadetime, 0)); // 15..19 - json += ','; json += '\n'; - json += to_json_object_value(F("fadedelay"), toString(fadedelay, 0)); // 15..19 - json += ','; json += '\n'; - json += to_json_object_value(F("dim"), toString(Plugin_128_pixels->GetBrightness(), 0)); // 8..10 - json += ','; json += '\n'; - json += to_json_object_value(F("rgb"), colorStr, true); // 15..17 - json += ','; json += '\n'; + HsbColor hsbColor = HsbColor(RgbColor(rgb.R, rgb.G, rgb.B)); // Calculate only once - HsbColor hsbColor = HsbColor(RgbColor(rgb.R, rgb.G, rgb.B)); // Calculate only once - - json += to_json_object_value(F("hue"), toString(hsbColor.H * 360.0f, 0)); // 17 - json += ','; json += '\n'; - json += to_json_object_value(F("saturation"), toString(hsbColor.S * 100.0f, 0)); // 26? - json += ','; json += '\n'; - json += to_json_object_value(F("brightness"), toString(hsbColor.B * 100.0f, 0)); // 26? - json += ','; json += '\n'; - json += to_json_object_value(F("bgcolor"), backgroundcolorStr, true); // 21..23 - json += ','; json += '\n'; - json += to_json_object_value(F("count"), toString(count, 0)); // 12..15 - json += ','; json += '\n'; - json += to_json_object_value(F("speed"), toString(speed, 0)); // 12..14 - json += ','; json += '\n'; - json += to_json_object_value(F("pixelcount"), toString(pixelCount, 0)); // 17..19 - json += '\n'; json += '}'; json += '\n'; // 4 - - SendStatus(eventSource, json); // send http response to controller (JSON format) + 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() + )); printToWeb = false; } diff --git a/src/src/PluginStructs/P128_data_struct.h b/src/src/PluginStructs/P128_data_struct.h index 36eee877b..b44c8f5e6 100644 --- a/src/src/PluginStructs/P128_data_struct.h +++ b/src/src/PluginStructs/P128_data_struct.h @@ -2249,7 +2249,7 @@ const uint8_t PROGMEM ftv_colors[] = { // # define BRG //A three element color in the order of Blue, Red, and then Green. // # define RBG //A three element color in the order of Red, Blue, and then Green. -# define NEOPIXEL_LIB NeoPixelBrightnessBus // Neopixel library type +# define NEOPIXEL_LIB NeoPixelBrightnessBus // Neopixel library type # if defined(ESP32) # define METHOD NeoWs2812xMethod // Automatic method, user selected pin # endif // if defined(ESP32) @@ -2431,27 +2431,29 @@ private: /// random number seed uint16_t rand16seed; // = RAND16_SEED; // leave uninitialized //-V457 - uint8_t random8(); - uint8_t random8(uint8_t lim); - uint8_t random8(uint8_t min, - uint8_t lim); - uint8_t qsub8(uint8_t i, - uint8_t j); - uint8_t qadd8(uint8_t i, - uint8_t j); - uint8_t scale8_video(uint8_t i, - uint8_t scale); + uint8_t random8(); + uint8_t random8(uint8_t lim); + uint8_t random8(uint8_t min, + uint8_t lim); + static uint8_t qsub8(uint8_t i, + uint8_t j); + static uint8_t qadd8(uint8_t i, + uint8_t j); + static uint8_t scale8_video(uint8_t i, + uint8_t scale); // Fire2012: Array of temperature readings at each simulation cell byte heat[ARRAYSIZE] = { 0 }; - void Fire2012(void); - void fire_flicker(); - void Plugin_128_simpleclock(); - uint32_t rgbStr2Num(String rgbStr); - void hex2rgb(const String& hexcolor); - void hex2rrggbb(const String& hexcolor); - void hex2rgb_pixel(const String& hexcolor); - void NeoPixelSendStatus(struct EventStruct *eventSource); + void Fire2012(void); + void fire_flicker(); + void Plugin_128_simpleclock(); + static uint32_t rgbStr2Num(const String& rgbStr); + static RgbColor rgbStr2RgbColor(const String& str); + static RgbwColor rgbStr2RgbWColor(const String& str); + void hex2rgb(const String& hexcolor); + void hex2rrggbb(const String& hexcolor); + void hex2rgb_pixel(const String& hexcolor); + void NeoPixelSendStatus(struct EventStruct *eventSource); }; #endif // ifdef USES_P128 diff --git a/src/src/PluginStructs/P129_data_struct.cpp b/src/src/PluginStructs/P129_data_struct.cpp index 02e6d758b..8d67c3e90 100644 --- a/src/src/PluginStructs/P129_data_struct.cpp +++ b/src/src/PluginStructs/P129_data_struct.cpp @@ -16,7 +16,7 @@ P129_data_struct::P129_data_struct(int8_t dataPin, bool P129_data_struct::plugin_init(struct EventStruct *event) { if (isInitialized()) { - for (uint8_t i = 0; i < P129_MAX_CHIP_COUNT; i++) { // Clear entire buffer + for (uint8_t i = 0; i < P129_MAX_CHIP_COUNT; ++i) { // Clear entire buffer readBuffer[i] = 0; } @@ -40,22 +40,16 @@ uint32_t P129_data_struct::getChannelState(uint8_t offset, uint16_t sft = 0u; const uint8_t last = offset + size; - for (uint8_t ofs = offset; ofs < last; ofs++, sft++) { + for (uint8_t ofs = offset; ofs < last; ++ofs, ++sft) { result += (readBuffer[ofs] << (8 * sft)); } # ifdef P129_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("SHIFTIN: getChannelState offset: "); - log += offset; - log += F(", size: "); - log += size; - log += F(", result: "); - log += result; - log += F("/0x"); - log += String(result, HEX); - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("SHIFTIN: getChannelState offset: %d, size: %d, result: %d/0x%x"), + offset, size, result, result)); } # endif // ifdef P129_DEBUG_LOG @@ -66,7 +60,7 @@ bool P129_data_struct::plugin_read(struct EventStruct *event) { const uint16_t last = P129_CONFIG_SHOW_OFFSET + (VARS_PER_TASK * 4); uint8_t varNr = 0; - for (uint16_t index = P129_CONFIG_SHOW_OFFSET; index < _chipCount && index < last && varNr < VARS_PER_TASK; index += 4, varNr++) { + for (uint16_t index = P129_CONFIG_SHOW_OFFSET; index < _chipCount && index < last && varNr < VARS_PER_TASK; index += 4, ++varNr) { uint32_t result = getChannelState(index, min(VARS_PER_TASK, _chipCount - index)); UserVar.setUint32(event->TaskIndex, varNr, result); } @@ -90,7 +84,7 @@ bool P129_data_struct::plugin_write(struct EventStruct *event, if (equals(command, F("shiftin"))) { const String subcommand = parseString(string, 2); - int command_i = GetCommandCode(subcommand.c_str(), p129_subcommands); + const int command_i = GetCommandCode(subcommand.c_str(), p129_subcommands); if (command_i == -1) { // No matching subcommand found @@ -114,16 +108,9 @@ bool P129_data_struct::plugin_write(struct EventStruct *event, # ifdef P129_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = command; - log += F(", pin: "); - log += event->Par2; - log += F(", value: "); - log += value; - log += F(", config: "); - log += ulong; - log += F(", bit: "); - log += bit; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("%s, pin: %d, value: %d, config: %u, bit: %u"), + command.c_str(), event->Par2, value, ulong, bit)); } # endif // ifdef P129_DEBUG_LOG } @@ -145,16 +132,9 @@ bool P129_data_struct::plugin_write(struct EventStruct *event, # ifdef P129_DEBUG_LOG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = command; - log += F(", chip: "); - log += event->Par2; - log += F(", value: "); - log += value; - log += F(", config: "); - log += ulong; - log += F(", bit: "); - log += bit; - addLogMove(LOG_LEVEL_DEBUG, log); + addLogMove(LOG_LEVEL_DEBUG, + strformat(F("%s, chip: %d, value: %d, config: %u, bit: %u"), + command.c_str(), event->Par2, value, ulong, bit)); } # endif // ifdef P129_DEBUG_LOG } @@ -209,7 +189,7 @@ bool P129_data_struct::plugin_readData(struct EventStruct *event) { if (validGpio(_enablePin)) { DIRECT_pinWrite(_enablePin, LOW); } - for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; i++) { + for (uint8_t i = 0; i < P129_CONFIG_CHIP_COUNT; ++i) { prevBuffer[i] = readBuffer[i]; readBuffer[i] = shiftIn(static_cast(_dataPin), static_cast(_clockPin), MSBFIRST); } @@ -230,7 +210,7 @@ void P129_data_struct::checkDiff(struct EventStruct *event) { const uint32_t read = readBuffer[i + 3] << 24 | readBuffer[i + 2] << 16 | readBuffer[i + 1] << 8 | readBuffer[i + 0]; const uint32_t prev = prevBuffer[i + 3] << 24 | prevBuffer[i + 2] << 16 | prevBuffer[i + 1] << 8 | prevBuffer[i + 0]; - for (uint8_t j = 0; j < 32; j++) { // Check all 32 bits + for (uint8_t j = 0; j < 32; ++j) { // Check all 32 bits if (bitRead(PCONFIG_ULONG(i / 4), j) && (bitRead(read, j) != bitRead(prev, j))) { // Event enabled and bit changed? sendInputEvent(event, i, j, bitRead(read, j)); // Send out new state } @@ -256,14 +236,7 @@ void P129_data_struct::sendInputEvent(struct EventStruct *event, send += '#'; send += pin; } - send += '='; - send += state; - send += ','; - send += chip; - send += ','; - send += port; - send += ','; - send += pin; + send += strformat(F("=%u,%u,%u,%u"), state, chip, port, pin); eventQueue.addMove(std::move(send)); } diff --git a/src/src/PluginStructs/P131_data_struct.cpp b/src/src/PluginStructs/P131_data_struct.cpp index af801d36c..6dc894464 100644 --- a/src/src/PluginStructs/P131_data_struct.cpp +++ b/src/src/PluginStructs/P131_data_struct.cpp @@ -31,10 +31,12 @@ P131_data_struct::P131_data_struct(uint8_t matrixWidth, uint8_t brightness, uint8_t maxbright, uint16_t fgcolor, - uint16_t bgcolor) + uint16_t bgcolor, + const uint8_t defaultFontId) : _matrixWidth(matrixWidth), _matrixHeight(matrixHeight), _tileWidth(tileWidth), _tileHeight(tileHeight), _pin(pin), _matrixType(matrixType), _ledType(ledType), _rotation(rotation), _fontscaling(fontscaling), _textmode(textmode), - _commandTrigger(commandTrigger), _brightness(brightness), _maxbright(maxbright), _fgcolor(fgcolor), _bgcolor(bgcolor) { + _commandTrigger(commandTrigger), _brightness(brightness), _maxbright(maxbright), _fgcolor(fgcolor), _bgcolor(bgcolor), + _defaultFontId(defaultFontId) { _commandTrigger.toLowerCase(); _commandTriggerCmd = _commandTrigger; _commandTriggerCmd += F("cmd"); @@ -112,7 +114,8 @@ bool P131_data_struct::plugin_init(struct EventStruct *event) { _fgcolor, _bgcolor, true, - _textBackFill); + _textBackFill, + _defaultFontId); success = (nullptr != gfxHelper); @@ -307,11 +310,11 @@ void P131_data_struct::display_content(struct EventStruct *event, _bgcolor); } - if (!content[x].rightScroll && (content[x].pixelPos + content[x].length < _xpix) && (content[x].stepWidth > 1)) { + if (!content[x].rightScroll && (content[x].pixelPos + content[x].length < _xpix) && (content[x].stepWidth >= 1)) { // Clear right from text - matrix->fillRect(content[x].pixelPos + content[x].length + 1, + matrix->fillRect(content[x].pixelPos + content[x].length, yPos, - content[x].stepWidth - 1, + content[x].stepWidth, h, _bgcolor); } diff --git a/src/src/PluginStructs/P131_data_struct.h b/src/src/PluginStructs/P131_data_struct.h index 4909822e7..34ba2014c 100644 --- a/src/src/PluginStructs/P131_data_struct.h +++ b/src/src/PluginStructs/P131_data_struct.h @@ -24,6 +24,7 @@ # define P131_CONFIG_MATRIX_HEIGHT PCONFIG(1) # define P131_CONFIG_TILE_WIDTH PCONFIG(2) # define P131_CONFIG_TILE_HEIGHT PCONFIG(3) +# define P131_CONFIG_DEFAULT_FONT PCONFIG(4) # define P131_CONFIG_FLAGS PCONFIG_ULONG(0) # define P131_CONFIG_FLAGS_B PCONFIG_ULONG(1) @@ -107,8 +108,9 @@ public: String commandTrigger, uint8_t brightness, uint8_t maxbright, - uint16_t fgcolor = ADAGFX_WHITE, - uint16_t bgcolor = ADAGFX_BLACK); + uint16_t fgcolor = ADAGFX_WHITE, + uint16_t bgcolor = ADAGFX_BLACK, + const uint8_t defaultFontId = 0); P131_data_struct() = delete; virtual ~P131_data_struct(); @@ -149,15 +151,16 @@ private: int8_t _pin = -1; uint8_t _matrixType = NEO_MATRIX_TOP | NEO_MATRIX_LEFT | NEO_MATRIX_ROWS | NEO_MATRIX_PROGRESSIVE | NEO_TILE_TOP | NEO_TILE_LEFT | NEO_TILE_ROWS | NEO_TILE_PROGRESSIVE; + neoPixelType _ledType = NEO_GRB + NEO_KHZ800; uint8_t _rotation = 0; uint8_t _fontscaling = 1; AdaGFXTextPrintMode _textmode = AdaGFXTextPrintMode::ContinueToNextLine; String _commandTrigger; uint8_t _brightness = 40; uint8_t _maxbright = 255; - neoPixelType _ledType = NEO_GRB + NEO_KHZ800; uint16_t _fgcolor = ADAGFX_WHITE; uint16_t _bgcolor = ADAGFX_BLACK; + uint8_t _defaultFontId; uint16_t _textcols = 0; uint16_t _textrows = 0; diff --git a/src/src/PluginStructs/P132_data_struct.cpp b/src/src/PluginStructs/P132_data_struct.cpp index 1882e5b94..5541cfa21 100644 --- a/src/src/PluginStructs/P132_data_struct.cpp +++ b/src/src/PluginStructs/P132_data_struct.cpp @@ -20,11 +20,9 @@ int16_t P132_data_struct::getBusVoltage_raw(byte reg) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("INA3221: get raw bus "); - log += value; - log += F(" reg - "); - log += reg; - addLog(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, + strformat(F("INA3221: get raw bus %d reg - %d"), + value, reg)); } # endif // ifndef BUILD_NO_DEBUG return (int16_t)((value >> 3) * 8); @@ -37,29 +35,24 @@ int16_t P132_data_struct::getShuntVoltage_raw(byte reg) { uint16_t value = I2C_read16_reg(_i2c_address, reg); # ifndef BUILD_NO_DEBUG - String log = F("INA3221: get raw shunt voltage "); - log += value; - log += F(" value2 - "); + String log = strformat(F("INA3221: get raw shunt voltage %d value2 - "), value); # endif // ifndef BUILD_NO_DEBUG // Shift to the right 3 to drop CNVR and OVF and multiply by LSB - if (value > 32767) { // check value is negative + if (value > 32767) { // check value is negative // value = 0; // no negative measure - value = ((value >> 3) | 57344); // correct int16_t value + value = ((value >> 3) | 0xE000); // correct int16_t value # ifndef BUILD_NO_DEBUG - log += F(" value_neg - "); - log += value; + log += concat(F(" value_neg - "), value); # endif // ifndef BUILD_NO_DEBUG } else { value = (value >> 3); # ifndef BUILD_NO_DEBUG - log += F(" value_pos - "); - log += value; + log += concat(F(" value_pos - "), value); # endif // ifndef BUILD_NO_DEBUG } # ifndef BUILD_NO_DEBUG - log += F(" reg - "); - log += reg; + log += concat(F(" reg - "), reg); addLog(LOG_LEVEL_DEBUG, log); # endif // ifndef BUILD_NO_DEBUG return value; @@ -74,11 +67,9 @@ float P132_data_struct::getShuntVoltage_mV(byte reg) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("INA3221: shunt voltage in mV * 0.04 "); - log += value; - log += F(" reg - "); - log += reg; - addLog(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, + strformat(F("INA3221: shunt voltage in mV * 0.04 %d reg - %d"), + value, reg)); } # endif // ifndef BUILD_NO_DEBUG return value * 0.04f; @@ -93,11 +84,9 @@ float P132_data_struct::getBusVoltage_V(byte reg) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("INA3221: get bus voltage "); - log += value; - log += F(" reg - "); - log += reg; - addLog(LOG_LEVEL_DEBUG, log); + addLog(LOG_LEVEL_DEBUG, + strformat(F("INA3221: get bus voltage %d reg - %d"), + value, reg)); } # endif // ifndef BUILD_NO_DEBUG return value * 0.001f; @@ -118,15 +107,9 @@ void P132_data_struct::setCalibration_INA3221(struct EventStruct *event) { # ifndef BUILD_NO_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("INA3221: init I2C: 0x"); - log += String(_i2c_address, HEX); - log += F(" mfg: 0x"); - log += String(mfgid, HEX); - log += F(", config: 0x"); - log += String(config, HEX); - log += F(", 0b"); - log += String(config, BIN); - addLog(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, + strformat(F("INA3221: init I2C: 0x%02x mfg: 0x%x, config: 0x%x, 0b%s"), + _i2c_address, mfgid, config, String(config, BIN).c_str())); } # endif // ifndef BUILD_NO_DEBUG diff --git a/src/src/PluginStructs/P133_data_struct.cpp b/src/src/PluginStructs/P133_data_struct.cpp index 86fa22204..febd658d1 100644 --- a/src/src/PluginStructs/P133_data_struct.cpp +++ b/src/src/PluginStructs/P133_data_struct.cpp @@ -33,7 +33,7 @@ bool P133_data_struct::plugin_read(struct EventStruct *event) { UserVar.setFloat(event->TaskIndex, 1, uviValue); UserVar.setFloat(event->TaskIndex, 2, alsValue); UserVar.setFloat(event->TaskIndex, 3, luxValue); - sensorRead = false; + sensorRead = false; return true; } return false; @@ -75,17 +75,9 @@ bool P133_data_struct::plugin_ten_per_second(struct EventStruct *event) { # if PLUGIN_133_DEBUG if (!sensorRead && loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("LTR390: data read. Mode: "); - log += mode; - log += F(", UV: "); - log += uvValue; - log += F(", UVindex: "); - log += uviValue; - log += F(", ALS: "); - log += alsValue; - log += F(", Lux: "); - log += luxValue; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("LTR390: data read. Mode: %d, UV: %d, UVindex: %.2f, ALS: %d, Lux: %.2f"), + mode, uvValue, uviValue, alsValue, luxValue)); } # endif // if PLUGIN_133_DEBUG sensorRead = true; @@ -121,17 +113,9 @@ bool P133_data_struct::plugin_init(struct EventStruct *event) { # if PLUGIN_133_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("LTR390: Configured, mode: "); - log += mode; - log += F(", UV gain: "); - log += _uvGain; - log += F(", UV resolution: "); - log += _uvResolution; - log += F(", ALS gain: "); - log += _alsGain; - log += F(", ALS resolution: "); - log += _alsResolution; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, + strformat(F("LTR390: Configured, mode: %d, UV gain: %d, UV resolution: %d, ALS gain: %d, ALS resolution: %d"), + mode, _uvGain, _uvResolution, _alsGain, _alsResolution)); } # endif // if PLUGIN_133_DEBUG } @@ -151,10 +135,8 @@ bool P133_data_struct::init_sensor() { } if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("LTR390: Initialized: "); - log += initialised ? F("OK") : F("ERROR"); - log += F(", chip ID: 0x"); - log += String(ltr390->getChipID(), HEX); + String log = concat(F("LTR390: Initialized: "), initialised ? F("OK") : F("ERROR")); + log += strformat(F(", chip ID: 0x%02x"), ltr390->getChipID()); addLogMove(LOG_LEVEL_INFO, log); } } diff --git a/src/src/PluginStructs/P135_data_struct.cpp b/src/src/PluginStructs/P135_data_struct.cpp index 078059055..a336963bd 100644 --- a/src/src/PluginStructs/P135_data_struct.cpp +++ b/src/src/PluginStructs/P135_data_struct.cpp @@ -13,8 +13,8 @@ P135_data_struct::P135_data_struct(taskIndex_t taskIndex, bool lowPowerMeasurement, bool useSingleShot) : _sensorType(sensorType), _altitude(altitude), _tempOffset(tempOffset), _autoCalibrate(autoCalibrate), - _lowPowerMeasurement(lowPowerMeasurement), _useSingleShot(useSingleShot), initialized(false) - {} + _lowPowerMeasurement(lowPowerMeasurement), _useSingleShot(useSingleShot), initialized(false) +{} bool P135_data_struct::init() { scd4x = new (std::nothrow) SCD4x(static_cast(_sensorType)); // Don't start measurement, we want to set arguments @@ -23,13 +23,12 @@ bool P135_data_struct::init() { if (scd4x->begin(false, _autoCalibrate)) { const uint16_t orgAltitude = scd4x->getSensorAltitude(); - if (_altitude != 0) { + if ((_altitude != 0) && (_altitude != orgAltitude)) { scd4x->setSensorAltitude(_altitude); } const float orgTempOffset = scd4x->getTemperatureOffset(); - // FIXME TD-er: Is this correct? Checking _tempOffset and not checking orgTempOffset? (same for altitude) - if (!essentiallyZero(_tempOffset)) { + if (!essentiallyZero(_tempOffset) && !essentiallyEqual(_tempOffset, orgTempOffset)) { scd4x->setTemperatureOffset(_tempOffset); } const bool hasSerial = scd4x->getSerialNumber(serialNumber); // Not yet reading, get serial @@ -47,11 +46,7 @@ bool P135_data_struct::init() { } else { log += F("(unknown)"); } - log += F(", org.alt.comp.: "); - log += orgAltitude; - log += F(" m, org.temp.offs.: "); - log += toString(orgTempOffset, 2); - log += 'C'; + log += strformat(F(", org.alt.comp.: %d m, org.temp.offs.: %.2f C"), orgAltitude, orgTempOffset); } else { log += F("error"); } @@ -130,17 +125,14 @@ bool P135_data_struct::plugin_read(struct EventStruct *event) { if (errorCount > P135_MAX_ERRORS) { initialized = false; - scd4x->stopPeriodicMeasurement(); // Stop measuring, no need to wait for completion + scd4x->stopPeriodicMeasurement(); // Stop measuring, no need to wait for completion UserVar.setFloat(event->TaskIndex, 0, 0); // Indicate an error state addLog(LOG_LEVEL_ERROR, F("SCD4x: Max. read errors reached, plugin stopped.")); } if (timerDelay != 0) { // Schedule next PLUGIN_READ if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("SCD4x: READ Scheduler started: +"); - log += timerDelay; - log += F(" ms."); - addLog(LOG_LEVEL_INFO, log); + addLog(LOG_LEVEL_INFO, strformat(F("SCD4x: READ Scheduler started: +%d ms."), timerDelay)); } Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + timerDelay); } @@ -194,10 +186,7 @@ bool P135_data_struct::plugin_read(struct EventStruct *event) { if (success) { initialized = startPeriodicMeasurements(); // Select the correct periodic measurement, and start a READ Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P135_STOP_MEASUREMENT_DELAY); - log += F("success. New setting: "); - log += frcValue; - log += F(", correction: "); - log += toString(frcCorrection, 2); + log += strformat(F("success. New setting: %d, correction: %.2f"), frcValue, frcCorrection); } else { lvl = LOG_LEVEL_ERROR; log += F("failed!"); @@ -244,15 +233,11 @@ bool P135_data_struct::plugin_write(struct EventStruct *event, if (serialNumber[10] != 0x0) { if (doSelftest) { - factoryResetCode += char(serialNumber[3]); - factoryResetCode += char(serialNumber[1]); - factoryResetCode += char(serialNumber[10]); - factoryResetCode += char(serialNumber[6]); + factoryResetCode += strformat(F("%c%c%c%c"), + char(serialNumber[3]), char(serialNumber[1]), char(serialNumber[10]), char(serialNumber[6])); } else { - factoryResetCode += char(serialNumber[1]); - factoryResetCode += char(serialNumber[3]); - factoryResetCode += char(serialNumber[7]); - factoryResetCode += char(serialNumber[10]); + factoryResetCode += strformat(F("%c%c%c%c"), + char(serialNumber[1]), char(serialNumber[3]), char(serialNumber[7]), char(serialNumber[10])); } } else { factoryResetCode += F("2022"); @@ -271,13 +256,12 @@ bool P135_data_struct::plugin_write(struct EventStruct *event, } else { log += F("Factory reset"); } - log += F(" code: "); - log += factoryResetCode; + log += concat(F(" code: "), factoryResetCode); addLog(LOG_LEVEL_ERROR, log); } success = true; } else { - String code = parseStringKeepCase(string, 3); // Case sensitive! + const String code = parseStringKeepCase(string, 3); // Case sensitive! if (code.equals(factoryResetCode)) { if (doSelftest) { @@ -319,23 +303,30 @@ bool P135_data_struct::plugin_write(struct EventStruct *event, *****************************************************/ bool P135_data_struct::plugin_get_config_value(struct EventStruct *event, String & string) { + if (nullptr == scd4x) { return false; } // Safeguard bool success = false; const String var = parseString(string, 1); - if (equals(var, F("getaltitude"))) { // [#getaltitude] = get sensor altitude - string = scd4x->getSensorAltitude(); + if (equals(var, F("getaltitude")) && + scd4x->stopPeriodicMeasurement()) { // [#getaltitude] = get sensor altitude + string = scd4x->getSensorAltitude(); + startPeriodicMeasurements(); success = true; - } else if (equals(var, F("gettempoffset"))) { // [#gettempoffset] = get sensor temperature offset - string = toString(scd4x->getTemperatureOffset(), 2); + } else if (equals(var, F("gettempoffset")) && + scd4x->stopPeriodicMeasurement()) { // [#gettempoffset] = get sensor temperature offset + string = toString(scd4x->getTemperatureOffset(), 2); + startPeriodicMeasurements(); success = true; - } else if (equals(var, F("getdataready"))) { // [#getdataready] = is data ready? (1/0) + } else if (equals(var, F("getdataready"))) { // [#getdataready] = is data ready? (1/0) string = scd4x->getDataReadyStatus(); success = true; - } else if (equals(var, F("getselfcalibration"))) { // [#getselfcalibration] = is self-calibration enabled? (1/0) - string = scd4x->getAutomaticSelfCalibrationEnabled(); + } else if (equals(var, F("getselfcalibration")) && + scd4x->stopPeriodicMeasurement()) { // [#getselfcalibration] = is self-calibration enabled? (1/0) + string = scd4x->getAutomaticSelfCalibrationEnabled(); + startPeriodicMeasurements(); success = true; - } else if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number + } else if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number string = String(serialNumber); success = true; } diff --git a/src/src/PluginStructs/P137_data_struct.cpp b/src/src/PluginStructs/P137_data_struct.cpp index 543132c88..5350174fa 100644 --- a/src/src/PluginStructs/P137_data_struct.cpp +++ b/src/src/PluginStructs/P137_data_struct.cpp @@ -41,7 +41,7 @@ void P137_CheckPredefinedParameters(struct EventStruct *event) { P137_CURRENT_PREDEFINED = P137_CONFIG_PREDEFINED; // Set defaults - for (int i = 0; i < 5; i++) { // GPI0..4 + for (int i = 0; i < 5; ++i) { // GPI0..4 P137_SET_GPIO_FLAGS(i, static_cast(P137_GPIOBootState_e::Default)); } @@ -189,7 +189,7 @@ bool P137_data_struct::plugin_read(struct EventStruct *event) { const uint8_t valueCount = P137_NR_OUTPUT_VALUES; - for (uint8_t i = 0; i < valueCount; i++) { + for (uint8_t i = 0; i < valueCount; ++i) { UserVar.setFloat(event->TaskIndex, i, read_value(static_cast(PCONFIG(P137_CONFIG_BASE + i)))); } @@ -262,10 +262,10 @@ bool P137_data_struct::plugin_write(struct EventStruct *event, const int subcommand_i = GetCommandCode(cmd.c_str(), P137_subcommands); const P137_subcommands_e subcmd = static_cast(subcommand_i); - String var3 = parseString(string, 3); - const bool empty3 = var3.isEmpty(); - const bool empty4 = parseString(string, 4).isEmpty(); - const bool state3 = !empty3 && (event->Par2 == 0 || event->Par2 == 1); + const String var3 = parseString(string, 3); + const bool empty3 = var3.isEmpty(); + const bool empty4 = parseString(string, 4).isEmpty(); + const bool state3 = !empty3 && (event->Par2 == 0 || event->Par2 == 1); success = true; if ((event->Par2 >= 0) && (event->Par2 <= P137_CONST_100_PERCENT) && !empty3 && empty4) { diff --git a/src/src/PluginStructs/P138_data_struct.cpp b/src/src/PluginStructs/P138_data_struct.cpp index ce9d56c80..a108120ef 100644 --- a/src/src/PluginStructs/P138_data_struct.cpp +++ b/src/src/PluginStructs/P138_data_struct.cpp @@ -39,11 +39,11 @@ P138_data_struct::~P138_data_struct() { // plugin_read: Read the values and send to controller(s) // **************************************************************************/ bool P138_data_struct::plugin_read(struct EventStruct *event) { - bool success = true; + bool success = true; const uint8_t valueCount = P138_NR_OUTPUT_VALUES; for (uint8_t i = 0; i < valueCount; i++) { - UserVar.setFloat(event->TaskIndex, i, read_value(static_cast(PCONFIG(P138_CONFIG_BASE + i)))); + UserVar.setFloat(event->TaskIndex, i, read_value(static_cast(PCONFIG(P138_CONFIG_BASE + i)))); } return success; @@ -58,9 +58,9 @@ bool P138_data_struct::plugin_fifty_per_second(struct EventStruct *event) { if (bitRead(P138_CONFIG_FLAGS, P138_FLAG_POWERCHANGE) && isInitialized()) { int8_t src = static_cast(_ip5306->power_source()); - if (_lastPowerSource != src) { // Changed? + if (_lastPowerSource != src) { // Changed? eventQueue.add(event->TaskIndex, F("PowerChanged"), src); // 0 = battery, 1 = Vin - _lastPowerSource = src; // Keep current + _lastPowerSource = src; // Keep current } } return success; @@ -114,34 +114,24 @@ bool P138_data_struct::plugin_write(struct EventStruct *event, return success; } +const char p138_getcommands[] PROGMEM = "none|batcurrent|chundervolt|stopvolt|inpcurrent|chargelvl|pwrsource"; + /**************************************************************************** * plugin_get_config_value: Retrieve values like [#] ***************************************************************************/ bool P138_data_struct::plugin_get_config_value(struct EventStruct *event, String & string) { - bool success = true; - String command = parseString(string, 1); - float value; + bool success = true; + const String command = parseString(string, 1); + const int command_i = GetCommandCode(command.c_str(), p138_getcommands); - if (equals(command, F("batcurrent"))) { // batcurrent - value = read_value(P138_valueOptions_e::BatteryCurrent); - } else if (equals(command, F("chundervolt"))) { // chundervolt - value = read_value(P138_valueOptions_e::ChargeUnderVoltage); - } else if (equals(command, F("stopvolt"))) { // stopvolt - value = read_value(P138_valueOptions_e::StopVoltage); - } else if (equals(command, F("inpcurrent"))) { // inpcurrent - value = read_value(P138_valueOptions_e::InCurrent); - } else if (equals(command, F("chargelvl"))) { // chargelvl - value = read_value(P138_valueOptions_e::ChargeLevel); - } else if (equals(command, F("pwrsource"))) { // pwrsource - value = read_value(P138_valueOptions_e::PowerSource); + if (command_i > 0) { // Ignore 'None' + const float value = read_value(static_cast(command_i)); + string = toString(value, static_cast(P138_CONFIG_DECIMALS)); } else { success = false; } - if (success) { - string = toString(value, static_cast(P138_CONFIG_DECIMALS)); - } return success; } diff --git a/src/src/PluginStructs/P138_data_struct.h b/src/src/PluginStructs/P138_data_struct.h index c1ec259a1..1a40ba983 100644 --- a/src/src/PluginStructs/P138_data_struct.h +++ b/src/src/PluginStructs/P138_data_struct.h @@ -11,7 +11,7 @@ # define P138_CONFIG_BASE 0 // Uses PCONFIG(0)..PCONFIG(3) to store the selection for 4 output values # define P138_SENSOR_TYPE_INDEX (P138_CONFIG_BASE + VARS_PER_TASK) # define P138_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P138_SENSOR_TYPE_INDEX))) -# define P138_CONFIG_DECIMALS PCONFIG(P138_CONFIG_BASE + VARS_PER_TASK + 1) +# define P138_CONFIG_DECIMALS PCONFIG(P138_SENSOR_TYPE_INDEX + 1) # define P138_CONFIG_FLAGS PCONFIG_ULONG(0) # define P138_FLAG_POWERCHANGE 0 // Flag 0: Send event on PowerChange event @@ -47,7 +47,7 @@ private: arduino::ip5306 *_ip5306 = nullptr; - bool isInitialized() { + bool isInitialized() const { return nullptr != _ip5306; } diff --git a/src/src/PluginStructs/P141_data_struct.cpp b/src/src/PluginStructs/P141_data_struct.cpp index 2fb8c9ba7..93fb7fd7e 100644 --- a/src/src/PluginStructs/P141_data_struct.cpp +++ b/src/src/PluginStructs/P141_data_struct.cpp @@ -64,16 +64,12 @@ bool P141_data_struct::plugin_init(struct EventStruct *event) { if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(90); - log += F("PCD8544: Init done, address: 0x"); - log += String(reinterpret_cast(pcd8544), HEX); - log += ' '; + log += strformat(F("PCD8544: Init done, address: 0x%x "), reinterpret_cast(pcd8544)); if (nullptr == pcd8544) { log += F("in"); } - log += F("valid, commands: "); - log += _commandTrigger; - log += F(", display: PCD8544"); + log += strformat(F("valid, commands: %s, display: PCD8544"), _commandTrigger.c_str()); addLogMove(LOG_LEVEL_INFO, log); } # endif // ifndef BUILD_NO_DEBUG @@ -121,7 +117,7 @@ bool P141_data_struct::plugin_init(struct EventStruct *event) { LoadCustomTaskSettings(event->TaskIndex, strings, P141_Nlines, 0); stringsLoaded = true; - for (uint8_t x = 0; x < P141_Nlines && !stringsHasContent; x++) { + for (uint8_t x = 0; x < P141_Nlines && !stringsHasContent; ++x) { stringsHasContent = !strings[x].isEmpty(); } } @@ -167,10 +163,10 @@ bool P141_data_struct::plugin_exit(struct EventStruct *event) { * cleanup: De-initialize pointers ***************************************************************************/ void P141_data_struct::cleanup() { - if (nullptr != gfxHelper) { delete gfxHelper; } + delete gfxHelper; gfxHelper = nullptr; - if (nullptr != pcd8544) { delete pcd8544; } + delete pcd8544; pcd8544 = nullptr; } @@ -186,7 +182,7 @@ bool P141_data_struct::plugin_read(struct EventStruct *event) { uint16_t udum = 0; uint16_t hText = 0; - for (uint8_t x = 0; x < P141_Nlines; x++) { + for (uint8_t x = 0; x < P141_Nlines; ++x) { String newString = AdaGFXparseTemplate(strings[x], _textcols, gfxHelper); # if ADAGFX_PARSE_SUBCOMMAND @@ -246,24 +242,24 @@ bool P141_data_struct::plugin_once_a_second(struct EventStruct *event) { ***************************************************************************/ bool P141_data_struct::plugin_write(struct EventStruct *event, const String & string) { - bool success = false; - String cmd = parseString(string, 1); + bool success = false; + const String cmd = parseString(string, 1); if ((nullptr != pcd8544) && cmd.equals(_commandTriggerCmd)) { - String arg1 = parseString(string, 2); + const String arg1 = parseString(string, 2); success = true; - if (equals(arg1, F("off"))) { // Screen off + if (equals(arg1, F("off"))) { // Screen off displayOnOff(false); } - else if (equals(arg1, F("on"))) { // Screen on + else if (equals(arg1, F("on"))) { // Screen on displayOnOff(true); } - else if (equals(arg1, F("clear"))) { // Empty screen + else if (equals(arg1, F("clear"))) { // Empty screen pcd8544->fillScreen(_bgcolor); pcd8544->display(); // Put on display } - else if (equals(arg1, F("inv")) && // Invert display + else if (equals(arg1, F("inv")) && // Invert display (event->Par2 >= 0) && (event->Par2 <= 1)) { if (parseString(string, 3).isEmpty()) { // No argument: flip previous state _displayInverted = !_displayInverted; @@ -281,7 +277,7 @@ bool P141_data_struct::plugin_write(struct EventStruct *event, } pcd8544->display(); // Put on display } - else if (equals(arg1, F("backlight"))) { // Backlight percentage + else if (equals(arg1, F("backlight"))) { // Backlight percentage if ((P141_CONFIG_BACKLIGHT_PIN != -1) && // All is valid? (event->Par2 >= 0) && (event->Par2 <= 100)) { @@ -295,8 +291,8 @@ bool P141_data_struct::plugin_write(struct EventStruct *event, else if (equals(arg1, F("contrast")) && // Display contrast (event->Par2 >= 0) && (event->Par2 <= 100)) { - P141_CONFIG_CONTRAST = event->Par2; // Set but don't store - _contrast = event->Par2; // Also set to current + P141_CONFIG_CONTRAST = event->Par2; // Set but don't store + _contrast = event->Par2; // Also set to current pcd8544->setContrast(_contrast); } else { success = false; diff --git a/src/src/PluginStructs/P143_data_struct.cpp b/src/src/PluginStructs/P143_data_struct.cpp index e9943da49..f1b7e36a8 100644 --- a/src/src/PluginStructs/P143_data_struct.cpp +++ b/src/src/PluginStructs/P143_data_struct.cpp @@ -1,894 +1,886 @@ -#include "../PluginStructs/P143_data_struct.h" - -#ifdef USES_P143 - -/************************************************************************** - * toString for P143_DeviceType_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_DeviceType_e device) { - switch (device) { - case P143_DeviceType_e::AdafruitEncoder: return F("Adafruit"); - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: return F("M5Stack"); - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: return F("DFRobot"); - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - return F(""); -} - -# if P143_FEATURE_COUNTER_COLORMAPPING - -/************************************************************************** - * toString for P143_CounterMapping_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_CounterMapping_e counter) { - switch (counter) { - case P143_CounterMapping_e::None: return F("None"); - case P143_CounterMapping_e::ColorMapping: return F("Color mapping"); - case P143_CounterMapping_e::ColorGradient: return F("Color gradient"); - } - return F(""); -} - -# endif // if P143_FEATURE_COUNTER_COLORMAPPING - -/************************************************************************** - * toString for P143_ButtonAction_e - *************************************************************************/ -const __FlashStringHelper* toString(P143_ButtonAction_e action) { - switch (action) { - case P143_ButtonAction_e::PushButton: return F("Pushbutton"); - case P143_ButtonAction_e::PushButtonInverted: return F("Pushbutton (inverted)"); - case P143_ButtonAction_e::ToggleSwitch: return F("Toggle switch"); - } - return F(""); -} - -/******************************************************************* - * P143_CheckEncoderDefaultSettings: Helper to set config defaults after changing the Encoder type - ******************************************************************/ -void P143_CheckEncoderDefaultSettings(struct EventStruct *event) { - if (P143_ENCODER_TYPE != P143_PREVIOUS_TYPE) { - switch (static_cast(P143_ENCODER_TYPE)) { - case P143_DeviceType_e::AdafruitEncoder: - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) - P143_OFFSET_POSITION = 0; - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) - P143_M5STACK_COLOR_AND_SELECTION = 0x00000000; // Black, with both Leds using Color mapping - P143_OFFSET_POSITION = 0; - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - P143_DFROBOT_LED_GAIN = P143_DFROBOT_MAX_GAIN; - P143_OFFSET_POSITION = 0; - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - P143_PREVIOUS_TYPE = P143_ENCODER_TYPE; // It's now up to date - } -} - -/************************************************************************** - * Constructor - *************************************************************************/ -P143_data_struct::P143_data_struct(struct EventStruct *event) { - _device = static_cast(P143_ENCODER_TYPE); - _i2cAddress = P143_I2C_ADDR; - _encoderPosition = P143_INITIAL_POSITION; - _encoderMin = P143_MINIMAL_POSITION; - _encoderMax = P143_MAXIMAL_POSITION; - _brightness = P143_NEOPIXEL_BRIGHTNESS; - _buttonLongPress = P143_GET_LONGPRESS_INTERVAL; - _enableLongPress = P143_PLUGIN_ENABLE_LONGPRESS; - # if P143_FEATURE_INCLUDE_DFROBOT - _initialOffset = P143_OFFSET_POSITION; - # endif // if P143_FEATURE_INCLUDE_DFROBOT -} - -/***************************************************** - * Destructor - ****************************************************/ -P143_data_struct::~P143_data_struct() { - delete Adafruit_Seesaw; - delete Adafruit_Spixel; -} - -/************************************************************************** - * plugin_init Initialize sensor and prepare for reading - *************************************************************************/ -bool P143_data_struct::plugin_init(struct EventStruct *event) { - if (!_initialized) { - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - Adafruit_Seesaw = new (std::nothrow) Adafruit_seesaw(); - Adafruit_Spixel = new (std::nothrow) seesaw_NeoPixel(1, P143_SEESAW_NEOPIX, NEO_GRB + NEO_KHZ800); - - if ((nullptr != Adafruit_Seesaw) && (nullptr != Adafruit_Spixel)) { - _initialized = Adafruit_Seesaw->begin(_i2cAddress) && Adafruit_Spixel->begin(_i2cAddress); - uint32_t version = ((Adafruit_Seesaw->getVersion() >> 16) & 0xFFFF); - - if (_initialized && (version != P143_ADAFRUIT_ENCODER_PRODUCTID)) { // Check Adafruit product ID - _initialized = false; - } - - if (_initialized) { - // use a pin for the built in encoder switch - Adafruit_Seesaw->pinMode(P143_SEESAW_SWITCH, INPUT_PULLUP); - - // set starting position - Adafruit_Seesaw->setEncoderPosition(_encoderPosition); - - // Enable interrupts on Switch pin - Adafruit_Seesaw->setGPIOInterrupts((uint32_t)1 << P143_SEESAW_SWITCH, 1); - Adafruit_Seesaw->enableEncoderInterrupt(); - - // We only have 1 pixel available... - Adafruit_Spixel->setBrightness(_brightness); // Set brightness before color! - _red = P143_ADAFRUIT_COLOR_RED; - _green = P143_ADAFRUIT_COLOR_GREEN; - _blue = P143_ADAFRUIT_COLOR_BLUE; - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - // Reset, only actually supported with upgraded firmware - I2C_write8_reg(_i2cAddress, P143_M5STACK_REG_MODE, 0x00); - - # if P143_FEATURE_M5STACK_V1_1 - - // Check if we need to use the offset method - // - Read current counter - // - Write incremented value, only works with upgraded firmware - // - re-read and if not changed we use an offset to handle passing the set limits - int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - encoderCount++; - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); - - if (encoderCount != I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER)) { - _useOffset = true; - _previousEncoder = encoderCount - 1; - _offsetEncoder = _previousEncoder - _encoderPosition; - } else { - // Don't need to use offset, set configured initial value - encoderCount = _encoderPosition; - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); - } - # else // if P143_FEATURE_M5STACK_V1_1 - _useOffset = true; // No check needed, we need to use the offset method - # endif // if P143_FEATURE_M5STACK_V1_1 - - _red = P143_ADAFRUIT_COLOR_RED; // Also used for M5Stack Led 1 - _green = P143_ADAFRUIT_COLOR_GREEN; - _blue = P143_ADAFRUIT_COLOR_BLUE; - - // Set LED initial state - m5stack_setPixelColor(1, _red, _green, _blue); - m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); - _initialized = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - { - _initialized = P143_DFROBOT_ENCODER_PID == I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_PID_MSB_REG); - - if (_initialized) { - // Set encoder position - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - - // Set led gain - I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_GAIN_REG, P143_DFROBOT_LED_GAIN & 0xFF); - } - break; - } - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - - // Set initial button state - _buttonState = (P143_ButtonAction_e::PushButtonInverted == static_cast(P143_PLUGIN_BUTTON_ACTION)) ? 0 : 1; - - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - - # if P143_FEATURE_COUNTER_COLORMAPPING - - _mapping = static_cast(P143_PLUGIN_COUNTER_MAPPING); - - // Load color mapping data - LoadCustomTaskSettings(event->TaskIndex, _colorMapping, P143_STRINGS, 0); - - for (int i = P143_STRINGS - 1; i >= 0; i--) { - _colorMapping[i].trim(); - - if ((_colorMaps == -1) && !_colorMapping[i].isEmpty()) { - _colorMaps = i; - } - } - - counterToColorMapping(event); // Update color - # endif // if P143_FEATURE_COUNTER_COLORMAPPING - - if (loglevelActiveFor(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR)) { - String log = F("I2CEncoders: INIT "); - log += toString(_device); - log += F(", "); - - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - log += F("using Offset method, "); - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - - if (!_initialized) { - log += F("FAILED."); - } else { - log += F("Success."); - } - addLogMove(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, log); - } - } - - return _initialized; -} - -/************************************************************************** - * plugin_exit De-initialize and prepare for destructor - *************************************************************************/ -bool P143_data_struct::plugin_exit(struct EventStruct *event) { - if (_initialized) { - _initialized = false; // We don't want any unexpected events - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - // Stop interrupthandler - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->disableEncoderInterrupt(); - } - - if ((nullptr != Adafruit_Spixel) && P143_PLUGIN_EXIT_LED_OFF) { - // Turn off Neopixel 0 by setting the R/G/B color to black - Adafruit_Spixel->setPixelColor(0, 0, 0, 0); - Adafruit_Spixel->show(); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (P143_PLUGIN_EXIT_LED_OFF) { - // Turn off both LEDs - m5stack_setPixelColor(0, 0, 0, 0); - } - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - - if (P143_PLUGIN_EXIT_LED_OFF) { - // Set encoder position to 0 will effectively turn off all LEDs - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, 0); - } - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - return true; -} - -/***************************************************** - * plugin_read - ****************************************************/ -bool P143_data_struct::plugin_read(struct EventStruct *event) { - if (_initialized) { - // Last obtained values - UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - return true; - } - return false; -} - -/***************************************************** - * plugin_write - ****************************************************/ -bool P143_data_struct::plugin_write(struct EventStruct *event, - String & string) { - bool success = false; - const String cmd = parseString(string, 1); - - if (_initialized && equals(cmd, F("i2cencoder"))) { - const String sub = parseString(string, 2); - - if ((equals(sub, F("led1")) || equals(sub, F("led2"))) // led1,,, (Adafruit and M5Stack) - && (event->Par2 >= 0) && (event->Par2 <= 255) // led2,,, (M5Stack only) - && (event->Par3 >= 0) && (event->Par3 <= 255) && - (event->Par4 >= 0) && (event->Par4 <= 255) - # if P143_FEATURE_INCLUDE_DFROBOT - && _device != P143_DeviceType_e::DFRobotEncoder - # endif // if P143_FEATURE_INCLUDE_DFROBOT - ) { - const bool led1 = equals(sub, F("led1")); - uint32_t lSettings = 0u; - - if (led1) { - _red = event->Par2; - _green = event->Par3; - _blue = event->Par4; - lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_RED, _red); - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_GREEN, _green); - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BLUE, _blue); - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; - } - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (led1 && (nullptr != Adafruit_Spixel)) { - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - success = true; - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (!led1) { - lSettings = P143_M5STACK_COLOR_AND_SELECTION; - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_RED, event->Par2 & 0xFF); - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_GREEN, event->Par3 & 0xFF); - set8BitToUL(lSettings, P143_M5STACK2_OFFSET_BLUE, event->Par4 & 0xFF); - P143_M5STACK_COLOR_AND_SELECTION = lSettings; - } - m5stack_setPixelColor(led1 ? 1 : 2, event->Par2 & 0xFF, event->Par3 & 0xFF, event->Par4 & 0xFF); - success = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } else - if (equals(sub, F("bright")) // bright, (range 1..255, Adafruit and M5Stack only) - && (event->Par2 >= 1) && (event->Par2 <= 255) - # if P143_FEATURE_INCLUDE_DFROBOT - && _device != P143_DeviceType_e::DFRobotEncoder - # endif // if P143_FEATURE_INCLUDE_DFROBOT - ) { - _brightness = event->Par2; - uint32_t lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; - set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BRIGHTNESS, _brightness); - P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (nullptr != Adafruit_Spixel) { - Adafruit_Spixel->setBrightness(_brightness); - - // Update with new brightness - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - success = true; - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - // Update with new brightness - m5stack_setPixelColor(1, _red, _green, _blue); - m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); - success = true; - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - # if P143_FEATURE_INCLUDE_DFROBOT - } else - if (equals(sub, F("gain")) // gain, (Range 1..51, DFRobot only) - && (event->Par2 >= P143_DFROBOT_MIN_GAIN) && (event->Par2 <= P143_DFROBOT_MAX_GAIN) - && (_device == P143_DeviceType_e::DFRobotEncoder) - ) { - P143_DFROBOT_LED_GAIN = event->Par2; - success = true; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } else - if (equals(sub, F("set"))) { // set,[,] (initial offset only for DFRobot) - _encoderPosition = event->Par2; - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->setEncoderPosition(_encoderPosition); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - if (_useOffset) { // Adjust offset - int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - _offsetEncoder = encoderCount - _encoderPosition; - # if P143_FEATURE_M5STACK_V1_1 - } else { // Set position using upgraded firmware - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, _encoderPosition); - # endif // if P143_FEATURE_M5STACK_V1_1 - } - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - { - if (!parseString(string, 4).isEmpty() && (event->Par3 >= P143_DFROBOT_MIN_OFFSET) && (event->Par3 <= P143_DFROBOT_MAX_OFFSET)) { - _initialOffset = event->Par3; - P143_OFFSET_POSITION = _initialOffset; - } - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - break; - } - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - } - - return success; -} - -/***************************************************** - * plugin_ten_per_second - ****************************************************/ -bool P143_data_struct::plugin_ten_per_second(struct EventStruct *event) { - bool result = false; - - if (_initialized) { - int32_t current = _encoderPosition; - # if PLUGIN_143_DEBUG - _oldPosition = _encoderPosition; - # endif // if PLUGIN_143_DEBUG - - // Read encoder - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - current = Adafruit_Seesaw->getEncoderPosition(); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - current = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - current = static_cast(I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG)) - _initialOffset; - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - # if P143_FEATURE_INCLUDE_M5STACK - const int32_t rawCurrent = current; - - if (_useOffset) { - current -= _offsetEncoder; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - const int32_t orgCurrent = current; - - // Check limits - if (_encoderMin != _encoderMax) { - if ((current < _encoderMin) || (current > _encoderMax)) { - // Have to check separately, as it's possible to move multiple steps within 1/10th second - if (current < _encoderMin) { - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - _offsetEncoder = rawCurrent - _encoderMin; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - current = _encoderMin; // keep minimal value - } - else if (current > _encoderMax) { - # if P143_FEATURE_INCLUDE_M5STACK - - if (_useOffset) { - _offsetEncoder = rawCurrent - _encoderMax; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - current = _encoderMax; // keep maximal value - } - _previousEncoder = current; - - // (Re)Set Encoder to current position if outside the set boundaries - if (current != orgCurrent) { - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - Adafruit_Seesaw->setEncoderPosition(current); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - - # if P143_FEATURE_M5STACK_V1_1 - - // Set encoder position. NB: will only work if the encoder firmware is updated to v1.1 - if (!_useOffset) { - I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, current); - } - # endif // if P143_FEATURE_M5STACK_V1_1 - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } - } - } - - if (current != _encoderPosition) { - // Generate event - if (Settings.UseRules) { - String eventvalues; - eventvalues += current; // Position - eventvalues += ','; - eventvalues += current - _encoderPosition; // Delta, positive = clock-wise - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, 0), eventvalues); - } - - // Set task value - _encoderPosition = current; - - UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); - - result = true; - - // Calculate colormapping - # if P143_FEATURE_COUNTER_COLORMAPPING - counterToColorMapping(event); - # endif // if P143_FEATURE_COUNTER_COLORMAPPING - - # if PLUGIN_143_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("I2CEncoder : "); - log += toString(_device); - log += F(", Changed: "); - log += _oldPosition; - log += F(" to: "); - log += _encoderPosition; - log += F(", delta: "); - log += _encoderPosition - _oldPosition; - addLogMove(LOG_LEVEL_INFO, log); - } - # endif // if PLUGIN_143_DEBUG - } - } - - return result; -} - -# if P143_FEATURE_COUNTER_COLORMAPPING - -/***************************************************** - * counterToColorMapping - ****************************************************/ -void P143_data_struct::counterToColorMapping(struct EventStruct *event) { - int16_t iRed = -1; - int16_t iGreen = -1; - int16_t iBlue = -1; - int16_t pRed = -1; - int16_t pGreen = -1; - int16_t pBlue = -1; - int32_t iCount = INT32_MIN; - int32_t pCount = INT32_MIN; - - switch (_mapping) { - case P143_CounterMapping_e::ColorMapping: - { - for (int i = 0; i <= _colorMaps; i++) { - if ((!_colorMapping[i].isEmpty()) && - (iCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && - (iCount < _encoderPosition)) { // Reset, out of range - iRed = -1; - iGreen = -1; - iBlue = -1; - iCount = INT32_MIN; - } - } - break; - } - - case P143_CounterMapping_e::ColorGradient: - { - for (int i = 0; i <= _colorMaps; i++) { - if (!_colorMapping[i].isEmpty()) { - if ((iCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && - ((iCount > _encoderPosition) || !rangeCheck(iCount, _encoderMin, _encoderMax))) { - iRed = -1; - iGreen = -1; - iBlue = -1; - iCount = INT32_MIN; - } - - if ((pCount == INT32_MIN) && - (parseColorMapLine(_colorMapping[i], pCount, pRed, pGreen, pBlue)) && - ((pCount < _encoderPosition) || !rangeCheck(pCount, _encoderMin, _encoderMax))) { - pRed = -1; - pGreen = -1; - pBlue = -1; - pCount = INT32_MIN; - } - } - } - - // Calculate R/G/B gradient-values for current Counter within upper and lower range - if ((pCount != iCount) && (iRed > -1) && (iGreen > -1) && (iBlue > -1) && (pRed > -1) && (pGreen > -1) && (pBlue > -1)) { - iRed = map(_encoderPosition, iCount, pCount, iRed, pRed); - iGreen = map(_encoderPosition, iCount, pCount, iGreen, pGreen); - iBlue = map(_encoderPosition, iCount, pCount, iBlue, pBlue); - } - - break; - } - case P143_CounterMapping_e::None: - // Do nothing - break; - } - - // Updated Led color? - if ((iRed > -1) && (iGreen > -1) && (iBlue > -1)) { - # if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("P143: Change color R:"); - log += iRed; - log += F(", G:"); - log += iGreen; - log += F(", B:"); - log += iBlue; - # ifdef LOG_LEVEL_DEBUG_DEV - log += F(", pR:"); // DEV-only from here - log += pRed; - log += F(", pG:"); - log += pGreen; - log += F(", pB:"); - log += pBlue; - log += F(", iC:"); - log += iCount; - log += F(", pC:"); - log += pCount; - # endif // ifdef LOG_LEVEL_DEBUG_DEV - addLog(LOG_LEVEL_DEBUG, log); - } - # endif // if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG - - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - { - _red = iRed; - _green = iGreen; - _blue = iBlue; - - if (nullptr != Adafruit_Spixel) { - Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); - Adafruit_Spixel->show(); - } - break; - } - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - { - _red = iRed; - _green = iGreen; - _blue = iBlue; - m5stack_setPixelColor(static_cast(P143_M5STACK_SELECTION), _red, _green, _blue); - break; - } - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - break; // N/A - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - } -} - -/***************************************************** - * parseColorMapLine, line prefixed with # is comment/disabled - ****************************************************/ -bool P143_data_struct::parseColorMapLine(const String& line, - int32_t & count, - int16_t & red, - int16_t & green, - int16_t & blue) { - bool result = false; - String tmp = parseString(line, 1); - - if (!tmp.isEmpty() && !tmp.startsWith(F("#"))) { - count = tmp.toInt(); - - tmp = parseString(line, 2); - - if (!tmp.isEmpty()) { - red = tmp.toInt(); - - tmp = parseString(line, 3); - - if (!tmp.isEmpty()) { - green = tmp.toInt(); - - tmp = parseString(line, 4); - - if (!tmp.isEmpty()) { - blue = tmp.toInt(); - result = true; - } - } - } - } - - return result; -} - -/***************************************************** - * rangeCheck - ****************************************************/ -bool P143_data_struct::rangeCheck(int32_t count, - int32_t min, - int32_t max) { - return !((min != max) && - ((count < min) || (count > max))); -} - -# endif // if P143_FEATURE_COUNTER_COLORMAPPING - -/***************************************************** - * plugin_fifty_per_second, handle button actions - ****************************************************/ -bool P143_data_struct::plugin_fifty_per_second(struct EventStruct *event) { - if (_initialized) { - const uint8_t pressedState = 0; // button has this value if pressed - uint8_t button = _buttonLast; - uint8_t state = _buttonState; - - // Read button - switch (_device) { - case P143_DeviceType_e::AdafruitEncoder: - - if (nullptr != Adafruit_Seesaw) { - button = Adafruit_Seesaw->digitalRead(P143_SEESAW_SWITCH); - } - break; - # if P143_FEATURE_INCLUDE_M5STACK - case P143_DeviceType_e::M5StackEncoder: - button = I2C_read8_reg(_i2cAddress, P143_M5STACK_REG_BUTTON); - break; - # endif // if P143_FEATURE_INCLUDE_M5STACK - # if P143_FEATURE_INCLUDE_DFROBOT - case P143_DeviceType_e::DFRobotEncoder: - uint8_t press = I2C_read8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG); - - if ((press & 0x01) != 0) { - I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG, 0x00); // Reset - // Trigger immediately - button = button ? 0 : 1; - _buttonDown = true; - _buttonTime = (60 / 20); - } - - break; - # endif // if P143_FEATURE_INCLUDE_DFROBOT - } - - if ((button == pressedState) && _buttonDown) { - _buttonTime++; // increment 20 msec - } else if (button == pressedState) { - _buttonDown = true; - _buttonTime = 0; - } - - if ((button != _buttonLast) && (_buttonTime >= (60 / 20))) { // Short-press = 60 msec - _buttonLast = button; - _buttonDown = false; - - switch (static_cast(P143_PLUGIN_BUTTON_ACTION)) { - case P143_ButtonAction_e::PushButton: - state = button; - break; - case P143_ButtonAction_e::PushButtonInverted: - state = button == 0 ? 1 : 0; - _buttonState = state == 0 ? 1 : 0; // Changed - break; - case P143_ButtonAction_e::ToggleSwitch: - - if (button == pressedState) { - state = _buttonState == 0 ? 1 : 0; // Toggle - } - _buttonTime = 0; // Ignore long-press - break; - } - } - - if (state != _buttonState) { - if (_enableLongPress && (_buttonTime >= (_buttonLongPress / 20))) { // Long-press - state += 10; // Eventvalues similar to Switch plugin - } - - // Generate event - if (Settings.UseRules) { - eventQueue.add(event->TaskIndex, getTaskValueName(event->TaskIndex, 1), state); - } - - // Set task value - _buttonState = state; - - UserVar.setFloat(event->TaskIndex, 1, _buttonState); - - return true; - } - } - return false; -} - -# if P143_FEATURE_INCLUDE_M5STACK - -/***************************************************** - * applyBrightness, calculate new color based on brightness, based on NeoPixel library setBrightness() - ****************************************************/ -uint8_t P143_data_struct::applyBrightness(uint8_t color) { - if (_brightness) { - color = (color * _brightness) >> 8; - } - - return color; -} - -/***************************************************** - * m5stack_setPixelColor - ****************************************************/ -void P143_data_struct::m5stack_setPixelColor(uint8_t pixel, - uint8_t red, - uint8_t green, - uint8_t blue) { - uint8_t data[4] = { - pixel, - applyBrightness(red), - applyBrightness(green), - applyBrightness(blue)}; - I2C_writeBytes_reg(_i2cAddress, P143_M5STACK_REG_LED, data, 4); -} - -# endif // if P143_FEATURE_INCLUDE_M5STACK - -#endif // ifdef USES_P143 +#include "../PluginStructs/P143_data_struct.h" + +#ifdef USES_P143 + +/************************************************************************** + * toString for P143_DeviceType_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_DeviceType_e device) { + switch (device) { + case P143_DeviceType_e::AdafruitEncoder: return F("Adafruit"); + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: return F("M5Stack"); + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: return F("DFRobot"); + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + return F(""); +} + +# if P143_FEATURE_COUNTER_COLORMAPPING + +/************************************************************************** + * toString for P143_CounterMapping_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_CounterMapping_e counter) { + switch (counter) { + case P143_CounterMapping_e::None: return F("None"); + case P143_CounterMapping_e::ColorMapping: return F("Color mapping"); + case P143_CounterMapping_e::ColorGradient: return F("Color gradient"); + } + return F(""); +} + +# endif // if P143_FEATURE_COUNTER_COLORMAPPING + +/************************************************************************** + * toString for P143_ButtonAction_e + *************************************************************************/ +const __FlashStringHelper* toString(P143_ButtonAction_e action) { + switch (action) { + case P143_ButtonAction_e::PushButton: return F("Pushbutton"); + case P143_ButtonAction_e::PushButtonInverted: return F("Pushbutton (inverted)"); + case P143_ButtonAction_e::ToggleSwitch: return F("Toggle switch"); + } + return F(""); +} + +/******************************************************************* + * P143_CheckEncoderDefaultSettings: Helper to set config defaults after changing the Encoder type + ******************************************************************/ +void P143_CheckEncoderDefaultSettings(struct EventStruct *event) { + if (P143_ENCODER_TYPE != P143_PREVIOUS_TYPE) { + switch (static_cast(P143_ENCODER_TYPE)) { + case P143_DeviceType_e::AdafruitEncoder: + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) + P143_OFFSET_POSITION = 0; + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = 0x0000001E; // Black, with 30 (0x1E) brightness (1..255) + P143_M5STACK_COLOR_AND_SELECTION = 0x00000000; // Black, with both Leds using Color mapping + P143_OFFSET_POSITION = 0; + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + P143_DFROBOT_LED_GAIN = P143_DFROBOT_MAX_GAIN; + P143_OFFSET_POSITION = 0; + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + P143_PREVIOUS_TYPE = P143_ENCODER_TYPE; // It's now up to date + } +} + +/************************************************************************** + * Constructor + *************************************************************************/ +P143_data_struct::P143_data_struct(struct EventStruct *event) { + _device = static_cast(P143_ENCODER_TYPE); + _i2cAddress = P143_I2C_ADDR; + _encoderPosition = P143_INITIAL_POSITION; + _encoderMin = P143_MINIMAL_POSITION; + _encoderMax = P143_MAXIMAL_POSITION; + _brightness = P143_NEOPIXEL_BRIGHTNESS; + _buttonLongPress = P143_GET_LONGPRESS_INTERVAL; + _enableLongPress = P143_PLUGIN_ENABLE_LONGPRESS; + # if P143_FEATURE_INCLUDE_DFROBOT + _initialOffset = P143_OFFSET_POSITION; + # endif // if P143_FEATURE_INCLUDE_DFROBOT +} + +/***************************************************** + * Destructor + ****************************************************/ +P143_data_struct::~P143_data_struct() { + delete Adafruit_Seesaw; + delete Adafruit_Spixel; +} + +/************************************************************************** + * plugin_init Initialize sensor and prepare for reading + *************************************************************************/ +bool P143_data_struct::plugin_init(struct EventStruct *event) { + if (!_initialized) { + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + Adafruit_Seesaw = new (std::nothrow) Adafruit_seesaw(); + Adafruit_Spixel = new (std::nothrow) seesaw_NeoPixel(1, P143_SEESAW_NEOPIX, NEO_GRB + NEO_KHZ800); + + if ((nullptr != Adafruit_Seesaw) && (nullptr != Adafruit_Spixel)) { + _initialized = Adafruit_Seesaw->begin(_i2cAddress) && Adafruit_Spixel->begin(_i2cAddress); + uint32_t version = ((Adafruit_Seesaw->getVersion() >> 16) & 0xFFFF); + + if (_initialized && (version != P143_ADAFRUIT_ENCODER_PRODUCTID)) { // Check Adafruit product ID + _initialized = false; + } + + if (_initialized) { + // use a pin for the built in encoder switch + Adafruit_Seesaw->pinMode(P143_SEESAW_SWITCH, INPUT_PULLUP); + + // set starting position + Adafruit_Seesaw->setEncoderPosition(_encoderPosition); + + // Enable interrupts on Switch pin + Adafruit_Seesaw->setGPIOInterrupts((uint32_t)1 << P143_SEESAW_SWITCH, 1); + Adafruit_Seesaw->enableEncoderInterrupt(); + + // We only have 1 pixel available... + Adafruit_Spixel->setBrightness(_brightness); // Set brightness before color! + _red = P143_ADAFRUIT_COLOR_RED; + _green = P143_ADAFRUIT_COLOR_GREEN; + _blue = P143_ADAFRUIT_COLOR_BLUE; + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + // Reset, only actually supported with upgraded firmware + I2C_write8_reg(_i2cAddress, P143_M5STACK_REG_MODE, 0x00); + + # if P143_FEATURE_M5STACK_V1_1 + + // Check if we need to use the offset method + // - Read current counter + // - Write incremented value, only works with upgraded firmware + // - re-read and if not changed we use an offset to handle passing the set limits + int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + encoderCount++; + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); + + if (encoderCount != I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER)) { + _useOffset = true; + _previousEncoder = encoderCount - 1; + _offsetEncoder = _previousEncoder - _encoderPosition; + } else { + // Don't need to use offset, set configured initial value + encoderCount = _encoderPosition; + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, encoderCount); + } + # else // if P143_FEATURE_M5STACK_V1_1 + _useOffset = true; // No check needed, we need to use the offset method + # endif // if P143_FEATURE_M5STACK_V1_1 + + _red = P143_ADAFRUIT_COLOR_RED; // Also used for M5Stack Led 1 + _green = P143_ADAFRUIT_COLOR_GREEN; + _blue = P143_ADAFRUIT_COLOR_BLUE; + + // Set LED initial state + m5stack_setPixelColor(1, _red, _green, _blue); + m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); + _initialized = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + { + _initialized = P143_DFROBOT_ENCODER_PID == I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_PID_MSB_REG); + + if (_initialized) { + // Set encoder position + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + + // Set led gain + I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_GAIN_REG, P143_DFROBOT_LED_GAIN & 0xFF); + } + break; + } + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + + // Set initial button state + _buttonState = (P143_ButtonAction_e::PushButtonInverted == static_cast(P143_PLUGIN_BUTTON_ACTION)) ? 0 : 1; + + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + + # if P143_FEATURE_COUNTER_COLORMAPPING + + _mapping = static_cast(P143_PLUGIN_COUNTER_MAPPING); + + // Load color mapping data + LoadCustomTaskSettings(event->TaskIndex, _colorMapping, P143_STRINGS, 0); + + for (int i = P143_STRINGS - 1; i >= 0; --i) { + _colorMapping[i].trim(); + + if ((_colorMaps == -1) && !_colorMapping[i].isEmpty()) { + _colorMaps = i; + } + } + + counterToColorMapping(event); // Update color + # endif // if P143_FEATURE_COUNTER_COLORMAPPING + + if (loglevelActiveFor(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR)) { + String log = concat(F("I2CEncoders: INIT "), toString(_device)); + log += F(", "); + + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + log += F("using Offset method, "); + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + + if (!_initialized) { + log += F("FAILED."); + } else { + log += F("Success."); + } + addLogMove(_initialized ? LOG_LEVEL_INFO : LOG_LEVEL_ERROR, log); + } + } + + return _initialized; +} + +/************************************************************************** + * plugin_exit De-initialize and prepare for destructor + *************************************************************************/ +bool P143_data_struct::plugin_exit(struct EventStruct *event) { + if (_initialized) { + _initialized = false; // We don't want any unexpected events + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + // Stop interrupthandler + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->disableEncoderInterrupt(); + } + + if ((nullptr != Adafruit_Spixel) && P143_PLUGIN_EXIT_LED_OFF) { + // Turn off Neopixel 0 by setting the R/G/B color to black + Adafruit_Spixel->setPixelColor(0, 0, 0, 0); + Adafruit_Spixel->show(); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (P143_PLUGIN_EXIT_LED_OFF) { + // Turn off both LEDs + m5stack_setPixelColor(0, 0, 0, 0); + } + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + + if (P143_PLUGIN_EXIT_LED_OFF) { + // Set encoder position to 0 will effectively turn off all LEDs + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, 0); + } + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + return true; +} + +/***************************************************** + * plugin_read + ****************************************************/ +bool P143_data_struct::plugin_read(struct EventStruct *event) { + if (_initialized) { + // Last obtained values + UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + return true; + } + return false; +} + +/***************************************************** + * plugin_write + ****************************************************/ +bool P143_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + const String cmd = parseString(string, 1); + + if (_initialized && equals(cmd, F("i2cencoder"))) { + const String sub = parseString(string, 2); + const bool led1 = equals(sub, F("led1")); + + if ((led1 || equals(sub, F("led2"))) // led1,,, (Adafruit and M5Stack) + && (event->Par2 >= 0) && (event->Par2 <= 255) // led2,,, (M5Stack only) + && (event->Par3 >= 0) && (event->Par3 <= 255) && + (event->Par4 >= 0) && (event->Par4 <= 255) + # if P143_FEATURE_INCLUDE_DFROBOT + && _device != P143_DeviceType_e::DFRobotEncoder + # endif // if P143_FEATURE_INCLUDE_DFROBOT + ) { + uint32_t lSettings = 0u; + + if (led1) { + _red = event->Par2; + _green = event->Par3; + _blue = event->Par4; + lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_RED, _red); + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_GREEN, _green); + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BLUE, _blue); + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; + } + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (led1 && (nullptr != Adafruit_Spixel)) { + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + success = true; + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (!led1) { + lSettings = P143_M5STACK_COLOR_AND_SELECTION; + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_RED, event->Par2 & 0xFF); + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_GREEN, event->Par3 & 0xFF); + set8BitToUL(lSettings, P143_M5STACK2_OFFSET_BLUE, event->Par4 & 0xFF); + P143_M5STACK_COLOR_AND_SELECTION = lSettings; + } + m5stack_setPixelColor(led1 ? 1 : 2, event->Par2 & 0xFF, event->Par3 & 0xFF, event->Par4 & 0xFF); + success = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } else + if (equals(sub, F("bright")) // bright, (range 1..255, Adafruit and M5Stack only) + && (event->Par2 >= 1) && (event->Par2 <= 255) + # if P143_FEATURE_INCLUDE_DFROBOT + && _device != P143_DeviceType_e::DFRobotEncoder + # endif // if P143_FEATURE_INCLUDE_DFROBOT + ) { + _brightness = event->Par2; + uint32_t lSettings = P143_ADAFRUIT_COLOR_AND_BRIGHTNESS; + set8BitToUL(lSettings, P143_ADAFRUIT_OFFSET_BRIGHTNESS, _brightness); + P143_ADAFRUIT_COLOR_AND_BRIGHTNESS = lSettings; + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (nullptr != Adafruit_Spixel) { + Adafruit_Spixel->setBrightness(_brightness); + + // Update with new brightness + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + success = true; + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + // Update with new brightness + m5stack_setPixelColor(1, _red, _green, _blue); + m5stack_setPixelColor(2, P143_M5STACK2_COLOR_RED, P143_M5STACK2_COLOR_GREEN, P143_M5STACK2_COLOR_BLUE); + success = true; + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + # if P143_FEATURE_INCLUDE_DFROBOT + } else + if (equals(sub, F("gain")) // gain, (Range 1..51, DFRobot only) + && (event->Par2 >= P143_DFROBOT_MIN_GAIN) && (event->Par2 <= P143_DFROBOT_MAX_GAIN) + && (_device == P143_DeviceType_e::DFRobotEncoder) + ) { + P143_DFROBOT_LED_GAIN = event->Par2; + success = true; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } else + if (equals(sub, F("set"))) { // set,[,] (initial offset only for DFRobot) + _encoderPosition = event->Par2; + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->setEncoderPosition(_encoderPosition); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + if (_useOffset) { // Adjust offset + int16_t encoderCount = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + _offsetEncoder = encoderCount - _encoderPosition; + # if P143_FEATURE_M5STACK_V1_1 + } else { // Set position using upgraded firmware + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, _encoderPosition); + # endif // if P143_FEATURE_M5STACK_V1_1 + } + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + { + if (!parseString(string, 4).isEmpty() && (event->Par3 >= P143_DFROBOT_MIN_OFFSET) && (event->Par3 <= P143_DFROBOT_MAX_OFFSET)) { + _initialOffset = event->Par3; + P143_OFFSET_POSITION = _initialOffset; + } + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + break; + } + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + } + + return success; +} + +/***************************************************** + * plugin_ten_per_second + ****************************************************/ +bool P143_data_struct::plugin_ten_per_second(struct EventStruct *event) { + bool result = false; + + if (_initialized) { + int32_t current = _encoderPosition; + # if PLUGIN_143_DEBUG + _oldPosition = _encoderPosition; + # endif // if PLUGIN_143_DEBUG + + // Read encoder + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + current = Adafruit_Seesaw->getEncoderPosition(); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + current = I2C_readS16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER); + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + current = static_cast(I2C_read16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG)) - _initialOffset; + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + # if P143_FEATURE_INCLUDE_M5STACK + const int32_t rawCurrent = current; + + if (_useOffset) { + current -= _offsetEncoder; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + const int32_t orgCurrent = current; + + // Check limits + if (_encoderMin != _encoderMax) { + if ((current < _encoderMin) || (current > _encoderMax)) { + // Have to check separately, as it's possible to move multiple steps within 1/10th second + if (current < _encoderMin) { + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + _offsetEncoder = rawCurrent - _encoderMin; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + current = _encoderMin; // keep minimal value + } + else if (current > _encoderMax) { + # if P143_FEATURE_INCLUDE_M5STACK + + if (_useOffset) { + _offsetEncoder = rawCurrent - _encoderMax; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + current = _encoderMax; // keep maximal value + } + _previousEncoder = current; + + // (Re)Set Encoder to current position if outside the set boundaries + if (current != orgCurrent) { + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + Adafruit_Seesaw->setEncoderPosition(current); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + + # if P143_FEATURE_M5STACK_V1_1 + + // Set encoder position. NB: will only work if the encoder firmware is updated to v1.1 + if (!_useOffset) { + I2C_write16_LE_reg(_i2cAddress, P143_M5STACK_REG_ENCODER, current); + } + # endif // if P143_FEATURE_M5STACK_V1_1 + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + I2C_write16_reg(_i2cAddress, P143_DFROBOT_ENCODER_COUNT_MSB_REG, _initialOffset + _encoderPosition); + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } + } + } + + if (current != _encoderPosition) { + // Generate event + if (Settings.UseRules) { + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, 0), + strformat(F("%d,%d"), current, current - _encoderPosition)); // Position, Delta (positive = clock-wise) + } + + // Set task value + _encoderPosition = current; + + UserVar.setFloat(event->TaskIndex, 0, _encoderPosition); + + result = true; + + // Calculate colormapping + # if P143_FEATURE_COUNTER_COLORMAPPING + counterToColorMapping(event); + # endif // if P143_FEATURE_COUNTER_COLORMAPPING + + # if PLUGIN_143_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = concat(F("I2CEncoder : "), toString(_device)); + log += strformat(F(", Changed: %d to: %d, delta: %d"), + _oldPosition, _encoderPosition, _encoderPosition - _oldPosition); + addLogMove(LOG_LEVEL_INFO, log); + } + # endif // if PLUGIN_143_DEBUG + } + } + + return result; +} + +# if P143_FEATURE_COUNTER_COLORMAPPING + +/***************************************************** + * counterToColorMapping + ****************************************************/ +void P143_data_struct::counterToColorMapping(struct EventStruct *event) { + int16_t iRed = -1; + int16_t iGreen = -1; + int16_t iBlue = -1; + int16_t pRed = -1; + int16_t pGreen = -1; + int16_t pBlue = -1; + int32_t iCount = INT32_MIN; + int32_t pCount = INT32_MIN; + + switch (_mapping) { + case P143_CounterMapping_e::ColorMapping: + { + for (int i = 0; i <= _colorMaps; ++i) { + if ((!_colorMapping[i].isEmpty()) && + (iCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && + (iCount < _encoderPosition)) { // Reset, out of range + iRed = -1; + iGreen = -1; + iBlue = -1; + iCount = INT32_MIN; + } + } + break; + } + + case P143_CounterMapping_e::ColorGradient: + { + for (int i = 0; i <= _colorMaps; ++i) { + if (!_colorMapping[i].isEmpty()) { + if ((iCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], iCount, iRed, iGreen, iBlue)) && + ((iCount > _encoderPosition) || !rangeCheck(iCount, _encoderMin, _encoderMax))) { + iRed = -1; + iGreen = -1; + iBlue = -1; + iCount = INT32_MIN; + } + + if ((pCount == INT32_MIN) && + (parseColorMapLine(_colorMapping[i], pCount, pRed, pGreen, pBlue)) && + ((pCount < _encoderPosition) || !rangeCheck(pCount, _encoderMin, _encoderMax))) { + pRed = -1; + pGreen = -1; + pBlue = -1; + pCount = INT32_MIN; + } + } + } + + // Calculate R/G/B gradient-values for current Counter within upper and lower range + if ((pCount != iCount) && (iRed > -1) && (iGreen > -1) && (iBlue > -1) && (pRed > -1) && (pGreen > -1) && (pBlue > -1)) { + iRed = map(_encoderPosition, iCount, pCount, iRed, pRed); + iGreen = map(_encoderPosition, iCount, pCount, iGreen, pGreen); + iBlue = map(_encoderPosition, iCount, pCount, iBlue, pBlue); + } + + break; + } + case P143_CounterMapping_e::None: + // Do nothing + break; + } + + // Updated Led color? + if ((iRed > -1) && (iGreen > -1) && (iBlue > -1)) { + # if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("P143: Change color R:"); + log += iRed; + log += F(", G:"); + log += iGreen; + log += F(", B:"); + log += iBlue; + # ifdef LOG_LEVEL_DEBUG_DEV + log += F(", pR:"); // DEV-only from here + log += pRed; + log += F(", pG:"); + log += pGreen; + log += F(", pB:"); + log += pBlue; + log += F(", iC:"); + log += iCount; + log += F(", pC:"); + log += pCount; + # endif // ifdef LOG_LEVEL_DEBUG_DEV + addLog(LOG_LEVEL_DEBUG, log); + } + # endif // if !defined(BUILD_NO_DEBUG) && PLUGIN_143_DEBUG + + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + { + _red = iRed; + _green = iGreen; + _blue = iBlue; + + if (nullptr != Adafruit_Spixel) { + Adafruit_Spixel->setPixelColor(0, _red, _green, _blue); + Adafruit_Spixel->show(); + } + break; + } + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + { + _red = iRed; + _green = iGreen; + _blue = iBlue; + m5stack_setPixelColor(static_cast(P143_M5STACK_SELECTION), _red, _green, _blue); + break; + } + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + break; // N/A + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + } +} + +/***************************************************** + * parseColorMapLine, line prefixed with # is comment/disabled + ****************************************************/ +bool P143_data_struct::parseColorMapLine(const String& line, + int32_t & count, + int16_t & red, + int16_t & green, + int16_t & blue) { + bool result = false; + String tmp = parseString(line, 1); + + if (!tmp.isEmpty() && !tmp.startsWith(F("#"))) { + count = tmp.toInt(); + + tmp = parseString(line, 2); + + if (!tmp.isEmpty()) { + red = tmp.toInt(); + + tmp = parseString(line, 3); + + if (!tmp.isEmpty()) { + green = tmp.toInt(); + + tmp = parseString(line, 4); + + if (!tmp.isEmpty()) { + blue = tmp.toInt(); + result = true; + } + } + } + } + + return result; +} + +/***************************************************** + * rangeCheck + ****************************************************/ +bool P143_data_struct::rangeCheck(int32_t count, + int32_t min, + int32_t max) { + return !((min != max) && + ((count < min) || (count > max))); +} + +# endif // if P143_FEATURE_COUNTER_COLORMAPPING + +/***************************************************** + * plugin_fifty_per_second, handle button actions + ****************************************************/ +bool P143_data_struct::plugin_fifty_per_second(struct EventStruct *event) { + if (_initialized) { + const uint8_t pressedState = 0; // button has this value if pressed + uint8_t button = _buttonLast; + uint8_t state = _buttonState; + + // Read button + switch (_device) { + case P143_DeviceType_e::AdafruitEncoder: + + if (nullptr != Adafruit_Seesaw) { + button = Adafruit_Seesaw->digitalRead(P143_SEESAW_SWITCH); + } + break; + # if P143_FEATURE_INCLUDE_M5STACK + case P143_DeviceType_e::M5StackEncoder: + button = I2C_read8_reg(_i2cAddress, P143_M5STACK_REG_BUTTON); + break; + # endif // if P143_FEATURE_INCLUDE_M5STACK + # if P143_FEATURE_INCLUDE_DFROBOT + case P143_DeviceType_e::DFRobotEncoder: + uint8_t press = I2C_read8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG); + + if ((press & 0x01) != 0) { + I2C_write8_reg(_i2cAddress, P143_DFROBOT_ENCODER_KEY_STATUS_REG, 0x00); // Reset + // Trigger immediately + button = button ? 0 : 1; + _buttonDown = true; + _buttonTime = (60 / 20); + } + + break; + # endif // if P143_FEATURE_INCLUDE_DFROBOT + } + + if ((button == pressedState) && _buttonDown) { + _buttonTime++; // increment 20 msec + } else if (button == pressedState) { + _buttonDown = true; + _buttonTime = 0; + } + + if ((button != _buttonLast) && (_buttonTime >= (60 / 20))) { // Short-press = 60 msec + _buttonLast = button; + _buttonDown = false; + + switch (static_cast(P143_PLUGIN_BUTTON_ACTION)) { + case P143_ButtonAction_e::PushButton: + state = button; + break; + case P143_ButtonAction_e::PushButtonInverted: + state = button == 0 ? 1 : 0; + _buttonState = state == 0 ? 1 : 0; // Changed + break; + case P143_ButtonAction_e::ToggleSwitch: + + if (button == pressedState) { + state = _buttonState == 0 ? 1 : 0; // Toggle + } + _buttonTime = 0; // Ignore long-press + break; + } + } + + if (state != _buttonState) { + if (_enableLongPress && (_buttonTime >= (_buttonLongPress / 20))) { // Long-press + state += 10; // Eventvalues similar to Switch plugin + } + + // Generate event + if (Settings.UseRules) { + eventQueue.add(event->TaskIndex, Cache.getTaskDeviceValueName(event->TaskIndex, 1), state); + } + + // Set task value + _buttonState = state; + + UserVar.setFloat(event->TaskIndex, 1, _buttonState); + + return true; + } + } + return false; +} + +# if P143_FEATURE_INCLUDE_M5STACK + +/***************************************************** + * applyBrightness, calculate new color based on brightness, based on NeoPixel library setBrightness() + ****************************************************/ +uint8_t P143_data_struct::applyBrightness(uint8_t color) { + if (_brightness) { + color = (color * _brightness) >> 8; + } + + return color; +} + +/***************************************************** + * m5stack_setPixelColor + ****************************************************/ +void P143_data_struct::m5stack_setPixelColor(uint8_t pixel, + uint8_t red, + uint8_t green, + uint8_t blue) { + uint8_t data[4] = { + pixel, + applyBrightness(red), + applyBrightness(green), + applyBrightness(blue) }; + + I2C_writeBytes_reg(_i2cAddress, P143_M5STACK_REG_LED, data, 4); +} + +# endif // if P143_FEATURE_INCLUDE_M5STACK + +#endif // ifdef USES_P143 diff --git a/src/src/PluginStructs/P144_data_struct.cpp b/src/src/PluginStructs/P144_data_struct.cpp index 38e328ac6..964355bf5 100644 --- a/src/src/PluginStructs/P144_data_struct.cpp +++ b/src/src/PluginStructs/P144_data_struct.cpp @@ -35,10 +35,7 @@ bool P144_data_struct::setSerial(ESPEasySerialPort portType, int8_t rxPin, int8_ String log = F("P144 : Init: "); if (success) { - log += F(" ESP GPIO-pin RX:"); - log += rxPin; - log += F(" TX:"); - log += txPin; + log += strformat(F(" ESP GPIO-pin RX:%d TX:%d"), rxPin, txPin); } else { @@ -47,7 +44,7 @@ bool P144_data_struct::setSerial(ESPEasySerialPort portType, int8_t rxPin, int8_ addLogMove(LOG_LEVEL_INFO, log); } #endif - return(success); + return success; } // ---------------------------------------------------------------------------- @@ -56,11 +53,8 @@ bool P144_data_struct::setSerial(ESPEasySerialPort portType, int8_t rxPin, int8_ // ---------------------------------------------------------------------------- void P144_data_struct::disconnectSerial() { - if (easySerial != nullptr) - { - delete(easySerial); - easySerial = nullptr; - } + delete easySerial; + easySerial = nullptr; } // ---------------------------------------------------------------------------- @@ -84,14 +78,12 @@ bool P144_data_struct::processSensor() { #ifdef PLUGIN_144_DEBUG if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("P144 : New value received "); - log += pm25Value; - addLogMove(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, concat(F("P144 : New value received "), pm25Value)); } #endif } } - return(true); + return true ; } // ---------------------------------------------------------------------------- @@ -164,18 +156,6 @@ bool P144_data_struct::processRx(char c) } #ifdef PLUGIN_144_DEBUG -// ---------------------------------------------------------------------------- -// Function toHex -// Helper to convert uint8/char to HEX representation -// ---------------------------------------------------------------------------- -char * P144_data_struct::toHex(char c, char * ptr) -{ - static const char hex[] = "0123456789ABCDEF"; - *ptr++ = hex[c>>4]; - *ptr++ = hex[c&0x7]; - return ptr; -} - // ---------------------------------------------------------------------------- // Function dump // Dump the received buffer @@ -186,18 +166,13 @@ void P144_data_struct::dump() if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log = F("P144 : Dump message: "); - char *ptr = debugBuffer; - for (int n=0; n< rxlen; n++) + log.reserve(100); + for (int n = 0; n < rxlen; ++n) { - ptr = toHex(serialRxBuffer[n], ptr); - *ptr++ = ' '; + log += formatToHex_no_prefix(serialRxBuffer[n]); + log += ' '; } - *ptr++ = '\0'; - log += debugBuffer; - log += " size "; - log += rxlen; - log += " csum "; - log += (rxChecksum & 0xFF); + log += strformat(F(" size %d csum %d"), rxlen, rxChecksum & 0xFF); addLogMove(LOG_LEVEL_INFO, log); } } diff --git a/src/src/PluginStructs/P144_data_struct.h b/src/src/PluginStructs/P144_data_struct.h index 74dc3fff9..822d08444 100644 --- a/src/src/PluginStructs/P144_data_struct.h +++ b/src/src/PluginStructs/P144_data_struct.h @@ -30,13 +30,9 @@ struct P144_data_struct : public PluginTaskData_base { private: bool processRx(char c); // Handle one received character according to protocol void dump(); // Diagnostics, dump the serialRxBuffer - char* toHex(char c, char * ptr); // Print an int in hexadecimal ESPeasySerial *easySerial = nullptr; // Setial port object char serialRxBuffer[P144_bufferSize] = {0}; // Receive buffer for serial RX characters - #ifdef PLUGIN_144_DEBUG - char debugBuffer[3*P144_bufferSize] = {0}; // Buffer to build debugging string during reception - #endif int rxChecksum = 0; // Build checksum value during message processing int rxIndex = 0; // Index in serialRxBuffer during message processing int rxlen = 0; // Size of the received message as stated in the received message diff --git a/src/src/PluginStructs/P145_data_struct.cpp b/src/src/PluginStructs/P145_data_struct.cpp index 014607248..4085059d7 100644 --- a/src/src/PluginStructs/P145_data_struct.cpp +++ b/src/src/PluginStructs/P145_data_struct.cpp @@ -210,7 +210,7 @@ const P145_SENSORDEF sensorDefs[] PROGMEM } }; /// @brief The number of types stored in the sensorDefs[] table -constexpr const int nbrOfTypes = (int)(sizeof(sensorDefs) / sizeof(struct P145_SENSORDEF)); +constexpr const int nbrOfTypes = NR_ELEMENTS(sensorDefs); // Digital ouput value to swicth heater ON/OFF #define P145_HEATER_OFF LOW @@ -282,6 +282,7 @@ float P145_data_struct::getRZero(float rSensor) const break; case p145AlgNone: newValue = rzero; + break; default: if (sensordef.cleanRatio > 0.0f) { @@ -305,7 +306,7 @@ float P145_data_struct::getRZero(float rSensor) const /*****************************************************************************/ float P145_data_struct::getCorrectedRZero(float rSensor, float temperature, float humidity) const { - float c = getTempHumCorrection(temperature, humidity); + const float c = getTempHumCorrection(temperature, humidity); return getRZero(rSensor/c); } @@ -352,7 +353,7 @@ float P145_data_struct::getPPM(float rSensor) /*****************************************************************************/ float P145_data_struct::getCorrectedPPM(float rSensor, float temperature, float humidity) { - float c = getTempHumCorrection(temperature, humidity); + const float c = getTempHumCorrection(temperature, humidity); return getPPM(rSensor/c); } diff --git a/src/src/PluginStructs/P147_data_struct.cpp b/src/src/PluginStructs/P147_data_struct.cpp index a681b338f..2ac3d39aa 100644 --- a/src/src/PluginStructs/P147_data_struct.cpp +++ b/src/src/PluginStructs/P147_data_struct.cpp @@ -1,442 +1,444 @@ -#include "../PluginStructs/P147_data_struct.h" - -#ifdef USES_P147 - -/************************************************************************** -* Constructor -**************************************************************************/ -P147_data_struct::P147_data_struct(struct EventStruct *event) -{ - _sensorType = static_cast(P147_SENSOR_TYPE); - _initialCounter = P147_LOW_POWER_MEASURE == 0 ? P147_SHORT_COUNTER : P147_LONG_COUNTER; - _secondsCounter = _initialCounter; - _ignoreFirstRead = P147_LOW_POWER_MEASURE == 1; - _useCompensation = P147_GET_USE_COMPENSATION; - # if P147_FEATURE_GASINDEXALGORITHM - _rawOnly = P147_GET_RAW_DATA_ONLY; - # endif // if P147_FEATURE_GASINDEXALGORITHM - - if (validTaskIndex(P147_TEMPERATURE_TASK) && validTaskVarIndex(P147_TEMPERATURE_VALUE)) { - _temperatureValueIndex = P147_TEMPERATURE_TASK * VARS_PER_TASK + P147_TEMPERATURE_VALUE; - } - - if (validTaskIndex(P147_HUMIDITY_TASK) && validTaskVarIndex(P147_HUMIDITY_VALUE)) { - _humidityValueIndex = P147_HUMIDITY_TASK * VARS_PER_TASK + P147_HUMIDITY_VALUE; - } - _initialized = false; -} - -/***************************************************** - * Init sensor and supporting objects - ****************************************************/ -bool P147_data_struct::init(struct EventStruct *event) { - if (I2C_wakeup(P147_I2C_ADDRESS) == 0) { - _initialized = true; - - // TODO Add initialization of IndexAlgorithm objects - # if P147_FEATURE_GASINDEXALGORITHM - vocGasIndexAlgorithm = new VOCGasIndexAlgorithm((P147_LOW_POWER_MEASURE == 0 ? P147_SHORT_COUNTER : P147_LONG_COUNTER) * 1.0f); - - if (nullptr == vocGasIndexAlgorithm) { - _initialized = false; - } - - if (_initialized && (_sensorType == P147_sensor_e::SGP41)) { - noxGasIndexAlgorithm = new NOxGasIndexAlgorithm(); // Algorithm doesn't support a sampling interval - - if (nullptr == noxGasIndexAlgorithm) { - _initialized = false; - } - } - # endif // if P147_FEATURE_GASINDEXALGORITHM - - // Read serial number - if (_initialized && I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_READ_SERIALNR_A, P147_CMD_READ_SERIALNR_B)) { - uint16_t serialNumber[3] = { 0 }; - bool is_ok = true; - delay(1); - - for (uint8_t d = 0; d < 3 && is_ok; d++) { - serialNumber[d] = readCheckedWord(is_ok); - } - - if (is_ok) { - _serial = static_cast(serialNumber[0]); - _serial <<= 16; - _serial |= static_cast(serialNumber[1]); - _serial <<= 16; - _serial |= static_cast(serialNumber[2]); - } else { - _initialized = false; - } - addLog(LOG_LEVEL_INFO, concat(F("SGP4x: Serial number: 0x"), ull2String(_serial, 16u))); - # if P147_FEATURE_GASINDEXALGORITHM - - if (!_rawOnly) { - addLog(LOG_LEVEL_INFO, F("SGP4x: Attention: First values will be available after initial indexing!")); - } - # endif // if P147_FEATURE_GASINDEXALGORITHM - } - - if (_initialized && I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_SELF_TEST_A, P147_CMD_SELF_TEST_B)) { - _state = P147_state_e::MeasureTest; - Scheduler.setPluginTaskTimer(P147_DELAY_SELFTEST, event->TaskIndex, 0); // Retrieve selftest result after 320 msec - } - - // addLog(LOG_LEVEL_INFO, - // concat(F("P147 : INIT State: "), static_cast(_state)) + - // boolToString(_initialized)); - } - return isInitialized(); -} - -/***************************************************** -* Destructor -*****************************************************/ -P147_data_struct::~P147_data_struct() { - # if P147_FEATURE_GASINDEXALGORITHM - delete vocGasIndexAlgorithm; - delete noxGasIndexAlgorithm; - # endif // if P147_FEATURE_GASINDEXALGORITHM -} - -/***************************************************** -* plugin_tasktimer_in : Handle several delay related tasks -*****************************************************/ -bool P147_data_struct::plugin_tasktimer_in(struct EventStruct *event) { - bool success = false; - bool is_ok; - - if (isInitialized()) { - switch (_state) - { - case P147_state_e::MeasureTest: - { - uint16_t result = readCheckedWord(is_ok); - - // addLog(LOG_LEVEL_INFO, concat(F("P147 : Selftest result: "), formatToHex(result)) + (is_ok ? F(" ok") : F(" error"))); - bool checkOk = false; - - if (_sensorType == P147_sensor_e::SGP40) { - checkOk = ((result >> 8) & 0xFF) == 0xD4; // 0xD4xx = OK, 0x4Bxx = Error - } else { - checkOk = (result & 0xFF) == 0x00; // 0xxx00 = OK 01..03 = Error - } - - if (is_ok && checkOk) { - success = true; - _state = P147_state_e::MeasureStart; // Start sequence - } else { - _state = P147_state_e::Uninitialized; - } - _initialized = success; // Failing selftest will disable the plugin - break; - } - - case P147_state_e::MeasureStart: - case P147_state_e::MeasureTrigger: - { - uint16_t compensationT = 0x6666; // Default values - uint16_t compensationRh = 0x8000; - float temperature = 25.0f; // Default values - float humidity = 50.0f; - - // addLog(LOG_LEVEL_INFO, F("P147 : MeasureStart")); - - if (_useCompensation && ((_temperatureValueIndex > -1) || (_humidityValueIndex > -1))) { - if (_temperatureValueIndex > -1) { - temperature = UserVar[_temperatureValueIndex]; - } - - if (_humidityValueIndex > -1) { - humidity = UserVar[_humidityValueIndex]; - } - - // Sanity checks - if (definitelyLessThan(temperature, -45.0f)) { temperature = -45.0f; } - - if (definitelyGreaterThan(temperature, 130.0f)) { temperature = 130.0f; } - - if (definitelyLessThan(humidity, 0.0f)) { humidity = 0.0f; } - - if (definitelyGreaterThan(humidity, 100.0f)) { humidity = 100.0f; } - - // Calculate ticks - compensationT = static_cast((temperature + 45) * 65535 / 175); - compensationRh = static_cast(humidity * 65535 / 100); - } - - if (startSensorRead(compensationRh, compensationT)) { - if ((_readLoop == 0) && (P147_LOW_POWER_MEASURE == 1) && (P147_state_e::MeasureTrigger != _state)) { - _readLoop = 1; // Skip first read after waking up - } - _state = P147_state_e::MeasureReading; // Starting a measurement also turns the heater on, just needs extra time - - Scheduler.setPluginTaskTimer((P147_LOW_POWER_MEASURE == 0 || _readLoop == 0) - ? (_sensorType == P147_sensor_e::SGP40 ? P147_DELAY_REGULAR : P147_DELAY_REGULAR_SGP41) - : P147_DELAY_LOW_POWER, - event->TaskIndex, - 0); - } - break; - } - - case P147_state_e::MeasureReading: // Get raw data - { - _rawVOC = readCheckedWord(is_ok); - - if (is_ok && (_lastCommand == P147_CMD_SGP41_READ_B) && (_sensorType == P147_sensor_e::SGP41)) { - _rawNOx = readCheckedWord(is_ok); - } - - // addLog(LOG_LEVEL_INFO, - // concat(F("P147 : MeasureReading raw VOC: "), _rawVOC) + - // concat(F(", raw NOx: "), _rawNOx) + - // concat(F(", loop: "), _readLoop) + - // (is_ok ? F(" ok") : F(" error"))); - - if (is_ok && (_readLoop == 0)) { - success = true; - _state = P147_state_e::Ready; - - # if P147_FEATURE_GASINDEXALGORITHM - - // Feed to normalizers - _vocIndex = vocGasIndexAlgorithm->process(_rawVOC); - - if (_vocIndex == 0) { - _skipCount++; - } - - if ((_lastCommand == P147_CMD_SGP41_READ_B) && (_sensorType == P147_sensor_e::SGP41)) { - _noxIndex = noxGasIndexAlgorithm->process(_rawNOx); - } - # endif // if P147_FEATURE_GASINDEXALGORITHM - - // Startup delay check for NOx measurement/normalizer - if ((_startupNOxCounter == 0) || (_sensorType == P147_sensor_e::SGP40)) { - _dataAvailable = true; // Data can be read - } - - if (P147_LOW_POWER_MEASURE == 1) { // Turn off heater - I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_HEATER_OFF_A, P147_CMD_HEATER_OFF_B); - } - Scheduler.setPluginTaskTimer(P147_DELAY_MINIMAL, event->TaskIndex, 0); // Next step - } else { - _state = P147_state_e::MeasureStart; // Restart from once_a_second - - if (_readLoop > 0) { - _state = P147_state_e::MeasureTrigger; // Trigger only - Scheduler.setPluginTaskTimer(_sensorType == P147_sensor_e::SGP40 ? P147_DELAY_REGULAR : P147_DELAY_REGULAR_SGP41, - event->TaskIndex, 0); // Trigger actual read after heating up - } - } - - if (_readLoop > 0) { _readLoop--; } - break; - } - - case P147_state_e::Ready: - _state = P147_state_e::MeasureStart; // When ready, start a new sequence from plugin_once_a_second - break; - - case P147_state_e::Uninitialized: // Keep compiler happy - break; - } - } - return success; -} - -/***************************************************** -* plugin_once_a_second -*****************************************************/ -bool P147_data_struct::plugin_once_a_second(struct EventStruct *event) { - bool success = false; - - // addLog(LOG_LEVEL_INFO, - // concat(F("P147 : State: "), static_cast(_state)) + - // concat(F(", Last _rawVOC: "), _rawVOC) + - // concat(F(", Last _rawNOx: "), _rawNOx) + - // concat(F(", count: "), _secondsCounter)); - - if (isInitialized()) { - _secondsCounter--; - - if (_startupNOxCounter > 0) { _startupNOxCounter--; } - - if (_secondsCounter == 0) { - // Execute a measurement cycle - if (_state == P147_state_e::MeasureStart) { - // Trigger a cycle - Scheduler.setPluginTaskTimer(P147_DELAY_MINIMAL, event->TaskIndex, 0); // Next step - success = true; - } - - // Reset counter - _secondsCounter = _initialCounter; - } - } - - return success; -} - -/***************************************************** -* plugin_read -*****************************************************/ -bool P147_data_struct::plugin_read(struct EventStruct *event) { - bool success = false; - - if (isInitialized()) { - if (_dataAvailable) { - if (_ignoreFirstRead) { - _ignoreFirstRead = false; - } else { - # if P147_FEATURE_GASINDEXALGORITHM - - if (_rawOnly) - # endif // if P147_FEATURE_GASINDEXALGORITHM - { - UserVar.setFloat(event->TaskIndex, 0, _rawVOC); - } - # if P147_FEATURE_GASINDEXALGORITHM - else { - UserVar.setFloat(event->TaskIndex, 0, _vocIndex); // Use normalized VOC index - } - # endif // if P147_FEATURE_GASINDEXALGORITHM - - if (_sensorType == P147_sensor_e::SGP41) { - # if P147_FEATURE_GASINDEXALGORITHM - - if (_rawOnly) - # endif // if P147_FEATURE_GASINDEXALGORITHM - { - UserVar.setFloat(event->TaskIndex, 1, _rawNOx); - } - # if P147_FEATURE_GASINDEXALGORITHM - else { - UserVar.setFloat(event->TaskIndex, 1, _noxIndex); // Use normalized NOx index - } - # endif // if P147_FEATURE_GASINDEXALGORITHM - } - # if P147_FEATURE_GASINDEXALGORITHM - success = (_rawOnly || _vocIndex != 0); // Accepted if the VOC index is no longer 0 (NOx index ignored for now) - - if (success && !_rawOnly && (_skipCount > 0)) { - addLog(LOG_LEVEL_INFO, concat(F("SGP4x: Valid values found, skipped samples: "), _skipCount)); - _skipCount = 0; - } - # else // if P147_FEATURE_GASINDEXALGORITHM - success = true; - # endif // if P147_FEATURE_GASINDEXALGORITHM - } - } - } - return success; -} - -/***************************************************** -* plugin_write -*****************************************************/ -bool P147_data_struct::plugin_write(struct EventStruct *event, - String & string) { - bool success = false; - - const String command = parseString(string, 1); - - if (equals(command, F("sgp4x"))) { - // const String sub = parseString(string, 2); - } - return success; -} - -/***************************************************** -* plugin_get_config_value -*****************************************************/ -bool P147_data_struct::plugin_get_config_value(struct EventStruct *event, - String & string) { - bool success = false; - - const String var = parseString(string, 1); - - if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number - string = ull2String(_serial); - success = true; - } else - if (equals(var, F("rawvoc"))) { // [#rawVOC] = the last raw VOC value retrieved from the sensor - string = _rawVOC; - success = true; - } else - if (equals(var, F("rawnox")) && - (_sensorType == P147_sensor_e::SGP41)) { // [#rawNOx] = the last raw NOx value retrieved from the sensor - string = _rawNOx; - success = true; - } - return success; -} - -// Private - -/***************************************************** - * readCheckedWord : Read 2 data bytes from I2C and validate checksum (3rd byte) - ****************************************************/ -uint16_t P147_data_struct::readCheckedWord(bool& is_ok, long extraDelay) { - uint16_t result = 0; - uint8_t data[3] = { 0 }; - uint32_t timeOut = millis(); - - is_ok = false; - Wire.requestFrom(P147_I2C_ADDRESS, 3); - - while (Wire.available() != 3 && timePassedSince(timeOut) < extraDelay) { // Wait extra 5 msec. - delay(1); - } - - if (Wire.available() == 3) { - for (uint8_t d = 0; d < 3; d++) { - data[d] = Wire.read(); - } - - if (calc_CRC8(data, 2) == data[2]) { // valid checksum? - result = (data[0] << 8) | data[1]; - is_ok = true; - } - } - - return result; -} - -bool P147_data_struct::startSensorRead(uint16_t compensationRh, uint16_t compensationT) { - uint8_t data[2] = { 0 }; - - Wire.beginTransmission(P147_I2C_ADDRESS); // Start - - if (_sensorType == P147_sensor_e::SGP40) { - Wire.write((uint8_t)P147_CMD_SGP40_READ_A); // SGP40 Command - Wire.write((uint8_t)P147_CMD_SGP40_READ_B); - _lastCommand = P147_CMD_SGP40_READ_B; // Read only VOC - } else { - if (_startupNOxCounter == 0) { - Wire.write((uint8_t)P147_CMD_SGP41_READ_A); // SGP41 regular read Command - Wire.write((uint8_t)P147_CMD_SGP41_READ_B); - _lastCommand = P147_CMD_SGP41_READ_B; // Read VOC and NOx - } else { - Wire.write((uint8_t)P147_CMD_SGP41_COND_A); // SGP41 NOx Conditioning Command - Wire.write((uint8_t)P147_CMD_SGP41_COND_B); // Only raw VOC is returned - _lastCommand = P147_CMD_SGP41_COND_B; // Conditioning, read only VOC - } - } - data[0] = (compensationRh >> 8); - data[1] = (compensationRh & 0xFF); - Wire.write(data[0]); // Rel. humidity compensation - Wire.write(data[1]); - Wire.write(calc_CRC8(data, 2)); // crc - data[0] = (compensationT >> 8); - data[1] = (compensationT & 0xFF); - Wire.write(data[0]); // Temperature compensation - Wire.write(data[1]); - Wire.write(calc_CRC8(data, 2)); // crc - - return Wire.endTransmission() == 0; -} - -#endif // ifdef USES_P147 +#include "../PluginStructs/P147_data_struct.h" + +#ifdef USES_P147 + +# include "../Helpers/CRC_functions.h" + +/************************************************************************** +* Constructor +**************************************************************************/ +P147_data_struct::P147_data_struct(struct EventStruct *event) +{ + _sensorType = static_cast(P147_SENSOR_TYPE); + _initialCounter = P147_LOW_POWER_MEASURE == 0 ? P147_SHORT_COUNTER : P147_LONG_COUNTER; + _secondsCounter = _initialCounter; + _ignoreFirstRead = P147_LOW_POWER_MEASURE == 1; + _useCompensation = P147_GET_USE_COMPENSATION; + # if P147_FEATURE_GASINDEXALGORITHM + _rawOnly = P147_GET_RAW_DATA_ONLY; + # endif // if P147_FEATURE_GASINDEXALGORITHM + + if (validTaskIndex(P147_TEMPERATURE_TASK) && validTaskVarIndex(P147_TEMPERATURE_VALUE)) { + _temperatureValueIndex = P147_TEMPERATURE_TASK * VARS_PER_TASK + P147_TEMPERATURE_VALUE; + } + + if (validTaskIndex(P147_HUMIDITY_TASK) && validTaskVarIndex(P147_HUMIDITY_VALUE)) { + _humidityValueIndex = P147_HUMIDITY_TASK * VARS_PER_TASK + P147_HUMIDITY_VALUE; + } + _initialized = false; +} + +/***************************************************** + * Init sensor and supporting objects + ****************************************************/ +bool P147_data_struct::init(struct EventStruct *event) { + if (I2C_wakeup(P147_I2C_ADDRESS) == 0) { + _initialized = true; + + // TODO Add initialization of IndexAlgorithm objects + # if P147_FEATURE_GASINDEXALGORITHM + vocGasIndexAlgorithm = new (std::nothrow) VOCGasIndexAlgorithm((P147_LOW_POWER_MEASURE == 0 ? P147_SHORT_COUNTER : P147_LONG_COUNTER) * 1.0f); + + if (nullptr == vocGasIndexAlgorithm) { + _initialized = false; + } + + if (_initialized && (_sensorType == P147_sensor_e::SGP41)) { + noxGasIndexAlgorithm = new (std::nothrow) NOxGasIndexAlgorithm(); // Algorithm doesn't support a sampling interval + + if (nullptr == noxGasIndexAlgorithm) { + _initialized = false; + } + } + # endif // if P147_FEATURE_GASINDEXALGORITHM + + // Read serial number + if (_initialized && I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_READ_SERIALNR_A, P147_CMD_READ_SERIALNR_B)) { + uint16_t serialNumber[3] = { 0 }; + bool is_ok = true; + delay(1); + + for (uint8_t d = 0; d < 3 && is_ok; d++) { + serialNumber[d] = readCheckedWord(is_ok); + } + + if (is_ok) { + _serial = static_cast(serialNumber[0]); + _serial <<= 16; + _serial |= static_cast(serialNumber[1]); + _serial <<= 16; + _serial |= static_cast(serialNumber[2]); + } else { + _initialized = false; + } + addLog(LOG_LEVEL_INFO, concat(F("SGP4x: Serial number: 0x"), ull2String(_serial, 16u))); + # if P147_FEATURE_GASINDEXALGORITHM + + if (!_rawOnly) { + addLog(LOG_LEVEL_INFO, F("SGP4x: Attention: First values will be available after initial indexing!")); + } + # endif // if P147_FEATURE_GASINDEXALGORITHM + } + + if (_initialized && I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_SELF_TEST_A, P147_CMD_SELF_TEST_B)) { + _state = P147_state_e::MeasureTest; + Scheduler.setPluginTaskTimer(P147_DELAY_SELFTEST, event->TaskIndex, 0); // Retrieve selftest result after 320 msec + } + + // addLog(LOG_LEVEL_INFO, + // concat(F("P147 : INIT State: "), static_cast(_state)) + + // boolToString(_initialized)); + } + return isInitialized(); +} + +/***************************************************** +* Destructor +*****************************************************/ +P147_data_struct::~P147_data_struct() { + # if P147_FEATURE_GASINDEXALGORITHM + delete vocGasIndexAlgorithm; + delete noxGasIndexAlgorithm; + # endif // if P147_FEATURE_GASINDEXALGORITHM +} + +/***************************************************** +* plugin_tasktimer_in : Handle several delay related tasks +*****************************************************/ +bool P147_data_struct::plugin_tasktimer_in(struct EventStruct *event) { + bool success = false; + bool is_ok; + + if (isInitialized()) { + switch (_state) + { + case P147_state_e::MeasureTest: + { + const uint16_t result = readCheckedWord(is_ok); + + // addLog(LOG_LEVEL_INFO, concat(F("P147 : Selftest result: "), formatToHex(result)) + (is_ok ? F(" ok") : F(" error"))); + bool checkOk = false; + + if (_sensorType == P147_sensor_e::SGP40) { + checkOk = ((result >> 8) & 0xFF) == 0xD4; // 0xD4xx = OK, 0x4Bxx = Error + } else { + checkOk = (result & 0xFF) == 0x00; // 0xxx00 = OK 01..03 = Error + } + + if (is_ok && checkOk) { + success = true; + _state = P147_state_e::MeasureStart; // Start sequence + } else { + _state = P147_state_e::Uninitialized; + } + _initialized = success; // Failing selftest will disable the plugin + break; + } + + case P147_state_e::MeasureStart: + case P147_state_e::MeasureTrigger: + { + uint16_t compensationT = 0x6666; // Default values + uint16_t compensationRh = 0x8000; + float temperature = 25.0f; // Default values + float humidity = 50.0f; + + // addLog(LOG_LEVEL_INFO, F("P147 : MeasureStart")); + + if (_useCompensation && ((_temperatureValueIndex > -1) || (_humidityValueIndex > -1))) { + if (_temperatureValueIndex > -1) { + temperature = UserVar[_temperatureValueIndex]; + } + + if (_humidityValueIndex > -1) { + humidity = UserVar[_humidityValueIndex]; + } + + // Sanity checks + if (definitelyLessThan(temperature, -45.0f)) { temperature = -45.0f; } + + if (definitelyGreaterThan(temperature, 130.0f)) { temperature = 130.0f; } + + if (definitelyLessThan(humidity, 0.0f)) { humidity = 0.0f; } + + if (definitelyGreaterThan(humidity, 100.0f)) { humidity = 100.0f; } + + // Calculate ticks + compensationT = static_cast((temperature + 45) * 65535 / 175); + compensationRh = static_cast(humidity * 65535 / 100); + } + + if (startSensorRead(compensationRh, compensationT)) { + if ((_readLoop == 0) && (P147_LOW_POWER_MEASURE == 1) && (P147_state_e::MeasureTrigger != _state)) { + _readLoop = 1; // Skip first read after waking up + } + _state = P147_state_e::MeasureReading; // Starting a measurement also turns the heater on, just needs extra time + + Scheduler.setPluginTaskTimer((P147_LOW_POWER_MEASURE == 0 || _readLoop == 0) + ? (_sensorType == P147_sensor_e::SGP40 ? P147_DELAY_REGULAR : P147_DELAY_REGULAR_SGP41) + : P147_DELAY_LOW_POWER, + event->TaskIndex, + 0); + } + break; + } + + case P147_state_e::MeasureReading: // Get raw data + { + _rawVOC = readCheckedWord(is_ok); + + if (is_ok && (_lastCommand == P147_CMD_SGP41_READ_B) && (_sensorType == P147_sensor_e::SGP41)) { + _rawNOx = readCheckedWord(is_ok); + } + + // addLog(LOG_LEVEL_INFO, + // concat(F("P147 : MeasureReading raw VOC: "), _rawVOC) + + // concat(F(", raw NOx: "), _rawNOx) + + // concat(F(", loop: "), _readLoop) + + // (is_ok ? F(" ok") : F(" error"))); + + if (is_ok && (_readLoop == 0)) { + success = true; + _state = P147_state_e::Ready; + + # if P147_FEATURE_GASINDEXALGORITHM + + // Feed to normalizers + _vocIndex = vocGasIndexAlgorithm->process(_rawVOC); + + if (_vocIndex == 0) { + _skipCount++; + } + + if ((_lastCommand == P147_CMD_SGP41_READ_B) && (_sensorType == P147_sensor_e::SGP41)) { + _noxIndex = noxGasIndexAlgorithm->process(_rawNOx); + } + # endif // if P147_FEATURE_GASINDEXALGORITHM + + // Startup delay check for NOx measurement/normalizer + if ((_startupNOxCounter == 0) || (_sensorType == P147_sensor_e::SGP40)) { + _dataAvailable = true; // Data can be read + } + + if (P147_LOW_POWER_MEASURE == 1) { // Turn off heater + I2C_write8_reg(P147_I2C_ADDRESS, P147_CMD_HEATER_OFF_A, P147_CMD_HEATER_OFF_B); + } + Scheduler.setPluginTaskTimer(P147_DELAY_MINIMAL, event->TaskIndex, 0); // Next step + } else { + _state = P147_state_e::MeasureStart; // Restart from once_a_second + + if (_readLoop > 0) { + _state = P147_state_e::MeasureTrigger; // Trigger only + Scheduler.setPluginTaskTimer(_sensorType == P147_sensor_e::SGP40 ? P147_DELAY_REGULAR : P147_DELAY_REGULAR_SGP41, + event->TaskIndex, 0); // Trigger actual read after heating up + } + } + + if (_readLoop > 0) { _readLoop--; } + break; + } + + case P147_state_e::Ready: + _state = P147_state_e::MeasureStart; // When ready, start a new sequence from plugin_once_a_second + break; + + case P147_state_e::Uninitialized: // Keep compiler happy + break; + } + } + return success; +} + +/***************************************************** +* plugin_once_a_second +*****************************************************/ +bool P147_data_struct::plugin_once_a_second(struct EventStruct *event) { + bool success = false; + + // addLog(LOG_LEVEL_INFO, + // concat(F("P147 : State: "), static_cast(_state)) + + // concat(F(", Last _rawVOC: "), _rawVOC) + + // concat(F(", Last _rawNOx: "), _rawNOx) + + // concat(F(", count: "), _secondsCounter)); + + if (isInitialized()) { + _secondsCounter--; + + if (_startupNOxCounter > 0) { _startupNOxCounter--; } + + if (_secondsCounter == 0) { + // Execute a measurement cycle + if (_state == P147_state_e::MeasureStart) { + // Trigger a cycle + Scheduler.setPluginTaskTimer(P147_DELAY_MINIMAL, event->TaskIndex, 0); // Next step + success = true; + } + + // Reset counter + _secondsCounter = _initialCounter; + } + } + + return success; +} + +/***************************************************** +* plugin_read +*****************************************************/ +bool P147_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isInitialized()) { + if (_dataAvailable) { + if (_ignoreFirstRead) { + _ignoreFirstRead = false; + } else { + # if P147_FEATURE_GASINDEXALGORITHM + + if (_rawOnly) + # endif // if P147_FEATURE_GASINDEXALGORITHM + { + UserVar.setFloat(event->TaskIndex, 0, _rawVOC); + } + # if P147_FEATURE_GASINDEXALGORITHM + else { + UserVar.setFloat(event->TaskIndex, 0, _vocIndex); // Use normalized VOC index + } + # endif // if P147_FEATURE_GASINDEXALGORITHM + + if (_sensorType == P147_sensor_e::SGP41) { + # if P147_FEATURE_GASINDEXALGORITHM + + if (_rawOnly) + # endif // if P147_FEATURE_GASINDEXALGORITHM + { + UserVar.setFloat(event->TaskIndex, 1, _rawNOx); + } + # if P147_FEATURE_GASINDEXALGORITHM + else { + UserVar.setFloat(event->TaskIndex, 1, _noxIndex); // Use normalized NOx index + } + # endif // if P147_FEATURE_GASINDEXALGORITHM + } + # if P147_FEATURE_GASINDEXALGORITHM + success = (_rawOnly || _vocIndex != 0); // Accepted if the VOC index is no longer 0 (NOx index ignored for now) + + if (success && !_rawOnly && (_skipCount > 0)) { + addLog(LOG_LEVEL_INFO, concat(F("SGP4x: Valid values found, skipped samples: "), _skipCount)); + _skipCount = 0; + } + # else // if P147_FEATURE_GASINDEXALGORITHM + success = true; + # endif // if P147_FEATURE_GASINDEXALGORITHM + } + } + } + return success; +} + +/***************************************************** +* plugin_write +*****************************************************/ +bool P147_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + + // const String command = parseString(string, 1); + + // if (equals(command, F("sgp4x"))) { + // // const String sub = parseString(string, 2); + // } + return success; +} + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P147_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + + const String var = parseString(string, 1); + + if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number + string = ull2String(_serial); + success = true; + } else + if (equals(var, F("rawvoc"))) { // [#rawVOC] = the last raw VOC value retrieved from the sensor + string = _rawVOC; + success = true; + } else + if (equals(var, F("rawnox")) && + (_sensorType == P147_sensor_e::SGP41)) { // [#rawNOx] = the last raw NOx value retrieved from the sensor + string = _rawNOx; + success = true; + } + return success; +} + +// Private + +/***************************************************** + * readCheckedWord : Read 2 data bytes from I2C and validate checksum (3rd byte) + ****************************************************/ +uint16_t P147_data_struct::readCheckedWord(bool& is_ok, long extraDelay) { + uint16_t result = 0; + uint8_t data[3] = { 0 }; + const uint32_t timeOut = millis(); + + is_ok = false; + Wire.requestFrom(P147_I2C_ADDRESS, 3); + + while (Wire.available() != 3 && timePassedSince(timeOut) < extraDelay) { // Wait extra 5 msec. + delay(1); + } + + if (Wire.available() == 3) { + for (uint8_t d = 0; d < 3; d++) { + data[d] = Wire.read(); + } + + if (calc_CRC8(data, 2) == data[2]) { // valid checksum? + result = (data[0] << 8) | data[1]; + is_ok = true; + } + } + + return result; +} + +bool P147_data_struct::startSensorRead(uint16_t compensationRh, uint16_t compensationT) { + uint8_t data[2] = { 0 }; + + Wire.beginTransmission(P147_I2C_ADDRESS); // Start + + if (_sensorType == P147_sensor_e::SGP40) { + Wire.write((uint8_t)P147_CMD_SGP40_READ_A); // SGP40 Command + Wire.write((uint8_t)P147_CMD_SGP40_READ_B); + _lastCommand = P147_CMD_SGP40_READ_B; // Read only VOC + } else { + if (_startupNOxCounter == 0) { + Wire.write((uint8_t)P147_CMD_SGP41_READ_A); // SGP41 regular read Command + Wire.write((uint8_t)P147_CMD_SGP41_READ_B); + _lastCommand = P147_CMD_SGP41_READ_B; // Read VOC and NOx + } else { + Wire.write((uint8_t)P147_CMD_SGP41_COND_A); // SGP41 NOx Conditioning Command + Wire.write((uint8_t)P147_CMD_SGP41_COND_B); // Only raw VOC is returned + _lastCommand = P147_CMD_SGP41_COND_B; // Conditioning, read only VOC + } + } + data[0] = (compensationRh >> 8); + data[1] = (compensationRh & 0xFF); + Wire.write(data[0]); // Rel. humidity compensation + Wire.write(data[1]); + Wire.write(calc_CRC8(data, 2)); // crc + data[0] = (compensationT >> 8); + data[1] = (compensationT & 0xFF); + Wire.write(data[0]); // Temperature compensation + Wire.write(data[1]); + Wire.write(calc_CRC8(data, 2)); // crc + + return Wire.endTransmission() == 0; +} + +#endif // ifdef USES_P147 diff --git a/src/src/PluginStructs/P147_data_struct.h b/src/src/PluginStructs/P147_data_struct.h index 8f14e72dd..313a7933c 100644 --- a/src/src/PluginStructs/P147_data_struct.h +++ b/src/src/PluginStructs/P147_data_struct.h @@ -15,8 +15,6 @@ # include # endif // if P147_FEATURE_GASINDEXALGORITHM -# include "../Helpers/CRC_functions.h" - # define P147_SENSOR_TYPE PCONFIG(0) # define P147_LOW_POWER_MEASURE PCONFIG(1) diff --git a/src/src/PluginStructs/P148_data_struct.cpp b/src/src/PluginStructs/P148_data_struct.cpp index 35d75cb53..9faa757a2 100644 --- a/src/src/PluginStructs/P148_data_struct.cpp +++ b/src/src/PluginStructs/P148_data_struct.cpp @@ -88,6 +88,10 @@ uint8_t P148_data_struct::TM1621GetFontCharacter(char character, bool firstrow) return 0u; } + +// FIXME TD-er: When used on ESP8266, this conversion union may not work +// However this is probably only used on ESP32 Sonoff units with display. + // Do not change the order of these as it is stored. union MonitorTaskValue_conversion { struct { diff --git a/src/src/PluginStructs/P150_data_struct.cpp b/src/src/PluginStructs/P150_data_struct.cpp index f5031c0a0..97fd42d9f 100644 --- a/src/src/PluginStructs/P150_data_struct.cpp +++ b/src/src/PluginStructs/P150_data_struct.cpp @@ -30,7 +30,9 @@ bool P150_data_struct::init() { // make sure the device ID reported by the TMP is correct // should always be 0x0117 if (deviceID != TMP117_DEVICE_ID_VALUE) { + # ifndef BUILD_NO_DEBUG addLog(LOG_LEVEL_ERROR, concat(F("TMP117: Device ID mismatch: "), formatToHex(deviceID, 4))); + # endif // ifndef BUILD_NO_DEBUG return false; } @@ -63,8 +65,7 @@ bool P150_data_struct::plugin_read(struct EventStruct *event) { UserVar.setFloat(event->TaskIndex, 1, _digitalTempC); if (_logEnabled && loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("TMP117: Temperature: "), formatUserVarNoCheck(event, 0)); - log += 'C'; + String log = strformat(F("TMP117: Temperature: %sC"), formatUserVarNoCheck(event, 0).c_str()); if (_rawEnabled) { log += concat(F(", Raw: "), formatUserVarNoCheck(event, 1)); @@ -92,9 +93,9 @@ bool P150_data_struct::plugin_once_a_second(struct EventStruct *event) { # if P150_USE_EXTRA_LOG if (_extraLog && loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = concat(F("TMP117: Read temp: "), toString(_finalTempC, 2)); - log += 'C'; - log += concat(F(", raw: "), static_cast(_digitalTempC)); + String log = strformat(F("TMP117: Read temp: %.2fC, raw: %d"), + _finalTempC, + _digitalTempC); if (P150_GET_CONF_CONVERSION_MODE == P150_CONVERSION_ONE_SHOT) { log += F(" (one-shot)"); @@ -119,7 +120,7 @@ float P150_data_struct::readTemp() { } void P150_data_struct::setTemperatureOffset(float offset) { - int16_t resolutionOffset = offset / TMP117_RESOLUTION; // Divide by resolution to send to the sensor + const int16_t resolutionOffset = offset / TMP117_RESOLUTION; // Divide by resolution to send to the sensor I2C_write16_reg(_deviceAddress, TMP117_TEMP_OFFSET, resolutionOffset); // Write to the offset temperature register } @@ -129,7 +130,7 @@ void P150_data_struct::setTemperatureOffset(float offset) { * Check if sensor os ready processing the measurement(s) */ bool P150_data_struct::dataReady() { - uint16_t response = I2C_read16_reg(_deviceAddress, TMP117_CONFIGURATION); + const uint16_t response = I2C_read16_reg(_deviceAddress, TMP117_CONFIGURATION); // If statement to see if the 13th bit of the register is 1 or not return bitRead(response, 13); diff --git a/src/src/PluginStructs/P150_data_struct.h b/src/src/PluginStructs/P150_data_struct.h index 2061126e9..828fdc17c 100644 --- a/src/src/PluginStructs/P150_data_struct.h +++ b/src/src/PluginStructs/P150_data_struct.h @@ -9,7 +9,11 @@ # define P150_CONFIG PCONFIG_ULONG(0) // Sensor config # define P150_OPTIONS PCONFIG_ULONG(1) // Plugin options -# define P150_USE_EXTRA_LOG 1 // Enable(1)/disable(0) low-level logging +# ifdef LIMIT_BUILD_SIZE +# define P150_USE_EXTRA_LOG 0 // Enable(1)/disable(0) low-level logging +# else // ifdef LIMIT_BUILD_SIZE +# define P150_USE_EXTRA_LOG 1 // Enable(1)/disable(0) low-level logging +# endif // ifdef LIMIT_BUILD_SIZE # define P150_CONFIG_RESET_MASK 0b1111000000011111 // Reset the bits we need to overwrite diff --git a/src/src/PluginStructs/P151_data_struct.cpp b/src/src/PluginStructs/P151_data_struct.cpp index a990d1022..20376b09f 100644 --- a/src/src/PluginStructs/P151_data_struct.cpp +++ b/src/src/PluginStructs/P151_data_struct.cpp @@ -6,6 +6,8 @@ P151_data_struct::~P151_data_struct() {} bool P151_data_struct::plugin_read(struct EventStruct *event) { + // FIXME TD-er: When used on ESP8266, this conversion union may not work + // It might work as it is 32-bit in size. union Honeywell_struct { struct { uint32_t dummy : 5; diff --git a/src/src/PluginStructs/P153_data_struct.cpp b/src/src/PluginStructs/P153_data_struct.cpp index d9487081c..25418ddda 100644 --- a/src/src/PluginStructs/P153_data_struct.cpp +++ b/src/src/PluginStructs/P153_data_struct.cpp @@ -1,281 +1,249 @@ -#include "../PluginStructs/P153_data_struct.h" - -#ifdef USES_P153 - -/************************************************************************** -* Constructor -**************************************************************************/ -P153_data_struct::P153_data_struct(uint8_t address, - float tempOffset, - P153_configuration_e startupConfiguration, - P153_configuration_e normalConfiguration, - uint16_t intervalLoops) : - _address(address), _tempOffset(tempOffset), _startupConfiguration(startupConfiguration), - _normalConfiguration(normalConfiguration), _intervalLoops(intervalLoops), initialized(false) -{} - -bool P153_data_struct::init() { - // - Read sensor serial number - if (I2C_wakeup(_address) == 0) { - if (I2C_write8(_address, P153_SHT4X_RESET)) { - delay(1); - - uint8_t data[6]{}; - - if (I2C_write8(_address, P153_SHT4X_READ_SERIAL)) { - delay(10); - - Wire.requestFrom(_address, (uint8_t)6); - - for (uint8_t d = 0; d < 6; d++) { - data[d] = Wire.read(); - } - - if (CRC8(data[0], data[1], data[2]) && CRC8(data[3], data[4], data[5])) { - serialNumber = data[0]; - serialNumber <<= 8; - serialNumber |= data[1]; - serialNumber <<= 8; - serialNumber |= data[3]; - serialNumber <<= 8; - serialNumber |= data[4]; - addLog(LOG_LEVEL_INFO, concat(F("SHT4x: Serial number: "), formatToHex_decimal(serialNumber))); - initialized = true; - } else { - // crc error - addLog(LOG_LEVEL_ERROR, F("SHT4x: Error reading serial number.")); - } - } - } - } - - return isInitialized(); -} - -/***************************************************** -* plugin_read -*****************************************************/ -bool P153_data_struct::plugin_read(struct EventStruct *event) { - bool success = false; - - if (isInitialized()) { - int16_t timeDelay = -1; - - // read in stages - if (P153_read_mode_e::Idle == readMode) { - // Determine delay per command - - switch ((_intervalLoops > 0) ? _startupConfiguration : _normalConfiguration) { - case P153_configuration_e::LowResolution: - timeDelay = P153_DELAY_LOW_RESOLUTION; - break; - case P153_configuration_e::MediumResolution: - timeDelay = P153_DELAY_MEDIUM_RESOLUTION; - break; - case P153_configuration_e::HighResolution: - timeDelay = P153_DELAY_HIGH_RESOLUTION; - break; - case P153_configuration_e::HighResolution200mW100msec: - case P153_configuration_e::HighResolution110mW100msec: - case P153_configuration_e::HighResolution20mW100msec: - timeDelay = P153_DELAY_100MS_HEATER; - break; - case P153_configuration_e::HighResolution200mW1000msec: - case P153_configuration_e::HighResolution110mW1000msec: - case P153_configuration_e::HighResolution20mW1000msec: - timeDelay = P153_DELAY_1S_HEATER; - break; - } - - // Start measurement - if (!I2C_write8(_address, static_cast((_intervalLoops > 0) ? _startupConfiguration : _normalConfiguration))) { - timeDelay = -1; // Don't continue if writing command fails - - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - - if (log.reserve(40)) { - log = getTaskDeviceName(event->TaskIndex); - log += F(": Error writing command to sensor"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - } - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, concat(F("P153 : READ delay: "), timeDelay)); - # endif // ifndef BUILD_NO_DEBUG - - measurementStart = millis(); - - if (timeDelay > P153_DELAY_HIGH_RESOLUTION) { // Short delays are handled locally, longer uses the task device timer - Scheduler.schedule_task_device_timer(event->TaskIndex, measurementStart + timeDelay); - } - } - - if ((P153_read_mode_e::Reading == readMode) || ((timeDelay >= 0) && (timeDelay <= P153_DELAY_HIGH_RESOLUTION))) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P153 : READ execute Readmode = Reading")); - # endif // ifndef BUILD_NO_DEBUG - - // Handle short delays immediately - if ((timeDelay > 0) && (timeDelay <= P153_DELAY_HIGH_RESOLUTION)) { - delay(timeDelay); - } - - // Read the sensor data - uint8_t data[6]{}; - - Wire.requestFrom(_address, (uint8_t)6); - - if (Wire.available() == 6) { - for (uint8_t d = 0; d < 6; d++) { - data[d] = Wire.read(); - } - } else { - UserVar.setFloat(event->TaskIndex, 0, NAN); // Read error or I/O error - UserVar.setFloat(event->TaskIndex, 1, NAN); - - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log; - - if (log.reserve(40)) { - log = getTaskDeviceName(event->TaskIndex); - log += F(": Error reading sensor"); - addLogMove(LOG_LEVEL_ERROR, log); - } - } - } - - // Data valid? - if (CRC8(data[0], data[1], data[2]) && CRC8(data[3], data[4], data[5])) { - float temp = static_cast(((uint16_t)data[0] << 8) | (uint16_t)data[1]); - float hum = static_cast(((uint16_t)data[3] << 8) | (uint16_t)data[4]); - temperature = -45.0f + 175.0f * temp / 65535.0f; - humidity = -6.0f + 125.0f * hum / 65535.0f; - - if (definitelyLessThan(humidity, 0.0f)) { humidity = 0.0f; } - - if (definitelyGreaterThan(humidity, 100.0f)) { humidity = 100.0f; } - - UserVar.setFloat(event->TaskIndex, 0, temperature + _tempOffset); // Apply offset - UserVar.setFloat(event->TaskIndex, 1, humidity); - - success = true; - errorCount = 0; - - if (_intervalLoops > 0) { - _intervalLoops--; - - if ((_intervalLoops == 0) && (_startupConfiguration != _normalConfiguration)) { - addLog(LOG_LEVEL_INFO, F("SHT4x: Switching from Startup to Normal Configuration.")); - } - } - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log; - - if (log.reserve(40)) { - log = getTaskDeviceName(event->TaskIndex); - log += F(": Temperature: "); - log += formatUserVarNoCheck(event->TaskIndex, 0); - addLogMove(LOG_LEVEL_INFO, log); - - log = getTaskDeviceName(event->TaskIndex); - log += F(": Humidity: "); - log += formatUserVarNoCheck(event->TaskIndex, 1); - addLogMove(LOG_LEVEL_INFO, log); - } - } - } else { - UserVar.setFloat(event->TaskIndex, 0, NAN); - UserVar.setFloat(event->TaskIndex, 1, NAN); - addLog(LOG_LEVEL_ERROR, concat(F("SHT4x: READ CRC Error, data: 0x"), formatToHex_array(data, 6))); - errorCount++; - - if (errorCount > P153_MAX_ERRORCOUNT) { - I2C_write8(_address, P153_SHT4X_RESET); - delay(1); - _intervalLoops = 0; - addLog(LOG_LEVEL_ERROR, F("SHT4x: READ Error count reached, reset to Normal Configuration.")); - } - } - - Scheduler.reschedule_task_device_timer(event->TaskIndex, measurementStart); // Sync with schedule - readMode = P153_read_mode_e::Idle; - timeDelay = -1; // Reset - } - - if ((P153_read_mode_e::Idle == readMode) && (timeDelay >= 0)) { - # ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG, F("P153 : READ set Readmode = Reading")); - # endif // ifndef BUILD_NO_DEBUG - readMode = P153_read_mode_e::Reading; - } - } - - return success; -} - -/***************************************************** -* plugin_write -*****************************************************/ -bool P153_data_struct::plugin_write(struct EventStruct *event, - String & string) { - bool success = false; - - const String command = parseString(string, 1); - - if (equals(command, F("sht4x"))) { - const String subCommand = parseString(string, 2); - - if (equals(subCommand, F("startup")) && (_startupConfiguration != _normalConfiguration) && (P153_INTERVAL_LOOPS > 0)) { - _intervalLoops = P153_INTERVAL_LOOPS; - success = true; - } - } - return success; -} - -/***************************************************** -* plugin_get_config_value -*****************************************************/ -bool P153_data_struct::plugin_get_config_value(struct EventStruct *event, - String & string) { - bool success = false; - - const String var = parseString(string, 1); - - if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number - string = String(serialNumber); - success = true; - } - return success; -} - -bool P153_data_struct::CRC8(uint8_t MSB, uint8_t LSB, uint8_t CRC) -{ - /* - * Name : CRC-8 - * Polynomial : 0x31 (x8 + x5 + x4 + 1) - * Initialization : 0xFF - * Reflect input : False - * Reflect output : False - * Final : XOR 0x00 - * Example : CRC8( 0xBE, 0xEF, 0x92) should be true - */ - uint8_t crc = 0xFF; - - for (uint8_t bytenr = 0; bytenr < 2; ++bytenr) { - crc ^= (bytenr == 0) ? MSB : LSB; - - for (uint8_t i = 0; i < 8; ++i) { - crc = crc & 0x80 ? (crc << 1) ^ 0x31 : crc << 1; - } - } - return crc == CRC; -} - -#endif // ifdef USES_P153 +#include "../PluginStructs/P153_data_struct.h" + +#ifdef USES_P153 + +# include "../Helpers/CRC_functions.h" + +/************************************************************************** +* Constructor +**************************************************************************/ +P153_data_struct::P153_data_struct(uint8_t address, + float tempOffset, + P153_configuration_e startupConfiguration, + P153_configuration_e normalConfiguration, + uint16_t intervalLoops) : + _address(address), _tempOffset(tempOffset), _startupConfiguration(startupConfiguration), + _normalConfiguration(normalConfiguration), _intervalLoops(intervalLoops), initialized(false) +{} + +bool P153_data_struct::init() { + // - Read sensor serial number + if (I2C_wakeup(_address) == 0) { + if (I2C_write8(_address, P153_SHT4X_RESET)) { + delay(1); + + uint8_t data[6]{}; + + if (I2C_write8(_address, P153_SHT4X_READ_SERIAL)) { + delay(10); + + Wire.requestFrom(_address, (uint8_t)6); + + for (uint8_t d = 0; d < 6; d++) { + data[d] = Wire.read(); + } + + if (calc_CRC8(data[0], data[1], data[2]) && calc_CRC8(data[3], data[4], data[5])) { + serialNumber = data[0]; + serialNumber <<= 8; + serialNumber |= data[1]; + serialNumber <<= 8; + serialNumber |= data[3]; + serialNumber <<= 8; + serialNumber |= data[4]; + addLog(LOG_LEVEL_INFO, concat(F("SHT4x: Serial number: "), formatToHex_decimal(serialNumber))); + initialized = true; + } else { + // crc error + addLog(LOG_LEVEL_ERROR, F("SHT4x: Error reading serial number.")); + } + } + } + } + + return isInitialized(); +} + +/***************************************************** +* plugin_read +*****************************************************/ +bool P153_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isInitialized()) { + int16_t timeDelay = -1; + + // read in stages + if (P153_read_mode_e::Idle == readMode) { + // Determine delay per command + + switch ((_intervalLoops > 0) ? _startupConfiguration : _normalConfiguration) { + case P153_configuration_e::LowResolution: + timeDelay = P153_DELAY_LOW_RESOLUTION; + break; + case P153_configuration_e::MediumResolution: + timeDelay = P153_DELAY_MEDIUM_RESOLUTION; + break; + case P153_configuration_e::HighResolution: + timeDelay = P153_DELAY_HIGH_RESOLUTION; + break; + case P153_configuration_e::HighResolution200mW100msec: + case P153_configuration_e::HighResolution110mW100msec: + case P153_configuration_e::HighResolution20mW100msec: + timeDelay = P153_DELAY_100MS_HEATER; + break; + case P153_configuration_e::HighResolution200mW1000msec: + case P153_configuration_e::HighResolution110mW1000msec: + case P153_configuration_e::HighResolution20mW1000msec: + timeDelay = P153_DELAY_1S_HEATER; + break; + } + + // Start measurement + if (!I2C_write8(_address, static_cast((_intervalLoops > 0) ? _startupConfiguration : _normalConfiguration))) { + timeDelay = -1; // Don't continue if writing command fails + + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLogMove(LOG_LEVEL_ERROR, strformat(F("%s: Error writing command to sensor"), getTaskDeviceName(event->TaskIndex).c_str())); + } + } + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, concat(F("P153 : READ delay: "), timeDelay)); + # endif // ifndef BUILD_NO_DEBUG + + measurementStart = millis(); + + if (timeDelay > P153_DELAY_HIGH_RESOLUTION) { // Short delays are handled locally, longer uses the task device timer + Scheduler.schedule_task_device_timer(event->TaskIndex, measurementStart + timeDelay); + } + } + + if ((P153_read_mode_e::Reading == readMode) || ((timeDelay >= 0) && (timeDelay <= P153_DELAY_HIGH_RESOLUTION))) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P153 : READ execute Readmode = Reading")); + # endif // ifndef BUILD_NO_DEBUG + + // Handle short delays immediately + if ((timeDelay > 0) && (timeDelay <= P153_DELAY_HIGH_RESOLUTION)) { + delay(timeDelay); + } + + // Read the sensor data + uint8_t data[6]{}; + + Wire.requestFrom(_address, (uint8_t)6); + + if (Wire.available() == 6) { + for (uint8_t d = 0; d < 6; d++) { + data[d] = Wire.read(); + } + } else { + UserVar.setFloat(event->TaskIndex, 0, NAN); // Read error or I/O error + UserVar.setFloat(event->TaskIndex, 1, NAN); + + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log; + + if (log.reserve(40)) { + log = getTaskDeviceName(event->TaskIndex); + log += F(": Error reading sensor"); + addLogMove(LOG_LEVEL_ERROR, log); + } + } + } + + // Data valid? + if (calc_CRC8(data[0], data[1], data[2]) && calc_CRC8(data[3], data[4], data[5])) { + const float temp = static_cast(((uint16_t)data[0] << 8) | (uint16_t)data[1]); + const float hum = static_cast(((uint16_t)data[3] << 8) | (uint16_t)data[4]); + temperature = -45.0f + 175.0f * temp / 65535.0f; + humidity = -6.0f + 125.0f * hum / 65535.0f; + + if (definitelyLessThan(humidity, 0.0f)) { humidity = 0.0f; } + + if (definitelyGreaterThan(humidity, 100.0f)) { humidity = 100.0f; } + + UserVar.setFloat(event->TaskIndex, 0, temperature + _tempOffset); // Apply offset + UserVar.setFloat(event->TaskIndex, 1, humidity); + + success = true; + errorCount = 0; + + if (_intervalLoops > 0) { + _intervalLoops--; + + if ((_intervalLoops == 0) && (_startupConfiguration != _normalConfiguration)) { + addLog(LOG_LEVEL_INFO, F("SHT4x: Switching from Startup to Normal Configuration.")); + } + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const String taskName = getTaskDeviceName(event->TaskIndex); + addLogMove(LOG_LEVEL_INFO, strformat(F("%s: Temperature: %s"), + taskName.c_str(), + formatUserVarNoCheck(event, 0).c_str())); + + addLogMove(LOG_LEVEL_INFO, strformat(F("%s: Humidity: %s"), + taskName.c_str(), + formatUserVarNoCheck(event, 1).c_str())); + } + } else { + UserVar.setFloat(event->TaskIndex, 0, NAN); + UserVar.setFloat(event->TaskIndex, 1, NAN); + addLog(LOG_LEVEL_ERROR, concat(F("SHT4x: READ CRC Error, data: 0x"), formatToHex_array(data, 6))); + errorCount++; + + if (errorCount > P153_MAX_ERRORCOUNT) { + I2C_write8(_address, P153_SHT4X_RESET); + delay(1); + _intervalLoops = 0; + addLog(LOG_LEVEL_ERROR, F("SHT4x: READ Error count reached, reset to Normal Configuration.")); + } + } + + Scheduler.reschedule_task_device_timer(event->TaskIndex, measurementStart); // Sync with schedule + readMode = P153_read_mode_e::Idle; + timeDelay = -1; // Reset + } + + if ((P153_read_mode_e::Idle == readMode) && (timeDelay >= 0)) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG, F("P153 : READ set Readmode = Reading")); + # endif // ifndef BUILD_NO_DEBUG + readMode = P153_read_mode_e::Reading; + } + } + + return success; +} + +/***************************************************** +* plugin_write +*****************************************************/ +bool P153_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + + const String command = parseString(string, 1); + + if (equals(command, F("sht4x"))) { + const String subCommand = parseString(string, 2); + + if (equals(subCommand, F("startup")) && (_startupConfiguration != _normalConfiguration) && (P153_INTERVAL_LOOPS > 0)) { + _intervalLoops = P153_INTERVAL_LOOPS; + success = true; + } + } + return success; +} + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P153_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + + const String var = parseString(string, 1); + + if (equals(var, F("serialnumber"))) { // [#serialnumber] = the devices electronic serial number + string = serialNumber; + success = true; + } + return success; +} + +#endif // ifdef USES_P153 diff --git a/src/src/PluginStructs/P153_data_struct.h b/src/src/PluginStructs/P153_data_struct.h index 97b7a8025..6fb1e5b4d 100644 --- a/src/src/PluginStructs/P153_data_struct.h +++ b/src/src/PluginStructs/P153_data_struct.h @@ -64,10 +64,6 @@ public: private: - bool CRC8(uint8_t MSB, - uint8_t LSB, - uint8_t CRC); - uint8_t _address; float _tempOffset; P153_configuration_e _startupConfiguration; diff --git a/src/src/PluginStructs/P154_data_struct.cpp b/src/src/PluginStructs/P154_data_struct.cpp index 3a6b35617..589dcc718 100644 --- a/src/src/PluginStructs/P154_data_struct.cpp +++ b/src/src/PluginStructs/P154_data_struct.cpp @@ -1,6 +1,6 @@ #include "../PluginStructs/P154_data_struct.h" -#ifdef USES_P154 +#if defined(USES_P154) || defined(USES_P172) # define P154_BMP3_CHIP_ID 0x50 # define P154_BMP390_CHIP_ID 0x60 @@ -8,12 +8,19 @@ P154_data_struct::P154_data_struct(struct EventStruct *event) : i2cAddress(P154_I2C_ADDR), - elevation(P154_ALTITUDE) + elevation(P154_ALTITUDE), + csPin(PIN(0)) {} -bool P154_data_struct::begin() +bool P154_data_struct::begin(bool _i2cMode) { - if (!bmp.begin_I2C(i2cAddress)) { + i2cMode = _i2cMode; + + if (i2cMode && !bmp.begin_I2C(i2cAddress)) { + return false; + } + + if (!i2cMode && !bmp.begin_SPI(csPin)) { return false; } @@ -48,17 +55,40 @@ bool P154_data_struct::read(float& temp, float& pressure) return true; } -bool P154_data_struct::webformLoad(struct EventStruct *event) -{ - addRowLabel(F("Detected Sensor Type")); - const uint32_t chipID = I2C_read8_reg(P154_I2C_ADDR, 0); +uint32_t P154_data_struct::chipID() { + return bmp.chipID(); +} - if (chipID == P154_BMP3_CHIP_ID) { - addHtml(F("BMP38x")); - } else if (chipID == P154_BMP390_CHIP_ID) { - addHtml(F("BMP390")); +bool P154_data_struct::webformLoad(struct EventStruct *event, + bool _i2cMode) +{ + uint32_t chipID{}; + bool chipIDvalid = _i2cMode; + + if (_i2cMode) { + chipID = I2C_read8_reg(P154_I2C_ADDR, 0); + # ifdef USES_P172 } else { - addHtmlInt(chipID); + P154_data_struct *P154_P172_data = + static_cast(getPluginTaskData(event->TaskIndex)); + + if (nullptr != P154_P172_data) { + chipID = P154_P172_data->chipID(); + chipIDvalid = true; + } + # endif // ifdef USES_P172 + } + + if (chipIDvalid) { + addRowLabel(F("Detected Sensor Type")); + + if (chipID == P154_BMP3_CHIP_ID) { + addHtml(F("BMP38x")); + } else if (chipID == P154_BMP390_CHIP_ID) { + addHtml(F("BMP390")); + } else { + addHtmlInt(chipID); + } } addFormNumericBox(F("Altitude"), F("elev"), P154_ALTITUDE); @@ -73,4 +103,4 @@ bool P154_data_struct::webformSave(struct EventStruct *event) return true; } -#endif // ifdef USES_P154 +#endif // if defined(USES_P154) || defined(USES_P172) diff --git a/src/src/PluginStructs/P154_data_struct.h b/src/src/PluginStructs/P154_data_struct.h index 55e7496fa..4c507ff01 100644 --- a/src/src/PluginStructs/P154_data_struct.h +++ b/src/src/PluginStructs/P154_data_struct.h @@ -2,7 +2,7 @@ #define PLUGINSTRUCTS_P154_DATA_STRUCT_H #include "../../_Plugin_Helper.h" -#ifdef USES_P154 +#if defined(USES_P154) || defined(USES_P172) # include # include @@ -19,12 +19,14 @@ public: P154_data_struct() = delete; virtual ~P154_data_struct() = default; - bool begin(); + bool begin(bool _i2cMode = true); bool read(float& temp, float& pressure); + uint32_t chipID(); - static bool webformLoad(struct EventStruct *event); + static bool webformLoad(struct EventStruct *event, + bool _i2cMode = true); static bool webformSave(struct EventStruct *event); private: @@ -33,8 +35,10 @@ private: uint8_t i2cAddress; int16_t elevation{}; + int16_t csPin{}; bool initialized = false; + bool i2cMode = true; }; -#endif // ifdef USES_P154 +#endif // if defined(USES_P154) || defined(USES_P172) #endif // ifndef PLUGINSTRUCTS_P154_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P159_data_struct.cpp b/src/src/PluginStructs/P159_data_struct.cpp index 0fe29fb53..45686bcf6 100644 --- a/src/src/PluginStructs/P159_data_struct.cpp +++ b/src/src/PluginStructs/P159_data_struct.cpp @@ -44,7 +44,7 @@ P159_data_struct::P159_data_struct(ESPEasySerialPort portType, if (nullptr != radar) { if (radar->begin(*easySerial, false)) { - bool rst = radar->requestRestart(); + const bool rst = radar->requestRestart(); // start initiated, now wait, next step: request configuration milestone = millis(); @@ -60,23 +60,19 @@ P159_data_struct::P159_data_struct(ESPEasySerialPort portType, } // constructor void P159_data_struct::disconnectSerial() { - if (nullptr != easySerial) { - delete easySerial; - easySerial = nullptr; - } + delete easySerial; + easySerial = nullptr; - if (nullptr != radar) { - delete radar; - radar = nullptr; - } + delete radar; + radar = nullptr; } // disconnectSerial() bool P159_data_struct::processSensor(struct EventStruct *event) { bool new_data = false; if (isValid()) { - uint32_t iStart = millis(); // FIXME Remove log - P159_state_e sState = state; + const uint32_t iStart = millis(); + P159_state_e sState = state; switch (state) { case P159_state_e::Initializing: @@ -144,7 +140,7 @@ bool P159_data_struct::processSensor(struct EventStruct *event) { for (int8_t i = 0; i < valueCount; ++i) { const uint8_t pconfigIndex = i + P159_QUERY1_CONFIG_POS; bool isChanged = false; - UserVar.setFloat(event->TaskIndex, i, getRadarValue(PCONFIG(pconfigIndex), UserVar[event->BaseVarIndex + i], isChanged)); + UserVar.setFloat(event->TaskIndex, i, getRadarValue(PCONFIG(pconfigIndex), UserVar[event->BaseVarIndex + i], isChanged)); result |= isChanged; } @@ -181,7 +177,7 @@ bool P159_data_struct::plugin_read(struct EventStruct *event) { for (int8_t i = 0; i < valueCount; ++i) { const uint8_t pconfigIndex = i + P159_QUERY1_CONFIG_POS; bool isChanged = false; - UserVar.setFloat(event->TaskIndex, i, getRadarValue(PCONFIG(pconfigIndex), UserVar[event->BaseVarIndex + i], isChanged)); + UserVar.setFloat(event->TaskIndex, i, getRadarValue(PCONFIG(pconfigIndex), UserVar[event->BaseVarIndex + i], isChanged)); result |= isChanged; } diff --git a/src/src/PluginStructs/P162_data_struct.cpp b/src/src/PluginStructs/P162_data_struct.cpp new file mode 100644 index 000000000..809b020eb --- /dev/null +++ b/src/src/PluginStructs/P162_data_struct.cpp @@ -0,0 +1,197 @@ +#include "../PluginStructs/P162_data_struct.h" + +#ifdef USES_P162 + +# include "../PluginStructs/P162_data_struct.h" + +# include + +// Needed also here for PlatformIO's library finder as the .h file +// is in a directory which is excluded in the src_filter + +P162_data_struct::P162_data_struct(int8_t csPin, + int8_t rstPin, + int8_t shdPin) + : _csPin(csPin), _rstPin(rstPin), _shdPin(shdPin) +{} + +P162_data_struct::~P162_data_struct() { + // +} + +bool P162_data_struct::plugin_init(struct EventStruct *event) { + if (validGpio(_csPin) && Settings.isSPI_valid()) { + pinMode(_csPin, OUTPUT); + _initialized = true; + } + + if (validGpio(_rstPin)) { + pinMode(_rstPin, OUTPUT); + hw_reset(); + updateUserVars(event); + } + + if (_initialized) { + // Set default values + if (P162_SHUTDOWN_W0) { + _pot0_value = P162_SHUTDOWN_VALUE; + write_pot(P162_POT0_SHUTDOWN, _pot0_value); // Value ignored for shutdown + } else { + _pot0_value = P162_INIT_W0; + write_pot(P162_POT0_SEL, _pot0_value); + } + + if (P162_SHUTDOWN_W1) { + _pot1_value = P162_SHUTDOWN_VALUE; + write_pot(P162_POT1_SHUTDOWN, _pot1_value); // Value ignored for shutdown + } else { + _pot1_value = P162_INIT_W1; + write_pot(P162_POT1_SEL, _pot1_value); + } + updateUserVars(event); + + if (validGpio(_shdPin)) { + pinMode(_shdPin, OUTPUT); + + if (P162_SHUTDOWN_W0 && P162_SHUTDOWN_W1) { + _shdState = LOW; + digitalWrite(_shdPin, _shdState); + } + } + } else { + addLog(LOG_LEVEL_ERROR, F("Digipot: Initialization failed, SPI/CS not configured.")); + } + + return _initialized; +} + +/**************************************************** + * Reset the chip if a reset pin is configured + ***************************************************/ +bool P162_data_struct::hw_reset() { + if (validGpio(_rstPin)) { + _pot0_value = P162_RESET_VALUE; + _pot1_value = P162_RESET_VALUE; + digitalWrite(_rstPin, LOW); + delayMicroseconds(1); // Reset requires low signal for at least 150 nsec, so 1 microsecond should suffice + digitalWrite(_rstPin, HIGH); + addLog(LOG_LEVEL_INFO, F("Digipot: Hardware reset applied.")); + return true; + } + return false; +} + +/********************************************************************************************* + * Handle command processing + ********************************************************************************************/ +bool P162_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + const String cmd = parseString(string, 1); + + if (equals(cmd, F("digipot"))) { + const String sub = parseString(string, 2); + const bool hasPar = !parseString(string, 3).isEmpty(); + + // digipot,reset : Reset via configured RST pin else via software to initial state (both channels at 128) + if (equals(sub, F("reset"))) { // Reset the digipot + success = hw_reset(); // if the reset pin is configured + + if (!success) { + _pot0_value = P162_RESET_VALUE; + _pot1_value = P162_RESET_VALUE; + updateUserVars(event); + write_pot(P162_BOTH_POT_SEL, _pot0_value); // Single command + success = true; + addLog(LOG_LEVEL_INFO, F("Digipot: Software reset applied.")); + } + } else + + // digipit,shutdown[,pot] : Shutdown pot 0, 1 or both (2), via hardware if both and pin shutdown configured + if (equals(sub, F("shutdown")) && (event->Par2 >= 0) && (event->Par2 <= 2)) { // Pot range: 0..2 + if (((hasPar && (2 == event->Par2)) || !hasPar) && validGpio(_shdPin)) { // no params or param = 2 (both) and shutdown pin defined + _shdState = LOW; + digitalWrite(_shdPin, _shdState); + _pot0_value = P162_SHUTDOWN_VALUE; + _pot1_value = P162_SHUTDOWN_VALUE; + updateUserVars(event); + success = true; + } else { + uint8_t shd = P162_BOTH_POT_SHUTDOWN; + + if (hasPar && (0 == event->Par2)) { + shd = P162_POT0_SHUTDOWN; + _pot0_value = P162_SHUTDOWN_VALUE; + } else if (hasPar && (1 == event->Par2)) { + shd = P162_POT1_SHUTDOWN; + _pot1_value = P162_SHUTDOWN_VALUE; + } + updateUserVars(event); + write_pot(shd, 0); + success = true; + } + } else + + // digipot,(0|1|2), : Set pot 0, 1 or both (2) to , where value = 0..255 + if (isdigit(parseString(string, 2)[0]) && (event->Par1 >= 0) && (event->Par1 <= 2)) { + if (hasPar && (event->Par2 >= 0) && (event->Par2 <= 255)) { // Argument mandatory + uint8_t sel = P162_BOTH_POT_SEL; + + if (0 == event->Par1) { + sel = P162_POT0_SEL; + _pot0_value = event->Par2; + } else if (1 == event->Par1) { + sel = P162_POT1_SEL; + _pot1_value = event->Par2; + } else { + _pot0_value = event->Par2; + _pot1_value = event->Par2; + } + updateUserVars(event); + write_pot(sel, event->Par2); + success = true; + } + } + } + + return success; +} + +/******************************************************************************************** + * Write to pot + *******************************************************************************************/ +void P162_data_struct::write_pot(uint8_t cmd, + uint8_t val) { + if (!_initialized) { return; } + + // set the CS pin to low to select the chip: + digitalWrite(_csPin, LOW); + + // send the command and value via SPI: + SPI.transfer(cmd); + SPI.transfer(val); + + // Set the CS pin high to execute the command: + digitalWrite(_csPin, HIGH); +} + +/********************************************************************************* + * Set current values to UserVar + ********************************************************************************/ +void P162_data_struct::updateUserVars(struct EventStruct *event) { + const int16_t pot0_old = UserVar.getFloat(event->TaskIndex, 0, true); + const int16_t pot1_old = UserVar.getFloat(event->TaskIndex, 1, true); + + UserVar.setFloat(event->TaskIndex, 0, _pot0_value); + UserVar.setFloat(event->TaskIndex, 1, _pot1_value); + + if (P162_CHANGED_EVENTS && ((pot0_old != _pot0_value) || (pot1_old != _pot1_value))) { + sendData(event); + } + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("Digipot: W0 = %d, W1 = %d"), _pot0_value, _pot1_value)); + } +} + +#endif // ifdef USES_P162 diff --git a/src/src/PluginStructs/P162_data_struct.h b/src/src/PluginStructs/P162_data_struct.h new file mode 100644 index 000000000..b26916a3b --- /dev/null +++ b/src/src/PluginStructs/P162_data_struct.h @@ -0,0 +1,63 @@ +#ifndef PLUGINSTRUCTS_P162_DATA_STRUCT_H +#define PLUGINSTRUCTS_P162_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P162 + +# define P162_CS_PIN PIN(0) +# define P162_RST_PIN PIN(1) +# define P162_SHD_PIN PIN(2) + +# define P162_INIT_W0 PCONFIG(0) +# define P162_INIT_W1 PCONFIG(1) +# define P162_SHUTDOWN_W0 PCONFIG(2) +# define P162_SHUTDOWN_W1 PCONFIG(3) +# define P162_SHUTDOWN_VALUE PCONFIG(4) +# define P162_CHANGED_EVENTS PCONFIG(5) + +// # define P162_REMOVALVALUE PCONFIG_LONG(0) +// # define P162_REMOVALTIMEOUT PCONFIG_LONG(1) + +// potentiometer select byte +const uint8_t P162_POT0_SEL = 0x11; +const uint8_t P162_POT1_SEL = 0x12; +const uint8_t P162_BOTH_POT_SEL = 0x13; + +// shutdown the device to put it into power-saving mode. +// In this mode, terminal A is open-circuited and the B and W terminals are shorted together. +// send new command and value to exit shutdowm mode. +const uint8_t P162_POT0_SHUTDOWN = 0x21; +const uint8_t P162_POT1_SHUTDOWN = 0x22; +const uint8_t P162_BOTH_POT_SHUTDOWN = 0x23; + +const uint8_t P162_RESET_VALUE = 0x80; // Pot setting on power-up/reset + +struct P162_data_struct : public PluginTaskData_base { + P162_data_struct(int8_t csPin, + int8_t rstPin, + int8_t shdPin); + P162_data_struct() = delete; + virtual ~P162_data_struct(); + + bool plugin_init(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + String & string); + +private: + + bool hw_reset(); + void write_pot(uint8_t cmd, + uint8_t val); + void updateUserVars(struct EventStruct *event); + + int16_t _pot0_value = P162_RESET_VALUE; + int16_t _pot1_value = P162_RESET_VALUE; + int8_t _csPin; + int8_t _rstPin; + int8_t _shdPin; + uint8_t _shdState = HIGH; + bool _initialized = false; +}; + +#endif // ifdef USES_P162 +#endif // ifndef PLUGINSTRUCTS_P162_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P164_data_struct.cpp b/src/src/PluginStructs/P164_data_struct.cpp new file mode 100644 index 000000000..0818e1f6a --- /dev/null +++ b/src/src/PluginStructs/P164_data_struct.cpp @@ -0,0 +1,933 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Plugin data structure for P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// Based upon: https://github.com/sciosense/ENS160_driver +// For documentation see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// Based upon: https://github.com/sciosense/ENS160_driver +// MIT License for the original code referenced above +// Copyright (c) 2020 Sciosense +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// 2023 By flashmark +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../PluginStructs/P164_data_struct.h" + +#ifdef USES_P164 + +// A curious delay inserted in the original code [ms] +#define ENS160_BOOTING 10 +// Max time for device to react on a reset [ms] +#define ENS160_MAXBOOTING 2000 + +// ENS160 registers for version V0 +#define ENS160_REG_PART_ID 0x00 // 2 byte register for part identification +#define ENS160_REG_OPMODE 0x10 // Operation mode register +#define ENS160_REG_CONFIG 0x11 // Pin configuration register +#define ENS160_REG_COMMAND 0x12 // Additional system commands +#define ENS160_REG_TEMP_IN 0x13 // Host ambient temperature information +#define ENS160_REG_RH_IN 0x15 // Host relative humidity information +#define ENS160_REG_DATA_STATUS 0x20 // Operating mode status readback +#define ENS160_REG_DATA_AQI 0x21 // Air quality according to index according to UBA +#define ENS160_REG_DATA_TVOC 0x22 // Equivalent TVOC concentartion (ppb) +#define ENS160_REG_DATA_ECO2 0x24 // Equivalent CO2 concentration (ppm) +#define ENS160_REG_DATA_AQI_S 0x26 // Relative Air Quality Index according to ScioSense [ENS161 only] +#define ENS160_REG_DATA_BL 0x28 // Undocumented +#define ENS160_REG_DATA_T 0x30 // Temperature used in calculations +#define ENS160_REG_DATA_RH 0x32 // Relative humidity used in calculations +#define ENS160_REG_DATA_MISR 0x38 //Data integrity field (optional) +#define ENS160_REG_GPR_WRITE_0 0x40 // General purpose write registers [8 bytes] +#define ENS160_REG_GPR_WRITE_1 ENS160_REG_GPR_WRITE_0 + 1 +#define ENS160_REG_GPR_WRITE_2 ENS160_REG_GPR_WRITE_0 + 2 +#define ENS160_REG_GPR_WRITE_3 ENS160_REG_GPR_WRITE_0 + 3 +#define ENS160_REG_GPR_WRITE_4 ENS160_REG_GPR_WRITE_0 + 4 +#define ENS160_REG_GPR_WRITE_5 ENS160_REG_GPR_WRITE_0 + 5 +#define ENS160_REG_GPR_WRITE_6 ENS160_REG_GPR_WRITE_0 + 6 +#define ENS160_REG_GPR_WRITE_7 ENS160_REG_GPR_WRITE_0 + 7 +#define ENS160_REG_GPR_READ_0 0x48 // General purpose read registers [8 bytes] +#define ENS160_REG_GPR_READ_1 ENS160_REG_GPR_READ_0 + 1 +#define ENS160_REG_GPR_READ_2 ENS160_REG_GPR_READ_0 + 2 +#define ENS160_REG_GPR_READ_3 ENS160_REG_GPR_READ_0 + 3 +#define ENS160_REG_GPR_READ_4 ENS160_REG_GPR_READ_0 + 4 +#define ENS160_REG_GPR_READ_5 ENS160_REG_GPR_READ_0 + 5 +#define ENS160_REG_GPR_READ_6 ENS160_REG_GPR_READ_0 + 6 +#define ENS160_REG_GPR_READ_7 ENS160_REG_GPR_READ_0 + 7 + +// ENS160_REG_PART_ID values for Chip ID +#define ENS160_PARTID 0x0160 // ENS160 +#define ENS161_PARTID 0x0161 // ENS161 + +//ENS160 COMMAND register values +#define ENS160_COMMAND_NOP 0x00 // NOP, No operation +#define ENS160_COMMAND_CLRGPR 0xCC // CLRGRP, Clears GPR Read Registers +#define ENS160_COMMAND_GET_APPVER 0x0E // GET_APPVER, Get firmware version +#define ENS160_COMMAND_SETTH 0x02 // Not specified in datasheet +#define ENS160_COMMAND_SETSEQ 0xC2 // Not specified in datasheet + +// ENS160 OPMODE register values +#define ENS160_OPMODE_RESET 0xF0 // RESET +#define ENS160_OPMODE_DEEP_SLEEP 0x00 // DEEPSLEEP +#define ENS160_OPMODE_IDLE 0x01 // IDLE +#define ENS160_OPMODE_STD 0x02 // STANDARD +#define ENS160_OPMODE_LP 0x03 // LOW POWER (ENS161 only) +#define ENS160_OPMODE_ULP 0x04 // ULTRA LOW POWER (ENS161 only) +#define ENS160_OPMODE_CUSTOM 0xC0 // Not specified in datasheet + +// ENS160 unspecified bitfields? +#define ENS160_BL_CMD_START 0x02 +#define ENS160_BL_CMD_ERASE_APP 0x04 +#define ENS160_BL_CMD_ERASE_BLINE 0x06 +#define ENS160_BL_CMD_WRITE 0x08 +#define ENS160_BL_CMD_VERIFY 0x0A +#define ENS160_BL_CMD_GET_BLVER 0x0C +#define ENS160_BL_CMD_GET_APPVER 0x0E +#define ENS160_BL_CMD_EXITBL 0x12 + +// ENS160 unspecified bitfields? +#define ENS160_SEQ_ACK_NOTCOMPLETE 0x80 +#define ENS160_SEQ_ACK_COMPLETE 0xC0 + +#define IS_ENS160_SEQ_ACK_NOT_COMPLETE(x) (ENS160_SEQ_ACK_NOTCOMPLETE == (ENS160_SEQ_ACK_NOTCOMPLETE & (x))) +#define IS_ENS160_SEQ_ACK_COMPLETE(x) (ENS160_SEQ_ACK_COMPLETE == (ENS160_SEQ_ACK_COMPLETE & (x))) + +// ENS160 STATUS bitfields +#define ENS160_STATUS_STATAS 0x80 // STATAS: Indicates that an OPMODE is running +#define ENS160_STATUS_STATER 0x40 // STATER: High indicated that an error is detected +#define ENS160_STATUS_VALIDITY 0x0C // VALIDITY FLAG +#define ENS160_STATUS_VAL_NORM 0x00 // 0: Normal operation +#define ENS160_STATUS_VAL_WARM 0x01 // 1: Warm-Up phase +#define ENS160_STATUS_VAL_NOUSE 0x02 // 2: Not used +#define ENS160_STATUS_VAL_INVAL 0x03 // 3: Invalid output +#define ENS160_STATUS_NEWDAT 0x02 // NEWDAT: 1= New data in data registers available +#define ENS160_STATUS_NEWGPR 0x01 // NEWGRP: 1= New data in GRP_READ registers available + +// Checkers for bitfields in STATUS register +#define IS_NEWDAT(x) (ENS160_STATUS_NEWDAT == (ENS160_STATUS_NEWDAT & (x))) +#define IS_NEWGPR(x) (ENS160_STATUS_NEWGPR == (ENS160_STATUS_NEWGPR & (x))) +#define IS_NEW_DATA_AVAILABLE(x) (0 != ((ENS160_STATUS_NEWDAT | ENS160_STATUS_NEWGPR ) & (x))) +#define GET_STATUS_VALIDITY(x) (((x) & ENS160_STATUS_VALIDITY) >> 2) + +// TODO: add comment on this +#define CONVERT_RS_RAW2OHMS_I(x) (1 << ((x) >> 11)) +#define CONVERT_RS_RAW2OHMS_F(x) (pow (2, (float)(x) / 2048)) + +// Form IDs used on the device setup page. Should be a short unique string. +#define P164_GUID_TEMP_T "f01" +#define P164_GUID_TEMP_V "f02" +#define P164_GUID_HUM_T "f03" +#define P164_GUID_HUM_V "f04" + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Constructor // +/////////////////////////////////////////////////////////////////////////////////////////////////// +P164_data_struct::P164_data_struct(struct EventStruct *event) : + i2cAddress(P164_PCONFIG_I2C_ADDR) +{ +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialization of the connected device // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::begin() +{ + // Start connecting the device over I2C + if (!start(i2cAddress)) { + addLogMove(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")); + #endif + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Fetch the processed device values as stored in the software object // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::read(float& tvoc, float& eco2) +{ + bool success = measure(); // Read measurement values from device + tvoc = (float)_data_tvoc; // Latest acquired TVOC value + eco2 = (float)_data_eco2; // Latest aquired eCO2 value + return success; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Fetch the processed device values as stored in the software object using compensation // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::read(float& tvoc, float& eco2, float temp, float hum) +{ + this->set_envdata(temp, hum); // Write new compensation temp & hum to device + bool success = measure(); // Read measurement values from device + tvoc = (float)_data_tvoc; // Latest acquired TVOC value + eco2 = (float)_data_eco2; // Latest aquired eCO2 value + return success; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_WEBFORM_LOAD call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::webformLoad(struct EventStruct *event) +{ + bool found = false; // A chip has responded at the I2C address + uint16_t chipID = 0; + + addRowLabel(F("Detected Sensor Type")); + chipID = I2C_read16_LE_reg(P164_PCONFIG_I2C_ADDR, ENS160_REG_PART_ID, &found); + if (!found) { + addHtml(F("No device found")); + } else if (chipID == ENS160_PARTID) { + addHtml(F("ENS160")); + } else if (chipID == ENS161_PARTID) { + addHtml(F("ENS161")); + } else { + addHtmlInt(chipID); + } + + P164_data_struct *P164_data = static_cast(getPluginTaskData(event->TaskIndex)); + if (P164_data != nullptr) { + addHtml(F(" Firmware: ")); + addHtmlInt(P164_data->getMajorRev()); + addHtml(F(".")); + addHtmlInt(P164_data->getMinorRev()); + addHtml(F(".")); + addHtmlInt(P164_data->getBuild()); + } + + addFormNote(F("Both Temperature and Humidity task & values are needed to enable compensation")); + // temperature + addRowLabel(F("Temperature Task")); + addTaskSelect(F(P164_GUID_TEMP_T), P164_PCONFIG_TEMP_TASK); + if (validTaskIndex(P164_PCONFIG_TEMP_TASK)) + { + LoadTaskSettings(P164_PCONFIG_TEMP_TASK); // we need to load the values from another task for selection! + addRowLabel(F("Temperature Value")); + addTaskValueSelect(F(P164_GUID_TEMP_V), P164_PCONFIG_TEMP_VAL, P164_PCONFIG_TEMP_TASK); + } + // humidity + addRowLabel(F("Humidity Task")); + addTaskSelect(F(P164_GUID_HUM_T), P164_PCONFIG_HUM_TASK); + if (validTaskIndex(P164_PCONFIG_HUM_TASK)) + { + LoadTaskSettings(P164_PCONFIG_HUM_TASK); // we need to load the values from another task for selection! + addRowLabel(F("Humidity Value")); + addTaskValueSelect(F(P164_GUID_HUM_V), P164_PCONFIG_HUM_VAL, P164_PCONFIG_HUM_TASK); + } + LoadTaskSettings(event->TaskIndex); // we need to restore our original taskvalues! + + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// Implementation of plugin PLUGIN_WEBFORM_SAVE call // +// Note: this is not a class function, only data from event is available // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::webformSave(struct EventStruct *event) +{ + P164_PCONFIG_I2C_ADDR = getFormItemInt(F("i2c_addr")); + P164_PCONFIG_TEMP_TASK = getFormItemInt(F(P164_GUID_TEMP_T)); + P164_PCONFIG_TEMP_VAL = getFormItemInt(F(P164_GUID_TEMP_V)); + P164_PCONFIG_HUM_TASK = getFormItemInt(F(P164_GUID_HUM_T)); + P164_PCONFIG_HUM_VAL = getFormItemInt(F(P164_GUID_HUM_V)); + return true; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Implementation of plugin PLUGIN_TEN_PER_SECOND call // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::tenPerSecond(struct EventStruct *event) +{ + return evaluateState(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Set operation mode of sensor // +// Note: This function is to set the operation mode to the plugin software structure only // +// The statemachine shall handle the actual programming of the OPMODE register // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::setMode(uint8_t mode) { + bool result = false; + + // LP only valid for rev>0 + if (!(mode == ENS160_OPMODE_LP) and (_revENS16x == 0)) { + this->_opmode = mode; + result = true; + } + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: setMode("); + log += mode; + log += F(")"); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// **** The sensor handling code **** // +// This is based upon Sciosense code on github // +// The code is adapted to fit the ESPEasy structures and statemachine behavior is added // +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Helper function to display the current state in readable format // +/////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef P164_ENS160_DEBUG +String printState(int state) { + switch (state) { + case P164_STATE_INITIAL: return F("initial"); break; + case P164_STATE_ERROR: return F("error"); break; + case P164_STATE_RESETTING: return F("resetting"); break; + case P164_STATE_STARTING1: return F("starting1"); break; + case P164_STATE_STARTING2: return F("starting2"); break; + case P164_STATE_IDLE: return F("idle"); break; + case P164_STATE_DEEPSLEEP: return F("deepsleep"); break; + case P164_STATE_OPERATIONAL: return F("operational"); break; + default: return F("***ERROR***"); break; + } +} +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Evaluate the plugin statemachine and determine next step // +// To prevent using delay() to wait for the device responses a statemachine is introduced // +// Main states follow the device states according datasheet: // +// + IDLE Enabled, waiting for commands // +// + DEEP SLEEP Low power standby // +// + OPERATIONAL Active gas sensing // +// Added states to administrate plugin software status: // +// + INITIAL Class is constructed, waiting for begin() // +// + ERROR Communication with the device failed or other fatal error conditions // +// + RESETTING Waiting for device reset to be finished // +// + STARTING1 Startup sequence waiting for previous command to be acknowledged // +// + STARTING2 Startup sequence waiting for previous command to be acknowledged // +// Note that the ENS161 device has various gas sensing operation modes which all map to the // +// same OPERATIONAL state. These are combined in the same OPMODE register in the device // +// - STANDARD // +// - LOW POWER // +// - ULTRA LOW POWER // +// The same OPMODE register is also used to reset the device. // +// Thus OPMODE register shall not be confused with this software state // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::evaluateState() +{ + bool success = true; // State transition went without problems with device + P164_state newState = this->_state; // Determine next state, start with current state + + switch (this->_state) { + case P164_STATE_INITIAL: + // Waiting for external call to begin() + this->_available = false; + break; + case P164_STATE_ERROR: + // Stay here until correct device ID is detected and status register can be read + // If there is a proper device connected then reset it and initialize it + this->_available = false; + success = this->checkPartID() && this->getStatus(); + if (success){ + success = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time + newState = P164_STATE_RESETTING; + } + break; + case P164_STATE_RESETTING: + // Device has been reset, give it some time to get accessible again + this->_available = false; + + // Once device has rebooted check if it is a supported device at the given I2C address + if (timePassedSince(this->_lastChange) > ENS160_BOOTING) { + if (this->checkPartID()) + { + newState = P164_STATE_STARTING1; + } + else if (timePassedSince(this->_lastChange) > ENS160_MAXBOOTING) { + newState = P164_STATE_ERROR; + } + } + break; + case P164_STATE_STARTING1: + // A valid device is found, check if its status is ready to continue + this->_available = false; + this->getStatus(); + if (GET_STATUS_VALIDITY(this->_statusReg) == ENS160_STATUS_VAL_NORM) + { + this->writeMode(ENS160_OPMODE_IDLE); + this->clearCommand(); + newState = P164_STATE_STARTING2; + } + break; + case P164_STATE_STARTING2: + this->_available = false; + this->getStatus(); + if (GET_STATUS_VALIDITY(this->_statusReg) == ENS160_STATUS_VAL_NORM) { + this->getFirmware(); + newState = P164_STATE_IDLE; + } + break; + case P164_STATE_IDLE: + // Set device into desired operation mode as requested through _opmode + this->_available = true; + + switch (this->_opmode) { + case ENS160_OPMODE_STD: + this->writeMode(ENS160_OPMODE_STD); + newState = P164_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_LP: + this->writeMode(ENS160_OPMODE_LP); + newState = P164_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_ULP: + this->writeMode(ENS160_OPMODE_ULP); + newState = P164_STATE_OPERATIONAL; + break; + case ENS160_OPMODE_RESET: + this->writeMode(ENS160_OPMODE_RESET); + this->_opmode = ENS160_OPMODE_IDLE; // Prevent reset loop + newState = P164_STATE_RESETTING; + break; + } + break; + case P164_STATE_DEEPSLEEP: + // Device is put to DEEPSLEEP mode. If requested move to another mode. But alsways through IDLE + this->_available = true; + + if (this->_opmode != ENS160_OPMODE_DEEP_SLEEP) { + this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state + newState = P164_STATE_IDLE; + } + break; + case P164_STATE_OPERATIONAL: + // Device is in one of the operational modes + this->_available = true; + + switch (this->_opmode) { + case ENS160_OPMODE_DEEP_SLEEP: + case P164_STATE_IDLE: + case ENS160_OPMODE_RESET: + this->writeMode(ENS160_OPMODE_IDLE); // Move through Idle state + newState = P164_STATE_IDLE; + break; + } + break; + default: + // Unplanned state, force into error state + newState = P164_STATE_ERROR; + break; + } + + if (newState != this->_state) { + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("P164: State transition "); + log += printState(this->_state); + log += F(" -> "); + log += printState(newState); + log += F("; opmode= "); + log += this->_opmode; + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + this->_state = newState; + this->_lastChange = millis(); + } + + return success; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Helper function to enter a new state // +// Function must be called outside evaluateState() when an action causes a state transition // +/////////////////////////////////////////////////////////////////////////////////////////////////// +void P164_data_struct::moveToState(P164_state newState) +{ + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: Move to state: "); + log += printState(this->_state); + log += F(" --> "); + log += printState(newState); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + this->_state = newState; // Enter the new state + this->_lastChange = millis(); // Mark time of transition + this->evaluateState(); // Check if we can already move on +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Init the device: // +// Reset ENS16x // +// Returns false on encountered errors // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::start(uint8_t slaveaddr) +{ + uint8_t result = 0; + + // Initialize internal bookkeeping; + this->_state = P164_STATE_INITIAL; // Assume nothing, start clean + this->_lastChange = millis(); // Bookmark last state change as now + this->_available = false; + this->_opmode = ENS160_OPMODE_STD; + this->i2cAddress = slaveaddr; + + result = this->writeMode(ENS160_OPMODE_RESET); // Reset the device, takes some time + this->moveToState(P164_STATE_RESETTING); // Go to next state RESETTING + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: start() result= "); + log += result ? F("ok") : F("nok"); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Perform prediction measurement and store result in internal variables // +// Return: true if data is fresh (first reading of new data) // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::measure() { + bool newData = false; // True when new data is availabl at device + bool is_ok; // Dump I2C transaction status + + if (this->_state == P164_STATE_OPERATIONAL) { + if (this->getStatus()) { // Check status register if new data is aquired + if (IS_NEWDAT(this->_statusReg)) { + newData = true; + _data_aqi = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_AQI, &is_ok); + _data_tvoc = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_TVOC, &is_ok); + _data_eco2 = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_ECO2, &is_ok); + if (_revENS16x > 0) { // AQI500 only available for ENS161 + _data_aqi500 = I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_AQI_S, &is_ok); + } + else { + _data_aqi500 = 0; + } + } + } + else { + // Some issues with the device connectivity, move to error state + this->moveToState(P164_STATE_ERROR); + } + } + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log; + log += F("P164: measure() state= "); + log += printState(this->_state); + log += F(", aqi= "); + log += _data_aqi; + log += F(", tvoc= "); + log += _data_tvoc; + log += F(", eco2= "); + log += _data_eco2; + log += F(", AQI500= "); + log += _data_aqi500; + log += F(", newdata = "); + log += newData; + addLogMove(LOG_LEVEL_INFO, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return newData; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Following code is used to handle custom aquisition modes as defined by Sciosense Github code // +// Note that custom modes are not documented in the official datasheet // +/////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef P164_USE_CUSTOMMODE + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Initialize definition of custom mode with steps // +// This feature is not documented in the datasheet, but code is provided by Sciosense // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::initCustomMode(uint16_t stepNum) { + bool result = false; + + if (stepNum > 0) { + this->_stepCount = stepNum; + result = this->writeMode(ENS160_OPMODE_IDLE); + result = this->clearCommand(); + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_SETSEQ); + } + else { + result = false; + } + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Add a step to custom measurement profile with definition of duration // +// enabled data acquisition and temperature for each hotplate // +// This feature is not documented in the datasheet, but code is provided by Sciosense // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, + bool measureHP2, bool measureHP3, uint16_t tempHP0, + uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3) { + uint8_t seq_ack; + uint8_t temp; + bool is_ok; + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("setCustomMode() write step "), this->_stepCount)); + } + #endif // ifdef P164_ENS160_DEBUG + + // TODO check if delay is needed + // delay(ENS160_BOOTING); // Wait to boot after reset + + temp = (uint8_t)(((time / 24) - 1) << 6); + + if (measureHP0) { temp = temp | 0x20; } + if (measureHP1) { temp = temp | 0x10; } + if (measureHP2) { temp = temp | 0x8; } + if (measureHP3) { temp = temp | 0x4; } + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_0, temp); + + temp = (uint8_t)(((time / 24) - 1) >> 2); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_1, temp); + + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_2, (uint8_t)(tempHP0 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_3, (uint8_t)(tempHP1 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_4, (uint8_t)(tempHP2 / 2)); + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_5, (uint8_t)(tempHP3 / 2)); + + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_6, (uint8_t)(this->_stepCount - 1)); + + if (this->_stepCount == 1) { + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_7, 128); + } else { + I2C_write8_reg(i2cAddress, ENS160_REG_GPR_WRITE_7, 0); + } + + // TODO check if delay is needed + // delay(ENS160_BOOTING); + seq_ack = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_7, &is_ok); + + + // TODO check if delay is needed + // delay(ENS160_BOOTING); // Wait to boot after reset + + if ((ENS160_SEQ_ACK_COMPLETE | this->_stepCount) != seq_ack) { + this->_stepCount = this->_stepCount - 1; + return false; + } else { + return true; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Perfrom raw measurement and store result in internal variables // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::measureRaw() { + bool newData = false; + bool is_ok = true; + + if (this->_state == P164_STATE_OPERATIONAL) { + this->getStatus(); + if (IS_NEWGPR(this->_statusReg)) { + newData = true; + + // Read raw resistance values + _hp0_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_0, &is_ok)); + _hp1_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_2, &is_ok)); + _hp2_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_4, &is_ok)); + _hp3_rs = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_GPR_READ_6, &is_ok)); + + // Read baselines + _hp0_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+0, &is_ok)); + _hp1_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+2, &is_ok)); + _hp2_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+4, &is_ok)); + _hp3_bl = CONVERT_RS_RAW2OHMS_F(I2C_read16_LE_reg(i2cAddress, ENS160_REG_DATA_BL+6, &is_ok)); + + _misr = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_MISR, &is_ok); + } + } + + return newData; +} +#endif // P164_USE_CUSTOMMODE + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Writes t (degC) and h (%rh) to ENV_DATA. Returns false on I2C problems. // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::set_envdata(float t, float h) { + uint16_t t_data = (uint16_t)((t + 273.15f) * 64.0f); + uint16_t rh_data = (uint16_t)(h * 512.0f); + + return this->set_envdata210(t_data, rh_data); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Writes t and h (in ENS210 format) to ENV_DATA. Returns false on I2C problems. // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::set_envdata210(uint16_t t, uint16_t h) { + uint8_t trh_in[4]; // Buffer for I2C registers TEMP_IN + RH_IN + + // temp = (uint16_t)((t + 273.15f) * 64.0f); + trh_in[0] = t & 0xff; // TEMP_IN LSB + trh_in[1] = (t >> 8) & 0xff; // TEMP_IN MSB + + // temp = (uint16_t)(h * 512.0f); + trh_in[2] = h & 0xff; // RH_IN LSB + trh_in[3] = (h >> 8) & 0xff; // RH_IN MSB + + uint8_t result = I2C_writeBytes_reg(i2cAddress, ENS160_REG_TEMP_IN, trh_in, 4); + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read firmware revision // +// Precondition: Device MODE is IDLE // +// Note: This is according to original Sciosense software and matches ENS161 datasheet // +// The ENS160 datasheet is unclear, suggesting GRP_READ0 and GRP_READ1 registers // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::getFirmware() { + bool result = false; // Build return value for function + bool is_ok; // Temporary flag to track status + unsigned long ts; // Timestamp to limit polling time + + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_GET_APPVER); + + // Wait till GRP_READ registers are available. + is_ok = result; + ts = millis(); + while (is_ok) { // Abuse is_ok to keep polling until: + is_ok &= this->getStatus(); // - Device is inaccessible + is_ok &= (! IS_NEWGPR(this->_statusReg)); // - Firmware data is available + is_ok &= (timePassedSince(ts) < ENS160_BOOTING); // - Timeout occured + } + if (! IS_NEWGPR(this->_statusReg)) { // Check if firmware could be read + result = false; + addLogMove(LOG_LEVEL_ERROR, F("P164: Could not read firmware version")); + } + + if (result) { + this->_fw_ver_major = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_4, &is_ok); + this->_fw_ver_minor = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_5, &is_ok); + this->_fw_ver_build = I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_6, &is_ok); + } + else { + this->_fw_ver_major = 0; + this->_fw_ver_minor = 0; + this->_fw_ver_build = 0; + } + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: getFirmware() result: "); + log += result ? F("ok") : F("nok"); + log += F(", major="); + log += this->_fw_ver_major; + log += F(", minor="); + log += this->_fw_ver_minor; + log += F(", build="); + log += this->_fw_ver_build; + log += F(", registers="); + for (int i=0; i<8; i++) + { + log += formatToHex_decimal(I2C_read8_reg(i2cAddress, ENS160_REG_GPR_READ_0+i, &is_ok)); + log += ", "; + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Write to opmode register of the device // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::writeMode(uint8_t mode) { + bool result; + + result = I2C_write8_reg(i2cAddress, ENS160_REG_OPMODE, mode); + + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: writeMode("); + log += mode; + log += F(") result: "); + log += result ? F("ok") : F("nok"); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read the part ID from ENS160 device and check for validity // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::checkPartID(void) { + uint16_t part_id; // Resulting PartID + bool result = false; + + part_id = I2C_read16_LE_reg(i2cAddress, ENS160_REG_PART_ID, &result); + + if (!result) { + this->_revENS16x = 0xFF; + result = false; + } + else if (part_id == ENS160_PARTID) { + this->_revENS16x = 0; + result = true; + } + else if (part_id == ENS161_PARTID) { + this->_revENS16x = 1; + result = true; + } + else { + this->_revENS16x = 0xFF; + result = false; + } + + #ifdef P164_ENS160_DEBUG + String log; + log += F("P164: checkPartID() result: "); + switch (part_id) { + case ENS160_PARTID: log += F("ENS160"); break; + case ENS161_PARTID: log += F("ENS161"); break; + default: log += F("no valid part ID read"); break; + } + addLogMove(LOG_LEVEL_DEBUG, log); + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Clear any pending command in device // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::clearCommand(void) { + bool result = false; + + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_NOP); + result = I2C_write8_reg(i2cAddress, ENS160_REG_COMMAND, ENS160_COMMAND_CLRGPR); + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + addLogMove(LOG_LEVEL_DEBUG, concat(F("P164: clearCommand() result: "), result ? "ok" : "nok")); + } + #endif // ifdef P164_ENS160_DEBUG + + return result; +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read status register from device // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::getStatus() +{ + bool ret = false; + + this->_statusReg = I2C_read8_reg(i2cAddress, ENS160_REG_DATA_STATUS, &ret); + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log; + log += F("P164: Status register: "); + log += formatToHex_decimal(this->_statusReg, HEX); + log += F(", VALIDITY: "); + log += GET_STATUS_VALIDITY(this->_statusReg); + log += F(", STATAS: "); + log += (this->_statusReg & ENS160_STATUS_STATAS) == ENS160_STATUS_STATAS; + log += F(", STATER: "); + log += (this->_statusReg & ENS160_STATUS_STATER) == ENS160_STATUS_STATER; + log += F(", NEWDAT: "); + log += (this->_statusReg & ENS160_STATUS_NEWDAT) == ENS160_STATUS_NEWDAT; + log += F(", NEWGRP: "); + log += (this->_statusReg & ENS160_STATUS_NEWGPR) == ENS160_STATUS_NEWGPR; + log += F(", return: "); + log += ret; + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + return ret; +} + +#ifdef P164_LEGACY_CODE +/////////////////////////////////////////////////////////////////////////////////////////////////// +// I2C functionality copied from Sciosense. // +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Read a consecutive range of registers from device // +// Return: boolean == true when I2C transaction was succesful // +// Note: This functionality is not found in ESPEasy I2C library // +/////////////////////////////////////////////////////////////////////////////////////////////////// +bool P164_data_struct::readI2C(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num) { + uint8_t pos = 0; + uint8_t result = 0; + + #ifdef P164_ENS160_DEBUG + String log; // Debug String concatenated in multiple sections. Keep outside local if statement + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F("P164: I2C read address: "); + log += formatToHex_decimal(addr); + log += F(", register: "); + log += formatToHex_decimal(reg); + log += F(" data:"); + } + #endif // ifdef P164_ENS160_DEBUG + + // on arduino we need to read in 32 byte chunks + while (pos < num) { + uint8_t read_now = min((uint8_t)32, (uint8_t)(num - pos)); + Wire.beginTransmission((uint8_t)addr); + + Wire.write((uint8_t)reg + pos); + result = Wire.endTransmission(); + Wire.requestFrom((uint8_t)addr, read_now); + + for (int i = 0; i < read_now; i++) { + buf[pos] = Wire.read(); + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F(" "); + log += formatToHex_decimal(buf[pos]); + } + #endif // ifdef P164_ENS160_DEBUG + pos++; + } + } + #ifdef P164_ENS160_DEBUG + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + log += F("."); + addLogMove(LOG_LEVEL_DEBUG, log); + } + #endif // ifdef P164_ENS160_DEBUG + + return result == 0; +} +#endif // P164_LEGACY_CODE + +#endif // ifdef USES_P164 diff --git a/src/src/PluginStructs/P164_data_struct.h b/src/src/PluginStructs/P164_data_struct.h new file mode 100644 index 000000000..11e476828 --- /dev/null +++ b/src/src/PluginStructs/P164_data_struct.h @@ -0,0 +1,136 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Plugin data structure for P164 "GASES - ENS16x (TVOC, eCO2)" +// Plugin for ENS160 & ENS161 TVOC and eCO2 sensor with I2C interface from ScioSense +// Based upon: https://github.com/sciosense/ENS160_driver +// For documentation see +// https://www.sciosense.com/wp-content/uploads/documents/SC-001224-DS-9-ENS160-Datasheet.pdf +// +// 2023 By flashmark +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H +#define PLUGINSTRUCTS_P164_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P164 + +//#define P164_USE_CUSTOMMODE // Enable usage of ENS160 custom modes +#ifndef BUILD_NO_DEBUG + #define P164_ENS160_DEBUG // Enable debugging using the serial port +#endif + +// ESPEasy plugin parameter storage +#define P164_PCONFIG_I2C_ADDR PCONFIG(0) +#define P164_PCONFIG_TEMP_TASK PCONFIG(1) +#define P164_PCONFIG_TEMP_VAL PCONFIG(2) +#define P164_PCONFIG_HUM_TASK PCONFIG(3) +#define P164_PCONFIG_HUM_VAL PCONFIG(4) + +// 7-bit I2C slave address of the ENS160 +#define P164_ENS160_I2CADDR_0 0x52 //ADDR low +#define P164_ENS160_I2CADDR_1 0x53 //ADDR high + +// Use a state machine to avoid blocking the CPU while waiting for the response +// See P164_data_struct.cpp for detailed description +enum P164_state +{ + P164_STATE_INITIAL, // Device is in an unknown state, typically after reset + P164_STATE_ERROR, // Device is in an error state + P164_STATE_RESETTING, // Waiting for response after reset + P164_STATE_STARTING1, // Warming up after reset, wait to become idle + P164_STATE_STARTING2, // Startup state waiting for previous command to be finished + P164_STATE_IDLE, // Device is brought into IDLE mode + P164_STATE_DEEPSLEEP, // Device is brought into DEEPSLEEP mode + P164_STATE_OPERATIONAL, // Device is brought into OPERATIONAL mode +}; + +struct P164_data_struct : public PluginTaskData_base { +public: + + P164_data_struct(struct EventStruct *event); + //////P164_data_struct() = delete; + virtual ~P164_data_struct() = default; + + bool begin(); + bool read(float& tvoc, float& eco2); + bool read(float& tvoc, float& eco2, float temp, float hum); + static bool webformLoad(struct EventStruct *event); + static bool webformSave(struct EventStruct *event); + bool tenPerSecond(struct EventStruct *event); + +private: + bool evaluateState(); // Evaluate state machine for next action. Should be called regularly. + bool start(uint8_t slaveaddr); // Init I2C communication, resets ENS160 and checks its PART_ID. Returns false on I2C problems or wrong PART_ID. + bool available() { return this->_available; } // Report availability of sensor + uint8_t revENS16x() { return this->_revENS16x; } // Report version of sensor (0: ENS160, 1: ENS161) + bool setMode(uint8_t mode); // Set operation mode of sensor + bool initCustomMode(uint16_t stepNum); // Initialize definition of custom mode with steps + bool addCustomStep(uint16_t time, bool measureHP0, bool measureHP1, bool measureHP2, bool measureHP3, uint16_t tempHP0, uint16_t tempHP1, uint16_t tempHP2, uint16_t tempHP3); + // Add a step to custom measurement profile with definition of duration, enabled data acquisition and temperature for each hotplate + bool measure(); // Perform measurement and stores result in internal variables + bool measureRaw(); // Perform raw measurement and stores result in internal variables + bool set_envdata(float t, float h); // Writes t (degC) and h (%rh) to ENV_DATA. Returns "0" if I2C transmission is successful + bool set_envdata210(uint16_t t, uint16_t h); // Writes t and h (in ENS210 format) to ENV_DATA. Returns "0" if I2C transmission is successful + uint8_t getMajorRev() { return this->_fw_ver_major; } // Get major revision number of used firmware + uint8_t getMinorRev() { return this->_fw_ver_minor; } // Get minor revision number of used firmware + uint8_t getBuild() { return this->_fw_ver_build; } // Get build revision number of used firmware + + uint8_t getAQI() { return this->_data_aqi; } // Get AQI value of last measurement + uint16_t getTVOC() { return this->_data_tvoc; } // Get TVOC value of last measurement + uint16_t geteCO2() { return this->_data_eco2; } // Get eCO2 value of last measurement + uint16_t getAQI500() { return this->_data_aqi500; } // Get AQI500 value of last measurement + uint32_t getHP0() { return this->_hp0_rs; } // Get resistance of HP0 of last measurement + uint32_t getHP1() { return this->_hp1_rs; } // Get resistance of HP1 of last measurement + uint32_t getHP2() { return this->_hp2_rs; } // Get resistance of HP2 of last measurement + uint32_t getHP3() { return this->_hp3_rs; } // Get resistance of HP3 of last measurement + uint32_t getHP0BL() { return this->_hp0_bl; } // Get baseline resistance of HP0 of last measurement + uint32_t getHP1BL() { return this->_hp1_bl; } // Get baseline resistance of HP1 of last measurement + uint32_t getHP2BL() { return this->_hp2_bl; } // Get baseline resistance of HP2 of last measurement + uint32_t getHP3BL() { return this->_hp3_bl; } // Get baseline resistance of HP3 of last measurement + uint8_t getMISR() { return this->_misr; } // Return status code of sensor + + bool checkPartID(); // Reads the part ID and confirms valid sensor + bool clearCommand(); // Initialize idle mode and confirms + bool getFirmware(); // Read firmware revisions + bool getStatus(); // Read status register + bool writeMode(uint8_t mode); // Write the opmode register + void moveToState(P164_state newState); // Trigger a state change + + uint8_t i2cAddress = P164_ENS160_I2CADDR_0; // The I2C address of the connected device + + P164_state _state = P164_STATE_INITIAL; // General state of the software + ulong _lastChange = 0u; // Timestamp of last state transition + ulong _dbgtm = 0u; // Timestamp used for some debugging + uint8_t _opmode = 0; // Selected ENS16x Mode (as requested by higher level software) + + bool _available = false; // ENS16x available + uint8_t _statusReg = 0; // ENS16x latest status register readout + uint8_t _revENS16x = 0; // ENS160 or ENS161 connected? (FW >7) + uint8_t _fw_ver_major =0 ; // Device firmware major version number + uint8_t _fw_ver_minor = 0; // Device firmware minor version number + uint8_t _fw_ver_build = 0; // Device firmware build version number + uint16_t _stepCount; // Counter for custom sequence + uint8_t _data_aqi = 0; // Last acquired AQI value (see datasheet) + uint16_t _data_tvoc = 0; // Last acquired TVOC value (see datasheet) + uint16_t _data_eco2 = 0; // Last aquired eCO2 value (see datasheet) + uint16_t _data_aqi500 = 0; // Last acquired AQI500 value (see datasheet) + uint32_t _hp0_rs; + uint32_t _hp0_bl; + uint32_t _hp1_rs; + uint32_t _hp1_bl; + uint32_t _hp2_rs; + uint32_t _hp2_bl; + uint32_t _hp3_rs; + uint32_t _hp3_bl; + uint8_t _misr; + +#ifdef P164_USE_CUSTOMMODE + //Isotherm, HP0 252°C / HP1 350°C / HP2 250°C / HP3 324°C / measure every 1008ms + uint8_t _seq_steps[1][8] = { { 0x7C, 0x0A, 0x7E, 0xAF, 0xAF, 0xA2, 0x00, 0x80 }, }; +#endif // P164_USE_CUSTOMMODE + + // I2C access functions + static bool readI2C(uint8_t addr, uint8_t reg, uint8_t *buf, uint8_t num); +}; +#endif // ifdef USES_P164 +#endif // ifndef PLUGINSTRUCTS_P164_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P166_data_struct.cpp b/src/src/PluginStructs/P166_data_struct.cpp new file mode 100644 index 000000000..93287637b --- /dev/null +++ b/src/src/PluginStructs/P166_data_struct.cpp @@ -0,0 +1,257 @@ +#include "../PluginStructs/P166_data_struct.h" + +#ifdef USES_P166 + +/************************************************************************** +* Constructor +**************************************************************************/ +P166_data_struct::P166_data_struct(uint8_t address, + DFRobot_GP8403::eOutPutRange_t range) : + _address(address), _range(range) +{} + +P166_data_struct::~P166_data_struct() { + delete gp8403; +} + +bool P166_data_struct::init(struct EventStruct *event) { + // + gp8403 = new (std::nothrow) DFRobot_GP8403(&Wire, _address); + + if (nullptr != gp8403) { + LoadCustomTaskSettings(event->TaskIndex, presets, P166_PresetEntries, 0); + maxPreset = 0; + + for (uint8_t i = 0; i < P166_PresetEntries; ++i) { + if (!presets[i].isEmpty()) { + ++maxPreset; + } else { + break; // done + } + } + initialized = true; + gp8403->setDACOutRange(_range); // Set the output voltage range + + const bool restoreValues = !essentiallyZero(UserVar.getFloat(event->TaskIndex, 3)) && (1 == P166_RESTORE_VALUES); + + for (uint8_t i = 0; i < P166_MAX_OUTPUTS; ++i) { // Set the initial output values + float fValue; + + if (restoreValues) { + fValue = UserVar.getFloat(event->TaskIndex, i); + } else { + fValue = P166_PRESET_OUTPUT(i); + } + const int iValue = static_cast(roundf(fValue * P166_FACTOR_mV)); + + if ((iValue >= 0) && (iValue <= ((_range == DFRobot_GP8403::eOutPutRange_t::eOutputRange5V) ? P166_RANGE_5V : P166_RANGE_10V))) { + gp8403->setDACOutVoltage(iValue, i); + setUserVarAndLog(event, i, true, fValue, restoreValues ? F("restore") : F("init")); + } + } + } + addLog(LOG_LEVEL_ERROR, concat(F("GP8403: Initialization "), isInitialized() ? F("succeeded") : F("failed"))); + return isInitialized(); +} + +/***************************************************** +* plugin_read +*****************************************************/ +bool P166_data_struct::plugin_read(struct EventStruct *event) { + return isInitialized(); +} + +const char P166_subcommands[] PROGMEM = "volt|mvolt|range|preset|init"; + +enum class P166_subcmd_e : int8_t { + invalid = -1, + volt = 0, + mvolt, + range, + preset, + init, +}; + +/***************************************************** +* plugin_write +*****************************************************/ +bool P166_data_struct::plugin_write(struct EventStruct *event, + String & string) { + bool success = false; + + const String command = parseString(string, 1); + + if (isInitialized() && equals(command, F("gp8403"))) { + const String subcommand = parseString(string, 2); + const String sValue = parseString(string, 4); + const int subcommand_i = GetCommandCode(subcommand.c_str(), P166_subcommands); + + if (subcommand_i < 0) { return false; } // Fail fast + + const P166_subcmd_e subcmd = static_cast(subcommand_i); + uint32_t nChannel{}; + const bool hasChannel = validUIntFromString(parseString(string, 3), nChannel); + + switch (subcmd) { + case P166_subcmd_e::invalid: + break; + case P166_subcmd_e::volt: + case P166_subcmd_e::mvolt: + case P166_subcmd_e::preset: + + if (hasChannel && (nChannel <= 2)) { // Channel range check + float fValue{}; + bool isValid = false; + + if (P166_subcmd_e::preset == subcmd) { + isValid = validPresetValue(sValue, fValue); + } else { + isValid = validFloatFromString(sValue, fValue); + } + + if (isValid) { // Value valid check + const bool voltValue = (P166_subcmd_e::volt == subcmd || P166_subcmd_e::preset == subcmd); + + // Calculate mV from requested voltage + const int iValue = static_cast(roundf(voltValue ? fValue * P166_FACTOR_mV : fValue)); + + if ((iValue >= 0) && (iValue <= ((_range == DFRobot_GP8403::eOutPutRange_t::eOutputRange5V) ? P166_RANGE_5V : P166_RANGE_10V))) { + gp8403->setDACOutVoltage(static_cast(iValue), static_cast(nChannel)); + + if ((0 == nChannel) || (2 == nChannel)) { + setUserVarAndLog(event, 0, voltValue, fValue, subcommand); + } + + if ((1 == nChannel) || (2 == nChannel)) { + setUserVarAndLog(event, 1, voltValue, fValue, subcommand); + } + + // Send out data for events and to controllers by scheduling a TaskRun/PLUGIN_READ + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + + success = true; + } + } + } + break; + + case P166_subcmd_e::init: + + if (hasChannel && (nChannel <= 2)) { // Channel range check + for (uint8_t i = 0; i < P166_MAX_OUTPUTS; ++i) { + const int iValue = static_cast(roundf(P166_PRESET_OUTPUT(i) * P166_FACTOR_mV)); + + if ((iValue >= 0) && (iValue <= ((_range == DFRobot_GP8403::eOutPutRange_t::eOutputRange5V) ? P166_RANGE_5V : P166_RANGE_10V))) { + if ((0 == i) && ((0 == nChannel) || (2 == nChannel))) { + gp8403->setDACOutVoltage(static_cast(iValue), static_cast(i)); + setUserVarAndLog(event, 0, true, P166_PRESET_OUTPUT(i), subcommand); + } + + if ((1 == i) && ((1 == nChannel) || (2 == nChannel))) { + gp8403->setDACOutVoltage(static_cast(iValue), static_cast(i)); + setUserVarAndLog(event, 1, true, P166_PRESET_OUTPUT(i), subcommand); + } + success = true; + } + } + + if (success) { + // Send out data for events and to controllers by scheduling a TaskRun/PLUGIN_READ + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + 10); + } + } + break; + + case P166_subcmd_e::range: + + if ((event->Par2 == 5) || (event->Par2 == 10)) { // Value options check + _range = (event->Par2 == 5 + ? DFRobot_GP8403::eOutPutRange_t::eOutputRange5V + : DFRobot_GP8403::eOutPutRange_t::eOutputRange10V); + P166_MAX_VOLTAGE = static_cast(_range); // Save manually to store new setting... + gp8403->setDACOutRange(_range); // Device output will be updated immediately! + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("%s: 'range', value: 0-%dV"), F("GP8403"), event->Par2)); + } + success = true; + } + break; + } + } + return success; +} + +/***************************************************** +* setUserVarAndLog +*****************************************************/ +void P166_data_struct::setUserVarAndLog(struct EventStruct *event, + taskVarIndex_t varNr, + const bool voltValue, + const float fValue, + const String & subcommand) { + UserVar.setFloat(event->TaskIndex, varNr, voltValue ? fValue : fValue / P166_FACTOR_mV); // V + UserVar.setFloat(event->TaskIndex, 3, 1.0f); // Mark as set, will be reset on cold boot + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("%s: '%s' Output %d, value: %.3fV"), F("GP8403"), + subcommand.c_str(), + varNr, + voltValue ? fValue : fValue / P166_FACTOR_mV)); + } +} + +/***************************************************** +* validPresetValue +*****************************************************/ +bool P166_data_struct::validPresetValue(const String& name, + float & value) { + bool isValid = false; + + if (!name.isEmpty()) { + for (uint8_t i = 0; i < maxPreset; ++i) { + if (parseString(presets[i], 1).equals(name)) { + isValid = validFloatFromString(parseString(presets[i], 2), value); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("%s: Preset '%s' found, value: %.3fV"), F("GP8403"), + name.c_str(), + value)); + } + break; // Found it + } + } + } + return isValid; +} + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P166_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + + const String var = parseString(string, 1); + + if (var.startsWith(F("preset")) && (var.length() > 6)) { // [#presetX] = the configured preset X value + uint32_t preIdx = 0; + + if (validUIntFromString(var.substring(6), preIdx) && (preIdx > 0) && (preIdx <= maxPreset)) { + string = parseString(presets[preIdx - 1], 2); + success = true; + } + } else if (equals(var, F("initial0"))) { // [#initial0] = the configured initial output 0 value + string = toString(P166_PRESET_OUTPUT(0), 3); + success = true; + } else if (equals(var, F("initial1"))) { // [#initial1] = the configured initial output 1 value + string = toString(P166_PRESET_OUTPUT(1), 3); + success = true; + } else if (equals(var, F("range"))) { // [#range] = the configured range setting 5 or 10 + string = static_cast(P166_MAX_VOLTAGE) == DFRobot_GP8403::eOutPutRange_t::eOutputRange5V ? 5 : 10; + success = true; + } + return success; +} + +#endif // ifdef USES_P166 diff --git a/src/src/PluginStructs/P166_data_struct.h b/src/src/PluginStructs/P166_data_struct.h new file mode 100644 index 000000000..0323bc932 --- /dev/null +++ b/src/src/PluginStructs/P166_data_struct.h @@ -0,0 +1,60 @@ +#ifndef PLUGINSTRUCTS_P166_DATA_STRUCT_H +#define PLUGINSTRUCTS_P166_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P166 + +# include + +# define P166_I2C_ADDRESS PCONFIG(0) +# define P166_MAX_VOLTAGE PCONFIG(1) // 0-5V or 0-10V +# define P166_RESTORE_VALUES PCONFIG(2) +# define P166_PRESET_OUTPUT(N) PCONFIG_FLOAT((N)) + +# define P166_PresetEntries 25 // Should be enough for now +# define P166_MAX_OUTPUTS 2 // Technical limit +# define P166_RANGE_5V 5000 // mV +# define P166_RANGE_10V 10000 // mV +# define P166_FACTOR_mV 1000.0f // V -> mV + +struct P166_data_struct : public PluginTaskData_base { +public: + + P166_data_struct(uint8_t address, + DFRobot_GP8403::eOutPutRange_t range); + + P166_data_struct() = delete; + virtual ~P166_data_struct(); + + bool init(struct EventStruct *event); + + bool plugin_read(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + String & string); + bool plugin_get_config_value(struct EventStruct *event, + String & string); + bool isInitialized() const { + return initialized; + } + +private: + + bool validPresetValue(const String& name, + float & value); + void setUserVarAndLog(struct EventStruct *event, + taskVarIndex_t varNr, + const bool voltValue, + const float fValue, + const String & subcommand); + + DFRobot_GP8403 *gp8403 = nullptr; + uint8_t _address; + DFRobot_GP8403::eOutPutRange_t _range; + bool initialized = false; + + String presets[P166_PresetEntries]{}; + uint8_t maxPreset = 0; +}; + +#endif // ifdef USES_P166 +#endif // ifndef PLUGINSTRUCTS_P166_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P167_data_struct.cpp b/src/src/PluginStructs/P167_data_struct.cpp new file mode 100644 index 000000000..2be60ab5d --- /dev/null +++ b/src/src/PluginStructs/P167_data_struct.cpp @@ -0,0 +1,1249 @@ +/////////////////////////////////////////////////////////////////////////////////////////////////// +// P167 device class for IKEA Vindstyrka SEN54 and Sensirion SEN5x temperature, humidity and air quality sensors +// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf +// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf +// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x +// Rewritten and adapted for ESPeasy by andibaciu and tonhuisman +// changelog in _P167_Vindstyrka.ino +////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../PluginStructs/P167_data_struct.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#include "../Helpers/CRC_functions.h" + +#include + +#ifdef USES_P167 + + +# define P167_START_MEAS 0x0021 // Start measurement command +# define P167_START_MEAS_RHT_GAS 0x0037 // Start measurement RHT/Gas command +# define P167_STOP_MEAS 0x0104 // Stop measurement command +# define P167_READ_DATA_RDY_FLAG 0x0202 // Read Data Ready Flag command +# define P167_READ_MEAS 0x03C4 // Read measurement command +# define P167_R_W_TEMP_COMP_PARAM 0x60B2 // Read/Write Temperature Compensation Parameters command +# define P167_R_W_TWARM_START_PARAM 0x60C6 // Read/Write Warm Start Parameters command +# define P167_R_W_VOC_ALG_PARAM 0x60D0 // Read/Write VOC Algorithm Tuning Parameters command +# define P167_R_W_NOX_ALG_PARAM 0x60E1 // Read/Write NOx Algorithm Tuning Parameters command +# define P167_R_W_RH_T_ACC_Mode 0x60F7 // Read/Write RH/T Acceleration Mode command +# define P167_R_W_VOC_ALG_STATE 0x6181 // Read/Write VOC Algorithm State command +# define P167_START_FAN_CLEAN 0x5607 // Start fan cleaning command +# define P167_R_W_AUTOCLEN_PARAM 0x8004 // Read/Write Autocleaning Interval Parameters command +# define P167_READ_PROD_NAME 0xD014 // Read Product Name command +# define P167_READ_SERIAL_NO 0xD033 // Read Serial Number command +# define P167_READ_FIRM_VER 0xD100 // Read Firmware Version command +# define P167_READ_DEVICE_STATUS 0xD206 // Read Device Status command +# define P167_CLEAR_DEVICE_STATUS 0xD210 // Clear Device Status command +# define P167_RESET_DEVICE 0xD304 // Reset Device command +# define P167_READ_RAW_MEAS 0x03D2 // Read relative humidity and temperature + // which are not compensated for temperature offset, and the + // VOC and NOx raw signals (proportional to the logarithm of the + // resistance of the MOX layer). It returns 4x2 bytes (+ 1 CRC + // byte each) command (see second datasheet fron header for more info) +# define P167_READ_RAW_MYS_MEAS 0x03F5 // Read relative humidity and temperature and MYSTERY word (probably signed offset + // temperature) + + +# define P167_START_MEAS_DELAY 50 // Timeout value for start measurement command [ms] +# define P167_START_MEAS_RHT_GAS_DELAY 50 // Timeout value for start measurement RHT/Gas command [ms] +# define P167_STOP_MEAS_DELAY 200 // Timeout value for start measurement command [ms] +# define P167_READ_DATA_RDY_FLAG_DELAY 20 // Timeout value for read data ready flag command [ms] +# define P167_READ_MEAS_DELAY 20 // Timeout value for read measurement command [ms] +# define P167_R_W_TEMP_COMP_PARAM_DELAY 20 // Timeout value for read/write temperature compensation parameters command [ms] +# define P167_R_W_WARM_START_PARAM_DELAY 20 // Timeout value for read/write warm start parameters command [ms] +# define P167_R_W_VOC_ALG_PARAM_DELAY 20 // Timeout value for read/write VOC algorithm tuning parameters command [ms] +# define P167_R_W_NOX_ALG_PARAM_DELAY 20 // Timeout value for read/write NOx algorithm tuning parameters command [ms] +# define P167_R_W_RH_T_ACC_MODE_DELAY 20 // Timeout value for read/write RH/T acceleration mode command [ms] +# define P167_R_W_VOC_ALG_STATE_DELAY 20 // Timeout value for read/write VOC algorithm State command [ms] +# define P167_START_FAN_CLEAN_DELAY 20 // Timeout value for start fan cleaning command [ms] +# define P167_R_W_AUTOCLEN_PARAM_DELAY 20 // Timeout value for read/write autoclean interval parameters command [ms] +# define P167_READ_PROD_NAME_DELAY 20 // Timeout value for read product name command [ms] +# define P167_READ_SERIAL_NO_DELAY 20 // Timeout value for read serial number command [ms] +# define P167_READ_FIRM_VER_DELAY 20 // Timeout value for read firmware version command [ms] +# define P167_READ_DEVICE_STATUS_DELAY 20 // Timeout value for read device status command [ms] +# define P167_CLEAR_DEVICE_STATUS_DELAY 20 // Timeout value for clear device status command [ms] +# define P167_RESET_DEVICE_DELAY 100 // Timeout value for reset device command [ms] +# define P167_READ_RAW_MEAS_DELAY 20 // Timeout value for read raw temp and humidity command [ms] + +# define P167_MAX_RETRY 250 // Give up after amount of retries befoe going to error + + +const __FlashStringHelper* toString(P167_model model) { + switch (model) { + case P167_model::Vindstyrka: return F("IKEA Vindstyrka"); + case P167_model::SEN54: return F("Sensirion SEN54"); + case P167_model::SEN55: return F("Sensirion SEN55"); + } + return F(""); +} + +/// @brief +/// @param query +/// @return +const __FlashStringHelper* P167_getQueryString(uint8_t query) { + switch (query) { + case 0: return F("Temperature (C)"); + case 1: return F("Humidity (% RH)"); + case 2: return F("tVOC (VOC index)"); + case 3: return F("NOx (NOx index)"); + case 4: return F("PM 1.0 (ug/m3)"); + case 5: return F("PM 2.5 (ug/m3)"); + case 6: return F("PM 4.0 (ug/m3)"); + case 7: return F("PM 10.0 (ug/m3)"); + case 8: return F("DewPoint (C)"); + } + return F(""); +} + +/// @brief +/// @param query +/// @return +const __FlashStringHelper* P167_getQueryValueString(uint8_t query) { + switch (query) { + case 0: return F("Temperature"); + case 1: return F("Humidity"); + case 2: return F("tVOC"); + case 3: return F("NOx"); + case 4: return F("PM1p0"); + case 5: return F("PM2p5"); + case 6: return F("PM4p0"); + case 7: return F("PM10p0"); + case 8: return F("DewPoint"); + } + return F(""); +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// PUBLIC +// +P167_data_struct::P167_data_struct() { + // +} + +P167_data_struct::~P167_data_struct() { + // +} + +// Initialize/setup device properties +// Must be called at least once before oP167::Wairperating the device +bool P167_data_struct::setupDevice(uint8_t i2caddr) { + _i2caddr = i2caddr; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: Setup with address= 0x%02x"), _i2caddr)); + } + return true; +} + +bool P167_data_struct::setupMonPin(int16_t monpin) { + if (validGpio(monpin)) { + _monpin = monpin; + pinMode(_monpin, INPUT_PULLUP); // declare monitoring pin as input with pullup's + attachInterruptArg(digitalPinToInterrupt(_monpin), + reinterpret_cast(Plugin_167_interrupt), + this, + RISING); + + # ifdef PLUGIN_167_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: Setup I2C SCL monpin= %d"), _monpin)); + } + # endif // ifdef PLUGIN_167_DEBUG + return true; + } + return false; +} + +void P167_data_struct::disableInterrupt_monpin(void) { + detachInterrupt(digitalPinToInterrupt(_monpin)); +} + +// Initialize/setup device properties +// Must be called at least once before operating the device +bool P167_data_struct::setupModel(P167_model model) { + _model = model; + + # ifdef PLUGIN_167_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: Setup model= %s"), String(toString(_model)).c_str())); + } + # endif // ifdef PLUGIN_167_DEBUG + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Evaluate FSM for data acquisition +// This is a state machine that is evaluated step by step by calling update() repetatively +// NOTE: Function is expected to run as critical section w.r.t. other provided functions +// This is typically met in ESPeasy plugin context when called from within the plugin +bool P167_data_struct::update() { + bool stable = false; // signals when a stable state is reached + + # ifdef PLUGIN_167_DEBUG + P167_state oldState = _state; + # endif // ifdef PLUGIN_167_DEBUG + + + if (!statusMonitoring) { + return stable; + } + + + switch (_state) { + case P167_state::Uninitialized: + + // we have to stop trying after a while + if (_errCount > P167_MAX_RETRY) { + _state = P167_state::Error; + stable = true; + } else if (I2C_wakeup(_i2caddr) != 0) { // Try to access the I2C device + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat(F("SEN5x: Not found at I2C address: 0x%02x"), _i2caddr)); + } + _errCount++; + } else if (_model == P167_model::Vindstyrka) { // sensor is Vindstyrka and don't need to be reset + _errCount = 0; // Device is reachable and initialized, reset error counter + + if (writeCmd(P167_READ_FIRM_VER)) { // Issue a reset command + _state = P167_state::Read_firm_version; // Will take <20ms according to datasheet + _last_action_started = millis(); + } + } + else if ((_model == P167_model::SEN54) || (_model == P167_model::SEN55)) { + _errCount = 0; // Device is reachable and initialized, reset error counter + + if (writeCmd(P167_RESET_DEVICE)) { // Issue a reset command + _state = P167_state::Wait_for_reset; // Will take <20ms according to datasheet + _last_action_started = millis(); + } + } + break; + + case P167_state::Wait_for_reset: + + if (timeOutReached(_last_action_started + P167_RESET_DEVICE_DELAY)) { // we need to wait for the chip to reset + if (I2C_wakeup(_i2caddr) != 0) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _errCount = 0; // Device is reachable and initialized, reset error counter + + if (writeCmd(P167_READ_FIRM_VER)) { + _state = P167_state::Read_firm_version; // Will take <20ms according to datasheet + _last_action_started = millis(); + } + } + } + break; + + case P167_state::Read_firm_version: + + if (timeOutReached(_last_action_started + P167_READ_FIRM_VER_DELAY)) { + // Start read flag + if (!getFirmwareVersion()) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else if (!writeCmd(P167_READ_PROD_NAME)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Read_prod_name; + } + } + break; + + case P167_state::Read_prod_name: + + if (timeOutReached(_last_action_started + P167_READ_PROD_NAME_DELAY)) { + // Start read flag + if (!getProductName()) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else if (!writeCmd(P167_READ_SERIAL_NO)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Read_serial_no; + } + } + break; + + case P167_state::Read_serial_no: + + if (timeOutReached(_last_action_started + P167_READ_SERIAL_NO_DELAY)) { + // Start read flag + if (!getSerialNumber()) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else if (!writeCmd(P167_READ_SERIAL_NO)) { // Read serialno again? + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Initialized; + } + } + break; + + case P167_state::Write_user_reg: + _state = P167_state::Initialized; + break; + + case P167_state::Initialized: + + // Trigger the first read cycle automatically on regular SEN5x + if (_model != P167_model::Vindstyrka) { + _state = P167_state::Ready; + } + break; + + case P167_state::Ready: + + // Ready to execute a measurement cycle, for Vindstyrka we're eavesdropping so no command needed? + if ((_model == P167_model::Vindstyrka) || (_model == P167_model::SEN54) || (_model == P167_model::SEN55)) { + // Start measuring data + if (!writeCmd(P167_START_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_start_meas; + } + } + break; + + case P167_state::Wait_for_start_meas: + + if (timeOutReached(_last_action_started + P167_START_MEAS_DELAY)) { + // Start read flag + if (!writeCmd(P167_READ_DATA_RDY_FLAG)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_read_flag; + } + } + break; + + case P167_state::Wait_for_read_flag: + + if (timeOutReached(_last_action_started + P167_READ_DATA_RDY_FLAG_DELAY)) { + if (readDataRdyFlag()) { + // Ready to execute a measurement cycle + if (!writeCmd(P167_READ_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_read_meas; + } + } else { // Ready Flag NOT ok, so send again Start Measurement + // Start measuring data + if (!writeCmd(P167_START_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_start_meas; + } + } + } + break; + + case P167_state::Wait_for_read_meas: + + if (timeOutReached(_last_action_started + P167_READ_MEAS_DELAY)) { + if (!readMeasValue()) { // Read the previously measured temperature + _errCount++; + + // _state = P167_state::Uninitialized; // Lost connection + _state = P167_state::cmdSTARTmeas; + } else { + if (!writeCmd(P167_READ_RAW_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_read_raw_meas; + } + } + } + break; + + case P167_state::Wait_for_read_raw_meas: + + // make sure we wait for the measurement to complete + if (timeOutReached(_last_action_started + P167_READ_RAW_MEAS_DELAY)) { + if (!readMeasRawValue()) { + _errCount++; + + // _state = P167_state::Uninitialized; // Lost connection + _state = P167_state::cmdSTARTmeas; + } else { + if (!writeCmd(P167_READ_RAW_MYS_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_read_raw_MYS_meas; + } + } + } + break; + + case P167_state::Wait_for_read_raw_MYS_meas: + + // make sure we wait for the measurement to complete + if (timeOutReached(_last_action_started + P167_READ_RAW_MEAS_DELAY)) { + if (!readMeasRawMYSValue()) { + _errCount++; + + // _state = P167_state::Uninitialized; // Lost connection + _state = P167_state::cmdSTARTmeas; + } else { + if (!writeCmd(P167_READ_DEVICE_STATUS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::Wait_for_read_status; + } + calculateValue(); + stable = true; + } + } + break; + + case P167_state::Wait_for_read_status: + + // make sure we wait for the measurement to complete + if (timeOutReached(_last_action_started + P167_READ_DEVICE_STATUS_DELAY)) { + if (!readDeviceStatus()) { + _errCount++; + + _state = P167_state::cmdSTARTmeas; + } else { + _last_action_started = millis(); + _state = P167_state::cmdSTARTmeas; + stable = true; + } + } + break; + + case P167_state::cmdSTARTmeas: + + // Start measuring data + if (_model == P167_model::Vindstyrka) { + if (!writeCmd(P167_START_MEAS)) { + _errCount++; + _state = P167_state::Uninitialized; // Retry + } else { + _last_action_started = millis(); + _state = P167_state::IDLE; + } + } else { + _state = P167_state::IDLE; + } + break; + + case P167_state::IDLE: + stepMonitoring = 1; + startMonitoringFlag = false; + + if (!_errmeas && !_errmeasraw && !_errmeasrawmys) { + _state = P167_state::New_Values_Available; + } + stable = true; + break; + + case P167_state::Error: + case P167_state::New_Values_Available: + // this state is used outside so all we need is to stay here + stable = true; + break; + + // Missing states (enum values) to be checked by the compiler + } // switch + + # ifdef PLUGIN_167_DEBUG + + if (_state != oldState) { + if (loglevelActiveFor(LOG_LEVEL_INFO) && _enableLogging) { + addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: State transition %d-->%d"), static_cast(oldState), static_cast(_state))); + } + } + # endif // ifdef PLUGIN_167_DEBUG + return stable; +} + +bool P167_data_struct::monitorSCL() { + if (_model == P167_model::Vindstyrka) { + if (startMonitoringFlag) { + if (stepMonitoring == 1) { + lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime / 1000; + + if (millis() - lastSCLLowTransitionMonitoringTime < 100) { + statusMonitoring = false; + return true; + } else { + lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime / 1000; + statusMonitoring = true; + stepMonitoring++; + } + } + + if (stepMonitoring == 2) { + if (millis() - lastSCLLowTransitionMonitoringTime < 100) { + lastSCLLowTransitionMonitoringTime = monpinLastTransitionTime / 1000; + statusMonitoring = false; + stepMonitoring = 1; + return true; + } else if (millis() - lastSCLLowTransitionMonitoringTime > 700) { + statusMonitoring = false; + stepMonitoring = 1; + startMonitoringFlag = false; + + // if _state not finish reading process then start from begining + if ((_state >= P167_state::Wait_for_read_meas) && (_state < P167_state::New_Values_Available)) { + _state = P167_state::Ready; + } + return true; + } else { + // processing + } + } + } + monpinValuelast = monpinValue; + } + + if ((_model == P167_model::SEN54) || (_model == P167_model::SEN55)) { + statusMonitoring = true; + startMonitoringFlag = false; + stepMonitoring = 0; + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns the I2C connection state +// Note: based upon the FSM state without actual accessing the device +bool P167_data_struct::isConnected() const { + switch (_state) { + case P167_state::Initialized: + case P167_state::Ready: + case P167_state::Wait_for_start_meas: + case P167_state::Wait_for_read_flag: + case P167_state::Wait_for_read_meas: + case P167_state::Wait_for_read_raw_meas: + case P167_state::Wait_for_read_raw_MYS_meas: + case P167_state::Wait_for_read_status: + case P167_state::cmdSTARTmeas: + case P167_state::New_Values_Available: + case P167_state::Read_firm_version: + case P167_state::Read_prod_name: + case P167_state::Read_serial_no: + case P167_state::Write_user_reg: + case P167_state::IDLE: + return true; + break; + case P167_state::Uninitialized: + case P167_state::Error: + case P167_state::Wait_for_reset: + return false; + break; + + // Missing states (enum values) to be checked by the compiler + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns if the device communication is in error +// Note: based upon the FSM state without actual accessing the device +bool P167_data_struct::inError() const { + return _state == P167_state::Error; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Returns if new acquired values are available +bool P167_data_struct::newValues() const { + return _state == P167_state::New_Values_Available; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Restart the FSM used to access the device +bool P167_data_struct::reset() { + startMonitoringFlag = true; + stepMonitoring = 1; + _state = P167_state::Uninitialized; + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Start a new measurement cycle +bool P167_data_struct::startMeasurements() { + if ((_state == P167_state::New_Values_Available) || + (_state == P167_state::Initialized) || + (_state == P167_state::IDLE)) { + _state = P167_state::Ready; + } + startMonitoringFlag = true; + stepMonitoring = 1; + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Get the electronic idenfification data store in the device +// Note: The data is read from the device during initialization +bool P167_data_struct::getEID(String& eid_productname, String& eid_serialnumber, uint8_t& firmware) const { + eid_productname = _eid_productname; + eid_serialnumber = _eid_serialnumber; + firmware = _firmware; + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Get the status informasion about different part of the sensor +// Note: The data is read from the device after every measurement read request +bool P167_data_struct::getStatusInfo(P167_statusinfo param) { + switch (param) { + case P167_statusinfo::sensor_speed: + return _devicestatus.speed; + + case P167_statusinfo::sensor_autoclean: + return _devicestatus.autoclean; + + case P167_statusinfo::sensor_gas: + return _devicestatus.gas; + + case P167_statusinfo::sensor_rht: + return _devicestatus.rht; + + case P167_statusinfo::sensor_laser: + return _devicestatus.laser; + + case P167_statusinfo::sensor_fan: + return _devicestatus.fan; + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Return the previously measured raw humidity data [bits] +float P167_data_struct::getRequestedValue(uint8_t request) const { + switch (request) { + case 0: + { + if (_model == P167_model::Vindstyrka) { + return _TemperatureX; + } else { + return _Temperature; + } + } + case 1: + { + if (_model == P167_model::Vindstyrka) { + return _HumidityX; + } else { + return _Humidity; + } + } + case 2: return _tVOC; + case 3: return _NOx; + case 4: return _PM1p0; + case 5: return _PM2p5; + case 6: return _PM4p0; + case 7: return _PM10p0; + case 8: return _DewPoint; + } + return -1.0f; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// PROTECTED +// +////////////////////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////////////////////// +// bool P167_data_struct::writeCmd(uint8_t cmd) +// { +// return I2C_write8(_i2caddr, cmd); +// } + +////////////////////////////////////////////////////////////////////////////////////////////////// +bool P167_data_struct::writeCmd(uint16_t cmd) +{ + Wire.beginTransmission(_i2caddr); + Wire.write((uint8_t)(cmd >> 8)); + Wire.write((uint8_t)cmd); + return Wire.endTransmission() == 0; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +bool P167_data_struct::writeCmd(uint16_t cmd, uint8_t value) { + Wire.beginTransmission(_i2caddr); + Wire.write((uint8_t)(cmd >> 8)); + Wire.write((uint8_t)cmd); + Wire.write((uint8_t)value); + return Wire.endTransmission() == 0; +} + +bool P167_data_struct::writeCmd(uint16_t cmd, uint8_t length, uint8_t *buffer) { + Wire.beginTransmission(_i2caddr); + Wire.write((uint8_t)(cmd >> 8)); + Wire.write((uint8_t)cmd); + + for (int i = 0; i < length; ++i) { + Wire.write(*(buffer + i)); + } + return Wire.endTransmission() == 0; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +bool P167_data_struct::readBytes(uint8_t n, uint8_t *val, uint8_t maxDuration) { + // TODO check if part can be delegated to the I2C_access libraray from ESPeasy + Wire.requestFrom(_i2caddr, (uint8_t)n); + uint32_t start = millis(); + + while (Wire.available() < n) { + if (timePassedSince(start) > maxDuration) { + return false; + } + yield(); + } + + for (uint8_t i = 0; i < n; i++) { + val[i] = Wire.read(); + } + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Read data ready flag from device +bool P167_data_struct::readDataRdyFlag() { + uint8_t value = 0; + uint8_t buffer[3]; + + if (!readBytes(3, (uint8_t *)&buffer[0], P167_READ_DATA_RDY_FLAG_DELAY)) { + return false; + } + + if (calc_CRC8(&buffer[0], 2) == buffer[2]) { + value += buffer[1]; + } + return value; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Read measurement values results from device +bool P167_data_struct::readMeasValue() { + uint16_t value = 0; + int16_t valuesign = 0; + uint8_t buffer[24]; + bool condition = true; + + _errmeas = false; + + if (!readBytes(24, (uint8_t *)&buffer[0], P167_READ_MEAS_DELAY)) { + _errmeas = true; + return false; + } + + String log = F("SEN5x: Measured value "); + + for (int xx = 0; xx < 24; ++xx) { + log += buffer[xx]; + log += ' '; + } + + if ((_model == P167_model::Vindstyrka) || (_model == P167_model::SEN54)) { + condition = (buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || + (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF) || + (buffer[12] == 0xFF && buffer[13] == 0xFF) || + (buffer[15] == 0xFF && buffer[16] == 0xFF) || (buffer[18] == 0xFF && buffer[19] == 0xFF); + } + + if (_model == P167_model::SEN55) { + condition = (buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || + (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF) || + (buffer[12] == 0xFF && buffer[13] == 0xFF) || + (buffer[15] == 0xFF && buffer[16] == 0xFF) || (buffer[18] == 0xFF && buffer[19] == 0xFF) || + (buffer[21] == 0xFF && buffer[22] == 0xFF); + } + + if (condition) { + log += F("- error"); + + if (_enableLogging) { + addLog(LOG_LEVEL_ERROR, log); + } + _errmeas = true; + _readingerrcount++; + return false; + } else { + for (int xx = 0; xx < 8; ++xx) { + if ((calc_CRC8(&buffer[xx * 3], 2) == buffer[xx * 3 + 2]) && ((buffer[xx * 3] != 0xFF) || (buffer[xx * 3 + 1] != 0xFF))) { + value = buffer[xx * 3] << 8; + value += buffer[xx * 3 + 1]; + valuesign = buffer[xx * 3] << 8; + valuesign += buffer[xx * 3 + 1]; + + if (xx == 0) { + _PM1p0 = value / 10.0f; + } + + if (xx == 1) { + _PM2p5 = value / 10.0f; + } + + if (xx == 2) { + _PM4p0 = value / 10.0f; + } + + if (xx == 3) { + _PM10p0 = value / 10.0; + } + + if (xx == 4) { + _Humidity = valuesign / 100.0f; + } + + if (xx == 5) { + _Temperature = valuesign / 200.0f; + } + + if (xx == 6) { + _tVOC = valuesign / 10.0f; + } + + if (xx == 7) + { + if (_model == P167_model::SEN55) { + _NOx = valuesign / 10.0f; + } else { + _NOx = 0.0f; + } + } + } else { + _errmeasrawmys = true; + } + } + + if (_errmeas) { + log += F("- crc error"); + _readingerrcount++; + } else { + log += F("- pass"); + _readingsuccesscount++; + + if (_enableLogging) { + addLog(LOG_LEVEL_INFO, log); + } + return !_errmeas; + } + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Read measurement values results from device +bool P167_data_struct::readMeasRawValue() { + uint16_t value = 0; + int16_t valuesign = 0; + uint8_t buffer[12]; + bool condition = true; + + _errmeasraw = false; + + if (!readBytes(12, &buffer[0], P167_READ_RAW_MEAS_DELAY)) { + _errmeasraw = true; + return false; + } + + String log = F("SEN5x: Measured RAW value 0x"); + + for (int xx = 0; xx < 12; xx++) { + log += strformat(F("%02x "), buffer[xx]); + } + + if ((_model == P167_model::Vindstyrka) || (_model == P167_model::SEN54)) { + condition = (buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || + (buffer[6] == 0xFF && buffer[7] == 0xFF); // || (buffer[9] == 0xFF && buffer[10] == 0xFF)) + } + + if (_model == P167_model::SEN55) { + condition = (buffer[0] == 0xFF && buffer[1] == 0xFF) || (buffer[3] == 0xFF && buffer[4] == 0xFF) || + (buffer[6] == 0xFF && buffer[7] == 0xFF) || (buffer[9] == 0xFF && buffer[10] == 0xFF); + } + + if (condition) { + log += F("- error"); + + if (_enableLogging) { + addLog(LOG_LEVEL_ERROR, log); + } + _errmeasraw = true; + _readingerrcount++; + return false; + } else { + for (int xx = 0; xx < 4; ++xx) { + if ((calc_CRC8(&buffer[xx * 3], 2) == buffer[xx * 3 + 2]) && ((buffer[xx * 3] != 0xFF) || (buffer[xx * 3 + 1] != 0xFF))) { + value = buffer[xx * 3] << 8; + value += buffer[xx * 3 + 1]; + valuesign = buffer[xx * 3] << 8; + valuesign += buffer[xx * 3 + 1]; + + if (xx == 0) { + _rawHumidity = valuesign / 100.0f; + } else + + if (xx == 1) { + _rawTemperature = valuesign / 200.0f; + } else + + if (xx == 2) { + _rawtVOC = value / 10.0f; + } else + + if (xx == 3) { + if (_model == P167_model::SEN55) { + _rawNOx = value / 10.0f; + } else { + _rawNOx = 0.0f; + } + } + } else { + _errmeasrawmys = true; + } + } + + if (_errmeasraw) { + log += F("- crc error"); + _readingerrcount++; + } else { + log += F("- pass"); + _readingsuccesscount++; + } + + if (_enableLogging) { + addLog(LOG_LEVEL_INFO, log); + } + return !_errmeasraw; + } + + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Read measurement values results from device +bool P167_data_struct::readMeasRawMYSValue() { + int16_t valuesign = 0; + uint8_t buffer[9]; + + _errmeasrawmys = false; + + if (!readBytes(9, (uint8_t *)&buffer[0], P167_READ_RAW_MEAS_DELAY)) { + _errmeasrawmys = true; + return false; + } + + String log = F("SEN5x: Measured MYS value 0x"); + + for (int xx = 0; xx < 9; ++xx) { + log += strformat(F("%02x "), buffer[xx]); + } + + if (((buffer[0] == 0xFF) && (buffer[1] == 0xFF)) || + ((buffer[3] == 0xFF) && (buffer[4] == 0xFF)) || + ((buffer[6] == 0xFF) && (buffer[7] == 0xFF))) { + log += F("- error"); + + if (_enableLogging) { + addLog(LOG_LEVEL_ERROR, log); + } + _errmeasrawmys = true; + _readingerrcount++; + return false; + } else { + for (int xx = 0; xx < 3; ++xx) { + if ((calc_CRC8(&buffer[xx * 3], 2) == buffer[xx * 3 + 2]) && ((buffer[xx * 3] != 0xFF) || (buffer[xx * 3 + 1] != 0xFF))) { + valuesign = buffer[xx * 3] << 8; + valuesign += buffer[xx * 3 + 1]; + + if (xx == 0) { + _mysHumidity = valuesign / 100.0f; + } else + + if (xx == 1) { + _mysTemperature = valuesign / 200.0f; + } else + + if (xx == 2) { + _mysOffset = valuesign / 200.0f; + } + } else { + _errmeasrawmys = true; + } + } + + if (_errmeasrawmys) { + log += F("- crc error"); + _readingerrcount++; + } else { + log += F("- pass"); + _readingsuccesscount++; + } + + if (_enableLogging) { + addLog(LOG_LEVEL_INFO, log); + } + return !_errmeasrawmys; + } + + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Calculate DewPoint, F Temp, F HUM +bool P167_data_struct::calculateValue() { + if (_model == P167_model::Vindstyrka) { + _TemperatureX = _mysTemperature + _mysOffset - 2.4f; // (2.4 - temperature offset because enclosure and esp8266 power disipation) + + + // version formula with interpolation + _HumidityX = _Humidity + (_TemperatureX - _Temperature) * ((_rawHumidity - _Humidity) / (_rawTemperature - _Temperature)); + + _DewPoint = compute_dew_point_temp(_TemperatureX, _HumidityX); + + if (_HumidityX < 0.0f) { + _HumidityX = 0.0f; + } + + if (_HumidityX > 100.0f) { + _HumidityX = 100.0f; + } + } else { + _DewPoint = compute_dew_point_temp(_Temperature, _Humidity); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Retrieve SEN5x identification code +// Sensirion_SEN5x +bool P167_data_struct::getProductName() { + String prodname; + uint8_t buffer[48]; + + // writeCmd(P167_READ_PROD_NAME); + if (!readBytes(48, (uint8_t *)buffer, P167_READ_PROD_NAME_DELAY)) { + return false; + } + + for (uint8_t i = 1; i <= 16; ++i) { + if (calc_CRC8(&buffer[i * 3 - 3], 2) == buffer[i * 3 - 1]) { + if (buffer[i * 3 - 3] < 32) { + break; + } + prodname += char(buffer[i * 3 - 3]); + + if (buffer[i * 3 - 2] < 32) { + break; + } + prodname += char(buffer[i * 3 - 2]); + } + } + _eid_productname = prodname; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("SEN5x: Product name: "), prodname)); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Retrieve SEN54 Serial Number +bool P167_data_struct::getSerialNumber() { + String serno; + uint8_t buffer[48]; + + // writeCmd(P167_READ_SERIAL_NO); + if (!readBytes(48, (uint8_t *)buffer, P167_READ_SERIAL_NO_DELAY)) { + return false; + } + + for (uint8_t i = 1; i <= 16; ++i) { + if (calc_CRC8(&buffer[i * 3 - 3], 2) == buffer[i * 3 - 1]) { + if (buffer[i * 3 - 3] < 32) { + break; + } + serno += char(buffer[i * 3 - 3]); + + if (buffer[i * 3 - 2] < 32) { + break; + } + serno += char(buffer[i * 3 - 2]); + } + } + _eid_serialnumber = serno; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, concat(F("SEN5x: Serial number: "), serno)); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Retrieve SEN54 Firmware version from device +bool P167_data_struct::getFirmwareVersion() { + uint8_t version = 0; + uint8_t read_data[3]; + + // writeCmd(P167_READ_FIRM_VER); + if (!readBytes(3, (uint8_t *)&read_data, P167_READ_FIRM_VER_DELAY)) { + return false; + } + + if (read_data[2] == calc_CRC8(&read_data[0], 2)) { + version = read_data[0]; + } + _firmware = version; + + addLog(LOG_LEVEL_INFO, strformat(F("SEN5x: Firmware version: %d"), version)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Retrieve SEN54 Device Status from device +bool P167_data_struct::readDeviceStatus() { + uint32_t value = 0; + uint8_t bufferstatus[6]; + + // writeCmd(P167_READ_FIRM_VER); + _errdevicestatus = false; + + if (!readBytes(6, (uint8_t *)&bufferstatus, P167_READ_DEVICE_STATUS_DELAY)) { + _errdevicestatus = true; + return false; + } + + String log = F("SEN5x: device status 0x"); + + for (int xx = 0; xx < 6; ++xx) { + log += strformat(F("%02x "), bufferstatus[xx]); + } + + if ((calc_CRC8(&bufferstatus[0], 2) == bufferstatus[2]) && (calc_CRC8(&bufferstatus[3], 2) == bufferstatus[5])) { + value = bufferstatus[0] << 24; + value += bufferstatus[1] << 16; + value += bufferstatus[3] << 8; + value += bufferstatus[4] << 0; + _devicestatus.val = value; + } else { + _errdevicestatus = true; + } + + if (_errdevicestatus) { + log += F("- crc error"); + _readingerrcount++; + } else { + log += strformat(F(", flags: sp:%d cln:%d gas:%d rht:%d las:%d fan:%d - pass"), + _devicestatus.speed, + _devicestatus.autoclean, + _devicestatus.gas, + _devicestatus.rht, + _devicestatus.laser, + _devicestatus.fan); + _readingsuccesscount++; + } + + if (_enableLogging) { + addLog(LOG_LEVEL_INFO, log); + } + return !_errdevicestatus; +} + +uint16_t P167_data_struct::getErrCode(bool _clear) { + uint16_t _tmp = _readingerrcode; + + if (_clear == true) { + clearErrCode(); + } + return _tmp; +} + +uint16_t P167_data_struct::getErrCount(bool _clear) { + uint16_t _tmp = _readingerrcount; + + if (_clear == true) { + clearErrCount(); + } + return _tmp; +} + +uint16_t P167_data_struct::getSuccCount(bool _clear) { + uint16_t _tmp = _readingsuccesscount; + + if (_clear == true) { + clearSuccCount(); + } + return _tmp; +} + +void P167_data_struct::clearErrCode() { + _readingerrcode = VIND_ERR_NO_ERROR; +} + +void P167_data_struct::clearErrCount() { + _readingerrcount = 0; +} + +void P167_data_struct::clearSuccCount() { + _readingsuccesscount = 0; +} + +void P167_data_struct::checkPin_interrupt() { + monpinValue = monpinValue + 1; // volatile + monpinLastTransitionTime = getMicros64(); + + // Mark pin value changed + monpinChanged = false; + + if (monpinValue != monpinValuelast) { + monpinChanged = true; + } +} + +void P167_data_struct::setLogging(bool logStatus) { + _enableLogging = logStatus; +} + +void P167_data_struct::startCleaning() { + writeCmd(P167_START_FAN_CLEAN); // Don't wait for a response, as the command causes the fan to run for 10 seconds +} + +// When using interrupts we have to call the library entry point +// whenever an interrupt is triggered +void P167_data_struct::Plugin_167_interrupt(P167_data_struct *self) { + // addLog(LOG_LEVEL_ERROR, F("********* SEN5X: interrupt apear!")); + if (self) { + self->checkPin_interrupt(); + } +} + +#endif // USES_P167 diff --git a/src/src/PluginStructs/P167_data_struct.h b/src/src/PluginStructs/P167_data_struct.h new file mode 100644 index 000000000..dbb79ac0f --- /dev/null +++ b/src/src/PluginStructs/P167_data_struct.h @@ -0,0 +1,276 @@ +#ifndef PLUGINSTRUCTS_P167_DATA_STRUCT_H +#define PLUGINSTRUCTS_P167_DATA_STRUCT_H + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// P167 device class for IKEA Vindstyrka SEN54 temperature , humidity and air quality sensors +// See datasheet https://sensirion.com/media/documents/6791EFA0/62A1F68F/Sensirion_Datasheet_Environmental_Node_SEN5x.pdf +// and info about extra request https://sensirion.com/media/documents/2B6FC1F3/6409E74A/PS_AN_Read_RHT_VOC_and_NOx_RAW_signals_D1.pdf +// Based upon code from Rob Tillaart, Viktor Balint, https://github.com/RobTillaart/SHT2x +// Rewritten and adapted for ESPeasy by andibaciu and tonhuisman +// changelog in _P167_Vindstyrka.ino +////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../../_Plugin_Helper.h" +#include "../ESPEasyCore/ESPEasyGPIO.h" +#ifdef USES_P167 + +# ifdef LIMIT_BUILD_SIZE +# define PLUGIN_167_DEBUG false +# else // ifdef LIMIT_BUILD_SIZE +# define PLUGIN_167_DEBUG true // set to true for extra log info in the debug +# endif // ifdef LIMIT_BUILD_SIZE + + +// ------------------------------------------------------------------------------ +# define VIND_ERR_NO_ERROR 0 // no error +# define VIND_ERR_CRC_ERROR 1 // crc error +# define VIND_ERR_WRONG_BYTES 2 // bytes b0,b1 or b2 wrong +# define VIND_ERR_NOT_ENOUGHT_BYTES 3 // not enough bytes from sdm +# define VIND_ERR_TIMEOUT 4 // timeout +// ------------------------------------------------------------------------------ + +// Make accessing specific parameters more readable in the code +# define P167_ENABLE_LOG PCONFIG(0) +# define P167_ENABLE_LOG_LABEL F("enlg") +# define P167_MODEL PCONFIG(1) +# define P167_MODEL_LABEL F("mdl") +# define P167_MON_SCL_PIN PCONFIG(2) +# define P167_QUERY1 PCONFIG(3) +# define P167_QUERY2 PCONFIG(4) +# define P167_QUERY3 PCONFIG(5) +# define P167_QUERY4 PCONFIG(6) + + +# define P167_I2C_ADDRESS_DFLT 0x69 +# define P167_MON_SCL_PIN_DFLT 13 +# define P167_MODEL_DFLT P167_MODEL_VINDSTYRKA // Vindstyrka or SEN54 or SEN55 +# define P167_QUERY1_DFLT 0 // Temperature (C) +# define P167_QUERY2_DFLT 1 // Humidity (%) +# define P167_QUERY3_DFLT 5 // PM2.5 (ug/m3) +# define P167_QUERY4_DFLT 2 // tVOC (index) + + +# define P167_NR_OUTPUT_OPTIONS 9 +# define P167_QUERY1_CONFIG_POS 3 +# define P167_SENSOR_TYPE_INDEX (P167_QUERY1_CONFIG_POS + VARS_PER_TASK) +# define P167_NR_OUTPUT_VALUES getValueCountFromSensorType(static_cast(PCONFIG(P167_SENSOR_TYPE_INDEX))) +# define P167_MAX_ATTEMPT 3 // Number of tentative before declaring NAN value +# define P167_VALUE_COUNT 9 // Number of available values + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// Access to the Vindstyrka device is mainly by sequencing a Final State Machine +enum class P167_state : uint8_t { + Uninitialized = 0, // Initial state, unknown status of sensor device + Wait_for_reset, // Reset being performed + Read_firm_version, // Reading firmware version + Read_prod_name, // Reading production + Read_serial_no, // Reading serial number + Write_user_reg, // Write the user register + Initialized, // Initialization completed + Ready, // Aquisition request is pending, ready to measure + Wait_for_start_meas, // Start measurement started + Wait_for_read_flag, // Read meas flag started + Wait_for_read_meas, // Read meas started + Wait_for_read_raw_meas, // RAW Read meas started + Wait_for_read_raw_MYS_meas, // RAW Read meas MYSTERY started + Wait_for_read_status, // Read status + cmdSTARTmeas, // send command START meas to leave SEN5x ready flag for Vindstyrka + IDLE, // Sensor device in IDLE mode + New_Values_Available, // Acqusition finished, new data available + Error // Sensor device cannot be accessed or in error +}; + + +enum class P167_statusinfo : uint8_t { + sensor_speed = 0, + sensor_autoclean, + sensor_gas, + sensor_rht, + sensor_laser, + sensor_fan +}; + +enum class P167_model : uint8_t { + Vindstyrka = 0u, + SEN54 = 1u, + SEN55 = 2u, +}; + + +# define P167_MODEL_VINDSTYRKA static_cast(P167_model::Vindstyrka) +# define P167_MODEL_SEN54 static_cast(P167_model::SEN54) +# define P167_MODEL_SEN55 static_cast(P167_model::SEN55) + +const __FlashStringHelper* toString(P167_model model); + +const __FlashStringHelper* P167_getQueryString(uint8_t query); +const __FlashStringHelper* P167_getQueryValueString(uint8_t query); + + +////////////////////////////////////////////////////////////////////////////////////////////////// +// ESPeasy standard PluginTaskData structure for this plugin +struct P167_data_struct : public PluginTaskData_base { +public: + + P167_data_struct(); + + ~P167_data_struct(); + + void IRAM_ATTR checkPin_interrupt(void); + static void IRAM_ATTR Plugin_167_interrupt(P167_data_struct *self); + + ///////////////////////////////////////////////////////// + // This method runs the FSM step by step on each call + // Returns true when a stable state is reached + bool update(); + bool monitorSCL(); + + ///////////////////////////////////////////////////////// + // (re)configure the device properties + // This will result in resetting and reloading the device + bool setupDevice(uint8_t i2caddr); + bool setupModel(P167_model model); + bool setupMonPin(int16_t monpin); + void disableInterrupt_monpin(void); + + void setLogging(bool logStatus); + + ///////////////////////////////////////////////////////// + // check sensor is reachable over I2C + bool isConnected() const; + + ///////////////////////////////////////////////////////// + bool newValues() const; + + ///////////////////////////////////////////////////////// + bool inError() const; + + ///////////////////////////////////////////////////////// + // Reset the FSM to initial state + bool reset(); + + ///////////////////////////////////////////////////////// + // Trigger a measurement cycle + // Only perform the measurements with big interval to prevent the sensor from warming up. + bool startMeasurements(); + + bool getStatusInfo(P167_statusinfo param); + + ///////////////////////////////////////////////////////// + // Electronic Identification Code + // Sensirion_Humidity_SHT2x_Electronic_Identification_Code_V1.1.pdf + // Electronic ID bytes + bool getEID(String & eid_productname, + String & eid_serialnumber, + uint8_t& firmware) const; + + ///////////////////////////////////////////////////////// + // Temperature, humidity, DewPoint, PMxpy retrieval + // Note: values are fetched from memory and reflect latest succesful read cycle + float getRequestedValue(uint8_t request) const; + + + uint16_t getErrCode(bool _clear = false); // return last errorcode (optional clear this value, default false) + uint16_t getErrCount(bool _clear = false); // return total errors count (optional clear this value, default false) + uint16_t getSuccCount(bool _clear = false); // return total success count (optional clear this value, default false) + void clearErrCode(); // clear last errorcode + void clearErrCount(); // clear total errors count + void clearSuccCount(); // clear total success count + void startCleaning(); // Start a fan cleaning session. + +private: + + union devicestatus + { + uint32_t val; + struct + { + uint32_t dummy4 : 4; // bit 0..3 + uint32_t fan : 1; // bit 4 + uint32_t laser : 1; // bit 5 + uint32_t rht : 1; // bit 6 + uint32_t gas : 1; // bit 7 + uint32_t dummy3 : 11; // bit 8..18 + uint32_t autoclean : 1; // bit 19 + uint32_t dummy2 : 1; // bit 20 + uint32_t speed : 1; // bit 21 + uint32_t dummy1 : 10; // bit 22..31 + }; + }; + + devicestatus _devicestatus; + P167_state _state; + + bool writeCmd(uint16_t cmd); + bool writeCmd(uint16_t cmd, + uint8_t value); + bool writeCmd(uint16_t cmd, + uint8_t length, + uint8_t *buffer); + bool readBytes(uint8_t n, + uint8_t *val, + uint8_t maxDuration); + + bool readMeasValue(); + bool readMeasRawValue(); + bool readMeasRawMYSValue(); + bool readDataRdyFlag(); + bool readDeviceStatus(); + bool calculateValue(); + + bool getProductName(); + bool getSerialNumber(); + bool getFirmwareVersion(); + + + float _Humidity = 0.0f; // Humidity as fetched from the device [bits] + float _HumidityX = 0.0f; // Humidity as calculated + float _Temperature = 0.0f; // Temperature as fetched from the device [bits] + float _TemperatureX = 0.0f; // Temperature as calculated + float _DewPoint = 0.0f; // DewPoint as calculated + float _rawHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] + float _rawTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] + float _mysHumidity = 0.0f; // Humidity as fetched from the device without compensation[bits] + float _mysTemperature = 0.0f; // Temperature as fetched from the device without compensation[bits] + float _tVOC = 0.0f; // tVOC as fetched from the device[bits] + float _NOx = 0.0f; // NOx as fetched from the device[bits] + float _rawtVOC = 0.0f; // tVOC as fetched from the device without compensation[bits] + float _rawNOx = 0.0f; // NOx as fetched from the device without compensation[bits] + float _mysOffset = 0.0f; // Temperature Offset fetched from the device[bits] + float _PM1p0 = 0.0f; // PM1.0 as fetched from the device[bits] + float _PM2p5 = 0.0f; // PM2.5 as fetched from the device[bits] + float _PM4p0 = 0.0f; // PM4.0 as fetched from the device[bits] + float _PM10p0 = 0.0f; // PM10.0 as fetched from the device[bits] + P167_model _model = P167_model::Vindstyrka; // Selected sensor model + uint8_t _i2caddr = 0; // Programmed I2C address + uint8_t _monpin = 0; // Pin to monitor I2C SCL to find when VindStyrka finish i2c communication + unsigned long _last_action_started = 0; // Timestamp for last action that takes processing time + uint16_t _errCount = 0; // Number of errors since last successful access + String _eid_productname; // Electronic Device ID - Product Name, read at initialization + String _eid_serialnumber; // Electronic Device ID - Serial Number, read at initialization + uint8_t _firmware = 0; // Firmware version numer, read at initialization + uint8_t _userreg = 0; // TODO debugging only + uint16_t _readingerrcode = VIND_ERR_NO_ERROR; // 4 = timeout; 3 = not enough bytes; 2 = number of bytes OK but bytes b0,b1 + // or b2 wrong, 1 = crc error + uint16_t _readingerrcount = 0; // total errors couter + uint32_t _readingsuccesscount = 0; // total success couter + uint8_t stepMonitoring = 0; // step for Monitorin SCL pin algorithm + bool _errmeas = false; + bool _errmeasraw = false; + bool _errmeasrawmys = false; + bool _errdevicestatus = false; + bool startMonitoringFlag = false; // flag to START/STOP Monitoring algorithm + bool statusMonitoring = false; // flag for status return from Monitoring algorithm + bool _enableLogging = false; // flag for enabling some technical logging + unsigned long lastSCLLowTransitionMonitoringTime = 0; // last time when SCL i2c pin rising + + volatile uint32_t monpinValue = 0; + volatile uint32_t monpinValuelast = 0; + volatile uint8_t monpinChanged = 0; + volatile uint64_t monpinLastTransitionTime = 0; +}; +#endif // USES_P167 + +#endif // ifndef PLUGINSTRUCTS_P167_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P168_data_struct.cpp b/src/src/PluginStructs/P168_data_struct.cpp new file mode 100644 index 000000000..07ff87d71 --- /dev/null +++ b/src/src/PluginStructs/P168_data_struct.cpp @@ -0,0 +1,91 @@ +#include "../PluginStructs/P168_data_struct.h" + +#ifdef USES_P168 + +# include "../Helpers/CRC_functions.h" + +/************************************************************************** +* Constructor +**************************************************************************/ +P168_data_struct::P168_data_struct(uint8_t alsGain, + uint8_t alsIntegration, + uint8_t psmMode, + uint8_t readMethod) : + _als_gain(alsGain), _als_integration(alsIntegration), _psm_mode(psmMode), _readMethod(readMethod), initialized(false) +{} + +P168_data_struct::~P168_data_struct() { + delete veml; +} + +bool P168_data_struct::init(struct EventStruct *event) { + veml = new (std::nothrow) Adafruit_VEML7700(); + + // - Read sensor serial number + if ((nullptr != veml) && + veml->begin(P168_I2C_ADDRESS)) { + // Set config & start sensor + veml->setGain(_als_gain); + veml->setIntegrationTime(_als_integration); + veml->setPowerSaveMode(_psm_mode); + veml->enable(true); + + addLog(LOG_LEVEL_INFO, F("VEML : 6030/7700 Initialized.")); + + initialized = true; + } else { + addLog(LOG_LEVEL_ERROR, F("VEML : 6030/7700 Init ERROR.")); + } + + return isInitialized(); +} + +/***************************************************** +* plugin_read +*****************************************************/ +bool P168_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isInitialized() && veml->readReady()) { + uint16_t amb = veml->readALS(); + float lux = veml->readLux(static_cast(_readMethod)); + uint16_t whi = veml->readWhite(); + + if (luxMethod::VEML_LUX_AUTO == static_cast(_readMethod)) { + addLog(LOG_LEVEL_INFO, strformat(F("VEML : 6030/7700 AutoLux, Lux: %.2f, Gain: %.3f, Integration: %d"), + lux, veml->getGainValue(), veml->getIntegrationTimeValue())); + } + UserVar.setFloat(event->TaskIndex, 0, lux); + UserVar.setFloat(event->TaskIndex, 1, whi); + UserVar.setFloat(event->TaskIndex, 2, amb); + + success = true; + } + + return success; +} + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P168_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + bool success = false; + + if (isInitialized()) { + const String val = parseString(string, 1, '.'); + + if (equals(val, F("gain"))) { + string = toString(veml->getGainValue(), 3); + success = true; + } else + if (equals(val, F("integration"))) { + string = veml->getIntegrationTimeValue(); + success = true; + } + } + + return success; +} + +#endif // ifdef USES_P168 diff --git a/src/src/PluginStructs/P168_data_struct.h b/src/src/PluginStructs/P168_data_struct.h new file mode 100644 index 000000000..f9a734a29 --- /dev/null +++ b/src/src/PluginStructs/P168_data_struct.h @@ -0,0 +1,55 @@ +#ifndef PLUGINSTRUCTS_P168_DATA_STRUCT_H +#define PLUGINSTRUCTS_P168_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P168 + +# include + +# define P168_I2C_ADDRESS PCONFIG(0) +# define P168_ALS_GAIN PCONFIG(1) +# define P168_ALS_INTEGRATION PCONFIG(2) +# define P168_PSM_MODE PCONFIG(3) +# define P168_READLUX_MODE PCONFIG(4) + +enum class P168_power_save_mode_e : uint8_t { + Disabled = 4, + Mode1 = 0, + Mode2 = 1, + Mode3 = 2, + Mode4 = 3, +}; + +struct P168_data_struct : public PluginTaskData_base { +public: + + P168_data_struct(uint8_t alsGain, + uint8_t alsIntegration, + uint8_t psmMode, + uint8_t readMethod); + + P168_data_struct() = delete; + virtual ~P168_data_struct(); + + bool init(struct EventStruct *event); + bool plugin_read(struct EventStruct *event); + bool plugin_get_config_value(struct EventStruct *event, + String & string); + bool isInitialized() const { + return initialized; + } + +private: + + Adafruit_VEML7700 *veml = nullptr; + + uint8_t _als_gain; + uint8_t _als_integration; + uint8_t _psm_mode; + uint8_t _readMethod; + + bool initialized = false; +}; + +#endif // ifdef USES_P168 +#endif // ifndef PLUGINSTRUCTS_P168_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P169_data_struct.cpp b/src/src/PluginStructs/P169_data_struct.cpp new file mode 100644 index 000000000..9f29c7efb --- /dev/null +++ b/src/src/PluginStructs/P169_data_struct.cpp @@ -0,0 +1,943 @@ +#include "../PluginStructs/P169_data_struct.h" + +#ifdef USES_P169 + +# include "../ESPEasyCore/ESPEasyGPIO.h" + +# include + + +# ifndef CORE_POST_3_0_0 +# ifdef ESP8266 +# define IRAM_ATTR ICACHE_RAM_ATTR +# endif // ifdef ESP8266 +# endif // ifndef CORE_POST_3_0_0 + +# define P169_AS3935_TIMEOUT_USEC 2000 + + +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) { + _sensor = new (std::nothrow) AS3935I2C(P169_I2C_ADDRESS, P169_IRQ_PIN); + } +} + +P169_data_struct::~P169_data_struct() +{ + if (_sensor != nullptr) { + _sensor->writePowerDown(true); + delete _sensor; + _sensor = nullptr; + } +} + +bool P169_data_struct::loop(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + if (_sensor->getInterruptMode() == AS3935MI::AS3935_INTERRUPT_NORMAL) { + // FIXME TD-er: Should also check for state of IRQ pin as it may still be high if the interrupt souce isn't checked. + const uint32_t timestamp = _sensor->getInterruptTimestamp(); + + if ((timestamp != 0ul) || DIRECT_pinRead(_irqPin)) { + if ((timestamp != 0ul) && (timePassedSince(timestamp) < 2)) { + // Check to make sure the sensor isn't still outputting a high freq. signal to the interrupt pin. + // Count should be 1 at most. + if (!_sensor->checkProperlySetToListenMode()) { + // Sensor not yet ready to report some data + addLog(LOG_LEVEL_ERROR, F("AS3935: Sensor was still showing LCO frequency on IRQ pin")); + return false; + } + + // Wait for the sensor to be ready to read the interrupt source + if (timePassedSince(_sensor->getInterruptTimestamp()) < 2) { + delay(1); + } + } + + // query the interrupt source from the AS3935 + switch (_sensor->readInterruptSource()) { + case AS3935MI::AS3935_INT_NH: + + // Noise floor too high + adjustForNoise(event); + break; + case AS3935MI::AS3935_INT_D: + + // Disturbance detected + // N.B. can be disabled with _sensor->writeMaskDisturbers(true); + adjustForDisturbances(event); + break; + case AS3935MI::AS3935_INT_L: + { + // Lightning detected + ++_lightningCount; + + // FIXME TD-er: What to do with the "Lightning Threshold" ? + // If it was > 15 minutes ago since the last detected lightning, + // or cleared statistics, then we should set _lightningCount to this + // threshold value and also increment the total counter accordingly. + + const int totalStrikes = UserVar.getFloat(event->TaskIndex, 3) + 1; + const uint32_t energy = getEnergy(); + + if (energy > _highestEnergy) { _highestEnergy = energy; } + + if (energy < _lowestEnergy) { _lowestEnergy = energy; } + UserVar.setFloat(event->TaskIndex, 0, computeDistanceFromEnergy(_highestEnergy, NAN)); + UserVar.setFloat(event->TaskIndex, 1, computeDistanceFromEnergy(_lowestEnergy, NAN)); + UserVar.setFloat(event->TaskIndex, 2, _lightningCount); + UserVar.setFloat(event->TaskIndex, 3, totalStrikes); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Lightning detected. DistNear: %.1f, DistFar: %.1f, Count: %u, Total: %d"), + computeDistanceFromEnergy(_highestEnergy, -1.0f), + computeDistanceFromEnergy(_lowestEnergy, -1.0f), + _lightningCount, + totalStrikes)); + } + + if (Settings.UseRules) { + // Lightning detected, Send event + // Eventvalues: + // - Distance + // - Energy + // - Lightning count since last PLUGIN_READ + // - Total Lightning count since this was reset (or power cycle of ESP) + eventQueue.addMove( + strformat( + F("%s#LightningDetected=%.1f,%.1f,%u,%d"), + getTaskDeviceName(event->TaskIndex).c_str(), + computeDistanceFromEnergy(_highestEnergy, -1.0f), + computeDistanceFromEnergy(_lowestEnergy, -1.0f), + _lightningCount, + totalStrikes)); + } + + return true; + } + case AS3935MI::AS3935_INT_DUPDATE: + { + // FIXME TD-er: No longer needed? + + + // Distance updated + const float distance = getDistance(); + + if (_lightningCount > 0) { + // Do not update until after this task interval or else the reported distance will be too low. + UserVar.setFloat(event->TaskIndex, 0, distance); + } else { + _sensor->clearStatistics(); + } + + if (Settings.UseRules) { + // Distance updated, Send event + // Eventvalue: + // - Distance + eventQueue.addMove( + strformat( + F("%s#DistanceUpdated=%.2f"), + getTaskDeviceName(event->TaskIndex).c_str(), + distance)); + } + break; + } + } + } + tryIncreasedSensitivity(event); + } + return false; +} + +void P169_data_struct::html_show_sensor_info(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + addFormSubHeader(F("Current Sensor Data")); + addRowLabel(F("Calibration")); + const int8_t ant_cap = _sensor->getCalibratedAntCap(); + + if (ant_cap != -1) { + const float deviation_pct = computeDeviationPct(_sensor->getAntCapFrequency(ant_cap)); + + if (fabs(deviation_pct) < (100 * AS3935MI_ALLOWED_DEVIATION)) { + addEnabled(true); + } else { + if (P169_GET_TOLERANT_CALIBRATION_RANGE) { + addEnabled(true); + addHtml(F(HTML_SYMBOL_WARNING)); + } else { + addEnabled(false); + } + } + + addRowLabel(F("Best Antenna cap")); + addHtml(strformat(F("%d (%.2f%%)"), ant_cap, deviation_pct)); + } else { + addEnabled(false); + } + + addRowLabel(F("Error % per cap")); +# if FEATURE_CHART_JS + addCalibrationChart(event); +# else // if FEATURE_CHART_JS + + for (uint8_t i = 0; i < 16; ++i) { + const int32_t freq = _sensor->getAntCapFrequency(i); + + if (i != 0) { + addHtml(','); + addHtml(' '); + } + + if (freq > 0) { + addHtml(strformat(F("%.2f%%"), computeDeviationPct(freq))); + } else { + addHtml('-'); + } + } +# endif // if FEATURE_CHART_JS + + addRowLabel(F("Current AFE gain")); + addHtmlFloat(_afeGain, 2); + addHtml('x'); + + addRowLabel(F("Current Noise Floor Threshold")); + addHtmlInt(_sensor->readNoiseFloorThreshold()); + + addRowLabel(F("Current Watchdog Threshold")); + addHtmlInt(_sensor->readWatchdogThreshold()); + + addRowLabel(F("Current Spike Rejection")); + addHtmlInt(_sensor->readSpikeRejection()); +} + +bool P169_data_struct::plugin_init(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + if (!(_sensor->begin() && _sensor->checkConnection())) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: Sensor not detected")); + return false; + } + addLog(LOG_LEVEL_INFO, F("AS3935: Sensor detected")); + + /* + if (!_sensor->checkIRQ()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: IRQ pin connection check failed")); + + // return false; + } + */ + + calibrate(event); + +# ifdef ESP32 + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) + { + // Short test checking effect of nr samples during calibration + { + String log = F("AS3935: Calibration test: "); + + for (size_t i = 0; i < 7; ++i) { + const uint32_t nrSamples = 2048 >> i; + log += strformat(F(",%d samples"), nrSamples); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + + for (int antcap = 0; antcap < 16; ++antcap) { + float deviation_pct[7]{}; + + for (size_t i = 0; i < 7; ++i) { + const uint32_t nrSamples = 2048 >> i; + _sensor->setFrequencyMeasureNrSamples(nrSamples); + const uint32_t freq = _sensor->measureResonanceFrequency(AS3935MI::display_frequency_source_t::LCO, antcap); + + if (freq > 0) { + deviation_pct[i] = computeDeviationPct(freq); + } else { + deviation_pct[i] = 0.0f; + } + } + String log = strformat(F("AS3935: LCO: cap %d "), antcap); + + for (size_t i = 0; i < 7; ++i) { + log += strformat(F(",%.2f%%"), deviation_pct[i]); + } + addLogMove(LOG_LEVEL_DEBUG, log); + } + } +# endif // ifdef ESP32 + + + // set the analog front end gain + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + setAFE_gain(event, P169_AFE_GAIN_LOW); + { + AS3935MI::min_num_lightnings_t min_num_lightnings = AS3935MI::AS3935_MNL_1; + + if ((P169_LIGHTNING_THRESHOLD >= AS3935MI::AS3935_MNL_1) && (P169_LIGHTNING_THRESHOLD <= AS3935MI::AS3935_MNL_16)) { + min_num_lightnings = static_cast(P169_LIGHTNING_THRESHOLD); + } + _sensor->writeMinLightnings(min_num_lightnings); + } + + _sensor->writeMaskDisturbers(P169_GET_MASK_DISTURBANCE); + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + return true; +} + +const char P169_subcommands[] PROGMEM = "clearstats|calibrate|setgain|setnf|setwd|setsrej"; + +enum class P169_subcmd_e : int8_t { + invalid = -1, + clearstats = 0, + calibrate, + setgain, + setnf, // Set noise floor + setwd, // Set Watchdog Threshold + setsrej // Set Spike Rejection +}; + +/***************************************************** +* plugin_write +*****************************************************/ +bool P169_data_struct::plugin_write(struct EventStruct *event, + String & string) { + if (_sensor == nullptr) { + return false; + } + + bool success = false; + + const String command = parseString(string, 1); + + if (equals(command, F("as3935"))) { + const String subcommand = parseString(string, 2); + const int subcommand_i = GetCommandCode(subcommand.c_str(), P169_subcommands); + + if (subcommand_i < 0) { return false; } // Fail fast + + const P169_subcmd_e subcmd = static_cast(subcommand_i); + uint32_t value{}; + const bool hasValue = validUIntFromString(parseString(string, 3), value); + + switch (subcmd) { + case P169_subcmd_e::invalid: + break; + case P169_subcmd_e::clearstats: + clearStatistics(); + success = true; + break; + case P169_subcmd_e::calibrate: + calibrate(event); + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + + success = true; + break; + case P169_subcmd_e::setgain: + + if (hasValue) { + success = true; + + // First check if it is a register value or gain factor. + setAFE_gain(event, AFE_gain_to_regValue(value)); + } + break; + case P169_subcmd_e::setnf: + + if (hasValue) { + success = true; + setNoiseFloorThreshold(event, value); + } + break; + case P169_subcmd_e::setwd: + + if (hasValue) { + success = true; + _sensor->writeWatchdogThreshold(value); + sendChangeEvent(event); + } + break; + case P169_subcmd_e::setsrej: + + if (hasValue) { + success = true; + _sensor->writeSpikeRejection(value); + sendChangeEvent(event); + } + break; + } + } + return success; +} + +const char P169_get_config[] PROGMEM = "noisefloor|watchdog|srej|gain"; + +enum class P169_get_config_e : int8_t { + invalid = -1, + noisefloor = 0, // [#noisefloor] + watchdog, // [#watchdog] + srej, // [#srej] = current spike rejection + gain // [#gain] +}; + + +/***************************************************** +* plugin_get_config_value +*****************************************************/ +bool P169_data_struct::plugin_get_config_value(struct EventStruct *event, + String & string) { + if (_sensor == nullptr) { + return false; + } + + const String var = parseString(string, 1); + const int config_i = GetCommandCode(var.c_str(), P169_get_config); + + if (config_i < 0) { return false; } // Fail fast + const P169_get_config_e config = static_cast(config_i); + + switch (config) + { + case P169_get_config_e::invalid: + return false; + case P169_get_config_e::noisefloor: + string = _sensor->readNoiseFloorThreshold(); + break; + case P169_get_config_e::watchdog: + string = _sensor->readWatchdogThreshold(); + break; + case P169_get_config_e::srej: + string = _sensor->readSpikeRejection(); + break; + case P169_get_config_e::gain: + string = toString(_afeGain, 2); + break; + } + return true; +} + +float P169_data_struct::getDistance() +{ + return computeDistanceFromEnergy(getEnergy(), -1.0f); +} + +uint32_t P169_data_struct::getEnergy() +{ + if (_sensor == nullptr) { + return 0u; + } + + const uint32_t rawEnergy = _sensor->readEnergy(); + + if ((rawEnergy == 0) || (rawEnergy == 0xFFFFFFFF)) { + return 0u; + } + + const int8_t ant_cap = _sensor->getCalibratedAntCap(); + + if (ant_cap != -1) { + const float deviation_pct = computeDeviationPct(_sensor->getAntCapFrequency(ant_cap)); + + // Compute correction factor for loss in reported energy due to offset from perfect calibration. + // Formula derived by TD-er using chart on this site: (section "Is Tuning Important?") + // https://sites.google.com/view/as3935workbook/home#h.n9qonjaydsbd + const float loss = + (0.0321f * deviation_pct * deviation_pct) + + (0.0279f * deviation_pct) + + 1.0f; + + return static_cast((rawEnergy * loss) / _afeGain); + } + + // No antenna calibration present, so no compensation possible + return static_cast(rawEnergy / _afeGain); +} + +uint32_t P169_data_struct::getAndClearLightningCount() +{ + const uint32_t res = _lightningCount; + + _lightningCount = 0; + _highestEnergy = 0; + _lowestEnergy = 0xFFFFFFFF; + return res; +} + +void P169_data_struct::clearStatistics() +{ + if (_sensor != nullptr) { + _sensor->clearStatistics(); + } +} + +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) +{ + if ((energy == 0) || (energy == 0xFFFFFFFF)) { return errorValue; } + + // TD-er: Distance vs Energy attenuation is roughly X / sqrt(energy) for some factor X. + // Factor of 2100 was determined experimentally evaluating a number of thunder storms + // by Michael Gasperi, the author of this site: https://sites.google.com/view/as3935workbook/home + // Verified by TD-er comparing live data mapped on https://map.blitzortung.org/ and the sensor. + // LCO calibration offset was taken into account. + return 2100.0f / sqrtf(energy); +} + +bool P169_data_struct::calibrate(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return false; + } + + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_DETACHED); + + + // calibrate the resonance frequency. failing the resonance frequency could indicate an issue + // of the sensor. + int32_t frequency = 0; + + _sensor->setCalibrateAllAntCap(P169_GET_SLOW_LCO_CALIBRATION); + + _sensor->setFrequencyMeasureNrSamples(P169_GET_SLOW_LCO_CALIBRATION ? AS3935MI_NR_CALIBRATION_SAMPLES : (AS3935MI_NR_CALIBRATION_SAMPLES / + 2)); + + if (!_sensor->calibrateResonanceFrequency(frequency)) + { + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, + strformat(F("AS3935: Resonance Frequency Calibration failed: %d Hz not in range 482500 Hz ... 517500 Hz"), frequency)); + } + } else { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, + strformat(F("AS3935: Resonance Frequency Calibration passed: ant_cap: %d, %d Hz, deviation: %.2f%%"), + _sensor->readAntennaTuning(), + frequency, computeDeviationPct(frequency))); + } + } + + // calibrate the RCO. + if (!_sensor->calibrateRCO()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: RCO Calibration failed.")); + } else { + addLog(LOG_LEVEL_INFO, F("AS3935: RCO Calibration passed.")); + } + + // stop displaying LCO on IRQ + _sensor->displayLcoOnIrq(false); + + return frequency != 0; +} + +void P169_data_struct::adjustForNoise(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // if the noise floor threshold setting is not yet maxed out, increase the setting. + // note that noise floor threshold events can also be triggered by an incorrect + // analog front end setting. + uint8_t nf_lev{}; + + if (_sensor->increaseNoiseFloorThreshold(nf_lev)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased noise floor threshold to: %u"), nf_lev)); + sendChangeEvent(event); + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Noise floor threshold already at maximum")); + } +} + +void P169_data_struct::adjustForDisturbances(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // increasing the Watchdog Threshold and / or Spike Rejection setting improves the AS3935s resistance + // against disturbers but also decrease the lightning detection efficiency (see AS3935 datasheet) + const uint8_t wdth = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + const uint8_t noise = _sensor->readNoiseFloorThreshold(); + + if ((wdth == AS3935MI::AS3935_WDTH_5) || + (srej == AS3935MI::AS3935_SREJ_5) || + (noise == AS3935MI::AS3935_NFL_5)) + { + int32_t frequency{}; + const bool valid = _sensor->validateCurrentResonanceFrequency(frequency); + + if (valid || P169_GET_TOLERANT_CALIBRATION_RANGE) { + // Resonance frequency is still OK, try lowering gain + uint8_t curGain = _sensor->readAFE(); + + if (curGain > P169_AFE_GAIN_LOW) { + --curGain; + + // Since we change the gain, reset the other values to default + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + setAFE_gain(event, curGain); + _sense_adj_last = millis(); + } else + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + addLog(LOG_LEVEL_ERROR, strformat( + F("AS3935: Watchdog Threshold and Spike Rejection settings are already maxed out. Freq = %d"), + frequency)); + } + } else if (timePassedSince(_sense_adj_last) > static_cast(_sense_increase_interval)) + { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Calibrate Resonance freq. Current frequency: %d"), + frequency)); + } + + if (_sensor->calibrateResonanceFrequency(frequency)) { + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat( + F("AS3935: Calibrate Resonance freq. Current frequency: %d"), + frequency)); + } + + // calibrate the RCO. + if (!_sensor->calibrateRCO()) + { + addLog(LOG_LEVEL_ERROR, F("AS3935: RCO Calibration failed.")); + } else { + addLog(LOG_LEVEL_INFO, F("AS3935: RCO Calibration passed.")); + } + } + + // FIXME TD-er: Should we do anything else here? + } + _sensor->setInterruptMode(AS3935MI::AS3935_INTERRUPT_NORMAL); + } + + // FIXME TD-er: Is this a good threshold for auto adjust algorithm? + if ((wdth < AS3935MI::AS3935_WDTH_5) || + (srej < AS3935MI::AS3935_SREJ_5) + + // || (noise < AS3935MI::AS3935_NFL_5) + ) + { + _sense_adj_last = millis(); + + // alternatively increase spike rejection and watchdog threshold + if (srej < wdth) + { + if (_sensor->increaseSpikeRejection()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased spike rejection ratio to: %d"), (srej + 1))); + } + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Spike rejection ratio already at maximum")); + } + } + else + { + if (_sensor->increaseWatchdogThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Increased watchdog threshold to %d"), (wdth + 1))); + } + } + else { + addLog(LOG_LEVEL_ERROR, F("AS3935: Watchdog threshold already at maximum")); + } + } + } +} + +void P169_data_struct::tryIncreasedSensitivity(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + // increase sensor sensitivity every once in a while. _sense_increase_interval controls how quickly the code + // attempts to increase sensitivity. + if (timePassedSince(_sense_adj_last) > static_cast(_sense_increase_interval)) + { + _sense_adj_last = millis(); + + addLog(LOG_LEVEL_INFO, F("AS3935: No disturber detected, attempting to decrease noise floor threshold.")); + + const uint8_t wdth = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + const uint8_t noise = _sensor->readNoiseFloorThreshold(); + + if ((wdth == AS3935MI::AS3935_WDTH_0) || + (srej == AS3935MI::AS3935_SREJ_0) || + (noise == AS3935MI::AS3935_NFL_0)) + { + uint8_t curGain = _sensor->readAFE(); + + if (curGain < P169_AFE_GAIN_HIGH) { + ++curGain; + + // Since we change the gain, reset the other values to default + _sensor->writeNoiseFloorThreshold(AS3935MI::AS3935_NFL_2); + _sensor->writeWatchdogThreshold(AS3935MI::AS3935_WDTH_2); + _sensor->writeSpikeRejection(AS3935MI::AS3935_SREJ_2); + + setAFE_gain(event, curGain); + return; + } + } + + if ((noise > srej) && (noise > wdth) && _sensor->decreaseNoiseFloorThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased noise floor to %d"), (noise - 1))); + } + } + + // alternatively decrease spike rejection and watchdog threshold + if (srej > wdth) + { + if (_sensor->decreaseSpikeRejection()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased spike rejection ratio to %d"), (srej - 1))); + } + } + # ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_DEBUG, F("AS3935: Spike rejection ratio already at minimum")); + } + # endif // ifndef BUILD_NO_DEBUG + } + else + { + if (_sensor->decreaseWatchdogThreshold()) { + sendChangeEvent(event); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, strformat(F("AS3935: Decreased watchdog threshold to: %d"), (wdth - 1))); + } + } + # ifndef BUILD_NO_DEBUG + else { + addLog(LOG_LEVEL_DEBUG, F("AS3935: Watchdog threshold already at minimum")); + } + # endif // ifndef BUILD_NO_DEBUG + } + } +} + +void P169_data_struct::setAFE_gain(struct EventStruct *event, uint8_t gain) +{ + if (_sensor == nullptr) { + return; + } + + _afeGain = regValue_AFE_gain_toFloat(gain); + _afeGainRegval = gain; + _sensor->writeAFE(gain); + + sendChangeEvent(event); +} + +void P169_data_struct::setNoiseFloorThreshold(struct EventStruct *event, uint8_t noiseFloor) +{ + if (_sensor == nullptr) { + return; + } + + _sensor->writeNoiseFloorThreshold(noiseFloor); + sendChangeEvent(event); +} + +float P169_data_struct::regValue_AFE_gain_toFloat(uint8_t gain) +{ + // Source: https://sites.google.com/view/as3935workbook/home + float afeGain = 1.0f; + + 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" + } + return afeGain; +} + +uint8_t P169_data_struct::AFE_gain_to_regValue(float gain) +{ + { + uint8_t regval = static_cast(roundf(gain)); + + if (regval >= 10) { + if (regval <= 18) { + return regval; + } + return AS3935MI::AS3935_OUTDOORS; + } + } + + float prevAFE_Gain = regValue_AFE_gain_toFloat(10); + + if (gain < prevAFE_Gain) { + return 10; + } + + for (uint8_t regval = 11; regval <= 18; ++regval) + { + const float afeGain = regValue_AFE_gain_toFloat(regval); + + if (gain < afeGain) { + // See which is closest, prev or current + if ((gain - prevAFE_Gain) < (afeGain - gain)) { + return regval - 1; + } + return regval; + } + prevAFE_Gain = afeGain; + } + + return AS3935MI::AS3935_INDOORS; +} + +void P169_data_struct::sendChangeEvent(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + if (Settings.UseRules) { + const uint8_t noiseFloor = _sensor->readNoiseFloorThreshold(); + const uint8_t watchdog = _sensor->readWatchdogThreshold(); + const uint8_t srej = _sensor->readSpikeRejection(); + + if (((_lastEvent_noiseFloor != noiseFloor) && ((_lastEvent_noiseFloor > 1) || (noiseFloor > 1))) || + ((_lastEvent_watchdog != watchdog) && ((_lastEvent_watchdog > 1) || (watchdog > 1))) || + (_lastEvent_srej != srej) || + (_lastEvent_gain != _afeGainRegval)) { + // Some value was updated, send event + // Eventvalue: + // - Gain + // - Noise Level + // - Watchdog Threshold + // - Spike Rejection + eventQueue.addMove( + strformat( + F("%s#ParamUpdate=%.2f,%u,%u,%u"), + getTaskDeviceName(event->TaskIndex).c_str(), + _afeGain, + noiseFloor, + watchdog, + srej)); + } + + _lastEvent_noiseFloor = noiseFloor; + _lastEvent_watchdog = watchdog; + _lastEvent_srej = srej; + _lastEvent_gain = _afeGainRegval; + } +} + +# if FEATURE_CHART_JS +void P169_data_struct::addCalibrationChart(struct EventStruct *event) +{ + if (_sensor == nullptr) { + return; + } + + const int valueCount = 16; + int xAxisValues[valueCount]{}; + float values[valueCount]{}; + + int actualValueCount = 0; + + for (int i = 0; i < valueCount; ++i) { + const int32_t freq = _sensor->getAntCapFrequency(i); + + if (freq > 0) { + values[actualValueCount] = computeDeviationPct(freq); + xAxisValues[actualValueCount] = i; + ++actualValueCount; + } + } + + String axisOptions; + + { + ChartJS_options_scales scales; + scales.add({ F("x"), F("Antenna capacitor") }); + scales.add({ F("y"), F("Error (%)") }); + axisOptions = scales.toString(); + } + + 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 + + +#endif // ifdef USES_P169 diff --git a/src/src/PluginStructs/P169_data_struct.h b/src/src/PluginStructs/P169_data_struct.h new file mode 100644 index 000000000..381e4da18 --- /dev/null +++ b/src/src/PluginStructs/P169_data_struct.h @@ -0,0 +1,158 @@ +#ifndef PLUGINSTRUCTS_P169_DATA_STRUCT_H +#define PLUGINSTRUCTS_P169_DATA_STRUCT_H + +////////////////////////////////////////////////////////////////////////////////////////////////// +// +// +// Using AS3935MI library written by Gregor Christandl +// https://bitbucket.org/christandlg/as3935mi/issues +////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "../../_Plugin_Helper.h" +#ifdef USES_P169 + +# include "../ESPEasyCore/ESPEasyGPIO.h" + +# include + +# define DEFAULT_SENSE_INCREASE_INTERVAL 15000 // 15 s sensitivity increase interval + +# define P169_IRQ_PIN CONFIG_PIN1 +# define P169_IRQ_PIN_LABEL "taskdevicepin1" + +# define P169_I2C_ADDRESS PCONFIG(0) +# define P169_I2C_ADDRESS_LABEL PCONFIG_LABEL(0) + +# define P169_LIGHTNING_THRESHOLD PCONFIG(1) +# define P169_LIGHTNING_THRESHOLD_LABEL PCONFIG_LABEL(1) + +# define P169_AFE_GAIN_LOW PCONFIG(3) +# define P169_AFE_GAIN_LOW_LABEL PCONFIG_LABEL(3) +# define P169_AFE_GAIN_HIGH PCONFIG(4) +# define P169_AFE_GAIN_HIGH_LABEL PCONFIG_LABEL(4) + + +// # define P169_GET_INDOOR bitRead(PCONFIG(2), 0) +// # define P169_SET_INDOOR(X) bitWrite(PCONFIG(2), 0, X) +// # define P169_INDOOR_LABEL "mode" + +# define P169_GET_MASK_DISTURBANCE bitRead(PCONFIG(2), 1) +# define P169_SET_MASK_DISTURBANCE(X) bitWrite(PCONFIG(2), 1, X) +# define P169_MASK_DISTURBANCE_LABEL "maskdist" + +# define P169_GET_SEND_ONLY_ON_LIGHTNING bitRead(PCONFIG(2), 2) +# define P169_SET_SEND_ONLY_ON_LIGHTNING(X) bitWrite(PCONFIG(2), 2, X) +# define P169_SEND_ONLY_ON_LIGHTNING_LABEL "sendonlightning" + +# define P169_GET_TOLERANT_CALIBRATION_RANGE bitRead(PCONFIG(2), 3) +# define P169_SET_TOLERANT_CALIBRATION_RANGE(X) bitWrite(PCONFIG(2), 3, X) +# define P169_TOLERANT_CALIBRATION_RANGE_LABEL "tolerantcalib" + +# define P169_GET_SLOW_LCO_CALIBRATION bitRead(PCONFIG(2), 4) +# define P169_SET_SLOW_LCO_CALIBRATION(X) bitWrite(PCONFIG(2), 4, X) +# define P169_SLOW_LCO_CALIBRATION_LABEL "slowcalib" + +// The device addresses for the AS3935 in read or write mode are defined by: +// 0-0-0-0-0-a1-a0-0: write mode device address (DW) +// 0-0-0-0-0-a1-a0-1: read mode device address (DR) +// Where a0 and a1 are defined by the pins 5 (ADD0) and 6 (ADD1). +// The combination a0 = 0 (low) and a1 =0 (low) is explicitly not allowed for I²C communication. +# define P169_I2C_ADDRESS_DFLT 0x03 + +// Franklin AS3935 has 10k pull-up on the SDA line. +// When no other I2C devices used: Max 400 kHz I2C clock, add 10k as pull-up on SCL. +// Along with upto 3 other I2C devices: Max 100 kHz I2C clock, add 10k on SDA and add 4k7 pull-up on SCL. + + +struct P169_data_struct : public PluginTaskData_base +{ +public: + + P169_data_struct(struct EventStruct *event); + virtual ~P169_data_struct(); + + bool loop(struct EventStruct *event); + + bool plugin_init(struct EventStruct *event); + bool plugin_write(struct EventStruct *event, + String & string); + + bool plugin_get_config_value(struct EventStruct *event, + String & string); + + void html_show_sensor_info(struct EventStruct *event); + + // Read distance in km + float getDistance(); + + // Get lightning strike energy in some raw value (no unit) + uint32_t getEnergy(); + + uint32_t getLightningCount() const { + return _lightningCount; + } + + uint32_t getAndClearLightningCount(); + + + // Clear lightning distance estimation statistics + void clearStatistics(); + +private: + + static float computeDeviationPct(uint32_t LCO_freq); + + static float computeDistanceFromEnergy(uint32_t energy, + float errorValue); + + bool calibrate(struct EventStruct *event); + + void adjustForNoise(struct EventStruct *event); + + void adjustForDisturbances(struct EventStruct *event); + + void tryIncreasedSensitivity(struct EventStruct *event); + + void setAFE_gain(struct EventStruct *event, uint8_t gain); + + void setNoiseFloorThreshold(struct EventStruct *event, uint8_t noiseFloor); + + // Convert internal register value for AFE gain to gain factor + static float regValue_AFE_gain_toFloat(uint8_t gain); + + // Convert AFE gain factor to internal register value. + // Register values range from 10 .. 18, gain factor from 0.3x .. 3.34x + // If given value is in range 10 .. 18, this value wil be returned. + // For out of range values, the default of gain factor 1.0x will be used. + static uint8_t AFE_gain_to_regValue(float gain); + + void sendChangeEvent(struct EventStruct *event); + +# if FEATURE_CHART_JS + void addCalibrationChart(struct EventStruct *event); +# endif // if FEATURE_CHART_JS + + + AS3935I2C *_sensor = nullptr; + int8_t _irqPin; + float _afeGain = 1.0f; + uint8_t _afeGainRegval = 0; + + uint32_t _sense_adj_last = 0; + + uint32_t _sense_increase_interval = DEFAULT_SENSE_INCREASE_INTERVAL; + + uint32_t _lightningCount = 0; + uint32_t _highestEnergy = 0; + uint32_t _lowestEnergy = 0xFFFFFFFF; + + // Keep track of previous value to only send #ParamUpdate events when changed. + uint8_t _lastEvent_noiseFloor = 255; + uint8_t _lastEvent_watchdog = 255; + uint8_t _lastEvent_srej = 255; + uint8_t _lastEvent_gain = 0; + +}; + +#endif // ifdef USES_P169 +#endif // ifndef PLUGINSTRUCTS_P169_DATA_STRUCT_H diff --git a/src/src/PluginStructs/P170_data_struct.cpp b/src/src/PluginStructs/P170_data_struct.cpp new file mode 100644 index 000000000..7921aa61c --- /dev/null +++ b/src/src/PluginStructs/P170_data_struct.cpp @@ -0,0 +1,159 @@ +#include "../PluginStructs/P170_data_struct.h" + +#ifdef USES_P170 + +/************************************************************************** +* Constructor +**************************************************************************/ +P170_data_struct::P170_data_struct(uint8_t level, + bool log) + : _level(level), _log(log) {} + +/************************************************************************** +* Start the plugin +**************************************************************************/ +bool P170_data_struct::init(struct EventStruct *event) { + if (!Settings.CheckI2Cdevice() || (0 == I2C_wakeup(P170_I2C_ADDRESS_HIGH))) { // Also fail if second address isn't found + initialized = true; + + if (0 == Settings.TaskDeviceTimer[event->TaskIndex]) { + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P170_DEFAULT_INTERVAL); // Schedule + } + } else { + addLog(LOG_LEVEL_ERROR, F("LQLVL: Initialization error.")); + } + return isInitialized(); +} + +/***************************************************** +* plugin_read +*****************************************************/ +bool P170_data_struct::plugin_read(struct EventStruct *event) { + bool success = false; + + if (isInitialized()) { + memset(data, 0, sizeof(data)); + + if (readLowSteps() && + readHighSteps()) { + steps = getSteps(); + level = steps * P170_MM_PER_STEP; + + UserVar.setFloat(event->TaskIndex, 0, level); + UserVar.setFloat(event->TaskIndex, 1, steps); + + if ((P170_TRIGGER_LOW_LEVEL > 0) && (level < P170_TRIGGER_LOW_LEVEL)) { + if (!P170_TRIGGER_ONCE || !lowlevel) { + eventQueue.add(event->TaskIndex, F("LowLevel"), level); + lowlevel = true; + } + + if (level >= P170_TRIGGER_LOW_LEVEL) { + lowlevel = false; + } + } + + if ((P170_TRIGGER_HIGH_LEVEL > 0) && (level > P170_TRIGGER_HIGH_LEVEL)) { + if (!P170_TRIGGER_ONCE || !highlevel) { + eventQueue.add(event->TaskIndex, F("HighLevel"), level); + highlevel = true; + } + + if (level <= P170_TRIGGER_HIGH_LEVEL) { + highlevel = false; + } + } + + if (0 == Settings.TaskDeviceTimer[event->TaskIndex]) { + Scheduler.schedule_task_device_timer(event->TaskIndex, millis() + P170_DEFAULT_INTERVAL); // Schedule again + } else { + success = true; + } + } else { + addLog(LOG_LEVEL_ERROR, F("LQLVL: Read error.")); + } + } + + return success; +} + +/***************************************************** +* count the number of steps +*****************************************************/ +uint8_t P170_data_struct::getSteps() { + uint8_t result = 0; + uint8_t max = 0; + + for (int i = 0; i < P170_TOTAL_STEPS; ++i) { + if (data[i] >= _level) { + ++result; + } + + if (data[i] > max) { + max = data[i]; + } + } + + if (_log) { + String log; + log.reserve(80); + + for (int i = 0; i < P170_TOTAL_STEPS; ++i) { + log += data[i]; + log += ','; + } + addLog(LOG_LEVEL_INFO, strformat(F("LQLVL: Max level: %d, data: %s result: %d"), max, log.c_str(), result)); + } + + return result; +} + +/***************************************************** +* read high part of the data (12 bytes) +*****************************************************/ +bool P170_data_struct::readHighSteps() { + const uint32_t start = millis(); + const uint8_t _addr = P170_I2C_ADDRESS_HIGH; + const uint8_t _offset = P170_LOW_STEPS; + const uint8_t _size = P170_HIGH_STEPS; + bool success = false; + + Wire.requestFrom(_addr, _size); + + while (_size != Wire.available() && 20 < timePassedSince(start)) {} // Limit waiting time when no device connected + + if (_size == Wire.available()) { + success = true; + + for (uint8_t i = 0; i < _size; i++) { + data[i + _offset] = Wire.read(); // receive a byte as character + } + } + return success; +} + +/***************************************************** +* read low part of the data (8 bytes) +*****************************************************/ +bool P170_data_struct::readLowSteps() { + const uint32_t start = millis(); + const uint8_t _addr = P170_I2C_ADDRESS; + const uint8_t _offset = 0; + const uint8_t _size = P170_LOW_STEPS; + bool success = false; + + Wire.requestFrom(_addr, _size); + + while (_size != Wire.available() && 20 < timePassedSince(start)) {} // Limit waiting time when no device connected + + if (_size == Wire.available()) { + success = true; + + for (uint8_t i = 0; i < _size; i++) { + data[i + _offset] = Wire.read(); // receive a byte as character + } + } + return success; +} + +#endif // ifdef USES_P170 diff --git a/src/src/PluginStructs/P170_data_struct.h b/src/src/PluginStructs/P170_data_struct.h new file mode 100644 index 000000000..86179203d --- /dev/null +++ b/src/src/PluginStructs/P170_data_struct.h @@ -0,0 +1,60 @@ +#ifndef PLUGINSTRUCTS_P170_DATA_STRUCT_H +#define PLUGINSTRUCTS_P170_DATA_STRUCT_H + +#include "../../_Plugin_Helper.h" +#ifdef USES_P170 + +# define P170_I2C_ADDRESS 0x77 +# define P170_I2C_ADDRESS_HIGH 0x78 + +# define P170_LOW_STEPS 8 +# define P170_HIGH_STEPS 12 +# define P170_TOTAL_STEPS (P170_LOW_STEPS + P170_HIGH_STEPS) +# define P170_MM_PER_STEP 5 +# define P170_MM_PER_STEP_STR "5" // String form of above value + +# define P170_STEP_ACTIVE_LEVEL_DEF 100 +# define P170_DEFAULT_INTERVAL 1000 // 1 second default interval + +# define P170_TRIGGER_LOW_LEVEL PCONFIG(0) +# define P170_TRIGGER_HIGH_LEVEL PCONFIG(1) +# define P170_TRIGGER_ONCE PCONFIG(2) +# define P170_STEP_ACTIVE_LEVEL PCONFIG(3) +# define P170_ENABLE_LOG PCONFIG(4) + +struct P170_data_struct : public PluginTaskData_base { +public: + + P170_data_struct(uint8_t level, + bool log); + + P170_data_struct() = delete; + virtual ~P170_data_struct() {} + + bool init(struct EventStruct *event); + + bool plugin_read(struct EventStruct *event); + bool isInitialized() const { + return initialized; + } + +private: + + uint8_t getSteps(); + bool readHighSteps(); + bool readLowSteps(); + + uint8_t _level; + bool _log; + + uint8_t data[P170_TOTAL_STEPS]{}; + uint8_t level; + uint8_t steps; + bool lowlevel = false; + bool highlevel = false; + + bool initialized = false; +}; + +#endif // ifdef USES_P170 +#endif // ifndef PLUGINSTRUCTS_P170_DATA_STRUCT_H diff --git a/src/src/Static/Fonts/7segment18pt7b.h b/src/src/Static/Fonts/7segment18pt7b.h new file mode 100644 index 000000000..1a6b597a7 --- /dev/null +++ b/src/src/Static/Fonts/7segment18pt7b.h @@ -0,0 +1,365 @@ +#ifndef FONTS_7SEGMENT18PT7B_H +#define FONTS_7SEGMENT18PT7B_H + +const uint8_t _7segment18pt7bBitmaps[] PROGMEM = { + 0x00, 0x00, 0x00, 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, + 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, + 0xE4, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x46, 0x77, 0x77, 0x77, 0x7F, + 0xF4, 0x1F, 0xF7, 0xFE, 0x6F, 0xE7, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, + 0x00, 0x70, 0x07, 0x00, 0xF0, 0x0F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, + 0x00, 0xDF, 0xCB, 0xFC, 0x7F, 0xC0, 0x3F, 0xF1, 0xFF, 0x61, 0xFD, 0x80, + 0x0F, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, + 0x03, 0x80, 0x0E, 0x00, 0x18, 0x00, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x78, + 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, + 0x3F, 0xF1, 0xFE, 0xCF, 0xFA, 0x00, 0x00, 0x00, 0x27, 0xFF, 0xFF, 0xEE, + 0xEE, 0xE6, 0x40, 0x7F, 0xBF, 0xF7, 0xF8, 0xFF, 0x00, 0x1F, 0xF8, 0xFF, + 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, + 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, 0x00, 0x10, + 0x02, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, + 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, + 0x21, 0x8C, 0xF7, 0x39, 0xCE, 0x73, 0x9C, 0xE3, 0x00, 0x8E, 0xF7, 0xBD, + 0xEE, 0x73, 0x9C, 0xE3, 0x10, 0x1F, 0xF8, 0x7F, 0xD8, 0x3F, 0xB0, 0x00, + 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, + 0x00, 0x70, 0x00, 0xE1, 0xFE, 0xC7, 0xFE, 0x17, 0xF8, 0x70, 0x00, 0xE0, + 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, + 0x70, 0x00, 0xDF, 0xC1, 0x7F, 0x81, 0xFF, 0x00, 0x3F, 0xF1, 0xFF, 0x61, + 0xFD, 0x80, 0x0F, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, + 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x3F, 0xD9, 0xFF, 0x83, 0xFD, 0x00, 0x0E, + 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x70, 0x01, 0xC0, 0x07, + 0x00, 0x1C, 0x3F, 0xF1, 0xFE, 0xCF, 0xFA, 0x00, 0x00, 0x08, 0x80, 0x19, + 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, + 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x07, 0xFA, + 0x00, 0x0E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0x80, + 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x30, 0x00, 0x40, 0x1F, + 0xF1, 0xFF, 0x86, 0xFE, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, + 0x00, 0x70, 0x01, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0x5F, 0xE0, 0xFF, 0xC1, + 0xFE, 0x80, 0x07, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x38, + 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x1F, 0xF8, 0xFF, 0x67, 0xFD, 0x00, 0x1F, + 0xF1, 0xFF, 0x86, 0xFE, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, + 0x00, 0x70, 0x01, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0x5F, 0xE0, 0xFF, 0xC5, + 0xFE, 0xB8, 0x07, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, + 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xDF, 0xFA, 0xFF, 0x67, 0xFD, 0x00, 0x7F, + 0xE7, 0xFD, 0x8F, 0xEC, 0x00, 0xF0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, + 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x80, + 0x0E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0E, 0x00, 0x70, 0x03, + 0x80, 0x1C, 0x00, 0xE0, 0x03, 0x00, 0x10, 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, + 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, + 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xFA, 0x70, + 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, + 0x38, 0x0E, 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x1F, 0xF8, + 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, + 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, + 0x07, 0xFA, 0x00, 0x0E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, + 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x1F, 0xF8, 0x7F, 0xB1, 0xFF, + 0x40, 0x77, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xE0, 0x00, 0x00, 0x3F, + 0xCF, 0xFC, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xF8, 0xFF, 0x3F, 0xE0, 0x00, 0x1F, 0xF8, 0x7F, + 0xD8, 0x3F, 0xB0, 0x00, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, + 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE1, 0xFE, 0xC7, 0xFE, 0x17, + 0xF8, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, + 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xC0, 0x01, 0x00, 0x00, 0x00, 0x1F, + 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, + 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, + 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, + 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xC0, 0x39, 0x00, 0x30, + 0x00, 0x40, 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, + 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, + 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, + 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xDF, + 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x1F, 0xF7, 0xFE, 0x6F, 0xE7, 0x00, 0x70, + 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0xF0, 0x0F, 0x00, 0x40, + 0x00, 0x00, 0x40, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, + 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xDF, 0xCB, 0xFC, 0x7F, 0xC0, 0x1F, 0xF8, + 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, + 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, 0x00, + 0x10, 0x02, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, + 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, + 0x40, 0x1F, 0xF7, 0xFE, 0x6F, 0xE7, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, + 0x00, 0x70, 0x07, 0x00, 0xF0, 0x0F, 0x00, 0x5F, 0xE3, 0xFF, 0x5F, 0xEE, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, + 0x00, 0xDF, 0xCB, 0xFC, 0x7F, 0xC0, 0x1F, 0xF7, 0xFE, 0x6F, 0xE7, 0x00, + 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0xF0, 0x0F, 0x00, + 0x5F, 0xE3, 0xFF, 0x5F, 0xEE, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xC0, 0x08, 0x00, 0x1F, 0xF1, 0xFF, + 0x86, 0xFE, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, + 0x01, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0x40, 0x00, 0x00, 0x04, 0x00, 0xB8, + 0x07, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, 0x80, 0xEE, + 0x03, 0xB8, 0x0E, 0xDF, 0xFA, 0xFF, 0x67, 0xFD, 0x00, 0x00, 0x08, 0x80, + 0x19, 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, + 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x17, + 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, + 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xC0, 0x39, 0x00, 0x30, 0x00, 0x40, + 0x21, 0x8C, 0xF7, 0x39, 0xCE, 0x73, 0x9C, 0xE3, 0x00, 0x8E, 0xF7, 0xBD, + 0xEE, 0x73, 0x9C, 0xE3, 0x10, 0x00, 0x08, 0x00, 0x18, 0x00, 0x30, 0x00, + 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, + 0x00, 0x70, 0x00, 0xE0, 0x00, 0xC0, 0x00, 0x10, 0x02, 0x70, 0x0E, 0xE0, + 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, + 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x00, 0x08, 0x80, 0x19, + 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, + 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xF8, + 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, + 0x00, 0x38, 0x00, 0x70, 0x00, 0xDF, 0xC1, 0x7F, 0x81, 0xFF, 0x00, 0x40, + 0x18, 0x07, 0x01, 0xC0, 0x70, 0x1C, 0x07, 0x01, 0xC0, 0x70, 0x3C, 0x0F, + 0x01, 0x00, 0x00, 0x10, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, + 0xE0, 0x38, 0x0E, 0x03, 0x7F, 0xBF, 0xDF, 0xF0, 0x1F, 0xF0, 0xFF, 0x80, + 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0xFF, 0xC5, 0xFE, 0xB8, 0x07, + 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, 0x80, 0xEE, 0x03, + 0xB8, 0x0E, 0xC0, 0x3A, 0x00, 0x60, 0x01, 0x00, 0x1F, 0xF8, 0xFF, 0xD9, + 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, + 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, 0x00, 0x10, 0x02, + 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, + 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xC0, 0x39, 0x00, 0x30, 0x00, 0x40, 0x1F, + 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, + 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, + 0x00, 0x10, 0x02, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, + 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, + 0xFF, 0x40, 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, + 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, + 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xF8, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, + 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xC0, + 0x01, 0x00, 0x00, 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, + 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, + 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1F, 0xC0, 0x7F, 0x81, 0xFF, 0x00, 0x1F, 0xF7, 0xFE, 0x6F, 0xE7, 0x00, + 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0xF0, 0x0F, 0x00, + 0x40, 0x00, 0x00, 0x40, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xC0, 0x08, 0x00, 0x1F, 0xF1, 0xFF, + 0x86, 0xFE, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, + 0x01, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0x5F, 0xE0, 0xFF, 0xC1, 0xFE, 0x80, + 0x07, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x38, 0x00, 0xE0, + 0x03, 0x80, 0x0E, 0x1F, 0xF8, 0xFF, 0x67, 0xFD, 0x00, 0x7F, 0xE7, 0xFD, + 0x8F, 0xEC, 0x00, 0xF0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00, 0x70, + 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x03, 0x00, 0x00, 0x00, 0x80, 0x0E, 0x00, + 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, + 0x00, 0xE0, 0x03, 0x00, 0x10, 0x00, 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, + 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, + 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, 0x00, 0x10, 0x02, 0x70, 0x0E, 0xE0, + 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, + 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x00, 0x08, 0x80, 0x19, + 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, + 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x7F, 0x81, 0xFF, 0x00, 0x00, + 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, + 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, + 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, 0x7B, 0x80, 0xF7, 0x01, + 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xDF, 0xF9, 0x7F, 0xB1, + 0xFF, 0x40, 0x3F, 0xEF, 0xF8, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFC, 0xFF, 0xCF, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, + 0x8F, 0xF3, 0xFE, 0x00, 0x00, 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, 0xF7, + 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, + 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x07, 0xFA, 0x00, 0x0E, 0x00, 0x3C, + 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, + 0x1C, 0x1F, 0xF8, 0x7F, 0xB1, 0xFF, 0x40, 0x1F, 0xF8, 0x7F, 0xD8, 0x3F, + 0xB0, 0x00, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, + 0x00, 0x38, 0x00, 0x70, 0x00, 0xE1, 0xFE, 0xC7, 0xFE, 0x17, 0xF8, 0x70, + 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, + 0x38, 0x00, 0x70, 0x00, 0xDF, 0xC1, 0x7F, 0x81, 0xFF, 0x00, 0x1F, 0xF7, + 0xFE, 0x6F, 0xE7, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, + 0x00, 0xF0, 0x0F, 0x00, 0x40, 0x00, 0x00, 0x40, 0x0E, 0x00, 0xE0, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xDF, 0xCB, + 0xFC, 0x7F, 0xC0, 0x00, 0x3F, 0xF1, 0xFF, 0x61, 0xFD, 0x80, 0x0F, 0x00, + 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, + 0x0E, 0x00, 0x18, 0x00, 0x00, 0x01, 0x00, 0x0E, 0x00, 0x78, 0x01, 0xE0, + 0x07, 0x80, 0x1E, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x3F, 0xF1, + 0xFE, 0xCF, 0xFA, 0x00, 0x00, 0x3F, 0xBF, 0xFF, 0xE0, 0x46, 0x77, 0x77, + 0x77, 0x7F, 0xF4, 0x1F, 0xF8, 0x7F, 0xD8, 0x3F, 0xB0, 0x00, 0xF0, 0x01, + 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, + 0x00, 0xE1, 0xFE, 0xC7, 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, + 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, + 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x40, 0x01, 0x80, 0x07, 0x00, 0x1C, + 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x03, 0xC0, 0x0F, + 0x00, 0x17, 0xF8, 0x3F, 0xF1, 0x7F, 0xAE, 0x01, 0xF8, 0x0F, 0xE0, 0x3F, + 0x80, 0xFE, 0x03, 0xF8, 0x0E, 0xE0, 0x3B, 0x80, 0xEE, 0x03, 0xB7, 0xFE, + 0xBF, 0xD9, 0xFF, 0x40, 0x1F, 0xE3, 0xFF, 0x5F, 0xEE, 0x00, 0xE0, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xDF, 0xCB, + 0xFC, 0x7F, 0xC0, 0x00, 0x08, 0x00, 0x18, 0x00, 0x30, 0x00, 0xF0, 0x01, + 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, + 0x00, 0xE1, 0xFE, 0xC7, 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, + 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, + 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, + 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, + 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xF8, 0x70, 0x00, + 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, + 0x00, 0x70, 0x00, 0xDF, 0xC1, 0x7F, 0x81, 0xFF, 0x00, 0x1F, 0xF7, 0xFE, + 0x6F, 0xE7, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, + 0xF0, 0x0F, 0x00, 0x5F, 0xE3, 0xFF, 0x5F, 0xEE, 0x00, 0xE0, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xC0, 0x08, 0x00, + 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, + 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, + 0xC7, 0xFE, 0x07, 0xFA, 0x00, 0x0E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, + 0x01, 0xE0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x1F, 0xF8, 0x7F, + 0xB1, 0xFF, 0x40, 0x40, 0x01, 0x80, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, + 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x03, 0xC0, 0x0F, 0x00, 0x17, 0xF8, + 0x3F, 0xF1, 0x7F, 0xAE, 0x01, 0xF8, 0x0F, 0xE0, 0x3F, 0x80, 0xFE, 0x03, + 0xF8, 0x0E, 0xE0, 0x3B, 0x80, 0xEE, 0x03, 0xB0, 0x0E, 0x80, 0x18, 0x00, + 0x40, 0x27, 0xFF, 0xFF, 0xEE, 0xEE, 0xE6, 0x40, 0x00, 0x10, 0x00, 0x60, + 0x01, 0x80, 0x0F, 0x00, 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x38, + 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x18, 0x00, 0x00, 0x01, 0x00, 0x0E, + 0x00, 0x78, 0x01, 0xE0, 0x07, 0x80, 0x1E, 0x00, 0x70, 0x01, 0xC0, 0x07, + 0x00, 0x1C, 0x3F, 0xF1, 0xFE, 0xCF, 0xFA, 0x00, 0x1F, 0xF1, 0xFF, 0x86, + 0xFE, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, + 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0x5F, 0xE0, 0xFF, 0xC5, 0xFE, 0xB8, 0x07, + 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, 0x80, 0xEE, 0x03, + 0xB8, 0x0E, 0xC0, 0x3A, 0x00, 0x60, 0x01, 0x00, 0x40, 0x18, 0x07, 0x01, + 0xC0, 0x70, 0x1C, 0x07, 0x01, 0xC0, 0x70, 0x3C, 0x0F, 0x01, 0x00, 0x00, + 0x10, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x38, 0x0E, + 0x03, 0x7F, 0xBF, 0xDF, 0xF0, 0x1F, 0xF0, 0xFF, 0x80, 0xFE, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1F, 0xE0, 0xFF, 0xC5, 0xFE, 0xB8, 0x07, 0xE0, 0x3F, 0x80, + 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xC0, + 0x3A, 0x00, 0x60, 0x01, 0x00, 0x1F, 0xE0, 0xFF, 0xC5, 0xFE, 0xB8, 0x07, + 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, 0x80, 0xEE, 0x03, + 0xB8, 0x0E, 0xC0, 0x3A, 0x00, 0x60, 0x01, 0x00, 0x1F, 0xE0, 0xFF, 0xC5, + 0xFE, 0xB8, 0x07, 0xE0, 0x3F, 0x80, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3B, + 0x80, 0xEE, 0x03, 0xB8, 0x0E, 0xDF, 0xFA, 0xFF, 0x67, 0xFD, 0x00, 0x1F, + 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, + 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, + 0xFE, 0x17, 0xF8, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, + 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xC0, 0x01, 0x00, 0x00, + 0x1F, 0xF8, 0xFF, 0xD9, 0xBF, 0xB3, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, + 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, + 0xC7, 0xFE, 0x07, 0xFA, 0x00, 0x0E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, + 0x01, 0xE0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, + 0x30, 0x00, 0x40, 0x1F, 0xE3, 0xFF, 0x5F, 0xEE, 0x00, 0xE0, 0x0E, 0x00, + 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xC0, 0x08, 0x00, + 0x40, 0x01, 0x80, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x07, 0x00, + 0x1C, 0x00, 0x70, 0x03, 0xC0, 0x0F, 0x00, 0x17, 0xF8, 0x3F, 0xF0, 0x7F, + 0xA0, 0x01, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0E, 0x00, + 0x38, 0x00, 0xE0, 0x03, 0x80, 0x0E, 0x00, 0x18, 0x00, 0x40, 0x40, 0x06, + 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x0F, + 0x00, 0xF0, 0x05, 0xFE, 0x3F, 0xF5, 0xFE, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, + 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0E, 0x00, 0xE0, 0x0D, 0xFC, 0xBF, 0xC7, + 0xFC, 0x40, 0x0B, 0x80, 0x7E, 0x03, 0xF8, 0x0F, 0xE0, 0x3F, 0x80, 0xFE, + 0x03, 0xB8, 0x0E, 0xE0, 0x3B, 0x80, 0xED, 0xFF, 0xAF, 0xF6, 0x7F, 0xD0, + 0x00, 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, + 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE4, 0x00, + 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xC0, 0x7F, + 0x81, 0xFF, 0x00, 0x00, 0x08, 0x80, 0x19, 0x80, 0x33, 0x80, 0xF7, 0x01, + 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, 0xE0, 0x3B, 0xC0, 0x77, + 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x17, 0xFA, 0x70, 0x0E, 0xE0, 0x3D, 0xC0, + 0x7B, 0x80, 0xF7, 0x01, 0xEE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, 0x1C, + 0xDF, 0xF9, 0x7F, 0xB1, 0xFF, 0x40, 0x3F, 0xEF, 0xF8, 0x7F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFC, + 0xFF, 0xCF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3F, 0x8F, 0xF3, 0xFE, 0x00, 0x00, 0x08, 0x80, 0x19, + 0x80, 0x33, 0x80, 0xF7, 0x01, 0xCE, 0x03, 0x9C, 0x07, 0x38, 0x0E, 0x70, + 0x1C, 0xE0, 0x3B, 0xC0, 0x77, 0x80, 0xE5, 0xFE, 0xC7, 0xFE, 0x07, 0xFA, + 0x00, 0x0E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0x80, + 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x1F, 0xF8, 0x7F, 0xB1, 0xFF, 0x40, 0x1F, + 0xF8, 0x7F, 0xD8, 0x3F, 0xB0, 0x00, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x07, + 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE1, 0xFE, 0xC7, + 0xFE, 0x17, 0xF8, 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, + 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xDF, 0xC1, 0x7F, 0x81, + 0xFF, 0x00, 0x00, 0x46, 0x77, 0x77, 0x77, 0x7F, 0xF4, 0x04, 0xEE, 0xEE, + 0xEE, 0xEE, 0xEC, 0x80, 0x00, 0x00 }; + +const GFXglyph _7segment18pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 17, 0, 0 }, // 0x20 ' ' + { 1, 1, 1, 0, 0, 0 }, // 0x21 '!' + { 2, 15, 13, 17, 2, -26 }, // 0x22 '"' + { 27, 1, 1, 0, 0, 0 }, // 0x23 '#' + { 28, 1, 1, 0, 0, 0 }, // 0x24 '$' + { 29, 1, 1, 0, 0, 0 }, // 0x25 '%' + { 30, 1, 1, 0, 0, 0 }, // 0x26 '&' + { 31, 4, 12, 17, 2, -25 }, // 0x27 ''' + { 37, 12, 27, 17, 2, -26 }, // 0x28 '(' + { 78, 14, 27, 17, 3, -26 }, // 0x29 ')' + { 126, 1, 1, 0, 0, 0 }, // 0x2A '*' + { 127, 1, 1, 0, 0, 0 }, // 0x2B '+' + { 128, 4, 13, 17, 12, -12 }, // 0x2C ',' + { 135, 10, 3, 17, 4, -14 }, // 0x2D '-' + { 139, 2, 4, 0, -1, -1 }, // 0x2E '.' + { 140, 1, 1, 0, 0, 0 }, // 0x2F '/' + { 141, 15, 27, 17, 2, -26 }, // 0x30 '0' + { 192, 5, 27, 17, 12, -26 }, // 0x31 '1' + { 209, 15, 27, 17, 2, -26 }, // 0x32 '2' + { 260, 14, 27, 17, 3, -26 }, // 0x33 '3' + { 308, 15, 27, 17, 2, -26 }, // 0x34 '4' + { 359, 14, 27, 17, 2, -26 }, // 0x35 '5' + { 407, 14, 27, 17, 2, -26 }, // 0x36 '6' + { 455, 13, 27, 17, 4, -26 }, // 0x37 '7' + { 499, 15, 27, 17, 2, -26 }, // 0x38 '8' + { 550, 15, 27, 17, 2, -26 }, // 0x39 '9' + { 601, 4, 15, 6, 2, -20 }, // 0x3A ':' + { 609, 1, 1, 0, 0, 0 }, // 0x3B ';' + { 610, 1, 1, 0, 0, 0 }, // 0x3C '<' + { 611, 11, 15, 17, 3, -14 }, // 0x3D '=' + { 632, 1, 1, 0, 0, 0 }, // 0x3E '>' + { 633, 15, 26, 17, 2, -26 }, // 0x3F '?' + { 682, 1, 1, 0, 0, 0 }, // 0x40 '@' + { 683, 15, 27, 17, 2, -26 }, // 0x41 'A' + { 734, 15, 27, 17, 2, -26 }, // 0x42 'B' + { 785, 12, 27, 17, 2, -26 }, // 0x43 'C' + { 826, 15, 27, 17, 2, -26 }, // 0x44 'D' + { 877, 12, 27, 17, 2, -26 }, // 0x45 'E' + { 918, 12, 26, 17, 2, -26 }, // 0x46 'F' + { 957, 14, 27, 17, 2, -26 }, // 0x47 'G' + { 1005, 15, 27, 17, 2, -26 }, // 0x48 'H' + { 1056, 5, 27, 17, 12, -26 }, // 0x49 'I' + { 1073, 15, 27, 17, 2, -26 }, // 0x4A 'J' + { 1124, 15, 27, 17, 2, -26 }, // 0x4B 'K' + { 1175, 10, 26, 17, 2, -25 }, // 0x4C 'L' + { 1208, 14, 27, 17, 2, -26 }, // 0x4D 'M' + { 1256, 15, 27, 17, 2, -26 }, // 0x4E 'N' + { 1307, 15, 27, 17, 2, -26 }, // 0x4F 'O' + { 1358, 15, 26, 17, 2, -26 }, // 0x50 'P' + { 1407, 15, 27, 17, 2, -26 }, // 0x51 'Q' + { 1458, 12, 26, 17, 2, -26 }, // 0x52 'R' + { 1497, 14, 27, 17, 2, -26 }, // 0x53 'S' + { 1545, 13, 27, 17, 4, -26 }, // 0x54 'T' + { 1589, 15, 27, 17, 2, -26 }, // 0x55 'U' + { 1640, 15, 27, 17, 2, -26 }, // 0x56 'V' + { 1691, 15, 27, 17, 2, -26 }, // 0x57 'W' + { 1742, 11, 27, 17, 3, -26 }, // 0x58 'X' + { 1780, 15, 27, 17, 2, -26 }, // 0x59 'Y' + { 1831, 15, 27, 17, 2, -26 }, // 0x5A 'Z' + { 1882, 12, 27, 17, 2, -26 }, // 0x5B '[' + { 1923, 1, 1, 0, 0, 0 }, // 0x5C '\' + { 1924, 14, 27, 17, 3, -26 }, // 0x5D ']' + { 1972, 1, 1, 0, 0, 0 }, // 0x5E '^' + { 1973, 9, 3, 17, 3, -2 }, // 0x5F '_' + { 1977, 4, 12, 17, 2, -25 }, // 0x60 '`' + { 1983, 15, 27, 17, 2, -26 }, // 0x61 'a' + { 2034, 14, 26, 17, 2, -25 }, // 0x62 'b' + { 2080, 12, 15, 17, 2, -14 }, // 0x63 'c' + { 2103, 15, 27, 17, 2, -26 }, // 0x64 'd' + { 2154, 15, 27, 17, 2, -26 }, // 0x65 'e' + { 2205, 12, 26, 17, 2, -26 }, // 0x66 'f' + { 2244, 15, 27, 17, 2, -26 }, // 0x67 'g' + { 2295, 14, 26, 17, 2, -25 }, // 0x68 'h' + { 2341, 4, 13, 17, 12, -12 }, // 0x69 'i' + { 2348, 14, 27, 17, 3, -26 }, // 0x6A 'j' + { 2396, 14, 27, 17, 2, -26 }, // 0x6B 'k' + { 2444, 10, 26, 17, 2, -25 }, // 0x6C 'l' + { 2477, 14, 27, 17, 2, -26 }, // 0x6D 'm' + { 2525, 14, 15, 17, 2, -14 }, // 0x6E 'n' + { 2552, 14, 15, 17, 2, -14 }, // 0x6F 'o' + { 2579, 15, 26, 17, 2, -26 }, // 0x70 'p' + { 2628, 15, 27, 17, 2, -26 }, // 0x71 'q' + { 2679, 12, 14, 17, 2, -14 }, // 0x72 'r' + { 2700, 14, 26, 17, 2, -25 }, // 0x73 's' + { 2746, 12, 26, 17, 2, -25 }, // 0x74 't' + { 2785, 14, 13, 17, 2, -12 }, // 0x75 'u' + { 2808, 15, 27, 17, 2, -26 }, // 0x76 'v' + { 2859, 15, 27, 17, 2, -26 }, // 0x77 'w' + { 2910, 11, 27, 17, 3, -26 }, // 0x78 'x' + { 2948, 15, 27, 17, 2, -26 }, // 0x79 'y' + { 2999, 15, 27, 17, 2, -26 }, // 0x7A 'z' + { 3050, 1, 1, 0, 0, 0 }, // 0x7B '{' + { 3051, 4, 25, 17, 2, -25 }, // 0x7C '|' + { 3064, 1, 1, 0, 0, 0 }, // 0x7D '}' + { 3065, 1, 1, 0, 0, 0 } }; // 0x7E '~' + +const GFXfont _7segment18pt7b PROGMEM = { + (uint8_t *)_7segment18pt7bBitmaps, + (GFXglyph *)_7segment18pt7bGlyphs, + 0x20, 0x7E, 36 }; + +// Approx. 3738 bytes +#endif // ifndef FONTS_7SEGMENT18PT7B_H diff --git a/src/src/Static/Fonts/7segment24pt7b.h b/src/src/Static/Fonts/7segment24pt7b.h new file mode 100644 index 000000000..fd2c5e0ab --- /dev/null +++ b/src/src/Static/Fonts/7segment24pt7b.h @@ -0,0 +1,557 @@ +#ifndef FONTS_7SEGMENT24PT7B_H +#define FONTS_7SEGMENT24PT7B_H + +const uint8_t _7segment24pt7bBitmaps[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x19, 0x80, 0x03, 0xB8, 0x00, 0xF7, + 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, + 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, + 0x00, 0xF7, 0x80, 0x1E, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x63, + 0x9E, 0xF7, 0xBD, 0xEF, 0x7F, 0xFF, 0xFF, 0xFB, 0x80, 0x0F, 0xFF, 0x87, + 0xFF, 0x8D, 0xFF, 0xC7, 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, + 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, + 0x03, 0xE0, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, + 0x78, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, + 0x03, 0xE0, 0x01, 0xE0, 0x00, 0xE7, 0xF8, 0x6F, 0xFE, 0x0F, 0xFF, 0x07, + 0xFF, 0xC0, 0x3F, 0xFF, 0x0F, 0xFF, 0x61, 0xFF, 0xDC, 0x3F, 0xEF, 0x00, + 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, + 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x78, + 0x00, 0x1E, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x07, 0x80, 0x01, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, + 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, 0x00, 0xF0, + 0x00, 0x3C, 0x1F, 0xEF, 0x1F, 0xFD, 0xCF, 0xFF, 0x63, 0xFF, 0xF0, 0x00, + 0x00, 0x3B, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x79, 0xCC, 0x40, + 0x3F, 0xF1, 0xFF, 0xEF, 0xFF, 0xCF, 0xFC, 0x77, 0xEE, 0x00, 0x0F, 0xFF, + 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, 0xC0, + 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, + 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, + 0x80, 0x1E, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, + 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, + 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, + 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, + 0xFD, 0x83, 0xFF, 0xF0, 0x10, 0x61, 0xCF, 0x3C, 0xF3, 0xDF, 0x7D, 0xF7, + 0xDF, 0x7D, 0xF7, 0x9E, 0x18, 0x00, 0x0E, 0x79, 0xEF, 0xBE, 0xFB, 0xEF, + 0xBE, 0xFB, 0xEF, 0x3C, 0xF1, 0xC6, 0x10, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, + 0x07, 0xFF, 0x70, 0x3F, 0xEF, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, + 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, + 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x07, + 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, 0x07, + 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, + 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, + 0x0F, 0x00, 0x00, 0xE7, 0xF8, 0x0D, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xFF, + 0xE0, 0x3F, 0xFF, 0x0F, 0xFF, 0x61, 0xFF, 0xDC, 0x3F, 0xEF, 0x00, 0x03, + 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, + 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x78, 0x00, + 0x1E, 0x1F, 0xF9, 0x8F, 0xFF, 0x07, 0xFF, 0xE0, 0x7F, 0xEE, 0x00, 0x07, + 0x80, 0x01, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, + 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, 0x00, 0xF0, 0x00, + 0x3C, 0x1F, 0xEF, 0x1F, 0xFD, 0xCF, 0xFF, 0x63, 0xFF, 0xF0, 0x00, 0x00, + 0x80, 0x00, 0x19, 0x80, 0x03, 0xB8, 0x00, 0xF7, 0x80, 0x1E, 0xF0, 0x03, + 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, + 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, + 0xEF, 0xFC, 0xC3, 0xFF, 0xC0, 0xFF, 0xFC, 0x07, 0xFE, 0xE0, 0x00, 0x3C, + 0x00, 0x07, 0x80, 0x01, 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, + 0x00, 0x1F, 0x00, 0x03, 0xE0, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xE0, + 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0x70, 0x00, 0x0C, 0x00, 0x01, 0x00, + 0x1F, 0xFF, 0x07, 0xFF, 0x86, 0xFF, 0xE1, 0xDF, 0xF0, 0x78, 0x00, 0x1E, + 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, + 0x03, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xC0, 0x00, + 0xEF, 0xFC, 0x07, 0xFF, 0x83, 0xFF, 0xF0, 0x3F, 0xF7, 0x00, 0x03, 0xC0, + 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, + 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x78, 0x00, 0x1E, + 0x0F, 0xF7, 0x8F, 0xFE, 0xE7, 0xFF, 0xB1, 0xFF, 0xF8, 0x0F, 0xFF, 0x81, + 0xFF, 0xE0, 0xDF, 0xFC, 0x1D, 0xFF, 0x03, 0xC0, 0x00, 0x78, 0x00, 0x0F, + 0x00, 0x01, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0xF0, 0x00, 0x3E, + 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, 0xC0, 0x00, 0x77, + 0xFE, 0x01, 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, 0x77, 0x80, 0x1E, 0xF0, + 0x03, 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7F, 0xE0, + 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, + 0x1E, 0xE7, 0xFB, 0xDB, 0xFF, 0xB8, 0xFF, 0xF6, 0x1F, 0xFF, 0x80, 0xFF, + 0xFC, 0xFF, 0xF6, 0x7F, 0xF7, 0x3F, 0xEF, 0x00, 0x0F, 0x00, 0x0F, 0x00, + 0x0F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, + 0x1F, 0x00, 0x1F, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x06, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1E, 0x00, 0x3E, 0x00, 0x3E, 0x00, + 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, + 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x1C, 0x00, 0x18, 0x00, 0x10, 0x0F, + 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, + 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, + 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, + 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, + 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, + 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, + 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, + 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x1F, 0xFF, 0x83, 0xFF, 0xD9, 0xBF, 0xFB, + 0xBB, 0xFE, 0xF7, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, 0x1F, + 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, + 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xEF, 0xFC, 0xC3, 0xFF, 0xC0, + 0xFF, 0xFC, 0x07, 0xFE, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x01, 0xF0, + 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, 0xE0, + 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xE0, 0x00, 0x3C, 0x0F, 0xF7, 0x87, + 0xFF, 0x71, 0xFF, 0xEC, 0x3F, 0xFF, 0x00, 0x7D, 0xF7, 0xDF, 0x7C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFB, 0xEF, 0xBE, 0x00, 0x00, + 0x1F, 0xF8, 0x7F, 0xF9, 0xFF, 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0xFF, 0xE3, 0xFF, 0xC7, + 0xFF, 0xC0, 0x00, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, 0x07, 0xFF, 0x70, 0x3F, + 0xEF, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x1F, 0x00, + 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, + 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x07, 0xFE, 0x60, 0xFF, 0xF0, + 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, + 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, + 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xE0, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, + 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, + 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, + 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, + 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, + 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, + 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, + 0x3C, 0xE0, 0x03, 0xCC, 0x00, 0x1C, 0x00, 0x01, 0x80, 0x00, 0x10, 0x0F, + 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, + 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, + 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, + 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, + 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, + 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, + 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, + 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x0F, 0xFF, 0x87, 0xFF, 0x8D, 0xFF, 0xC7, + 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, + 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xF0, + 0x00, 0xF0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x07, + 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, + 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, + 0x00, 0xE7, 0xF8, 0x6F, 0xFE, 0x0F, 0xFF, 0x07, 0xFF, 0xC0, 0x0F, 0xFF, + 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, 0xC0, + 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, + 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, + 0x80, 0x1E, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, + 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, + 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, + 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, + 0xFD, 0x83, 0xFF, 0xF0, 0x0F, 0xFF, 0x87, 0xFF, 0x8D, 0xFF, 0xC7, 0x7F, + 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, + 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xF0, 0x00, + 0xF0, 0x00, 0x77, 0xFE, 0x07, 0xFF, 0x87, 0xFF, 0xEE, 0xFF, 0xC7, 0x80, + 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x3E, + 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, 0x00, + 0xE7, 0xF8, 0x6F, 0xFE, 0x0F, 0xFF, 0x07, 0xFF, 0xC0, 0x0F, 0xFF, 0x87, + 0xFF, 0x8D, 0xFF, 0xC7, 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, + 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, + 0x03, 0xE0, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x77, 0xFE, 0x07, 0xFF, 0x87, + 0xFF, 0xEE, 0xFF, 0xC7, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, + 0x78, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, + 0x03, 0xE0, 0x01, 0xE0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x00, 0x0F, 0xFF, + 0x81, 0xFF, 0xE0, 0xDF, 0xFC, 0x1D, 0xFF, 0x03, 0xC0, 0x00, 0x78, 0x00, + 0x0F, 0x00, 0x01, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0xF0, 0x00, + 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, 0xC0, 0x00, + 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x77, 0x80, 0x1E, + 0xF0, 0x03, 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7F, + 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, + 0x80, 0x1E, 0xE7, 0xFB, 0xDB, 0xFF, 0xB8, 0xFF, 0xF6, 0x1F, 0xFF, 0x80, + 0x00, 0x00, 0x40, 0x00, 0x06, 0x30, 0x00, 0x73, 0x80, 0x0F, 0x3C, 0x00, + 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, + 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, + 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, + 0x7F, 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, + 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, + 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE0, 0x03, 0xCC, 0x00, + 0x1C, 0x00, 0x01, 0x80, 0x00, 0x10, 0x10, 0x61, 0xCF, 0x3C, 0xF3, 0xDF, + 0x7D, 0xF7, 0xDF, 0x7D, 0xF7, 0x9E, 0x18, 0x00, 0x0E, 0x79, 0xEF, 0xBE, + 0xFB, 0xEF, 0xBE, 0xFB, 0xEF, 0x3C, 0xF1, 0xC6, 0x10, 0x00, 0x00, 0x40, + 0x00, 0x06, 0x00, 0x00, 0x70, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, + 0x00, 0x00, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, + 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, + 0x1E, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, 0x78, + 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, + 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, + 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, + 0x83, 0xFF, 0xF0, 0x00, 0x00, 0x40, 0x00, 0x06, 0x30, 0x00, 0x73, 0x80, + 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, + 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, + 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, + 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, + 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, + 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xE7, + 0xF8, 0x0D, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xFF, 0xE0, 0x30, 0x00, 0x70, + 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x1E, 0x00, + 0x3C, 0x00, 0xF8, 0x01, 0xF0, 0x03, 0xE0, 0x07, 0xC0, 0x0F, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, + 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8, 0x01, 0xF0, 0x03, 0xE0, 0x07, + 0xC0, 0x0F, 0x80, 0x1E, 0x00, 0x39, 0xFE, 0x6F, 0xFE, 0x3F, 0xFC, 0x7F, + 0xFC, 0x0F, 0xFF, 0x81, 0xFF, 0xE0, 0x1F, 0xFC, 0x01, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x07, 0xFE, 0x01, 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, + 0x77, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, + 0xEF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, + 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xE0, 0x03, 0xD8, 0x00, 0x38, 0x00, 0x06, + 0x00, 0x00, 0x80, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, + 0xEF, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, + 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, + 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x70, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x00, 0x07, 0x00, 0x0E, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, + 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, + 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE0, + 0x03, 0xCC, 0x00, 0x1C, 0x00, 0x01, 0x80, 0x00, 0x10, 0x0F, 0xFF, 0xC0, + 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, + 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, + 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, + 0x1E, 0x70, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, 0x78, + 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, + 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, + 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, + 0x83, 0xFF, 0xF0, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, + 0xEF, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, + 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, + 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, + 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, + 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, + 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xE0, + 0x00, 0x0C, 0x00, 0x00, 0x1F, 0xFF, 0x83, 0xFF, 0xD9, 0xBF, 0xFB, 0xBB, + 0xFE, 0xF7, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, + 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, + 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xEF, 0xFC, 0xC3, 0xFF, 0xC0, 0xFF, + 0xFC, 0x07, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x07, 0xFF, + 0x01, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, 0x0F, 0xFF, 0x87, 0xFF, 0x8D, 0xFF, + 0xC7, 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, + 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, + 0xF0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, + 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, + 0xE0, 0x00, 0xE0, 0x00, 0x60, 0x00, 0x00, 0x1F, 0xFF, 0x07, 0xFF, 0x86, + 0xFF, 0xE1, 0xDF, 0xF0, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, + 0x00, 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x03, 0xE0, 0x00, 0xF8, 0x00, + 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xC0, 0x00, 0xEF, 0xFC, 0x07, 0xFF, 0x83, + 0xFF, 0xF0, 0x3F, 0xF7, 0x00, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x7C, 0x00, + 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, + 0xC0, 0x01, 0xF0, 0x00, 0x78, 0x00, 0x1E, 0x0F, 0xF7, 0x8F, 0xFE, 0xE7, + 0xFF, 0xB1, 0xFF, 0xF8, 0xFF, 0xFC, 0xFF, 0xF6, 0x7F, 0xF7, 0x3F, 0xEF, + 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x0F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, + 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x1E, 0x00, 0x1E, + 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x1E, 0x00, 0x1E, + 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3E, + 0x00, 0x3E, 0x00, 0x3E, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x3C, 0x00, 0x1C, + 0x00, 0x18, 0x00, 0x10, 0x00, 0x00, 0x40, 0x00, 0x06, 0x30, 0x00, 0x73, + 0x80, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, + 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, + 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x70, 0x00, 0x60, 0x00, + 0x00, 0x00, 0x00, 0x07, 0x00, 0x0E, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, + 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, + 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, + 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x00, 0x00, + 0x80, 0x00, 0x19, 0x80, 0x03, 0xB8, 0x00, 0xF7, 0x80, 0x1E, 0xF0, 0x03, + 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, + 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, + 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0F, 0xF0, 0x07, 0xFF, 0x01, 0xFF, 0xE0, 0x3F, 0xFE, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x06, 0x30, 0x00, 0x73, 0x80, 0x0F, 0x3C, 0x00, + 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, + 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, + 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, + 0x7F, 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, + 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, + 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, + 0xDC, 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x3F, 0xFE, 0x7F, 0xF8, 0x7F, 0xF0, + 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x1F, 0xF8, 0x7F, 0xF9, 0xFF, 0xF8, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0xFF, 0xE3, 0xFF, 0xC7, + 0xFF, 0xC0, 0x00, 0x00, 0x80, 0x00, 0x19, 0x80, 0x03, 0xB8, 0x00, 0xF7, + 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, + 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, + 0x00, 0xF7, 0x80, 0x1E, 0xEF, 0xFC, 0xC3, 0xFF, 0xC0, 0xFF, 0xFC, 0x07, + 0xFE, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x01, 0xF0, 0x00, 0x3E, 0x00, + 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, 0xE0, 0x00, 0x7C, 0x00, + 0x0F, 0x80, 0x01, 0xE0, 0x00, 0x3C, 0x0F, 0xF7, 0x87, 0xFF, 0x71, 0xFF, + 0xEC, 0x3F, 0xFF, 0x00, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, 0x07, 0xFF, 0x70, + 0x3F, 0xEF, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x1F, + 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, + 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x07, 0xFE, 0x60, 0xFF, + 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, + 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0xF8, 0x00, 0x0F, + 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, + 0xE7, 0xF8, 0x0D, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, 0xFF, 0xE0, 0x0F, 0xFF, + 0x87, 0xFF, 0x8D, 0xFF, 0xC7, 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, + 0x00, 0x78, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, + 0xC0, 0x03, 0xE0, 0x01, 0xF0, 0x00, 0xF0, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0E, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, + 0x00, 0x78, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, + 0xC0, 0x03, 0xE0, 0x01, 0xE0, 0x00, 0xE7, 0xF8, 0x6F, 0xFE, 0x0F, 0xFF, + 0x07, 0xFF, 0xC0, 0x00, 0x3F, 0xFF, 0x0F, 0xFF, 0x61, 0xFF, 0xDC, 0x3F, + 0xEF, 0x00, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x1F, 0x00, 0x07, + 0xC0, 0x01, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, + 0x00, 0x78, 0x00, 0x1E, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, + 0x80, 0x03, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, + 0x00, 0xF0, 0x00, 0x3C, 0x1F, 0xEF, 0x1F, 0xFD, 0xCF, 0xFF, 0x63, 0xFF, + 0xF0, 0x00, 0x1F, 0xE3, 0xFF, 0xBF, 0xFD, 0xFF, 0xF0, 0x63, 0x9E, 0xF7, + 0xBD, 0xEF, 0x7F, 0xFF, 0xFF, 0xFB, 0x80, 0x0F, 0xFF, 0xC0, 0xFF, 0xF6, + 0x07, 0xFF, 0x70, 0x3F, 0xEF, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, + 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, + 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x07, + 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xEE, 0x78, 0x01, 0xE7, + 0x80, 0x1E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, + 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, + 0xCF, 0x00, 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, 0x83, 0xFF, + 0xF0, 0x30, 0x00, 0x07, 0x00, 0x00, 0xF0, 0x00, 0x1E, 0x00, 0x03, 0xC0, + 0x00, 0x78, 0x00, 0x0F, 0x00, 0x01, 0xE0, 0x00, 0x3C, 0x00, 0x0F, 0x80, + 0x01, 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF0, 0x00, 0x1D, 0xFF, + 0x80, 0x7F, 0xF8, 0x1F, 0xFF, 0x8E, 0xFF, 0xDD, 0xE0, 0x07, 0xBC, 0x00, + 0xF7, 0x80, 0x3E, 0xF0, 0x07, 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0xF8, 0x03, + 0xFF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3D, 0xE0, 0x07, + 0xB9, 0xFE, 0xF6, 0xFF, 0xEE, 0x3F, 0xFD, 0x87, 0xFF, 0xE0, 0x07, 0xFE, + 0x07, 0xFF, 0x87, 0xFF, 0xEE, 0xFF, 0xC7, 0x80, 0x03, 0xC0, 0x01, 0xE0, + 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, + 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, 0x00, 0xE7, 0xF8, 0x6F, 0xFE, + 0x0F, 0xFF, 0x07, 0xFF, 0xC0, 0x00, 0x00, 0x40, 0x00, 0x06, 0x00, 0x00, + 0x70, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, 0x00, 0xF0, 0x00, + 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, + 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, 0x07, 0xFE, 0x60, + 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, + 0x78, 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, + 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, + 0x3C, 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x0F, + 0xFF, 0xC0, 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, + 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, + 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, + 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, + 0xE0, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, + 0x00, 0x07, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, + 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xE7, 0xF8, 0x0D, 0xFF, 0xC0, + 0x3F, 0xFC, 0x03, 0xFF, 0xE0, 0x0F, 0xFF, 0x87, 0xFF, 0x8D, 0xFF, 0xC7, + 0x7F, 0xC3, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, + 0x1E, 0x00, 0x0F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xF0, + 0x00, 0xF0, 0x00, 0x77, 0xFE, 0x07, 0xFF, 0x87, 0xFF, 0xEE, 0xFF, 0xC7, + 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, + 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, + 0x00, 0xE0, 0x00, 0x60, 0x00, 0x00, 0x1F, 0xFF, 0x83, 0xFF, 0xD9, 0xBF, + 0xFB, 0xBB, 0xFE, 0xF7, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, + 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, + 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xEF, 0xFC, 0xC3, 0xFF, + 0xC0, 0xFF, 0xFC, 0x07, 0xFE, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x01, + 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, + 0xE0, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xE0, 0x00, 0x3C, 0x0F, 0xF7, + 0x87, 0xFF, 0x71, 0xFF, 0xEC, 0x3F, 0xFF, 0x00, 0x30, 0x00, 0x07, 0x00, + 0x00, 0xF0, 0x00, 0x1E, 0x00, 0x03, 0xC0, 0x00, 0x78, 0x00, 0x0F, 0x00, + 0x01, 0xE0, 0x00, 0x3C, 0x00, 0x0F, 0x80, 0x01, 0xF0, 0x00, 0x3E, 0x00, + 0x07, 0xC0, 0x00, 0xF0, 0x00, 0x1D, 0xFF, 0x80, 0x7F, 0xF8, 0x1F, 0xFF, + 0x8E, 0xFF, 0xDD, 0xE0, 0x07, 0xBC, 0x00, 0xF7, 0x80, 0x3E, 0xF0, 0x07, + 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0xF8, 0x03, 0xFF, 0x00, 0x7F, 0xE0, 0x0F, + 0xFC, 0x01, 0xFF, 0x80, 0x3D, 0xE0, 0x07, 0xB8, 0x00, 0xF6, 0x00, 0x0E, + 0x00, 0x01, 0x80, 0x00, 0x20, 0x3B, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xEF, 0x79, 0xCC, 0x40, 0x00, 0x01, 0x00, 0x00, 0x60, 0x00, 0x1C, 0x00, + 0x0F, 0x00, 0x03, 0xC0, 0x00, 0xF0, 0x00, 0x3C, 0x00, 0x1F, 0x00, 0x07, + 0xC0, 0x01, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, 0x01, 0xF0, + 0x00, 0x78, 0x00, 0x1E, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, + 0x80, 0x03, 0xE0, 0x00, 0xF8, 0x00, 0x3E, 0x00, 0x0F, 0x80, 0x03, 0xE0, + 0x00, 0xF0, 0x00, 0x3C, 0x1F, 0xEF, 0x1F, 0xFD, 0xCF, 0xFF, 0x63, 0xFF, + 0xF0, 0x0F, 0xFF, 0x81, 0xFF, 0xE0, 0xDF, 0xFC, 0x1D, 0xFF, 0x03, 0xC0, + 0x00, 0x78, 0x00, 0x0F, 0x00, 0x01, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, + 0x00, 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, + 0x03, 0xC0, 0x00, 0x77, 0xFE, 0x01, 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, + 0x77, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, + 0xEF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, + 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xE0, 0x03, 0xD8, 0x00, 0x38, 0x00, 0x06, + 0x00, 0x00, 0x80, 0x30, 0x00, 0x70, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, + 0x07, 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0xF8, 0x01, 0xF0, 0x03, + 0xE0, 0x07, 0xC0, 0x0F, 0x00, 0x1C, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, + 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, + 0xF8, 0x01, 0xF0, 0x03, 0xE0, 0x07, 0xC0, 0x0F, 0x80, 0x1E, 0x00, 0x39, + 0xFE, 0x6F, 0xFE, 0x3F, 0xFC, 0x7F, 0xFC, 0x0F, 0xFF, 0x81, 0xFF, 0xE0, + 0x1F, 0xFC, 0x01, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFE, 0x01, + 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, 0x77, 0x80, 0x1E, 0xF0, 0x03, 0xDE, + 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, + 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xE0, + 0x03, 0xD8, 0x00, 0x38, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xFE, 0x01, + 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, 0x77, 0x80, 0x1E, 0xF0, 0x03, 0xDE, + 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, + 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xE0, + 0x03, 0xD8, 0x00, 0x38, 0x00, 0x06, 0x00, 0x00, 0x80, 0x07, 0xFE, 0x01, + 0xFF, 0xE0, 0x7F, 0xFE, 0x3B, 0xFF, 0x77, 0x80, 0x1E, 0xF0, 0x03, 0xDE, + 0x00, 0xFB, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, + 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xE7, + 0xFB, 0xDB, 0xFF, 0xB8, 0xFF, 0xF6, 0x1F, 0xFF, 0x80, 0x0F, 0xFF, 0xC0, + 0xFF, 0xF6, 0x37, 0xFF, 0x73, 0xBF, 0xEF, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, + 0x3C, 0x00, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, + 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, + 0x1E, 0x77, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, + 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, + 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, + 0xF8, 0x00, 0x0F, 0x00, 0x00, 0xE0, 0x00, 0x0C, 0x00, 0x00, 0x1F, 0xFF, + 0x83, 0xFF, 0xD9, 0xBF, 0xFB, 0xBB, 0xFE, 0xF7, 0x80, 0x1E, 0xF0, 0x03, + 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, + 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, + 0xEF, 0xFC, 0xC3, 0xFF, 0xC0, 0xFF, 0xFC, 0x07, 0xFE, 0xE0, 0x00, 0x3C, + 0x00, 0x07, 0x80, 0x01, 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, + 0x00, 0x1F, 0x00, 0x03, 0xE0, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xE0, + 0x00, 0x3C, 0x00, 0x07, 0x80, 0x00, 0x70, 0x00, 0x0C, 0x00, 0x01, 0x00, + 0x07, 0xFE, 0x07, 0xFF, 0x87, 0xFF, 0xEE, 0xFF, 0xC7, 0x80, 0x03, 0xC0, + 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, + 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, 0xE0, 0x01, 0xE0, 0x00, 0xE0, 0x00, + 0x60, 0x00, 0x00, 0x60, 0x00, 0x1C, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, + 0x78, 0x00, 0x1E, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x3E, + 0x00, 0x0F, 0x80, 0x03, 0xE0, 0x00, 0xF8, 0x00, 0x3C, 0x00, 0x0E, 0xFF, + 0xC0, 0x7F, 0xF8, 0x3F, 0xFF, 0x03, 0xFF, 0x70, 0x00, 0x3C, 0x00, 0x0F, + 0x00, 0x07, 0xC0, 0x01, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0xC0, + 0x01, 0xF0, 0x00, 0x7C, 0x00, 0x1F, 0x00, 0x07, 0x80, 0x01, 0xE0, 0x00, + 0x78, 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x80, 0x30, 0x00, 0x1C, 0x00, + 0x0F, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0x78, + 0x00, 0x3C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0xC0, 0x03, + 0xC0, 0x01, 0xDF, 0xF8, 0x1F, 0xFE, 0x1F, 0xFF, 0xBB, 0xFF, 0x1E, 0x00, + 0x0F, 0x00, 0x07, 0x80, 0x03, 0xC0, 0x01, 0xE0, 0x00, 0xF0, 0x00, 0xF8, + 0x00, 0x7C, 0x00, 0x3E, 0x00, 0x1F, 0x00, 0x0F, 0x80, 0x07, 0x80, 0x03, + 0x9F, 0xE1, 0xBF, 0xF8, 0x3F, 0xFC, 0x1F, 0xFF, 0x00, 0x70, 0x00, 0xEF, + 0x00, 0x3D, 0xE0, 0x07, 0xBC, 0x01, 0xF7, 0x80, 0x3E, 0xF0, 0x07, 0xDE, + 0x00, 0xFF, 0xC0, 0x1F, 0xF8, 0x03, 0xFF, 0x00, 0x7F, 0xE0, 0x0F, 0xFC, + 0x01, 0xEF, 0x00, 0x3D, 0xCF, 0xF7, 0xB7, 0xFF, 0x71, 0xFF, 0xEC, 0x3F, + 0xFF, 0x00, 0x00, 0x00, 0x80, 0x00, 0x19, 0x80, 0x03, 0xB8, 0x00, 0xF7, + 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, 0x1F, 0x78, 0x03, 0xEF, + 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, 0x3F, 0xF0, 0x07, 0xFE, + 0x00, 0xF7, 0x80, 0x1E, 0xE0, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x07, 0xFF, 0x01, 0xFF, + 0xE0, 0x3F, 0xFE, 0x00, 0x00, 0x00, 0x40, 0x00, 0x06, 0x30, 0x00, 0x73, + 0x80, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x0F, 0x3C, 0x00, 0xF3, 0xC0, 0x1F, + 0x3C, 0x01, 0xF3, 0xC0, 0x1F, 0x3C, 0x01, 0xF7, 0xC0, 0x1F, 0x7C, 0x01, + 0xF7, 0xC0, 0x1F, 0x7C, 0x01, 0xE7, 0x80, 0x1E, 0x77, 0xFE, 0x60, 0xFF, + 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xEE, 0x78, 0x01, 0xE7, 0x80, 0x1E, 0x78, + 0x03, 0xE7, 0x80, 0x3E, 0x78, 0x03, 0xE7, 0x80, 0x3E, 0xF8, 0x03, 0xEF, + 0x80, 0x3E, 0xF8, 0x03, 0xEF, 0x80, 0x3E, 0xF8, 0x03, 0xCF, 0x00, 0x3C, + 0xE7, 0xFB, 0xCD, 0xFF, 0xDC, 0x3F, 0xFD, 0x83, 0xFF, 0xF0, 0x3F, 0xFE, + 0x7F, 0xF8, 0x7F, 0xF0, 0x7F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x1F, 0xF8, 0x7F, 0xF9, 0xFF, 0xF8, 0xFF, 0xC0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, + 0xFF, 0xE3, 0xFF, 0xC7, 0xFF, 0xC0, 0x00, 0x00, 0x80, 0x00, 0x19, 0x80, + 0x03, 0xB8, 0x00, 0xF7, 0x80, 0x1E, 0xF0, 0x03, 0xDE, 0x00, 0x7B, 0xC0, + 0x1F, 0x78, 0x03, 0xEF, 0x00, 0x7D, 0xE0, 0x0F, 0xFC, 0x01, 0xFF, 0x80, + 0x3F, 0xF0, 0x07, 0xFE, 0x00, 0xF7, 0x80, 0x1E, 0xEF, 0xFC, 0xC3, 0xFF, + 0xC0, 0xFF, 0xFC, 0x07, 0xFE, 0xE0, 0x00, 0x3C, 0x00, 0x07, 0x80, 0x01, + 0xF0, 0x00, 0x3E, 0x00, 0x07, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x00, 0x03, + 0xE0, 0x00, 0x7C, 0x00, 0x0F, 0x80, 0x01, 0xE0, 0x00, 0x3C, 0x0F, 0xF7, + 0x87, 0xFF, 0x71, 0xFF, 0xEC, 0x3F, 0xFF, 0x00, 0x0F, 0xFF, 0xC0, 0xFF, + 0xF6, 0x07, 0xFF, 0x70, 0x3F, 0xEF, 0x00, 0x00, 0xF0, 0x00, 0x0F, 0x00, + 0x00, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xF0, + 0x00, 0x1F, 0x00, 0x01, 0xF0, 0x00, 0x1F, 0x00, 0x01, 0xE0, 0x00, 0x1E, + 0x07, 0xFE, 0x60, 0xFF, 0xF0, 0x1F, 0xFF, 0x87, 0x7F, 0xE0, 0x78, 0x00, + 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, 0x00, 0x78, 0x00, 0x07, 0x80, + 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x80, 0x00, 0xF8, + 0x00, 0x0F, 0x00, 0x00, 0xE7, 0xF8, 0x0D, 0xFF, 0xC0, 0x3F, 0xFC, 0x03, + 0xFF, 0xE0, 0x00, 0x30, 0xE3, 0xCF, 0x3C, 0xF3, 0xCF, 0x3D, 0xF7, 0xDF, + 0x7D, 0xE7, 0x00, 0x01, 0xC7, 0x9E, 0x79, 0xE7, 0x9E, 0xFB, 0xEF, 0xBE, + 0xFB, 0xCE, 0x30, 0x00, 0x00 }; + +const GFXglyph _7segment24pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 22, 0, 0 }, // 0x20 ' ' + { 1, 1, 1, 0, 0, 0 }, // 0x21 '!' + { 2, 19, 17, 22, 3, -35 }, // 0x22 '"' + { 43, 1, 1, 0, 0, 0 }, // 0x23 '#' + { 44, 1, 1, 0, 0, 0 }, // 0x24 '$' + { 45, 1, 1, 0, 0, 0 }, // 0x25 '%' + { 46, 1, 1, 0, 0, 0 }, // 0x26 '&' + { 47, 5, 15, 22, 3, -33 }, // 0x27 ''' + { 57, 17, 36, 22, 2, -35 }, // 0x28 '(' + { 134, 18, 36, 22, 4, -35 }, // 0x29 ')' + { 215, 1, 1, 0, 0, 0 }, // 0x2A '*' + { 216, 1, 1, 0, 0, 0 }, // 0x2B '+' + { 217, 5, 17, 22, 16, -16 }, // 0x2C ',' + { 228, 14, 4, 22, 5, -19 }, // 0x2D '-' + { 235, 4, 4, 0, -2, -1 }, // 0x2E '.' + { 237, 1, 1, 0, 0, 0 }, // 0x2F '/' + { 238, 20, 36, 22, 2, -35 }, // 0x30 '0' + { 328, 6, 36, 22, 16, -35 }, // 0x31 '1' + { 355, 20, 36, 22, 2, -35 }, // 0x32 '2' + { 445, 18, 36, 22, 4, -35 }, // 0x33 '3' + { 526, 19, 36, 22, 3, -35 }, // 0x34 '4' + { 612, 18, 36, 22, 3, -35 }, // 0x35 '5' + { 693, 19, 36, 22, 2, -35 }, // 0x36 '6' + { 779, 16, 36, 22, 6, -35 }, // 0x37 '7' + { 851, 20, 36, 22, 2, -35 }, // 0x38 '8' + { 941, 19, 36, 22, 3, -35 }, // 0x39 '9' + { 1027, 6, 20, 8, 2, -27 }, // 0x3A ':' + { 1042, 1, 1, 0, 0, 0 }, // 0x3B ';' + { 1043, 1, 1, 0, 0, 0 }, // 0x3C '<' + { 1044, 15, 20, 22, 4, -19 }, // 0x3D '=' + { 1082, 1, 1, 0, 0, 0 }, // 0x3E '>' + { 1083, 20, 34, 22, 2, -35 }, // 0x3F '?' + { 1168, 1, 1, 0, 0, 0 }, // 0x40 '@' + { 1169, 20, 36, 22, 2, -35 }, // 0x41 'A' + { 1259, 20, 36, 22, 2, -35 }, // 0x42 'B' + { 1349, 17, 36, 22, 2, -35 }, // 0x43 'C' + { 1426, 20, 36, 22, 2, -35 }, // 0x44 'D' + { 1516, 17, 36, 22, 2, -35 }, // 0x45 'E' + { 1593, 17, 34, 22, 2, -35 }, // 0x46 'F' + { 1666, 19, 36, 22, 2, -35 }, // 0x47 'G' + { 1752, 20, 36, 22, 2, -35 }, // 0x48 'H' + { 1842, 6, 36, 22, 16, -35 }, // 0x49 'I' + { 1869, 20, 36, 22, 2, -35 }, // 0x4A 'J' + { 1959, 20, 36, 22, 2, -35 }, // 0x4B 'K' + { 2049, 15, 34, 22, 2, -33 }, // 0x4C 'L' + { 2113, 19, 36, 22, 2, -35 }, // 0x4D 'M' + { 2199, 20, 36, 22, 2, -35 }, // 0x4E 'N' + { 2289, 20, 36, 22, 2, -35 }, // 0x4F 'O' + { 2379, 20, 34, 22, 2, -35 }, // 0x50 'P' + { 2464, 19, 36, 22, 3, -35 }, // 0x51 'Q' + { 2550, 17, 34, 22, 2, -35 }, // 0x52 'R' + { 2623, 18, 36, 22, 3, -35 }, // 0x53 'S' + { 2704, 16, 36, 22, 6, -35 }, // 0x54 'T' + { 2776, 20, 36, 22, 2, -35 }, // 0x55 'U' + { 2866, 19, 36, 22, 3, -35 }, // 0x56 'V' + { 2952, 20, 36, 22, 2, -35 }, // 0x57 'W' + { 3042, 15, 36, 22, 4, -35 }, // 0x58 'X' + { 3110, 19, 36, 22, 3, -35 }, // 0x59 'Y' + { 3196, 20, 36, 22, 2, -35 }, // 0x5A 'Z' + { 3286, 17, 36, 22, 2, -35 }, // 0x5B '[' + { 3363, 1, 1, 0, 0, 0 }, // 0x5C '\' + { 3364, 18, 36, 22, 4, -35 }, // 0x5D ']' + { 3445, 1, 1, 0, 0, 0 }, // 0x5E '^' + { 3446, 13, 4, 22, 4, -3 }, // 0x5F '_' + { 3453, 5, 15, 22, 3, -33 }, // 0x60 '`' + { 3463, 20, 36, 22, 2, -35 }, // 0x61 'a' + { 3553, 19, 34, 22, 2, -33 }, // 0x62 'b' + { 3634, 17, 20, 22, 2, -19 }, // 0x63 'c' + { 3677, 20, 36, 22, 2, -35 }, // 0x64 'd' + { 3767, 20, 36, 22, 2, -35 }, // 0x65 'e' + { 3857, 17, 34, 22, 2, -35 }, // 0x66 'f' + { 3930, 19, 36, 22, 3, -35 }, // 0x67 'g' + { 4016, 19, 34, 22, 2, -33 }, // 0x68 'h' + { 4097, 5, 17, 22, 16, -16 }, // 0x69 'i' + { 4108, 18, 36, 22, 4, -35 }, // 0x6A 'j' + { 4189, 19, 36, 22, 2, -35 }, // 0x6B 'k' + { 4275, 15, 34, 22, 2, -33 }, // 0x6C 'l' + { 4339, 19, 36, 22, 2, -35 }, // 0x6D 'm' + { 4425, 19, 20, 22, 2, -19 }, // 0x6E 'n' + { 4473, 19, 20, 22, 2, -19 }, // 0x6F 'o' + { 4521, 20, 34, 22, 2, -35 }, // 0x70 'p' + { 4606, 19, 36, 22, 3, -35 }, // 0x71 'q' + { 4692, 17, 18, 22, 2, -19 }, // 0x72 'r' + { 4731, 18, 34, 22, 3, -33 }, // 0x73 's' + { 4808, 17, 34, 22, 2, -33 }, // 0x74 't' + { 4881, 19, 17, 22, 2, -16 }, // 0x75 'u' + { 4922, 19, 36, 22, 3, -35 }, // 0x76 'v' + { 5008, 20, 36, 22, 2, -35 }, // 0x77 'w' + { 5098, 15, 36, 22, 4, -35 }, // 0x78 'x' + { 5166, 19, 36, 22, 3, -35 }, // 0x79 'y' + { 5252, 20, 36, 22, 2, -35 }, // 0x7A 'z' + { 5342, 1, 1, 0, 0, 0 }, // 0x7B '{' + { 5343, 6, 32, 22, 2, -33 }, // 0x7C '|' + { 5367, 1, 1, 0, 0, 0 }, // 0x7D '}' + { 5368, 1, 1, 0, 0, 0 } }; // 0x7E '~' + +const GFXfont _7segment24pt7b PROGMEM = { + (uint8_t *)_7segment24pt7bBitmaps, + (GFXglyph *)_7segment24pt7bGlyphs, + 0x20, 0x7E, 48 }; + +// Approx. 6041 bytes +#endif // ifndef FONTS_7SEGMENT24PT7B_H diff --git a/src/src/Static/Fonts/LCD14cond18pt7b.h b/src/src/Static/Fonts/LCD14cond18pt7b.h new file mode 100644 index 000000000..1abd1bc59 --- /dev/null +++ b/src/src/Static/Fonts/LCD14cond18pt7b.h @@ -0,0 +1,317 @@ +#ifndef FONTS_LCD14COND18PT7B_H +#define FONTS_LCD14COND18PT7B_H +const uint8_t LCD14cond18pt7bBitmaps[] PROGMEM = { + 0x00, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3B, 0xBF, 0xEF, 0xE0, 0x8C, 0x63, 0x18, + 0xC6, 0x31, 0x8C, 0x63, 0x18, 0x80, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, + 0x42, 0x21, 0x10, 0x88, 0x44, 0x22, 0x11, 0x0F, 0xF7, 0xFF, 0xFD, 0x10, + 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, 0x7F, 0xBF, + 0xEF, 0xE0, 0x7F, 0x7F, 0xFF, 0xD1, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, + 0x44, 0x22, 0x11, 0x08, 0x87, 0xFB, 0xFE, 0xFF, 0x08, 0x84, 0x42, 0x21, + 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x5F, 0xFF, 0xF7, 0xF0, 0x7F, + 0xBF, 0xFF, 0xFA, 0x22, 0x89, 0xA2, 0x68, 0x9A, 0x2C, 0x8B, 0x23, 0x88, + 0xE2, 0x38, 0x8C, 0x3F, 0xEF, 0xFD, 0xFF, 0x18, 0x46, 0x13, 0x84, 0xE1, + 0x28, 0x4A, 0x16, 0x85, 0xA1, 0x48, 0x52, 0x17, 0xFF, 0xFF, 0x7F, 0x80, + 0x7F, 0x7F, 0xDD, 0xC8, 0x24, 0x12, 0x09, 0x8C, 0xC6, 0x22, 0x1B, 0x0D, + 0x82, 0x81, 0x40, 0x00, 0x02, 0x01, 0x14, 0x8A, 0x4D, 0xA6, 0xD2, 0x2B, + 0x1D, 0x8E, 0x83, 0x41, 0xA0, 0xDD, 0xFF, 0xF7, 0xF0, 0xFF, 0xF8, 0x7F, + 0x7F, 0xFD, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x01, 0x00, 0x80, 0x40, 0x3D, 0xDF, 0xF7, 0xF0, 0x7F, 0x7F, 0xDD, 0xE0, + 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, + 0x80, 0x5D, 0xFF, 0xF7, 0xF0, 0x08, 0x24, 0x92, 0x4D, 0x66, 0xB1, 0x50, + 0xA8, 0x7C, 0x3E, 0x0E, 0x07, 0x0F, 0xEF, 0xFB, 0xF8, 0x70, 0x38, 0x3E, + 0x1F, 0x0A, 0x85, 0x46, 0xB3, 0x59, 0x24, 0x92, 0x08, 0x00, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x07, + 0xF7, 0xFD, 0xFC, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, + 0x10, 0x08, 0x04, 0x02, 0x00, 0x25, 0xB4, 0xB6, 0x90, 0x77, 0x7F, 0xDD, + 0xC0, 0xFF, 0xF8, 0x02, 0x04, 0x18, 0x30, 0x40, 0x83, 0x06, 0x08, 0x10, + 0x00, 0x00, 0x04, 0x08, 0x30, 0x60, 0x81, 0x06, 0x0C, 0x10, 0x20, 0x00, + 0x7E, 0xFF, 0xF7, 0x81, 0x83, 0x83, 0x83, 0x87, 0x87, 0x85, 0x85, 0x85, + 0x85, 0x81, 0x80, 0x81, 0x91, 0xA1, 0xB1, 0xA1, 0xA1, 0xE1, 0xE1, 0xC1, + 0xC1, 0xC1, 0xF7, 0xFF, 0x7E, 0x13, 0x37, 0x75, 0x5D, 0xD9, 0x91, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x7F, 0xBF, 0xF7, 0x3C, 0x01, 0x00, + 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x5C, 0xFF, + 0xFF, 0xCE, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, + 0x80, 0x20, 0x0F, 0x3B, 0xFF, 0x7F, 0x80, 0x7F, 0x7F, 0xDD, 0xE0, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x0B, 0xBF, 0xFE, + 0xEF, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, + 0x5D, 0xFF, 0xF7, 0xF0, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0xF3, 0xFF, 0xF7, 0x3C, 0x01, + 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, + 0x10, 0x04, 0x7F, 0x7F, 0xDD, 0xC8, 0x04, 0x02, 0x01, 0x80, 0xC0, 0x20, + 0x18, 0x0C, 0x02, 0x01, 0x00, 0x38, 0x3E, 0x0F, 0x00, 0x80, 0x40, 0x20, + 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x5D, 0xFF, 0xF7, 0xF0, 0x7F, + 0xBF, 0xFF, 0x3A, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, + 0x02, 0x00, 0x80, 0x3C, 0xEF, 0xFF, 0xCF, 0x80, 0x60, 0x18, 0x06, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x1F, 0x3F, 0xFF, 0x7F, 0x80, + 0x7F, 0x7F, 0xDD, 0xE0, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, + 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, + 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x7F, 0xBF, 0xFF, 0x3E, + 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, + 0x7C, 0xFF, 0xFF, 0xCF, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x1F, 0x3F, 0xFF, 0x7F, 0x80, 0x7F, 0xBF, 0xFF, + 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, + 0x80, 0x7C, 0xFF, 0xFD, 0xCF, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, + 0x10, 0x04, 0x01, 0x00, 0x40, 0x17, 0x3F, 0xFF, 0x7F, 0x80, 0xFC, 0x00, + 0x00, 0x00, 0x00, 0x3F, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x10, 0x22, + 0x66, 0x44, 0xCC, 0x88, 0x25, 0xB4, 0xB6, 0x90, 0x01, 0x26, 0xC9, 0x36, + 0x48, 0x77, 0x7F, 0xDD, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0xBB, 0xFE, 0xFE, 0x93, 0x64, 0x9B, 0x24, 0x00, + 0x4B, 0x69, 0x6D, 0x20, 0x7F, 0x7F, 0xFD, 0xF0, 0x18, 0x0C, 0x06, 0x03, + 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x3E, 0x3E, 0x1E, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, + 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x04, 0x01, 0x12, 0x44, 0x91, 0x24, + 0xCD, 0x33, 0x48, 0x56, 0x1D, 0x87, 0x40, 0xD0, 0x37, 0x3F, 0xFF, 0x7F, + 0x80, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, + 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFF, 0xCF, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, + 0x7F, 0x7F, 0xDF, 0xE1, 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, + 0x21, 0x10, 0x88, 0x7C, 0x3C, 0x1F, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, + 0x44, 0x22, 0x11, 0x08, 0x84, 0x5F, 0xFF, 0xF7, 0xF0, 0x7F, 0x7F, 0xFD, + 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, + 0x80, 0x40, 0x3D, 0xDF, 0xF7, 0xF0, 0x7F, 0x7F, 0xDF, 0xE1, 0x10, 0x88, + 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, 0x44, 0x22, 0x11, + 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x5F, + 0xFF, 0xF7, 0xF0, 0x7F, 0x7F, 0xFD, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, + 0x80, 0x40, 0x20, 0x10, 0x08, 0x07, 0xBB, 0xFF, 0xEE, 0x80, 0x40, 0x20, + 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x3D, 0xDF, 0xF7, 0xF0, + 0x7F, 0x7F, 0xFD, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, + 0x10, 0x08, 0x07, 0xBB, 0xFF, 0xEE, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x00, 0x7F, 0xBF, 0xFF, 0x3A, + 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, + 0x20, 0xE8, 0x7E, 0x0F, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x1F, 0x3F, 0xFF, 0x7F, 0x80, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, + 0xF3, 0xFF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x04, 0x7F, 0x7F, 0xDF, 0xC1, 0x00, + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, + 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x1F, 0xDF, 0xF7, 0xF0, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, + 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x60, 0x18, 0x06, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, + 0xFF, 0xFD, 0xFE, 0x80, 0x40, 0x60, 0x70, 0x38, 0x14, 0x1A, 0x0D, 0x04, + 0x86, 0x43, 0x21, 0x1E, 0x0F, 0x87, 0x82, 0x11, 0x0C, 0x86, 0x41, 0x20, + 0xD0, 0x68, 0x14, 0x0E, 0x07, 0x01, 0x80, 0x00, 0x80, 0x40, 0x20, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, + 0x7B, 0xBF, 0xEF, 0xE0, 0x81, 0xC1, 0xC3, 0xC3, 0xE3, 0xE7, 0xA7, 0xA5, + 0xB5, 0x95, 0x95, 0x81, 0x80, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xC1, 0xC1, 0xC1, 0xE1, 0xE1, 0xA1, + 0xA1, 0xB1, 0xB1, 0x91, 0x81, 0x81, 0x81, 0x85, 0x85, 0x85, 0x85, 0x87, + 0x87, 0x83, 0x83, 0x83, 0x81, 0x81, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, + 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, + 0x80, 0x60, 0x1F, 0x3F, 0xFF, 0x7F, 0x80, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, + 0xFF, 0xFF, 0xCE, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, + 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, + 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x82, 0x60, 0x98, 0x26, 0x0D, 0x83, 0x60, 0x58, 0x1E, 0x07, + 0x80, 0xE0, 0x3F, 0x3F, 0xFF, 0x7F, 0x80, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, + 0xFF, 0xFF, 0xCE, 0x82, 0x20, 0x88, 0x22, 0x0C, 0x83, 0x20, 0x48, 0x1A, + 0x06, 0x80, 0xA0, 0x28, 0x00, 0x7F, 0xBF, 0xFF, 0x3A, 0x00, 0x80, 0x20, + 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x3C, 0xEF, 0xFD, + 0xCF, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, + 0x40, 0x17, 0x3F, 0xFF, 0x7F, 0x80, 0x7F, 0x7F, 0xDF, 0xC1, 0x00, 0x80, + 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x01, 0x00, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFD, + 0xFE, 0x01, 0x81, 0x81, 0x81, 0xC1, 0xC1, 0x41, 0x61, 0x61, 0x21, 0x21, + 0x01, 0x01, 0x01, 0x09, 0x09, 0x0D, 0x0D, 0x05, 0x07, 0x07, 0x03, 0x03, + 0x03, 0x01, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x95, 0xA5, 0xB5, 0xA5, 0xA7, 0xE7, 0xE3, 0xC3, + 0xC3, 0xC1, 0x81, 0x83, 0x06, 0x0E, 0x3C, 0x68, 0x9B, 0x36, 0x28, 0x50, + 0x00, 0x00, 0x05, 0x0A, 0x36, 0x6C, 0x8B, 0x1E, 0x38, 0x30, 0x60, 0x80, + 0x83, 0x07, 0x1E, 0x34, 0x48, 0x9B, 0x36, 0x28, 0x50, 0x00, 0x81, 0x02, + 0x04, 0x08, 0x10, 0x20, 0x40, 0x81, 0x02, 0x04, 0x08, 0x7F, 0x7F, 0xDD, + 0xC0, 0x20, 0x10, 0x18, 0x0C, 0x04, 0x02, 0x03, 0x01, 0x80, 0x80, 0x43, + 0xBB, 0xFE, 0xEE, 0x10, 0x08, 0x0C, 0x06, 0x02, 0x01, 0x01, 0x80, 0xC0, + 0x40, 0x20, 0x1D, 0xDF, 0xF7, 0xF0, 0x7F, 0x7F, 0xFF, 0xD1, 0x08, 0x84, + 0x42, 0x21, 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, 0x21, 0x10, + 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, 0x44, 0x3F, + 0xDF, 0xF7, 0xF0, 0x81, 0x03, 0x06, 0x04, 0x08, 0x18, 0x30, 0x20, 0x40, + 0x00, 0x00, 0x01, 0x02, 0x06, 0x0C, 0x08, 0x10, 0x30, 0x60, 0x40, 0x80, + 0x7F, 0x7F, 0xDF, 0xE1, 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, + 0x21, 0x10, 0x88, 0x44, 0x22, 0x11, 0x08, 0x84, 0x42, 0x21, 0x10, 0x88, + 0x44, 0x22, 0x11, 0x08, 0x84, 0x5F, 0xFF, 0xF7, 0xF0, 0x28, 0x51, 0xB3, + 0x64, 0x58, 0xF1, 0xC1, 0x83, 0x04, 0x77, 0x7F, 0xDF, 0xC0, 0x93, 0x64, + 0x9B, 0x24, 0x7F, 0xBF, 0xF7, 0x3C, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, + 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x5C, 0xFF, 0xFF, 0xCF, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x1F, 0x3F, + 0xFF, 0x7F, 0x80, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, + 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0xF3, 0xBF, 0xFF, 0x3E, 0x01, 0x80, + 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, + 0xFD, 0xFE, 0x77, 0x7F, 0xFD, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, + 0x40, 0x20, 0x10, 0x08, 0x07, 0xBB, 0xFE, 0xFE, 0x00, 0x40, 0x10, 0x04, + 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x73, + 0xFF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFD, 0xFE, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, + 0xFF, 0xFF, 0xCE, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, + 0x00, 0x80, 0x20, 0x0F, 0x3B, 0xFF, 0x7F, 0x80, 0x7F, 0x7F, 0xFD, 0xD0, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x07, 0x83, + 0xE1, 0xE0, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, + 0x40, 0x20, 0x10, 0x00, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, + 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFD, 0xCF, + 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, + 0x17, 0x3F, 0xFF, 0x7F, 0x80, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, + 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0xF3, 0xBF, 0xFF, 0x3E, + 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, + 0x60, 0x18, 0x04, 0x7F, 0x7F, 0xDD, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x04, 0x02, + 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x7F, + 0x7F, 0xDD, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x5D, 0xFF, 0xF7, 0xF0, 0x80, 0x40, 0x60, 0x70, + 0x38, 0x14, 0x1A, 0x0D, 0x04, 0x86, 0x43, 0x21, 0x1E, 0x0F, 0x87, 0x82, + 0x11, 0x0C, 0x86, 0x41, 0x20, 0xD0, 0x68, 0x14, 0x0E, 0x07, 0x01, 0x80, + 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0x77, 0x7F, 0xFF, 0xF1, 0x18, 0x8C, 0x46, + 0x23, 0x11, 0x88, 0xC4, 0x62, 0x31, 0x18, 0x8C, 0x46, 0x22, 0x73, 0xBF, + 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, + 0x01, 0x80, 0x60, 0x18, 0x04, 0x73, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFD, + 0xFE, 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, + 0x60, 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFF, 0xCE, 0x80, 0x20, 0x08, + 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, + 0x7F, 0xBF, 0xFF, 0x3E, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x7C, 0xFF, 0xFD, 0xCF, 0x00, 0x40, 0x10, 0x04, + 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x77, + 0x7F, 0xFD, 0xD0, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, + 0x08, 0x04, 0x02, 0x00, 0x7F, 0xBF, 0xFF, 0x3A, 0x00, 0x80, 0x20, 0x08, + 0x02, 0x00, 0x80, 0x20, 0x08, 0x02, 0x00, 0x80, 0x3C, 0xEF, 0xFD, 0xCF, + 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, 0x40, + 0x17, 0x3F, 0xFF, 0x7F, 0x80, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, + 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x07, 0xF7, 0xFD, 0xFC, 0x10, 0x08, + 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, + 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, + 0x18, 0x06, 0x01, 0xF3, 0xFF, 0xF7, 0xF8, 0x19, 0x9D, 0xD5, 0x57, 0x73, + 0x31, 0x81, 0x95, 0xA5, 0xB5, 0xA5, 0xA7, 0xE7, 0xE3, 0xC3, 0xC3, 0xC1, + 0x81, 0x83, 0x06, 0x0E, 0x3C, 0x68, 0x9B, 0x36, 0x28, 0x50, 0x00, 0x00, + 0x05, 0x0A, 0x36, 0x6C, 0x8B, 0x1E, 0x38, 0x30, 0x60, 0x80, 0x80, 0x60, + 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, 0x01, 0x80, 0x60, 0x18, 0x06, + 0x01, 0xF3, 0xFF, 0xF7, 0x3C, 0x01, 0x00, 0x40, 0x10, 0x04, 0x01, 0x00, + 0x40, 0x10, 0x04, 0x01, 0x00, 0x5C, 0xFF, 0xFD, 0xFE, 0x7F, 0x7F, 0xDD, + 0xC0, 0x20, 0x10, 0x18, 0x0C, 0x04, 0x02, 0x03, 0x01, 0x80, 0x80, 0x40, + 0x00, 0x00, 0x00, 0x10, 0x08, 0x0C, 0x06, 0x02, 0x01, 0x01, 0x80, 0xC0, + 0x40, 0x20, 0x1D, 0xDF, 0xF7, 0xF0, 0x7F, 0x7F, 0xFD, 0xD0, 0x08, 0x04, + 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x07, 0x83, 0xE1, 0xE0, + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x3D, + 0xDF, 0xF7, 0xF0, 0xFF, 0xFF, 0xFF, 0xC0, 0x7F, 0x7F, 0xDD, 0xE0, 0x10, + 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x3C, 0x3E, + 0x0F, 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x00, 0x80, + 0x5D, 0xFF, 0xF7, 0xF0, 0x80, 0xC0, 0xC0, 0xC0, 0xE0, 0xE0, 0xA0, 0xA0, + 0xB0, 0x90, 0x90, 0x80, 0x80, 0x01, 0x05, 0x05, 0x05, 0x05, 0x07, 0x07, + 0x03, 0x03, 0x03, 0x01, 0x01 }; + +const GFXglyph LCD14cond18pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 13, 0, 0 }, // 0x20 ' ' + { 1, 9, 28, 13, 2, -27 }, // 0x21 '!' + { 33, 5, 13, 12, 1, -26 }, // 0x22 '"' + { 42, 9, 28, 12, 1, -27 }, // 0x23 '#' + { 74, 9, 29, 11, 1, -28 }, // 0x24 '$' + { 107, 10, 29, 12, 1, -28 }, // 0x25 '%' + { 144, 9, 29, 12, 2, -28 }, // 0x26 '&' + { 177, 1, 13, 13, 6, -26 }, // 0x27 ''' + { 179, 9, 29, 12, 1, -28 }, // 0x28 '(' + { 212, 9, 29, 12, 2, -28 }, // 0x29 ')' + { 245, 9, 25, 13, 2, -26 }, // 0x2A '*' + { 274, 9, 27, 13, 2, -27 }, // 0x2B '+' + { 305, 3, 10, 13, 3, -12 }, // 0x2C ',' + { 309, 9, 3, 13, 2, -15 }, // 0x2D '-' + { 313, 1, 13, 12, 1, -13 }, // 0x2E '.' + { 315, 7, 23, 13, 3, -25 }, // 0x2F '/' + { 336, 8, 29, 13, 1, -28 }, // 0x30 '0' + { 365, 4, 25, 13, 7, -26 }, // 0x31 '1' + { 378, 10, 29, 13, 1, -28 }, // 0x32 '2' + { 415, 9, 29, 13, 2, -28 }, // 0x33 '3' + { 448, 10, 27, 13, 1, -27 }, // 0x34 '4' + { 482, 9, 29, 13, 2, -28 }, // 0x35 '5' + { 515, 10, 29, 13, 1, -28 }, // 0x36 '6' + { 552, 9, 28, 13, 2, -28 }, // 0x37 '7' + { 584, 10, 29, 13, 1, -28 }, // 0x38 '8' + { 621, 10, 29, 13, 1, -28 }, // 0x39 '9' + { 658, 2, 24, 4, 1, -26 }, // 0x3A ':' + { 664, 4, 24, 13, 3, -26 }, // 0x3B ';' + { 676, 3, 23, 13, 7, -25 }, // 0x3C '<' + { 685, 9, 16, 13, 2, -15 }, // 0x3D '=' + { 703, 3, 23, 13, 3, -25 }, // 0x3E '>' + { 712, 9, 28, 11, 1, -28 }, // 0x3F '?' + { 744, 10, 29, 12, 1, -28 }, // 0x40 '@' + { 781, 10, 28, 12, 1, -28 }, // 0x41 'A' + { 816, 9, 29, 12, 2, -28 }, // 0x42 'B' + { 849, 9, 29, 12, 1, -28 }, // 0x43 'C' + { 882, 9, 29, 12, 2, -28 }, // 0x44 'D' + { 915, 9, 29, 12, 1, -28 }, // 0x45 'E' + { 948, 9, 28, 12, 1, -28 }, // 0x46 'F' + { 980, 10, 29, 12, 1, -28 }, // 0x47 'G' + { 1017, 10, 27, 12, 1, -27 }, // 0x48 'H' + { 1051, 9, 29, 13, 2, -28 }, // 0x49 'I' + { 1084, 10, 28, 12, 1, -27 }, // 0x4A 'J' + { 1119, 9, 25, 12, 1, -26 }, // 0x4B 'K' + { 1148, 9, 28, 12, 1, -27 }, // 0x4C 'L' + { 1180, 8, 25, 11, 1, -26 }, // 0x4D 'M' + { 1205, 8, 25, 11, 1, -26 }, // 0x4E 'N' + { 1230, 10, 29, 12, 1, -28 }, // 0x4F 'O' + { 1267, 10, 28, 12, 1, -28 }, // 0x50 'P' + { 1302, 10, 29, 12, 1, -28 }, // 0x51 'Q' + { 1339, 10, 27, 12, 1, -28 }, // 0x52 'R' + { 1373, 10, 29, 12, 1, -28 }, // 0x53 'S' + { 1410, 9, 28, 13, 2, -28 }, // 0x54 'T' + { 1442, 10, 28, 12, 1, -27 }, // 0x55 'U' + { 1477, 8, 25, 12, 3, -26 }, // 0x56 'V' + { 1502, 8, 25, 11, 1, -26 }, // 0x57 'W' + { 1527, 7, 23, 13, 3, -25 }, // 0x58 'X' + { 1548, 7, 24, 13, 3, -25 }, // 0x59 'Y' + { 1569, 9, 29, 13, 2, -28 }, // 0x5A 'Z' + { 1602, 9, 29, 12, 1, -28 }, // 0x5B '[' + { 1635, 7, 23, 13, 3, -25 }, // 0x5C '\' + { 1656, 9, 29, 12, 2, -28 }, // 0x5D ']' + { 1689, 7, 10, 13, 3, -12 }, // 0x5E '^' + { 1698, 9, 3, 13, 2, -2 }, // 0x5F '_' + { 1702, 3, 10, 13, 3, -25 }, // 0x60 '`' + { 1706, 10, 29, 12, 1, -28 }, // 0x61 'a' + { 1743, 10, 28, 12, 1, -27 }, // 0x62 'b' + { 1778, 9, 16, 12, 1, -15 }, // 0x63 'c' + { 1796, 10, 28, 12, 1, -27 }, // 0x64 'd' + { 1831, 10, 29, 12, 1, -28 }, // 0x65 'e' + { 1868, 9, 28, 12, 1, -28 }, // 0x66 'f' + { 1900, 10, 29, 12, 1, -28 }, // 0x67 'g' + { 1937, 10, 27, 12, 1, -27 }, // 0x68 'h' + { 1971, 9, 28, 13, 2, -28 }, // 0x69 'i' + { 2003, 9, 29, 12, 2, -28 }, // 0x6A 'j' + { 2036, 9, 25, 12, 1, -26 }, // 0x6B 'k' + { 2065, 1, 26, 13, 6, -26 }, // 0x6C 'l' + { 2069, 9, 15, 11, 1, -15 }, // 0x6D 'm' + { 2086, 10, 15, 12, 1, -15 }, // 0x6E 'n' + { 2105, 10, 16, 12, 1, -15 }, // 0x6F 'o' + { 2125, 10, 28, 12, 1, -28 }, // 0x70 'p' + { 2160, 10, 28, 12, 1, -28 }, // 0x71 'q' + { 2195, 9, 15, 12, 1, -15 }, // 0x72 'r' + { 2212, 10, 29, 12, 1, -28 }, // 0x73 's' + { 2249, 9, 27, 13, 2, -27 }, // 0x74 't' + { 2280, 10, 15, 12, 1, -14 }, // 0x75 'u' + { 2299, 4, 12, 12, 7, -13 }, // 0x76 'v' + { 2305, 8, 12, 11, 1, -13 }, // 0x77 'w' + { 2317, 7, 23, 13, 3, -25 }, // 0x78 'x' + { 2338, 10, 28, 12, 1, -27 }, // 0x79 'y' + { 2373, 9, 29, 13, 2, -28 }, // 0x7A 'z' + { 2406, 9, 29, 12, 1, -28 }, // 0x7B '{' + { 2439, 1, 26, 13, 6, -26 }, // 0x7C '|' + { 2443, 9, 29, 12, 2, -28 }, // 0x7D '}' + { 2476, 8, 25, 11, 1, -26 } }; // 0x7E '~' + +const GFXfont LCD14cond18pt7b PROGMEM = { + (uint8_t *)LCD14cond18pt7bBitmaps, + (GFXglyph *)LCD14cond18pt7bGlyphs, + 0x20, 0x7E, 38 }; + +// Approx. 5734 bytes +#endif // ifndef FONTS_LCD14COND18PT7B_H diff --git a/src/src/Static/Fonts/LCD14cond24pt7b.h b/src/src/Static/Fonts/LCD14cond24pt7b.h new file mode 100644 index 000000000..853eb4d56 --- /dev/null +++ b/src/src/Static/Fonts/LCD14cond24pt7b.h @@ -0,0 +1,474 @@ +#ifndef FONTS_LCD14COND24PT7B_H +#define FONTS_LCD14COND24PT7B_H + +const uint8_t LCD14Condensed24pt7bBitmaps[] PROGMEM = { + 0x00, 0x04, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0xE7, 0xFE, 0x7F, 0xE7, 0xFE, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, + 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0x82, + 0x82, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, + 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, 0x61, 0x83, 0x0C, + 0x1F, 0xFE, 0x7F, 0xF3, 0xFF, 0xBF, 0xFD, 0x86, 0x0C, 0x30, 0x61, 0x83, + 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, 0x61, + 0x83, 0x0C, 0x18, 0x60, 0xFF, 0xF3, 0xFF, 0x9F, 0xFC, 0x7F, 0xE0, 0x3F, + 0xF1, 0xFF, 0xE7, 0xFF, 0xBF, 0xFC, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, + 0xC0, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0x0C, 0x0C, + 0x30, 0x30, 0xC0, 0xC3, 0x03, 0xFF, 0xC7, 0xFF, 0x9F, 0xFE, 0x3F, 0xFC, + 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, + 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3, 0x03, 0x0C, 0x0C, 0x33, 0xFF, + 0xDF, 0xFE, 0x7F, 0xF8, 0xFF, 0xC0, 0x3F, 0xE3, 0xFF, 0x9F, 0xFD, 0xFF, + 0xCC, 0x32, 0x61, 0x93, 0x0C, 0x98, 0x6C, 0xC3, 0x66, 0x1B, 0x30, 0xD1, + 0x87, 0x8C, 0x3C, 0x61, 0xE3, 0x0E, 0x18, 0x70, 0xC3, 0x87, 0xFF, 0x1F, + 0xFC, 0xFF, 0xE3, 0xFF, 0x83, 0x8C, 0x3C, 0x61, 0xE3, 0x0F, 0x18, 0x78, + 0xC6, 0xC6, 0x36, 0x31, 0xB1, 0x99, 0x8C, 0xCC, 0x66, 0x63, 0x33, 0x19, + 0x18, 0xCF, 0xFE, 0xFF, 0xE7, 0xFF, 0x1F, 0xF0, 0x7F, 0xCF, 0xFE, 0xFB, + 0xE7, 0xBC, 0x40, 0x44, 0x04, 0x60, 0xC6, 0x0C, 0x60, 0xC6, 0x0C, 0x31, + 0x83, 0x18, 0x31, 0x81, 0xB0, 0x1B, 0x01, 0xB0, 0x0B, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x00, 0x30, 0xA3, 0x1B, 0x31, 0xB3, 0x1B, 0x33, 0x1B, 0x31, + 0xB3, 0x1B, 0x60, 0xF6, 0x0F, 0x60, 0xF6, 0x0F, 0x40, 0x74, 0x07, 0x7B, + 0xFF, 0xBE, 0xFF, 0xE7, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x3F, 0xF7, + 0xFF, 0x7D, 0xFF, 0xDF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0x80, 0x08, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0xFD, 0xF7, 0xDF, 0x7F, 0xF3, 0xFF, 0xFF, 0xCF, 0xFE, 0xFB, 0xEF, + 0xBF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x20, + 0x02, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0xFB, 0xFF, + 0xBE, 0xFF, 0xEF, 0xFC, 0x00, 0x03, 0x0C, 0xCF, 0x33, 0xCC, 0xF3, 0x36, + 0xD9, 0xB6, 0x6D, 0x8F, 0xC3, 0xF0, 0xFC, 0x3F, 0x07, 0x81, 0xE3, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFC, 0x78, 0x1E, 0x0F, 0xC3, 0xF0, 0xFC, 0x3F, 0x1B, + 0x66, 0xD9, 0xB6, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0x0C, 0x00, 0x00, 0x04, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x07, 0xFE, 0x7F, + 0xE7, 0xFE, 0x7F, 0xE0, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x40, 0x13, 0x33, 0x36, 0x66, 0xCC, 0xCC, 0x80, 0x7B, 0xDF, 0x7F, + 0xEF, 0xBD, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0xC0, 0x30, 0x0C, + 0x07, 0x01, 0x80, 0x60, 0x38, 0x0C, 0x03, 0x01, 0xC0, 0x60, 0x18, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0x00, 0xC0, 0x30, 0x1C, 0x06, + 0x01, 0x80, 0x60, 0x30, 0x0C, 0x03, 0x00, 0x80, 0x20, 0x00, 0x3F, 0xE3, + 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x03, 0xE0, 0x1F, 0x01, 0xF8, 0x0F, 0xC0, + 0x7E, 0x03, 0xF0, 0x37, 0x81, 0xBC, 0x0D, 0xE0, 0xCF, 0x06, 0x78, 0x33, + 0xC1, 0x9E, 0x00, 0xE0, 0x05, 0x00, 0x2C, 0x01, 0xE2, 0x0F, 0x30, 0x79, + 0x83, 0xCC, 0x1E, 0xC0, 0xF6, 0x07, 0xB0, 0x3F, 0x81, 0xF8, 0x0F, 0xC0, + 0x7E, 0x03, 0xE0, 0x1F, 0x00, 0xFF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, + 0x00, 0x31, 0xC7, 0x3C, 0xF3, 0xCF, 0x6D, 0xB6, 0xF3, 0xCF, 0x3C, 0xC3, + 0x00, 0x20, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, + 0x0C, 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7C, 0x7B, 0xF0, 0x01, 0x80, 0x0C, + 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, + 0x0C, 0x00, 0x60, 0x03, 0x00, 0x19, 0xEF, 0xDF, 0x7C, 0xFF, 0xEF, 0xDE, + 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, + 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x3F, 0x78, 0xFB, + 0xE7, 0xFF, 0x1F, 0xF0, 0xFF, 0xCF, 0xFE, 0xFB, 0xEF, 0xBF, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x3F, 0xBF, 0xFB, 0xEF, 0xFE, 0xFB, 0xF0, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, + 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0xFB, 0xFF, 0xBE, 0xFF, 0xEF, + 0xFC, 0x80, 0x16, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, + 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, + 0x00, 0x7F, 0xBF, 0x7D, 0xF3, 0xFF, 0x8F, 0x7E, 0x00, 0x30, 0x01, 0x80, + 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0x80, 0xFF, 0xCF, 0xFE, + 0xFF, 0xEF, 0xBC, 0xC0, 0x0C, 0x00, 0xC0, 0x0E, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x03, 0x00, 0x30, 0x03, 0x00, 0x10, 0x01, 0x00, 0x10, 0x00, 0x3C, + 0x07, 0xE0, 0x7E, 0x03, 0xF0, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0xFB, 0xFF, 0xFE, 0xFF, 0xEF, 0xFC, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, + 0xCC, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xEF, 0x1F, + 0x7C, 0xFF, 0xEF, 0xDF, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, + 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, + 0x00, 0xFF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0xFF, 0xCF, 0xFE, 0xFB, + 0xEF, 0xBF, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x20, 0x00, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x02, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, 0xE0, 0x0F, + 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, + 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, 0xDF, 0x7C, 0xFF, 0xEF, 0xDF, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xFF, 0x7E, 0xFB, + 0xE7, 0xFF, 0x1F, 0xF0, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, 0xDF, 0x7C, 0xFB, + 0xE3, 0xDF, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, + 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xCF, + 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xFF, 0x00, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, + 0x30, 0xC3, 0x08, 0x00, 0x04, 0x10, 0xC3, 0x0C, 0x31, 0x86, 0x18, 0xC3, + 0x0C, 0x30, 0x11, 0x33, 0x36, 0x66, 0xEC, 0xCC, 0x80, 0x00, 0x08, 0xCC, + 0xCC, 0x66, 0x63, 0x33, 0x11, 0x7B, 0xDF, 0x7F, 0xEF, 0xBD, 0xE0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0F, 0x7B, 0xEF, 0xFF, 0xF7, 0xFC, 0x88, 0xCC, + 0xC6, 0x66, 0x33, 0x33, 0x10, 0x00, 0x01, 0x33, 0x37, 0x66, 0x6C, 0xCC, + 0x88, 0x3F, 0xF1, 0xFF, 0xE7, 0xFF, 0xBF, 0x3F, 0xC0, 0x0F, 0x00, 0x3C, + 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, + 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x03, 0xF8, 0x1F, 0x80, 0x7E, + 0x03, 0xF0, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, + 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, + 0x00, 0x30, 0x00, 0x80, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xE0, 0x04, 0x00, + 0x20, 0x01, 0x82, 0x8C, 0x36, 0x61, 0xB3, 0x0D, 0x98, 0xC6, 0xC6, 0x36, + 0x31, 0xB3, 0x07, 0x98, 0x3C, 0xC1, 0xE6, 0x0F, 0x20, 0x39, 0x01, 0xCF, + 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, + 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, + 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, 0xDF, + 0x7C, 0xFB, 0xEF, 0xDF, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, + 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, + 0x00, 0xF0, 0x07, 0x00, 0x20, 0x7F, 0xE3, 0xFF, 0x9F, 0xFC, 0xFF, 0xF0, + 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, + 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x3F, 0xC0, 0xFC, + 0x07, 0xE0, 0x7F, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, + 0x86, 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, + 0xDF, 0xFE, 0xFF, 0xE7, 0xFF, 0x3F, 0xF0, 0x3F, 0xF7, 0xFF, 0x7D, 0xFF, + 0xDF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x80, 0x08, + 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, + 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFD, 0xF7, + 0xDF, 0x7F, 0xF3, 0xFF, 0x7F, 0xE3, 0xFF, 0x9F, 0xFC, 0xFF, 0xF0, 0x61, + 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, + 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x04, 0x08, + 0x20, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, + 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xDF, + 0xFE, 0xFF, 0xE7, 0xFF, 0x3F, 0xF0, 0x3F, 0xF7, 0xFF, 0x7D, 0xFF, 0xDF, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xDF, 0x7D, 0xF7, 0xFF, + 0xFD, 0xFC, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFD, 0xF7, 0xDF, + 0x7F, 0xF3, 0xFF, 0x3F, 0xF7, 0xFF, 0x7D, 0xFF, 0xDF, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xDF, 0x7D, 0xF7, 0xFF, 0xFD, 0xFC, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x08, 0x00, 0x3F, 0xE3, 0xFF, + 0x9F, 0x7D, 0xFB, 0xCC, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, + 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, + 0x06, 0x0F, 0x20, 0x7D, 0x03, 0xEC, 0x1F, 0xE0, 0x0F, 0x00, 0x78, 0x03, + 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, + 0x03, 0xC0, 0x1E, 0x00, 0xFF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x80, + 0x16, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, + 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x7F, + 0xBF, 0x7D, 0xF3, 0xFF, 0xBF, 0x7F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, + 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, + 0x00, 0x78, 0x03, 0xC0, 0x1C, 0x00, 0x80, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, + 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x04, 0x00, + 0x40, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, + 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x7F, 0xE7, + 0xFE, 0x7F, 0xE7, 0xFE, 0x00, 0x10, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, + 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x14, 0x00, 0xB0, 0x07, 0x80, + 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, + 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFD, 0xFB, 0xEF, 0x9F, + 0xFC, 0x7F, 0xC0, 0x00, 0x0C, 0x00, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x07, + 0xC0, 0x6C, 0x06, 0xC0, 0xEC, 0x0C, 0xC0, 0xCC, 0x1C, 0xC1, 0x8C, 0x18, + 0xC1, 0x8F, 0xC0, 0x7C, 0x07, 0xC0, 0xFC, 0x0C, 0x18, 0xC1, 0x8C, 0x18, + 0xC1, 0xCC, 0x0C, 0xC0, 0xCC, 0x0E, 0xC0, 0x6C, 0x06, 0xC0, 0x7C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x00, 0x00, 0x80, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x00, 0x08, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, + 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFD, 0xF7, 0xDF, 0x7F, 0xF3, 0xFF, + 0x00, 0x06, 0x00, 0xF8, 0x0F, 0xC0, 0x7F, 0x07, 0xF8, 0x3F, 0xC1, 0xFB, + 0x0F, 0xD8, 0xDE, 0xC6, 0xF6, 0x37, 0x9B, 0x3C, 0xD9, 0xE6, 0xCF, 0x14, + 0x78, 0x03, 0x00, 0x04, 0x00, 0xB0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, + 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, + 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x18, 0x00, 0x00, 0x00, 0x06, 0x00, 0xF8, + 0x07, 0xC0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xD8, 0x1E, 0xC0, + 0xF6, 0x07, 0x98, 0x3C, 0xC1, 0xE6, 0x0F, 0x10, 0x78, 0x03, 0x80, 0x14, + 0x00, 0xB0, 0x07, 0x83, 0x3C, 0x19, 0xE0, 0xCF, 0x06, 0x78, 0x1B, 0xC0, + 0xDE, 0x06, 0xF0, 0x1F, 0x80, 0xFC, 0x07, 0xE0, 0x3F, 0x00, 0xF8, 0x07, + 0xC0, 0x18, 0x00, 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xE0, 0x05, 0x00, + 0x2C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, + 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xFF, + 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, + 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, + 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, 0xDF, + 0x7C, 0xFB, 0xEF, 0xDE, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, + 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, + 0x00, 0x30, 0x01, 0x00, 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, + 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xE0, 0x05, + 0x00, 0x2C, 0x01, 0xE0, 0x8F, 0x06, 0x78, 0x33, 0xC1, 0x9E, 0x06, 0xF0, + 0x37, 0x81, 0xBC, 0x07, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x3E, 0x01, + 0xFF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, + 0xFB, 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, + 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, + 0xDF, 0x7C, 0xFB, 0xEF, 0xDE, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, + 0x06, 0x30, 0x31, 0x81, 0x8C, 0x06, 0x60, 0x33, 0x01, 0x98, 0x04, 0xC0, + 0x26, 0x01, 0x30, 0x00, 0x00, 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, + 0xCC, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x07, 0xEF, 0x1F, + 0x7C, 0xFF, 0xE3, 0xDF, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, + 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, + 0x00, 0xCF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x7F, 0xE7, 0xFE, 0x7F, + 0xE7, 0xFE, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x04, + 0x00, 0x40, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, + 0x00, 0x40, 0x80, 0x16, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, + 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, + 0x0F, 0x00, 0x78, 0x03, 0x00, 0x14, 0x00, 0xB0, 0x07, 0x80, 0x3C, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFD, 0xFB, 0xEF, 0x9F, 0xFC, 0x7F, + 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3E, 0x03, 0x60, 0x36, + 0x03, 0x60, 0x33, 0x03, 0x30, 0x33, 0x03, 0x10, 0x31, 0x03, 0x10, 0x30, + 0x03, 0x00, 0x20, 0x02, 0x00, 0x30, 0x23, 0x03, 0x30, 0x33, 0x03, 0x30, + 0x3B, 0x01, 0xB0, 0x1B, 0x01, 0xB0, 0x0F, 0x00, 0xF0, 0x0F, 0x00, 0x70, + 0x07, 0x00, 0x30, 0x00, 0x00, 0x06, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0x80, 0x10, 0x00, 0xB0, 0x07, 0x8A, + 0x3C, 0xD9, 0xE6, 0xCF, 0x36, 0x7B, 0x1B, 0xD8, 0xDE, 0xC6, 0xF6, 0x1F, + 0xE0, 0xFF, 0x07, 0xF8, 0x3F, 0x80, 0xFC, 0x07, 0xC0, 0x18, 0x00, 0x00, + 0x80, 0xC0, 0x70, 0x78, 0x3C, 0x1E, 0x0D, 0x8C, 0xC6, 0x63, 0x1B, 0x0D, + 0x86, 0xC1, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0D, 0x86, 0xC3, 0x63, + 0x19, 0x8C, 0xC6, 0xC1, 0xE0, 0xF0, 0x78, 0x38, 0x0C, 0x04, 0x80, 0xC0, + 0x70, 0x38, 0x3C, 0x1B, 0x0D, 0x84, 0xC6, 0x33, 0x19, 0x8C, 0x82, 0x41, + 0x20, 0x00, 0x00, 0x10, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, + 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x00, 0x7F, 0xFF, + 0xFF, 0xFF, 0xBD, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x07, 0x00, 0xC0, 0x18, + 0x07, 0x00, 0xC0, 0x18, 0x07, 0x00, 0xC0, 0x18, 0x03, 0x0F, 0x7F, 0xFF, + 0xFF, 0xF7, 0xBE, 0x10, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0xC0, 0x18, + 0x03, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x08, 0x01, 0xEF, 0xFF, 0xFF, + 0xFE, 0xFF, 0xC0, 0x3F, 0xF3, 0xFF, 0x9F, 0xFD, 0xFF, 0xEC, 0x30, 0x61, + 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, 0x0C, 0x30, + 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x20, 0x81, 0x04, 0x0C, + 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, 0x86, + 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x3F, 0xFC, + 0xFF, 0xE7, 0xFF, 0x1F, 0xF8, 0x80, 0x20, 0x0C, 0x03, 0x00, 0xC0, 0x18, + 0x06, 0x01, 0x80, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x80, 0x60, 0x18, 0x07, 0x00, 0xC0, 0x30, 0x0E, 0x01, + 0x80, 0x60, 0x1C, 0x03, 0x00, 0xC0, 0x30, 0x7F, 0xE3, 0xFF, 0x9F, 0xFC, + 0xFF, 0xF0, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, 0xC1, + 0x86, 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, 0x30, + 0xC1, 0x04, 0x08, 0x20, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, 0x18, + 0x30, 0xC1, 0x86, 0x0C, 0x30, 0x61, 0x83, 0x0C, 0x18, 0x60, 0xC3, 0x06, + 0x18, 0x30, 0xDF, 0xFE, 0xFF, 0xE7, 0xFF, 0x3F, 0xF0, 0x14, 0x1B, 0x0D, + 0x86, 0xC6, 0x33, 0x19, 0x8C, 0xC2, 0xC1, 0xE0, 0xF0, 0x70, 0x18, 0x08, + 0x7B, 0xDF, 0x7F, 0xFF, 0xBF, 0xE0, 0x88, 0xCC, 0xC6, 0x66, 0x33, 0x31, + 0x10, 0x3F, 0xE3, 0xFF, 0x9F, 0x7C, 0x7B, 0xF0, 0x01, 0x80, 0x0C, 0x00, + 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, + 0x00, 0x60, 0x03, 0x00, 0x19, 0xEF, 0xDF, 0x7C, 0xFF, 0xEF, 0xDF, 0xE0, + 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xFF, 0x7E, 0xFB, 0xE7, + 0xFF, 0x1F, 0xF0, 0x80, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, + 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, + 0x60, 0x03, 0x00, 0x1F, 0xBC, 0x7D, 0xF3, 0xFF, 0xBF, 0x7F, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, + 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xFD, 0xFB, 0xEF, 0x9F, 0xFC, + 0x7F, 0xC0, 0x3D, 0xF7, 0xDF, 0x7D, 0xFF, 0xDF, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0F, 0xDF, 0x7D, 0xF7, 0xFF, 0x3F, 0xF0, 0x00, 0x10, + 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, + 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x67, 0xBF, + 0x7D, 0xF3, 0xFF, 0xBF, 0x7F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, + 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, + 0x78, 0x03, 0xFD, 0xFB, 0xEF, 0x9F, 0xFC, 0x7F, 0xC0, 0x3F, 0xE3, 0xFF, + 0x9F, 0x7D, 0xFB, 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, + 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, + 0x1F, 0xEF, 0xDF, 0x7C, 0xFB, 0xEF, 0xDE, 0x60, 0x03, 0x00, 0x18, 0x00, + 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, + 0x00, 0xC0, 0x06, 0x00, 0x3F, 0x78, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x3F, + 0xF7, 0xFF, 0x7D, 0xFF, 0xDF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0F, 0xC0, 0x7C, 0x07, 0xC0, 0xFC, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x08, 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, + 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1F, 0xEF, 0xDF, 0x7C, + 0xFB, 0xE3, 0xDF, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, + 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, + 0xCF, 0x7E, 0xFB, 0xE7, 0xFF, 0x1F, 0xF0, 0x80, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, + 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x1F, 0xBC, 0x7D, 0xF3, 0xEF, + 0xBF, 0x7F, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, + 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, + 0x1C, 0x00, 0x80, 0x7F, 0xE7, 0xFE, 0x7F, 0xE7, 0x9E, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x40, 0xFF, 0xCF, 0xFE, + 0xFB, 0xEF, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, + 0xFB, 0xFF, 0xBE, 0xFF, 0xEF, 0xFC, 0x00, 0x0C, 0x00, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x07, 0xC0, 0x6C, 0x06, 0xC0, 0xEC, 0x0C, 0xC0, 0xCC, 0x1C, + 0xC1, 0x8C, 0x18, 0xC1, 0x8F, 0xC0, 0x7C, 0x07, 0xC0, 0xFC, 0x0C, 0x18, + 0xC1, 0x8C, 0x18, 0xC1, 0xCC, 0x0C, 0xC0, 0xCC, 0x0E, 0xC0, 0x6C, 0x06, + 0xC0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, + 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xE0, 0x3C, 0xF1, 0xFF, 0xE7, 0xFF, 0xBF, + 0xFF, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, + 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, + 0x0C, 0x38, 0x20, 0x80, 0x3D, 0xE3, 0xEF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xE0, 0x04, 0x3D, + 0xE3, 0xEF, 0x9F, 0xFD, 0xFB, 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, + 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, + 0x03, 0xC0, 0x1F, 0xEF, 0xDF, 0x7C, 0xFF, 0xE3, 0xFE, 0x00, 0x3F, 0xE3, + 0xFF, 0x9F, 0x7D, 0xFB, 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, + 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, + 0xC0, 0x1F, 0xEF, 0xDF, 0x7C, 0xFB, 0xEF, 0xDE, 0x60, 0x03, 0x00, 0x18, + 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, + 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x00, 0x00, 0x3F, 0xE3, 0xFF, + 0x9F, 0x7D, 0xFB, 0xFC, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, + 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, + 0x1F, 0xEF, 0xDF, 0x7C, 0xFB, 0xE3, 0xDF, 0x80, 0x0C, 0x00, 0x60, 0x03, + 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, + 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x20, 0x3D, 0xF7, 0xDF, 0x7D, + 0xFF, 0xDF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0x80, + 0x00, 0x3F, 0xE3, 0xFF, 0x9F, 0x7D, 0xFB, 0xCC, 0x00, 0x60, 0x03, 0x00, + 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, + 0x00, 0x18, 0x00, 0xC0, 0x07, 0xEF, 0x1F, 0x7C, 0xFF, 0xE3, 0xDF, 0x80, + 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, 0x00, 0x30, 0x01, + 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xCF, 0x7E, 0xFB, 0xE7, + 0xFF, 0x1F, 0xF0, 0x04, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x07, 0xFE, 0x7F, 0xE7, 0xFE, 0x7F, 0xE0, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x40, 0x80, 0x16, 0x00, 0xF0, 0x07, 0x80, + 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, + 0x80, 0x3C, 0x01, 0xE0, 0x0F, 0x00, 0x7F, 0xBF, 0x7D, 0xF3, 0xFF, 0x8F, + 0xF8, 0x00, 0x3C, 0xF3, 0xCF, 0x36, 0xDB, 0x6C, 0xF3, 0xCF, 0x3C, 0x71, + 0xC3, 0x00, 0x00, 0x06, 0x00, 0xF1, 0x47, 0x9B, 0x3C, 0xD9, 0xE6, 0xCF, + 0x63, 0x7B, 0x1B, 0xD8, 0xDE, 0xC3, 0xFC, 0x1F, 0xE0, 0xFF, 0x07, 0xF0, + 0x1F, 0x80, 0xF8, 0x03, 0x00, 0x00, 0x80, 0xC0, 0x70, 0x78, 0x3C, 0x1E, + 0x0D, 0x8C, 0xC6, 0x63, 0x1B, 0x0D, 0x86, 0xC1, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x0A, 0x0D, 0x86, 0xC3, 0x63, 0x19, 0x8C, 0xC6, 0xC1, 0xE0, 0xF0, + 0x78, 0x38, 0x0C, 0x04, 0x80, 0x16, 0x00, 0xF0, 0x07, 0x80, 0x3C, 0x01, + 0xE0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x01, 0xE0, 0x0F, 0x00, 0x7F, 0xBF, 0x7D, 0xF3, 0xFF, 0x8F, 0x7E, 0x00, + 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x18, 0x00, 0xC0, 0x06, + 0x00, 0x30, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x03, 0x3D, 0xFB, 0xEF, 0x9F, + 0xFC, 0x7F, 0xC0, 0x7F, 0xFF, 0xFF, 0xFF, 0xBD, 0xF0, 0x06, 0x00, 0xC0, + 0x18, 0x07, 0x00, 0xC0, 0x18, 0x07, 0x00, 0xC0, 0x18, 0x07, 0x00, 0xC0, + 0x18, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x06, 0x00, 0xC0, + 0x18, 0x07, 0x00, 0xC0, 0x18, 0x03, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x40, + 0x08, 0x01, 0xEF, 0xFF, 0xFF, 0xFE, 0xFF, 0xC0, 0x3F, 0xF7, 0xFF, 0x7D, + 0xFF, 0xDF, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0F, 0xC0, 0x7C, + 0x07, 0xC0, 0xFC, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, + 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0x00, 0xFD, + 0xF7, 0xDF, 0x7F, 0xF3, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, + 0xFF, 0xE0, 0xFF, 0xCF, 0xFE, 0xFB, 0xEF, 0xBF, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x3F, 0x03, 0xE0, 0x3E, 0x03, 0xF0, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, 0x30, 0x03, 0x00, + 0x30, 0x03, 0x00, 0x30, 0x03, 0xFB, 0xFF, 0xBE, 0xFF, 0xEF, 0xFC, 0x00, + 0x06, 0x00, 0x38, 0x01, 0xC0, 0x0F, 0x00, 0x78, 0x03, 0xC0, 0x1B, 0x00, + 0xD8, 0x06, 0xC0, 0x36, 0x01, 0x98, 0x0C, 0xC0, 0x66, 0x03, 0x10, 0x18, + 0x00, 0x80, 0x00, 0x00, 0x80, 0x06, 0x02, 0x30, 0x19, 0x80, 0xCC, 0x06, + 0x60, 0x1B, 0x00, 0xD8, 0x06, 0xC0, 0x1E, 0x00, 0xF0, 0x07, 0x80, 0x3C, + 0x00, 0xE0, 0x07, 0x00, 0x18, 0x00, 0x00 }; + +const GFXglyph LCD14Condensed24pt7bGlyphs[] PROGMEM = { + { 0, 1, 1, 17, 0, 0 }, // 0x20 ' ' + { 1, 12, 36, 17, 3, -35 }, // 0x21 '!' + { 55, 8, 17, 17, 2, -35 }, // 0x22 '"' + { 72, 13, 36, 17, 2, -35 }, // 0x23 '#' + { 131, 14, 38, 18, 2, -37 }, // 0x24 '$' + { 198, 13, 38, 17, 2, -37 }, // 0x25 '%' + { 260, 12, 38, 17, 3, -37 }, // 0x26 '&' + { 317, 2, 17, 17, 8, -35 }, // 0x27 ''' + { 322, 12, 38, 17, 2, -37 }, // 0x28 '(' + { 379, 12, 38, 17, 3, -37 }, // 0x29 ')' + { 436, 10, 34, 16, 3, -35 }, // 0x2A '*' + { 479, 12, 34, 17, 3, -35 }, // 0x2B '+' + { 530, 4, 13, 17, 4, -16 }, // 0x2C ',' + { 537, 11, 4, 17, 3, -20 }, // 0x2D '-' + { 543, 2, 17, 17, 2, -18 }, // 0x2E '.' + { 548, 10, 30, 17, 4, -33 }, // 0x2F '/' + { 586, 13, 38, 17, 2, -37 }, // 0x30 '0' + { 648, 6, 34, 17, 9, -35 }, // 0x31 '1' + { 674, 13, 38, 17, 2, -37 }, // 0x32 '2' + { 736, 12, 38, 17, 3, -37 }, // 0x33 '3' + { 793, 13, 34, 17, 2, -35 }, // 0x34 '4' + { 849, 12, 38, 17, 3, -37 }, // 0x35 '5' + { 906, 13, 38, 17, 2, -37 }, // 0x36 '6' + { 968, 12, 36, 17, 3, -37 }, // 0x37 '7' + { 1022, 13, 38, 17, 2, -37 }, // 0x38 '8' + { 1084, 13, 38, 17, 2, -37 }, // 0x39 '9' + { 1146, 2, 32, 6, 2, -35 }, // 0x3A ':' + { 1154, 6, 32, 16, 3, -35 }, // 0x3B ';' + { 1178, 4, 30, 17, 9, -33 }, // 0x3C '<' + { 1193, 11, 21, 17, 3, -20 }, // 0x3D '=' + { 1222, 4, 30, 17, 4, -33 }, // 0x3E '>' + { 1237, 14, 36, 18, 2, -37 }, // 0x3F '?' + { 1300, 13, 38, 17, 2, -37 }, // 0x40 '@' + { 1362, 13, 36, 17, 2, -37 }, // 0x41 'A' + { 1421, 13, 38, 18, 3, -37 }, // 0x42 'B' + { 1483, 12, 38, 17, 2, -37 }, // 0x43 'C' + { 1540, 13, 38, 18, 3, -37 }, // 0x44 'D' + { 1602, 12, 38, 17, 2, -37 }, // 0x45 'E' + { 1659, 12, 36, 17, 2, -37 }, // 0x46 'F' + { 1713, 13, 38, 17, 2, -37 }, // 0x47 'G' + { 1775, 13, 34, 17, 2, -35 }, // 0x48 'H' + { 1831, 12, 38, 17, 3, -37 }, // 0x49 'I' + { 1888, 13, 36, 17, 2, -35 }, // 0x4A 'J' + { 1947, 12, 34, 17, 2, -35 }, // 0x4B 'K' + { 1998, 12, 36, 17, 2, -35 }, // 0x4C 'L' + { 2052, 13, 34, 17, 2, -35 }, // 0x4D 'M' + { 2108, 13, 34, 17, 2, -35 }, // 0x4E 'N' + { 2164, 13, 38, 17, 2, -37 }, // 0x4F 'O' + { 2226, 13, 36, 17, 2, -37 }, // 0x50 'P' + { 2285, 13, 38, 17, 2, -37 }, // 0x51 'Q' + { 2347, 13, 36, 17, 2, -37 }, // 0x52 'R' + { 2406, 13, 38, 17, 2, -37 }, // 0x53 'S' + { 2468, 12, 36, 17, 3, -37 }, // 0x54 'T' + { 2522, 13, 36, 17, 2, -35 }, // 0x55 'U' + { 2581, 12, 34, 17, 3, -35 }, // 0x56 'V' + { 2632, 13, 34, 17, 2, -35 }, // 0x57 'W' + { 2688, 9, 30, 17, 4, -33 }, // 0x58 'X' + { 2722, 9, 32, 17, 4, -33 }, // 0x59 'Y' + { 2758, 11, 38, 17, 3, -37 }, // 0x5A 'Z' + { 2811, 13, 38, 17, 2, -37 }, // 0x5B '[' + { 2873, 10, 30, 17, 4, -33 }, // 0x5C '\' + { 2911, 13, 38, 18, 3, -37 }, // 0x5D ']' + { 2973, 9, 13, 17, 4, -16 }, // 0x5E '^' + { 2988, 11, 4, 17, 3, -3 }, // 0x5F '_' + { 2994, 4, 13, 17, 4, -33 }, // 0x60 '`' + { 3001, 13, 38, 17, 2, -37 }, // 0x61 'a' + { 3063, 13, 36, 17, 2, -35 }, // 0x62 'b' + { 3122, 12, 21, 17, 2, -20 }, // 0x63 'c' + { 3154, 13, 36, 17, 2, -35 }, // 0x64 'd' + { 3213, 13, 38, 17, 2, -37 }, // 0x65 'e' + { 3275, 12, 36, 17, 2, -37 }, // 0x66 'f' + { 3329, 13, 38, 17, 2, -37 }, // 0x67 'g' + { 3391, 13, 34, 17, 2, -35 }, // 0x68 'h' + { 3447, 12, 36, 17, 3, -37 }, // 0x69 'i' + { 3501, 12, 38, 17, 3, -37 }, // 0x6A 'j' + { 3558, 12, 34, 17, 2, -35 }, // 0x6B 'k' + { 3609, 2, 34, 17, 8, -35 }, // 0x6C 'l' + { 3618, 14, 19, 18, 2, -20 }, // 0x6D 'm' + { 3652, 13, 19, 17, 2, -20 }, // 0x6E 'n' + { 3683, 13, 21, 17, 2, -20 }, // 0x6F 'o' + { 3718, 13, 36, 17, 2, -37 }, // 0x70 'p' + { 3777, 13, 36, 17, 2, -37 }, // 0x71 'q' + { 3836, 12, 19, 17, 2, -20 }, // 0x72 'r' + { 3865, 13, 38, 17, 2, -37 }, // 0x73 's' + { 3927, 12, 34, 17, 3, -35 }, // 0x74 't' + { 3978, 13, 19, 17, 2, -18 }, // 0x75 'u' + { 4009, 6, 17, 17, 9, -18 }, // 0x76 'v' + { 4022, 13, 17, 17, 2, -18 }, // 0x77 'w' + { 4050, 9, 30, 17, 4, -33 }, // 0x78 'x' + { 4084, 13, 36, 17, 2, -35 }, // 0x79 'y' + { 4143, 11, 38, 17, 3, -37 }, // 0x7A 'z' + { 4196, 12, 38, 17, 2, -37 }, // 0x7B '{' + { 4253, 2, 34, 17, 8, -35 }, // 0x7C '|' + { 4262, 12, 38, 17, 3, -37 }, // 0x7D '}' + { 4319, 13, 34, 17, 2, -35 } }; // 0x7E '~' + +const GFXfont LCD14cond24pt7b PROGMEM = { + (uint8_t *)LCD14Condensed24pt7bBitmaps, + (GFXglyph *)LCD14Condensed24pt7bGlyphs, + 0x20, 0x7E, 51 }; + +// Approx. 5047 bytes +#endif // ifndef FONTS_LCD14COND24PT7B_H diff --git a/src/src/WebServer/AdvancedConfigPage.cpp b/src/src/WebServer/AdvancedConfigPage.cpp index 8de04c71c..4009769e7 100644 --- a/src/src/WebServer/AdvancedConfigPage.cpp +++ b/src/src/WebServer/AdvancedConfigPage.cpp @@ -1,496 +1,498 @@ -#include "../WebServer/AdvancedConfigPage.h" - -#ifdef WEBSERVER_ADVANCED - -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/ESPEasy_WebServer.h" - -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/ESPEasy_time.h" -#include "../Globals/Settings.h" -#include "../Globals/TimeZone.h" - -#include "../Helpers/_Plugin_Helper_serial.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/ESPEasy_time.h" -#include "../Helpers/Hardware_defines.h" -#include "../Helpers/StringConverter.h" - -void setLogLevelFor(uint8_t destination, LabelType::Enum label) { - setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); -} - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_advanced() { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_advanced")); - #endif - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_TOOLS; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - if (!webArg(F("edit")).isEmpty()) - { -// Settings.MessageDelay_unused = getFormItemInt(F("messagedelay")); - Settings.IP_Octet = webArg(F("ip")).toInt(); - strncpy_webserver_arg(Settings.NTPHost, F("ntphost")); - Settings.TimeZone = getFormItemInt(F("timezone")); - TimeChangeRule dst_start(getFormItemInt(F("dststartweek")), getFormItemInt(F("dststartdow")), getFormItemInt(F("dststartmonth")), getFormItemInt(F("dststarthour")), Settings.TimeZone); - - if (dst_start.isValid()) { Settings.DST_Start = dst_start.toFlashStoredValue(); } - TimeChangeRule dst_end(getFormItemInt(F("dstendweek")), getFormItemInt(F("dstenddow")), getFormItemInt(F("dstendmonth")), getFormItemInt(F("dstendhour")), Settings.TimeZone); - - if (dst_end.isValid()) { Settings.DST_End = dst_end.toFlashStoredValue(); } - webArg2ip(F("syslogip"), Settings.Syslog_IP); - Settings.WebserverPort = getFormItemInt(F("webport")); - Settings.UDPPort = getFormItemInt(F("udpport")); - - Settings.SyslogFacility = getFormItemInt(F("syslogfacility")); - Settings.SyslogPort = getFormItemInt(F("syslogport")); - Settings.UseSerial = isFormItemChecked(LabelType::ENABLE_SERIAL_PORT_CONSOLE); - -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - Settings.console_serial_rxpin = getFormItemInt(F("taskdevicepin1"), Settings.console_serial_rxpin); - Settings.console_serial_txpin = getFormItemInt(F("taskdevicepin2"), Settings.console_serial_txpin); - - serialHelper_webformSave( - Settings.console_serial_port, - Settings.console_serial_rxpin, - Settings.console_serial_txpin); -#if USES_ESPEASY_CONSOLE_FALLBACK_PORT - Settings.console_serial0_fallback = isFormItemChecked(LabelType::CONSOLE_FALLBACK_TO_SERIAL0); -#endif - -#endif - setLogLevelFor(LOG_TO_SYSLOG, LabelType::SYSLOG_LOG_LEVEL); - setLogLevelFor(LOG_TO_SERIAL, LabelType::SERIAL_LOG_LEVEL); - setLogLevelFor(LOG_TO_WEBLOG, LabelType::WEB_LOG_LEVEL); -#if FEATURE_SD - setLogLevelFor(LOG_TO_SDCARD, LabelType::SD_LOG_LEVEL); -#endif // if FEATURE_SD - Settings.UseValueLogger = isFormItemChecked(F("valuelogger")); - Settings.BaudRate = getFormItemInt(F("baudrate")); - Settings.UseNTP(isFormItemChecked(F("usentp"))); - Settings.ExtTimeSource( - static_cast(getFormItemInt(F("exttimesource"))) - ); - Settings.DST = isFormItemChecked(F("dst")); - Settings.WDI2CAddress = getFormItemInt(F("wdi2caddress")); - #if FEATURE_SSDP - Settings.UseSSDP = isFormItemChecked(F("usessdp")); - #endif // if FEATURE_SSDP - Settings.WireClockStretchLimit = getFormItemInt(F("wireclockstretchlimit")); - Settings.UseRules = isFormItemChecked(F("userules")); - Settings.ConnectionFailuresThreshold = getFormItemInt(LabelType::CONNECTION_FAIL_THRESH); - Settings.ArduinoOTAEnable = isFormItemChecked(F("arduinootaenable")); - Settings.UseRTOSMultitasking = isFormItemChecked(F("usertosmultitasking")); - - // MQTT settings now moved to the controller settings. -// Settings.MQTTRetainFlag_unused = isFormItemChecked(F("mqttretainflag")); -// Settings.MQTTUseUnitNameAsClientId = isFormItemChecked(F("mqttuseunitnameasclientid")); -// Settings.uniqueMQTTclientIdReconnect(isFormItemChecked(F("uniquemqttclientidreconnect"))); - Settings.Latitude = getFormItemFloat(F("latitude")); - Settings.Longitude = getFormItemFloat(F("longitude")); - #ifdef WEBSERVER_NEW_RULES - Settings.OldRulesEngine(isFormItemChecked(F("oldrulesengine"))); - #endif // WEBSERVER_NEW_RULES - 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)); -#endif - Settings.AllowTaskValueSetAllPlugins(isFormItemChecked(LabelType::TASKVALUESET_ALL_PLUGINS)); -#if FEATURE_CLEAR_I2C_STUCK - Settings.EnableClearHangingI2Cbus(isFormItemChecked(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS)); -#endif - #if FEATURE_I2C_DEVICE_CHECK - Settings.CheckI2Cdevice(isFormItemChecked(LabelType::ENABLE_I2C_DEVICE_CHECK)); - #endif // if FEATURE_I2C_DEVICE_CHECK - - Settings.WaitWiFiConnect(isFormItemChecked(LabelType::WAIT_WIFI_CONNECT)); - Settings.HiddenSSID_SlowConnectPerBSSID(isFormItemChecked(LabelType::HIDDEN_SSID_SLOW_CONNECT)); - Settings.SDK_WiFi_autoreconnect(isFormItemChecked(LabelType::SDK_WIFI_AUTORECONNECT)); - - -#ifndef BUILD_NO_RAM_TRACKER - Settings.EnableRAMTracking(isFormItemChecked(LabelType::ENABLE_RAM_TRACKING)); -#endif - - #ifdef ESP8266 - Settings.UseAlternativeDeepSleep(isFormItemChecked(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL)); - #endif - - Settings.EnableRulesCaching(isFormItemChecked(LabelType::ENABLE_RULES_CACHING)); -// Settings.EnableRulesEventReorder(isFormItemChecked(LabelType::ENABLE_RULES_EVENT_REORDER)); // TD-er: Disabled for now - -#ifndef NO_HTTP_UPDATER - Settings.AllowOTAUnlimited(isFormItemChecked(LabelType::ALLOW_OTA_UNLIMITED)); -#endif // NO_HTTP_UPDATER -#if FEATURE_AUTO_DARK_MODE - Settings.setCssMode(getFormItemInt(getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE))); -#endif // FEATURE_AUTO_DARK_MODE -#if FEATURE_RULES_EASY_COLOR_CODE - Settings.DisableRulesCodeCompletion(isFormItemChecked(LabelType::DISABLE_RULES_AUTOCOMPLETE)); -#endif // if FEATURE_RULES_EASY_COLOR_CODE - - addHtmlError(SaveSettings()); - - if (node_time.systemTimePresent()) { - node_time.initTime(); - } - } - - addHtml(F("
")); - html_table_class_normal(); - - addFormHeader(F("Advanced Settings"), F("RTDTools/Tools.html#advanced")); - - addFormSubHeader(F("Rules Settings")); - - addFormCheckBox(F("Rules"), F("userules"), Settings.UseRules); - #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(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)")); - addFormCheckBox(F("SendToHTTP wait for ack"), F("sendtohttp_ack"), Settings.SendToHttp_ack()); - addFormCheckBox(F("SendToHTTP Follow Redirects"), F("sendtohttp_redir"), Settings.SendToHTTP_follow_redirects()); - - /* - // MQTT settings now moved to the controller settings. - addFormSubHeader(F("Controller Settings")); - - addFormNumericBox(F("Message Interval"), F("messagedelay"), Settings.MessageDelay_unused, 0, INT_MAX); - addUnit(F("ms")); - - addFormCheckBox(F("MQTT Retain Msg"), F("mqttretainflag"), Settings.MQTTRetainFlag_unused); - addFormCheckBox(F("MQTT use unit name as ClientId"), F("mqttuseunitnameasclientid"), Settings.MQTTUseUnitNameAsClientId); - addFormCheckBox(F("MQTT change ClientId at reconnect"), F("uniquemqttclientidreconnect"), Settings.uniqueMQTTclientIdReconnect_unused()); -*/ - - addFormSubHeader(F("Time Source")); - - addFormCheckBox(F("Use NTP"), F("usentp"), Settings.UseNTP()); - addFormTextBox(F("NTP Hostname"), F("ntphost"), Settings.NTPHost, 63); - #if FEATURE_EXT_RTC - addFormExtTimeSourceSelect(F("External Time Source"), F("exttimesource"), Settings.ExtTimeSource()); - if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { - addFormNote(concat(getLabel(LabelType::EXT_RTC_UTC_TIME), F(": ")) + getValue(LabelType::EXT_RTC_UTC_TIME)); - } - #endif - - addFormSubHeader(F("DST Settings")); - addFormDstSelect(true, Settings.DST_Start); - addFormDstSelect(false, Settings.DST_End); - addFormCheckBox(F("DST"), F("dst"), Settings.DST); - - addFormSubHeader(F("Location Settings")); - addFormNumericBox(F("Timezone Offset (UTC +)"), F("timezone"), Settings.TimeZone, -720, 840); // UTC-12H ... UTC+14h - addUnit(F("minutes")); - addFormFloatNumberBox(F("Latitude"), F("latitude"), Settings.Latitude, -90.0f, 90.0f); - addUnit(F("°")); - addFormFloatNumberBox(F("Longitude"), F("longitude"), Settings.Longitude, -180.0f, 180.0f); - addUnit(F("°")); - addFormNote(F("Longitude and Latitude are used to compute sunrise and sunset")); - - addFormSubHeader(F("Log Settings")); - - addFormIPBox(F("Syslog IP"), F("syslogip"), Settings.Syslog_IP); - addFormNumericBox(F("Syslog UDP port"), F("syslogport"), Settings.SyslogPort, 0, 65535); - - addFormLogLevelSelect(LabelType::SYSLOG_LOG_LEVEL, Settings.SyslogLevel); - addFormLogFacilitySelect(F("Syslog Facility"), F("syslogfacility"), Settings.SyslogFacility); - addFormLogLevelSelect(LabelType::SERIAL_LOG_LEVEL, Settings.SerialLogLevel); - addFormLogLevelSelect(LabelType::WEB_LOG_LEVEL, Settings.WebLogLevel); - -#if FEATURE_SD - addFormLogLevelSelect(LabelType::SD_LOG_LEVEL, Settings.SDLogLevel); - - addFormCheckBox(F("SD Card Value Logger"), F("valuelogger"), Settings.UseValueLogger); -#endif // if FEATURE_SD - - - addFormSubHeader(F("Serial Console Settings")); - addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE, Settings.UseSerial); - addFormNumericBox(F("Baud Rate"), F("baudrate"), Settings.BaudRate, 0, 1000000); - -#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT - serialHelper_webformLoad( - static_cast(Settings.console_serial_port), - Settings.console_serial_rxpin, - Settings.console_serial_txpin, - true); - - // Show serial port selection - addFormPinSelect( - PinSelectPurpose::Serial_input, - formatGpioName_serialRX(false), - F("taskdevicepin1"), - Settings.console_serial_rxpin); - addFormPinSelect( - PinSelectPurpose::Serial_output, - formatGpioName_serialTX(false), - F("taskdevicepin2"), - Settings.console_serial_txpin); - - 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); -#endif - -#endif - - - addFormSubHeader(F("Inter-ESPEasy Network")); - if (Settings.UDPPort != 8266 ) addFormNote(F("Preferred P2P port is 8266")); - addFormNumericBox(F("ESPEasy p2p UDP port"), F("udpport"), Settings.UDPPort, 0, 65535); - - // TODO sort settings in groups or move to other pages/groups - addFormSubHeader(F("Special and Experimental Settings")); - - addFormNumericBox(F("Webserver port"), F("webport"), Settings.WebserverPort, 0, 65535); - addFormNote(F("Requires reboot to activate")); - - addFormNumericBox(F("Fixed IP Octet"), F("ip"), Settings.IP_Octet, 0, 255); - - addFormNumericBox(F("WD I2C Address"), F("wdi2caddress"), Settings.WDI2CAddress, 0, 127); - addHtml(F(" (decimal)")); - - addFormNumericBox(F("I2C ClockStretchLimit"), F("wireclockstretchlimit"), Settings.WireClockStretchLimit); // TODO define limits - #ifdef ESP8266 - addUnit(F("usec")); - #endif - #ifdef ESP32 - addUnit(F("1/80 usec")); - #endif - #if FEATURE_ARDUINO_OTA - addFormCheckBox(F("Enable Arduino OTA"), F("arduinootaenable"), Settings.ArduinoOTAEnable); - #endif // if FEATURE_ARDUINO_OTA - #if defined(ESP32) - addFormCheckBox_disabled(F("Enable RTOS Multitasking"), F("usertosmultitasking"), Settings.UseRTOSMultitasking); - #endif // if defined(ESP32) - - 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 - - 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 - - # ifndef NO_HTTP_UPDATER - addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); - addFormNote(F("When enabled, OTA updating can overwrite the filesystem and settings!")); - addFormNote(F("Requires reboot to activate")); - # endif // ifndef NO_HTTP_UPDATER - #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(cssModeOptions); - addFormSelector(getLabel(LabelType::ENABLE_AUTO_DARK_MODE), - getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), - nrCssModeOptions, - cssModeNames, - cssModeOptions, - Settings.getCssMode()); - #endif // FEATURE_AUTO_DARK_MODE - - #if FEATURE_RULES_EASY_COLOR_CODE - addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); - addFormNote(F("Also disables Rules syntax highlighting!")); - #endif // if FEATURE_RULES_EASY_COLOR_CODE - - #ifdef ESP8266 - addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); - #endif - - - #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()); - addFormNote(F("Change WiFi sleep settings requires reboot to activate")); -#ifdef SUPPORT_ARP - addFormCheckBox(LabelType::PERIODICAL_GRAT_ARP, Settings.gratuitousARP()); -#endif // ifdef SUPPORT_ARP - addFormCheckBox(LabelType::CPU_ECO_MODE, Settings.EcoPowerMode()); - addFormNote(F("Node may miss receiving packets with Eco mode enabled")); -#if FEATURE_SET_WIFI_TX_PWR - { - float maxTXpwr; - float sensitivity = GetRSSIthreshold(maxTXpwr); - - addFormFloatNumberBox(LabelType::WIFI_TX_MAX_PWR, Settings.getWiFi_TX_power(), 0.0f, MAX_TX_PWR_DBM_11b, 2, 0.25f); - addUnit(F("dBm")); - addFormNote(strformat( - F("Current max: %.2f dBm"), maxTXpwr)); - - addFormNumericBox(LabelType::WIFI_SENS_MARGIN, Settings.WiFi_sensitivity_margin, -20, 30); - addUnit(F("dB")); // Relative, thus the unit is dB, not dBm - addFormNote(strformat( - F("Adjust TX power to target the AP with (sensitivity + margin) dBm signal strength. Current sensitivity: %.2f dBm"), - sensitivity)); - } - addFormCheckBox(LabelType::WIFI_SEND_AT_MAX_TX_PWR, Settings.UseMaxTXpowerForSending()); -#endif - { - addFormNumericBox(LabelType::WIFI_NR_EXTRA_SCANS, Settings.NumberExtraWiFiScans, 0, 5); - addFormNote(F("Number of extra times to scan all channels to have higher chance of finding the desired AP")); - } - addFormCheckBox(LabelType::WIFI_USE_LAST_CONN_FROM_RTC, Settings.UseLastWiFiFromRTC()); - - - addFormCheckBox(LabelType::WAIT_WIFI_CONNECT, Settings.WaitWiFiConnect()); - addFormCheckBox(LabelType::SDK_WIFI_AUTORECONNECT, Settings.SDK_WiFi_autoreconnect()); - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - - - - addFormSeparator(2); - - html_TR_TD(); - html_TD(); - addSubmitButton(); - addHtml(F("")); - html_end_table(); - html_end_form(); - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -void addFormDstSelect(bool isStart, uint16_t choice) { - uint16_t tmpstart(choice); - uint16_t tmpend(choice); - - if (!TimeChangeRule(choice, 0).isValid()) { - time_zone.getDefaultDst_flash_values(tmpstart, tmpend); - } - TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); - { - const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; - constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; - addRowLabel(concat( - isStart ? F("Start") : F("End"), - F(" (week, dow, month)"))); - addSelector( - isStart ? F("dststartweek") : F("dstendweek"), - NR_ELEMENTS(weekValues), week, weekValues, nullptr, rule.week); - } - html_BR(); - { - const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; - constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; - - addSelector( - isStart ? F("dststartdow") : F("dstenddow"), - NR_ELEMENTS(dowValues), dow, dowValues, nullptr, rule.dow); - } - html_BR(); - { - const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( - "Dec") }; - constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; - - addSelector(isStart ? F("dststartmonth") : F("dstendmonth"), - NR_ELEMENTS(monthValues), month, monthValues, nullptr, rule.month); - } - - addFormNumericBox( - isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), - isStart ? F("dststarthour") : F("dstendhour"), - rule.hour, 0, 23); - addUnit(isStart ? F("hour ↷") : F("hour ↶")); -} - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) -{ - addRowLabel(label); - const __FlashStringHelper * options[] = - { F("None"), F("DS1307"), F("DS3231"), F("PCF8523"), F("PCF8563")}; - constexpr int optionValues[] = { - static_cast(ExtTimeSource_e::None), - static_cast(ExtTimeSource_e::DS1307), - static_cast(ExtTimeSource_e::DS3231), - static_cast(ExtTimeSource_e::PCF8523), - static_cast(ExtTimeSource_e::PCF8563) - }; - - addSelector(id, NR_ELEMENTS(optionValues), options, optionValues, nullptr, static_cast(choice)); -} - - -void addFormLogLevelSelect(LabelType::Enum label, int choice) -{ - #ifdef BUILD_NO_DEBUG - if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; - #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]); - } - addSelector(getInternalLabel(label), LOG_LEVEL_NRELEMENTS + 1, options, optionValues, nullptr, choice); - -} - -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel(label); - const __FlashStringHelper * options[12] = - { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), - F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; - const int optionValues[12] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; - - addSelector(id, 12, options, optionValues, nullptr, choice); -} - -#endif // ifdef WEBSERVER_ADVANCED +#include "../WebServer/AdvancedConfigPage.h" + +#ifdef WEBSERVER_ADVANCED + +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/ESPEasy_WebServer.h" + +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/ESPEasy_time.h" +#include "../Globals/Settings.h" +#include "../Globals/TimeZone.h" + +#include "../Helpers/_Plugin_Helper_serial.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/ESPEasy_time.h" +#include "../Helpers/Hardware_defines.h" +#include "../Helpers/StringConverter.h" + +void setLogLevelFor(uint8_t destination, LabelType::Enum label) { + setLogLevelFor(destination, getFormItemInt(getInternalLabel(label))); +} + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_advanced() { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_advanced")); + #endif + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_TOOLS; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + if (!webArg(F("edit")).isEmpty()) + { +// Settings.MessageDelay_unused = getFormItemInt(F("messagedelay")); + Settings.IP_Octet = webArg(F("ip")).toInt(); + strncpy_webserver_arg(Settings.NTPHost, F("ntphost")); + Settings.TimeZone = getFormItemInt(F("timezone")); + TimeChangeRule dst_start(getFormItemInt(F("dststartweek")), getFormItemInt(F("dststartdow")), getFormItemInt(F("dststartmonth")), getFormItemInt(F("dststarthour")), Settings.TimeZone); + + if (dst_start.isValid()) { Settings.DST_Start = dst_start.toFlashStoredValue(); } + TimeChangeRule dst_end(getFormItemInt(F("dstendweek")), getFormItemInt(F("dstenddow")), getFormItemInt(F("dstendmonth")), getFormItemInt(F("dstendhour")), Settings.TimeZone); + + if (dst_end.isValid()) { Settings.DST_End = dst_end.toFlashStoredValue(); } + webArg2ip(F("syslogip"), Settings.Syslog_IP); + Settings.WebserverPort = getFormItemInt(F("webport")); + Settings.UDPPort = getFormItemInt(F("udpport")); + + Settings.SyslogFacility = getFormItemInt(F("syslogfacility")); + Settings.SyslogPort = getFormItemInt(F("syslogport")); + Settings.UseSerial = isFormItemChecked(LabelType::ENABLE_SERIAL_PORT_CONSOLE); + +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + Settings.console_serial_rxpin = getFormItemInt(F("taskdevicepin1"), Settings.console_serial_rxpin); + Settings.console_serial_txpin = getFormItemInt(F("taskdevicepin2"), Settings.console_serial_txpin); + + serialHelper_webformSave( + Settings.console_serial_port, + Settings.console_serial_rxpin, + Settings.console_serial_txpin); +#if USES_ESPEASY_CONSOLE_FALLBACK_PORT + Settings.console_serial0_fallback = isFormItemChecked(LabelType::CONSOLE_FALLBACK_TO_SERIAL0); +#endif + +#endif + setLogLevelFor(LOG_TO_SYSLOG, LabelType::SYSLOG_LOG_LEVEL); + setLogLevelFor(LOG_TO_SERIAL, LabelType::SERIAL_LOG_LEVEL); + setLogLevelFor(LOG_TO_WEBLOG, LabelType::WEB_LOG_LEVEL); +#if FEATURE_SD + setLogLevelFor(LOG_TO_SDCARD, LabelType::SD_LOG_LEVEL); +#endif // if FEATURE_SD + Settings.UseValueLogger = isFormItemChecked(F("valuelogger")); + Settings.BaudRate = getFormItemInt(F("baudrate")); + Settings.UseNTP(isFormItemChecked(F("usentp"))); + Settings.ExtTimeSource( + static_cast(getFormItemInt(F("exttimesource"))) + ); + Settings.DST = isFormItemChecked(F("dst")); + Settings.WDI2CAddress = getFormItemInt(F("wdi2caddress")); + #if FEATURE_SSDP + Settings.UseSSDP = isFormItemChecked(F("usessdp")); + #endif // if FEATURE_SSDP + Settings.WireClockStretchLimit = getFormItemInt(F("wireclockstretchlimit")); + Settings.UseRules = isFormItemChecked(F("userules")); + Settings.ConnectionFailuresThreshold = getFormItemInt(LabelType::CONNECTION_FAIL_THRESH); + Settings.ArduinoOTAEnable = isFormItemChecked(F("arduinootaenable")); + Settings.UseRTOSMultitasking = isFormItemChecked(F("usertosmultitasking")); + + // MQTT settings now moved to the controller settings. +// Settings.MQTTRetainFlag_unused = isFormItemChecked(F("mqttretainflag")); +// Settings.MQTTUseUnitNameAsClientId = isFormItemChecked(F("mqttuseunitnameasclientid")); +// Settings.uniqueMQTTclientIdReconnect(isFormItemChecked(F("uniquemqttclientidreconnect"))); + Settings.Latitude = getFormItemFloat(F("latitude")); + Settings.Longitude = getFormItemFloat(F("longitude")); + #ifdef WEBSERVER_NEW_RULES + Settings.OldRulesEngine(isFormItemChecked(F("oldrulesengine"))); + #endif // WEBSERVER_NEW_RULES + 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)); +#endif + Settings.AllowTaskValueSetAllPlugins(isFormItemChecked(LabelType::TASKVALUESET_ALL_PLUGINS)); +#if FEATURE_CLEAR_I2C_STUCK + Settings.EnableClearHangingI2Cbus(isFormItemChecked(LabelType::ENABLE_CLEAR_HUNG_I2C_BUS)); +#endif + #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 + + #ifdef ESP8266 + Settings.UseAlternativeDeepSleep(isFormItemChecked(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL)); + #endif + + Settings.EnableRulesCaching(isFormItemChecked(LabelType::ENABLE_RULES_CACHING)); +// Settings.EnableRulesEventReorder(isFormItemChecked(LabelType::ENABLE_RULES_EVENT_REORDER)); // TD-er: Disabled for now + +#ifndef NO_HTTP_UPDATER + Settings.AllowOTAUnlimited(isFormItemChecked(LabelType::ALLOW_OTA_UNLIMITED)); +#endif // NO_HTTP_UPDATER +#if FEATURE_AUTO_DARK_MODE + Settings.setCssMode(getFormItemInt(getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE))); +#endif // FEATURE_AUTO_DARK_MODE +#if FEATURE_RULES_EASY_COLOR_CODE + Settings.DisableRulesCodeCompletion(isFormItemChecked(LabelType::DISABLE_RULES_AUTOCOMPLETE)); +#endif // if FEATURE_RULES_EASY_COLOR_CODE +#if FEATURE_TARSTREAM_SUPPORT + Settings.DisableSaveConfigAsTar(isFormItemChecked(LabelType::DISABLE_SAVE_CONFIG_AS_TAR)); +#endif // if FEATURE_TARSTREAM_SUPPORT + + addHtmlError(SaveSettings()); + + if (node_time.systemTimePresent()) { + node_time.initTime(); + } + } + + addHtml(F("")); + html_table_class_normal(); + + addFormHeader(F("Advanced Settings"), F("RTDTools/Tools.html#advanced")); + + addFormSubHeader(F("Rules Settings")); + + addFormCheckBox(F("Rules"), F("userules"), Settings.UseRules); + #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(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)")); + addFormCheckBox(F("SendToHTTP wait for ack"), F("sendtohttp_ack"), Settings.SendToHttp_ack()); + addFormCheckBox(F("SendToHTTP Follow Redirects"), F("sendtohttp_redir"), Settings.SendToHTTP_follow_redirects()); + + /* + // MQTT settings now moved to the controller settings. + addFormSubHeader(F("Controller Settings")); + + addFormNumericBox(F("Message Interval"), F("messagedelay"), Settings.MessageDelay_unused, 0, INT_MAX); + addUnit(F("ms")); + + addFormCheckBox(F("MQTT Retain Msg"), F("mqttretainflag"), Settings.MQTTRetainFlag_unused); + addFormCheckBox(F("MQTT use unit name as ClientId"), F("mqttuseunitnameasclientid"), Settings.MQTTUseUnitNameAsClientId); + addFormCheckBox(F("MQTT change ClientId at reconnect"), F("uniquemqttclientidreconnect"), Settings.uniqueMQTTclientIdReconnect_unused()); +*/ + + addFormSubHeader(F("Time Source")); + + addFormCheckBox(F("Use NTP"), F("usentp"), Settings.UseNTP()); + addFormTextBox(F("NTP Hostname"), F("ntphost"), Settings.NTPHost, 63); + #if FEATURE_EXT_RTC + addFormExtTimeSourceSelect(F("External Time Source"), F("exttimesource"), Settings.ExtTimeSource()); + if (Settings.ExtTimeSource() != ExtTimeSource_e::None) { + addFormNote(concat(getLabel(LabelType::EXT_RTC_UTC_TIME), F(": ")) + getValue(LabelType::EXT_RTC_UTC_TIME)); + } + #endif + + addFormSubHeader(F("DST Settings")); + addFormDstSelect(true, Settings.DST_Start); + addFormDstSelect(false, Settings.DST_End); + addFormCheckBox(F("DST"), F("dst"), Settings.DST); + + addFormSubHeader(F("Location Settings")); + addFormNumericBox(F("Timezone Offset (UTC +)"), F("timezone"), Settings.TimeZone, -720, 840); // UTC-12H ... UTC+14h + addUnit(F("minutes")); + addFormFloatNumberBox(F("Latitude"), F("latitude"), Settings.Latitude, -90.0f, 90.0f); + addUnit(F("°")); + addFormFloatNumberBox(F("Longitude"), F("longitude"), Settings.Longitude, -180.0f, 180.0f); + addUnit(F("°")); + addFormNote(F("Longitude and Latitude are used to compute sunrise and sunset")); + + addFormSubHeader(F("Log Settings")); + + addFormIPBox(F("Syslog IP"), F("syslogip"), Settings.Syslog_IP); + addFormNumericBox(F("Syslog UDP port"), F("syslogport"), Settings.SyslogPort, 0, 65535); + + addFormLogLevelSelect(LabelType::SYSLOG_LOG_LEVEL, Settings.SyslogLevel); + addFormLogFacilitySelect(F("Syslog Facility"), F("syslogfacility"), Settings.SyslogFacility); + addFormLogLevelSelect(LabelType::SERIAL_LOG_LEVEL, Settings.SerialLogLevel); + addFormLogLevelSelect(LabelType::WEB_LOG_LEVEL, Settings.WebLogLevel); + +#if FEATURE_SD + addFormLogLevelSelect(LabelType::SD_LOG_LEVEL, Settings.SDLogLevel); + + addFormCheckBox(F("SD Card Value Logger"), F("valuelogger"), Settings.UseValueLogger); +#endif // if FEATURE_SD + + + addFormSubHeader(F("Serial Console Settings")); + addFormCheckBox(LabelType::ENABLE_SERIAL_PORT_CONSOLE, Settings.UseSerial); + addFormNumericBox(F("Baud Rate"), F("baudrate"), Settings.BaudRate, 0, 1000000); + +#if FEATURE_DEFINE_SERIAL_CONSOLE_PORT + serialHelper_webformLoad( + static_cast(Settings.console_serial_port), + Settings.console_serial_rxpin, + Settings.console_serial_txpin, + true); + + // Show serial port selection + addFormPinSelect( + PinSelectPurpose::Serial_input, + formatGpioName_serialRX(false), + F("taskdevicepin1"), + Settings.console_serial_rxpin); + addFormPinSelect( + PinSelectPurpose::Serial_output, + formatGpioName_serialTX(false), + F("taskdevicepin2"), + Settings.console_serial_txpin); + + 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); +#endif + +#endif + + + addFormSubHeader(F("Inter-ESPEasy Network")); + if (Settings.UDPPort != 8266 ) addFormNote(F("Preferred P2P port is 8266")); + addFormNumericBox(F("ESPEasy p2p UDP port"), F("udpport"), Settings.UDPPort, 0, 65535); + + // TODO sort settings in groups or move to other pages/groups + addFormSubHeader(F("Special and Experimental Settings")); + + addFormNumericBox(F("Webserver port"), F("webport"), Settings.WebserverPort, 0, 65535); + addFormNote(F("Requires reboot to activate")); + + addFormNumericBox(F("Fixed IP Octet"), F("ip"), Settings.IP_Octet, 0, 255); + + addFormNumericBox(F("WD I2C Address"), F("wdi2caddress"), Settings.WDI2CAddress, 0, 127); + addHtml(F(" (decimal)")); + + addFormNumericBox(F("I2C ClockStretchLimit"), F("wireclockstretchlimit"), Settings.WireClockStretchLimit); // TODO define limits + #ifdef ESP8266 + addUnit(F("usec")); + #endif + #ifdef ESP32 + addUnit(F("1/80 usec")); + #endif + #if FEATURE_ARDUINO_OTA + addFormCheckBox(F("Enable Arduino OTA"), F("arduinootaenable"), Settings.ArduinoOTAEnable); + #endif // if FEATURE_ARDUINO_OTA + #if defined(ESP32) + addFormCheckBox_disabled(F("Enable RTOS Multitasking"), F("usertosmultitasking"), Settings.UseRTOSMultitasking); + #endif // if defined(ESP32) + + 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 + + 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 + + # ifndef NO_HTTP_UPDATER + addFormCheckBox(LabelType::ALLOW_OTA_UNLIMITED, Settings.AllowOTAUnlimited()); + # endif // ifndef NO_HTTP_UPDATER + #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(cssModeOptions); + addFormSelector(getLabel(LabelType::ENABLE_AUTO_DARK_MODE), + getInternalLabel(LabelType::ENABLE_AUTO_DARK_MODE), + nrCssModeOptions, + cssModeNames, + cssModeOptions, + Settings.getCssMode()); + #endif // FEATURE_AUTO_DARK_MODE + + #if FEATURE_RULES_EASY_COLOR_CODE + addFormCheckBox(LabelType::DISABLE_RULES_AUTOCOMPLETE, Settings.DisableRulesCodeCompletion()); + #endif // if FEATURE_RULES_EASY_COLOR_CODE + #if FEATURE_TARSTREAM_SUPPORT + addFormCheckBox(LabelType::DISABLE_SAVE_CONFIG_AS_TAR, Settings.DisableSaveConfigAsTar()); + #endif // if FEATURE_TARSTREAM_SUPPORT + + #ifdef ESP8266 + addFormCheckBox(LabelType::DEEP_SLEEP_ALTERNATIVE_CALL, Settings.UseAlternativeDeepSleep()); + #endif + + + #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 + + + + addFormSeparator(2); + + html_TR_TD(); + html_TD(); + addSubmitButton(); + addHtml(F("")); + html_end_table(); + html_end_form(); + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +void addFormDstSelect(bool isStart, uint16_t choice) { + uint16_t tmpstart(choice); + uint16_t tmpend(choice); + + if (!TimeChangeRule(choice, 0).isValid()) { + time_zone.getDefaultDst_flash_values(tmpstart, tmpend); + } + TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); + { + const __FlashStringHelper * week[] = { F("Last"), F("1st"), F("2nd"), F("3rd"), F("4th") }; + constexpr int weekValues[] = { 0, 1, 2, 3, 4 }; + addRowLabel(concat( + isStart ? F("Start") : F("End"), + F(" (week, dow, month)"))); + addSelector( + isStart ? F("dststartweek") : F("dstendweek"), + NR_ELEMENTS(weekValues), week, weekValues, nullptr, rule.week); + } + html_BR(); + { + const __FlashStringHelper * dow[] = { F("Sun"), F("Mon"), F("Tue"), F("Wed"), F("Thu"), F("Fri"), F("Sat") }; + constexpr int dowValues[] = { 1, 2, 3, 4, 5, 6, 7 }; + + addSelector( + isStart ? F("dststartdow") : F("dstenddow"), + NR_ELEMENTS(dowValues), dow, dowValues, nullptr, rule.dow); + } + html_BR(); + { + const __FlashStringHelper * month[] = { F("Jan"), F("Feb"), F("Mar"), F("Apr"), F("May"), F("Jun"), F("Jul"), F("Aug"), F("Sep"), F("Oct"), F("Nov"), F( + "Dec") }; + constexpr int monthValues[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; + + addSelector(isStart ? F("dststartmonth") : F("dstendmonth"), + NR_ELEMENTS(monthValues), month, monthValues, nullptr, rule.month); + } + + addFormNumericBox( + isStart ? F("Start (localtime, e.g. 2h→3h)") : F("End (localtime, e.g. 3h→2h)"), + isStart ? F("dststarthour") : F("dstendhour"), + rule.hour, 0, 23); + addUnit(isStart ? F("hour ↷") : F("hour ↶")); +} + +void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice) +{ + addRowLabel(label); + const __FlashStringHelper * options[] = + { F("None"), F("DS1307"), F("DS3231"), F("PCF8523"), F("PCF8563")}; + constexpr int optionValues[] = { + static_cast(ExtTimeSource_e::None), + static_cast(ExtTimeSource_e::DS1307), + static_cast(ExtTimeSource_e::DS3231), + static_cast(ExtTimeSource_e::PCF8523), + static_cast(ExtTimeSource_e::PCF8563) + }; + + addSelector(id, NR_ELEMENTS(optionValues), options, optionValues, nullptr, static_cast(choice)); +} + + +void addFormLogLevelSelect(LabelType::Enum label, int choice) +{ + #ifdef BUILD_NO_DEBUG + if (choice > LOG_LEVEL_INFO) choice = LOG_LEVEL_INFO; + #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]); + } + addSelector(getInternalLabel(label), LOG_LEVEL_NRELEMENTS + 1, options, optionValues, nullptr, choice); + +} + +void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel(label); + const __FlashStringHelper * options[12] = + { F("Kernel"), F("User"), F("Daemon"), F("Message"), F("Local0"), F("Local1"), + F("Local2"), F("Local3"), F("Local4"), F("Local5"), F("Local6"), F("Local7") }; + const int optionValues[12] = { 0, 1, 3, 5, 16, 17, 18, 19, 20, 21, 22, 23 }; + + addSelector(id, 12, options, optionValues, nullptr, choice); +} + +#endif // ifdef WEBSERVER_ADVANCED diff --git a/src/src/WebServer/AdvancedConfigPage.h b/src/src/WebServer/AdvancedConfigPage.h index cb9285b2a..e1b26c23c 100644 --- a/src/src/WebServer/AdvancedConfigPage.h +++ b/src/src/WebServer/AdvancedConfigPage.h @@ -1,30 +1,30 @@ -#ifndef WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H -#define WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H - -#include "../WebServer/common.h" - -#ifdef WEBSERVER_ADVANCED - -#include "../DataTypes/TimeSource.h" - - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_advanced(); - -void addFormDstSelect(bool isStart, uint16_t choice); - -void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice); - -void addFormLogLevelSelect(LabelType::Enum label, int choice); - -void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); - - -#endif // ifdef WEBSERVER_ADVANCED - - - - +#ifndef WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H +#define WEBSERVER_WEBSERVER_ADVANCEDCONFIGPAGE_H + +#include "../WebServer/common.h" + +#ifdef WEBSERVER_ADVANCED + +#include "../DataTypes/TimeSource.h" + + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_advanced(); + +void addFormDstSelect(bool isStart, uint16_t choice); + +void addFormExtTimeSourceSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, ExtTimeSource_e choice); + +void addFormLogLevelSelect(LabelType::Enum label, int choice); + +void addFormLogFacilitySelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); + + +#endif // ifdef WEBSERVER_ADVANCED + + + + #endif \ No newline at end of file diff --git a/src/src/WebServer/Chart_JS.cpp b/src/src/WebServer/Chart_JS.cpp index b0aae8c11..e82c43e70 100644 --- a/src/src/WebServer/Chart_JS.cpp +++ b/src/src/WebServer/Chart_JS.cpp @@ -1,177 +1,252 @@ -#include "../WebServer/Chart_JS.h" - -#if FEATURE_CHART_JS - -# include "../Helpers/StringConverter.h" -# include "../WebServer/HTML_wrappers.h" - - -void add_ChartJS_array(int valueCount, - const String array[]) -{ - for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(','); - } - addHtml(wrapIfContains(array[i], ' ', '"')); - } -} - -void add_ChartJS_array(int valueCount, - const float array[], - unsigned int nrDecimals) -{ - for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlFloat(array[i], nrDecimals); - } -} - -void add_ChartJS_array(int valueCount, - const int array[]) -{ - for (int i = 0; i < valueCount; ++i) { - if (i != 0) { - addHtml(','); - } - addHtmlInt(array[i]); - } -} - -void add_ChartJS_chart_header( - const __FlashStringHelper *chartType, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - int width, - int height, - const String & options, - size_t nrSamples) -{ - add_ChartJS_chart_header(chartType, String(id), chartTitle, width, height, options, nrSamples); -} - -void add_ChartJS_chart_header( - const __FlashStringHelper *chartType, - const String & id, - const ChartJS_title & chartTitle, - int width, - int height, - const String & options, - size_t nrSamples) -{ - addHtml(F("")); - addHtml(F("")); -} - -#endif // if FEATURE_CHART_JS +#include "../WebServer/Chart_JS.h" + +#if FEATURE_CHART_JS + +# include "../Helpers/StringConverter.h" +# include "../WebServer/HTML_wrappers.h" + + +void add_ChartJS_array(int valueCount, + const String array[]) +{ + for (int i = 0; i < valueCount; ++i) { + if (i != 0) { + addHtml(',', '\n'); + } + addHtml(to_json_value(array[i])); + } +} + +void add_ChartJS_array(int valueCount, + const float array[], + unsigned int nrDecimals) +{ + for (int i = 0; i < valueCount; ++i) { + if (i != 0) { + addHtml(',', '\n'); + } + addHtmlFloat(array[i], nrDecimals); + } +} + +void add_ChartJS_array(int valueCount, + const int array[]) +{ + for (int i = 0; i < valueCount; ++i) { + if (i != 0) { + addHtml(',', '\n'); + } + addHtmlInt(array[i]); + } +} + +void add_ChartJS_chart_header( + const __FlashStringHelper *chartType, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + int width, + int height, + const String & options, + bool enableZoom, + size_t nrSamples, + bool onlyJSON) +{ + add_ChartJS_chart_header( + chartType, + String(id), + chartTitle, + width, + height, + options, + enableZoom, + nrSamples, + onlyJSON); +} + +void add_ChartJS_chart_header( + const __FlashStringHelper *chartType, + const String & id, + const ChartJS_title & chartTitle, + int width, + int height, + const String & options, + bool enableZoom, + size_t nrSamples, + bool onlyJSON) +{ + if (!onlyJSON) { + addHtml(F("")); + const char *id_c_str = id.c_str(); + addHtml(strformat( + F("")); + } +} + +#endif // if FEATURE_CHART_JS diff --git a/src/src/WebServer/Chart_JS.h b/src/src/WebServer/Chart_JS.h index 99ce8c668..73ae441b4 100644 --- a/src/src/WebServer/Chart_JS.h +++ b/src/src/WebServer/Chart_JS.h @@ -1,70 +1,82 @@ -#ifndef WEBSERVER_CHART_JS_H -#define WEBSERVER_CHART_JS_H - -#include "../WebServer/common.h" - -// ********************************************* -// Support for ChartJS charts -// -// Typical way of adding a chart: -// - add_ChartJS_chart_header -// - add_ChartJS_chart_labels -// - add_ChartJS_dataset (1x or more) -// - add_ChartJS_chart_footer -// -// Split into several parts so a long array of -// values can also be served directly -// to reduce memory usage. -// ********************************************* - -#if FEATURE_CHART_JS - -# include "../WebServer/Chart_JS_scale.h" -# include "../DataStructs/ChartJS_dataset_config.h" - -void add_ChartJS_chart_header( - const __FlashStringHelper *chartType, - const __FlashStringHelper *id, - const ChartJS_title & chartTitle, - int width, - int height, - const String & options = EMPTY_STRING, - size_t nrSamples = 0); - -void add_ChartJS_chart_header( - const __FlashStringHelper *chartType, - const String & id, - const ChartJS_title & chartTitle, - int width, - int height, - const String & options = EMPTY_STRING, - size_t nrSamples = 0); - - -void add_ChartJS_chart_labels( - int valueCount, - const int labels[]); - -void add_ChartJS_chart_labels( - int valueCount, - const String labels[]); - - -void add_ChartJS_scatter_data_point(float x, float y, int nrDecimals); - -void add_ChartJS_dataset( - const ChartJS_dataset_config& config, - const float values[], - int valueCount, - unsigned int 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); - - -void add_ChartJS_chart_footer(); -#endif // if FEATURE_CHART_JS - -#endif // ifndef WEBSERVER_CHART_JS_H +#ifndef WEBSERVER_CHART_JS_H +#define WEBSERVER_CHART_JS_H + +#include "../WebServer/common.h" + +// ********************************************* +// Support for ChartJS charts +// +// Typical way of adding a chart: +// - add_ChartJS_chart_header +// - add_ChartJS_chart_labels +// - add_ChartJS_dataset (1x or more) +// - add_ChartJS_chart_footer +// +// Split into several parts so a long array of +// values can also be served directly +// to reduce memory usage. +// ********************************************* + +#if FEATURE_CHART_JS + +# include "../WebServer/Chart_JS_scale.h" +# include "../DataStructs/ChartJS_dataset_config.h" + +void add_ChartJS_chart_header( + const __FlashStringHelper *chartType, + const __FlashStringHelper *id, + const ChartJS_title & chartTitle, + int width, + int height, + const String & options = EMPTY_STRING, + bool enableZoom = false, + size_t nrSamples = 0, + bool onlyJSON = false); + +void add_ChartJS_chart_header( + const __FlashStringHelper *chartType, + const String & id, + const ChartJS_title & chartTitle, + int width, + int height, + const String & options = EMPTY_STRING, + bool enableZoom = false, + size_t nrSamples = 0, + bool onlyJSON = false); + +void add_ChartJS_chart_JSON_header( + const __FlashStringHelper *chartType, + const String & plugins, + const ChartJS_title & chartTitle, + const String & options, + size_t nrSamples); + +void add_ChartJS_chart_labels( + int valueCount, + const int labels[]); + +void add_ChartJS_chart_labels( + int valueCount, + const String labels[]); + + +void add_ChartJS_scatter_data_point(float x, + float y, + int nrDecimals); + +void add_ChartJS_dataset( + const ChartJS_dataset_config& config, + const float values[], + int valueCount, + unsigned int 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); + + +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 364a92949..010b74abe 100644 --- a/src/src/WebServer/Chart_JS_scale.cpp +++ b/src/src/WebServer/Chart_JS_scale.cpp @@ -1,147 +1,153 @@ -#include "../WebServer/Chart_JS_scale.h" - -#if FEATURE_CHART_JS - -# include "../Helpers/StringConverter.h" - -ChartJS_options_scale::ChartJS_options_scale(const String& id, const String& title) - : axisID(id), position(Position::Left) -{ - // Set some proper defaults, based on the id string. - if (axisID.startsWith(F("x"))) { - position = Position::Bottom; - - // Make sure the X-axis is shown even when no dataset is selected. - display = Display::True; - } - else if (axisID.indexOf(F("right")) != -1) { - position = Position::Right; - } - axisTitle.text = title; -} - -ChartJS_options_scale::ChartJS_options_scale(const PluginStats_Config_t& config, const String& title) -{ - const bool isLeft = config.isLeft(); - - position = isLeft ? Position::Left : Position::Right; - weight = config.getAxisIndex(); - axisID = strformat((isLeft ? F("y-left-%d") : F("y-right-%d")), - weight); - axisTitle.text = title; - display = Display::Auto; -} - -String ChartJS_options_scale::toString() const -{ - if (!axisID.isEmpty()) { - // In JSON, boolean values do not need quotes - const String displayStr = - display == Display::Auto ? F("\"auto\"") : - display == Display::True ? F("true") : F("false"); - - String typeStr = scaleType; - - if (typeStr.isEmpty()) { typeStr = F("linear"); } - - 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; - } - - String ticksStr; - - if (tickCount > 0) { - ticksStr = strformat(F(",ticks:{count:%d}"), tickCount); - } - return strformat( - F("\"%s\":{display:%s,type:\"%s\",position:\"%s\",title:%s,weight:%d%s}"), - axisID.c_str(), - displayStr.c_str(), - typeStr.c_str(), - positionStr.c_str(), - axisTitle.toString().c_str(), - weight, - ticksStr.c_str()); - } - return EMPTY_STRING; -} - -bool ChartJS_options_scale::is_Y_axis() const -{ - return position == Position::Left || - position == Position::Right; -} - -void ChartJS_options_scales::add(const ChartJS_options_scale& scale) -{ - for (auto it = _scales.begin(); it != _scales.end(); ++it) { - if (it->axisID.equals(scale.axisID)) { - // Found an axis with same ID. - // Combine labels and don't create a new one. - if (!scale.axisTitle.text.isEmpty()) { - if (!it->axisTitle.text.isEmpty()) { - it->axisTitle.color.clear(); - it->axisTitle.text += F(" / "); - } - it->axisTitle.text += scale.axisTitle.text; - } - return; - } - } - _scales.push_back(scale); -} - -void ChartJS_options_scales::update_Yaxis_TickCount() -{ - // For single Y-axis, use a dynamic tick count based on the data. - // For multiple Y-axis, we want 10 intervals, thus 11 ticks. - const int newTickCount = (nr_Y_scales() <= 1) ? 0 : 11; - - for (auto it = _scales.begin(); it != _scales.end(); ++it) { - if (it->is_Y_axis()) { - it->tickCount = newTickCount; - } - } -} - -String ChartJS_options_scales::toString() const -{ - if (_scales.empty()) { return EMPTY_STRING; } - - 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 += scale_str; - } - } - res += '}'; - res += ','; - return res; -} - -size_t ChartJS_options_scales::nr_Y_scales() const -{ - size_t count{}; - - for (auto it = _scales.begin(); it != _scales.end(); ++it) { - if (it->is_Y_axis()) { ++count; } - } - - return count; -} - -#endif // if FEATURE_CHART_JS +#include "../WebServer/Chart_JS_scale.h" + +#if FEATURE_CHART_JS + +# include "../Helpers/StringConverter.h" + +ChartJS_options_scale::ChartJS_options_scale(const String& id, const String& title) + : axisID(id), position(Position::Left) +{ + // Set some proper defaults, based on the id string. + if (axisID.startsWith(F("x"))) { + position = Position::Bottom; + + // Make sure the X-axis is shown even when no dataset is selected. + display = Display::True; + } + else if (axisID.indexOf(F("right")) != -1) { + position = Position::Right; + } + axisTitle.text = title; +} + +ChartJS_options_scale::ChartJS_options_scale(const PluginStats_Config_t& config, const String& title) +{ + const bool isLeft = config.isLeft(); + + position = isLeft ? Position::Left : Position::Right; + weight = config.getAxisIndex(); + axisID = strformat((isLeft ? F("y-left-%d") : F("y-right-%d")), + weight); + axisTitle.text = title; + display = Display::Auto; +} + +String ChartJS_options_scale::toString() const +{ + if (!axisID.isEmpty()) { + // In JSON, boolean values do not need quotes + const String displayStr = + display == Display::Auto ? F("\"auto\"") : + display == Display::True ? F("true") : F("false"); + + String typeStr = scaleType; + + if (typeStr.isEmpty()) { typeStr = F("linear"); } + + 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; + } + + 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\"}"); + } + + 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(), + displayStr.c_str(), + typeStr.c_str(), + positionStr.c_str(), + axisTitle.toString().c_str(), + weight, + extraOptions.c_str()); + } + return EMPTY_STRING; +} + +bool ChartJS_options_scale::is_Y_axis() const +{ + return position == Position::Left || + position == Position::Right; +} + +ChartJS_options_scales::ChartJS_options_scales() {} + +void ChartJS_options_scales::add(const ChartJS_options_scale& scale) +{ + for (auto it = _scales.begin(); it != _scales.end(); ++it) { + if (it->axisID.equals(scale.axisID)) { + // Found an axis with same ID. + // Combine labels and don't create a new one. + if (!scale.axisTitle.text.isEmpty()) { + if (!it->axisTitle.text.isEmpty()) { + it->axisTitle.color.clear(); + it->axisTitle.text += F(" / "); + } + it->axisTitle.text += scale.axisTitle.text; + } + return; + } + } + _scales.push_back(scale); +} + +void ChartJS_options_scales::update_Yaxis_TickCount() +{ + // For single Y-axis, use a dynamic tick count based on the data. + // For multiple Y-axis, we want 10 intervals, thus 11 ticks. + const int newTickCount = (nr_Y_scales() <= 1) ? 0 : 11; + + for (auto it = _scales.begin(); it != _scales.end(); ++it) { + if (it->is_Y_axis()) { + it->tickCount = newTickCount; + } + } +} + +String ChartJS_options_scales::toString() const +{ + if (_scales.empty()) { return EMPTY_STRING; } + + 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; + } + } + res += '}'; + return res; +} + +size_t ChartJS_options_scales::nr_Y_scales() const +{ + size_t count{}; + + for (auto it = _scales.begin(); it != _scales.end(); ++it) { + if (it->is_Y_axis()) { ++count; } + } + + return count; +} + +#endif // if FEATURE_CHART_JS diff --git a/src/src/WebServer/Chart_JS_scale.h b/src/src/WebServer/Chart_JS_scale.h index 075f5f62e..e19c90ccb 100644 --- a/src/src/WebServer/Chart_JS_scale.h +++ b/src/src/WebServer/Chart_JS_scale.h @@ -51,7 +51,7 @@ struct ChartJS_options_scale { }; struct ChartJS_options_scales { - ChartJS_options_scales() = default; + ChartJS_options_scales(); void add(const ChartJS_options_scale& scale); diff --git a/src/src/WebServer/Chart_JS_title.cpp b/src/src/WebServer/Chart_JS_title.cpp index b2ed0a3bc..14313d135 100644 --- a/src/src/WebServer/Chart_JS_title.cpp +++ b/src/src/WebServer/Chart_JS_title.cpp @@ -18,7 +18,7 @@ ChartJS_title::ChartJS_title(const String& titleText, Align alignment) String ChartJS_title::toString() const { if (text.isEmpty()) { - return F("{display: false}"); + return F("{\"display\": false}"); } const String alignStr = @@ -28,11 +28,11 @@ String ChartJS_title::toString() const { String colorStr; if (!color.isEmpty()) { - colorStr = strformat(F(",color:\"%s\""), color.c_str()); + colorStr = strformat(F(",\"color\":\"%s\""), color.c_str()); } return strformat( - F("{display: true,align:\"%s\",text:\"%s\"%s}"), + F("{\"display\": true,\"align\":\"%s\",\"text\":\"%s\"%s}"), alignStr.c_str(), text.c_str(), colorStr.c_str()); diff --git a/src/src/WebServer/ConfigPage.cpp b/src/src/WebServer/ConfigPage.cpp index 55b25ec93..7bd36fd8f 100644 --- a/src/src/WebServer/ConfigPage.cpp +++ b/src/src/WebServer/ConfigPage.cpp @@ -1,288 +1,294 @@ -#include "../WebServer/ConfigPage.h" - -#ifdef WEBSERVER_CONFIG - -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/ESPEasy_WebServer.h" - -#ifdef USES_ESPEASY_NOW -#include "../DataStructs/MAC_address.h" -#include "../DataStructs/NodeStruct.h" -#endif - -#include "../ESPEasyCore/Controller.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" - -#include "../Globals/MQTT.h" -#include "../Globals/Nodes.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" - -#include "../Helpers/DeepSleep.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Networking.h" -#include "../Helpers/StringConverter.h" - - -// ******************************************************************************** -// Web Interface config page -// ******************************************************************************** -void handle_config() { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_config")); - #endif - - if (!isLoggedIn()) { return; } - - navMenuIndex = MENU_INDEX_CONFIG; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - if (web_server.args() != 0) - { - String name = webArg(F("name")); - name.trim(); - - Settings.Delay = getFormItemInt(F("delay"), Settings.Delay); - Settings.deepSleep_wakeTime = getFormItemInt(F("awaketime"), Settings.deepSleep_wakeTime); - Settings.Unit = getFormItemInt(F("unit"), Settings.Unit); - - if (strcmp(Settings.Name, name.c_str()) != 0) { - addLog(LOG_LEVEL_INFO, F("Unit Name changed.")); - - if (CPluginCall(CPlugin::Function::CPLUGIN_GOT_INVALID, 0)) { // inform controllers that the old name will be invalid from now on. -#if FEATURE_MQTT - MQTTDisconnect(); // disconnect form MQTT Server if invalid message was sent succesfull. -#endif // if FEATURE_MQTT - } -#if FEATURE_MQTT - MQTTclient_should_reconnect = true; -#endif // if FEATURE_MQTT - } - - // Unit name - safe_strncpy(Settings.Name, name.c_str(), sizeof(Settings.Name)); - Settings.appendUnitToHostname(isFormItemChecked(F("appendunittohostname"))); - - // 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)); - - // 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: - - webArg2ip(F("iprangelow"), SecuritySettings.AllowedIPrangeLow); - webArg2ip(F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); - break; - } - - #ifdef USES_ESPEASY_NOW - for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { - String peer_mac = webArg(concat(F("peer"), peer)); - if (peer_mac.length() == 0) { - peer_mac = F("00:00:00:00:00:00"); - } - MAC_address mac; - if (mac.set(peer_mac.c_str())) { - mac.get(SecuritySettings.EspEasyNowPeerMAC[peer]); - } - /* - String log = F("MAC decoding "); - log += peer_mac; - log += F(" => "); - log += mac.toString(); - addLog(LOG_LEVEL_INFO, log); - */ - } - #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 - addHtmlError(SaveSettings()); - } - - html_add_form(); - html_table_class_normal(); - - addFormHeader(F("Main Settings")); - - Settings.Name[25] = 0; - SecuritySettings.Password[25] = 0; - addFormTextBox(F("Unit Name"), F("name"), Settings.Name, 25); - addFormNote(concat(F("Hostname: "), 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()); - addFormNote(F("Must be checked to connect to a hidden SSID")); - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - addFormNote(F("Required for some AP brands like Mikrotik to connect to hidden SSID")); - - 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); - } - - 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) { - addFormMACBox(concat(F("Peer "), peer + 1), - concat(F("peer"), peer), - SecuritySettings.EspEasyNowPeerMAC[peer]); - - bool match_STA; - const NodeStruct* nodeInfo = Nodes.getNodeByMac(SecuritySettings.EspEasyNowPeerMAC[peer], match_STA); - if (nodeInfo != nullptr) - { - String summary = nodeInfo->getSummary(); - summary += match_STA ? F(" (STA)") : F(" (AP)"); - addFormNote(summary); - } - - } -#endif - - addFormSubHeader(F("Sleep Mode")); - - addFormNumericBox(F("Sleep awake time"), F("awaketime"), Settings.deepSleep_wakeTime, 0, 255); - addUnit(F("sec")); - addHelpButton(F("SleepMode")); - addFormNote(F("0 = Sleep Disabled, else time awake from sleep")); - - int dsmax = getDeepSleepMax(); - addFormNumericBox(F("Sleep time"), F("delay"), Settings.Delay, 0, dsmax); // limited by hardware - { - addUnit(concat(F("sec (max: "), dsmax) + ')'); - } - - addFormCheckBox(F("Sleep on connection failure"), F("deepsleeponfail"), Settings.deepSleepOnFail); - - addFormSeparator(2); - - #if FEATURE_ALTERNATIVE_CDN_URL - addFormSubHeader(F("CDN (Content delivery network)")); - - addFormTextBox(F("Custom CDN URL"), F("alturl"), get_CDN_url_custom(), 255); - addFormNote(concat(F("Leave empty for default CDN url: "), get_CDN_url_prefix())); - - addFormSeparator(2); - #endif // if FEATURE_ALTERNATIVE_CDN_URL - - html_TR_TD(); - html_TD(); - addSubmitButton(); - html_end_table(); - html_end_form(); - - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - +#include "../WebServer/ConfigPage.h" + +#ifdef WEBSERVER_CONFIG + +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/ESPEasy_WebServer.h" + +#ifdef USES_ESPEASY_NOW +#include "../DataStructs/MAC_address.h" +#include "../DataStructs/NodeStruct.h" +#endif + +#include "../ESPEasyCore/Controller.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" + +#include "../Globals/MQTT.h" +#include "../Globals/Nodes.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" + +#include "../Helpers/DeepSleep.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Networking.h" +#include "../Helpers/StringConverter.h" + + +// ******************************************************************************** +// Web Interface config page +// ******************************************************************************** +void handle_config() { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_config")); + #endif + + if (!isLoggedIn()) { return; } + + navMenuIndex = MENU_INDEX_CONFIG; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + if (web_server.args() != 0) + { + String name = webArg(F("name")); + name.trim(); + + Settings.Delay = getFormItemInt(F("delay"), Settings.Delay); + Settings.deepSleep_wakeTime = getFormItemInt(F("awaketime"), Settings.deepSleep_wakeTime); + Settings.Unit = getFormItemInt(F("unit"), Settings.Unit); + + if (strcmp(Settings.Name, name.c_str()) != 0) { + addLog(LOG_LEVEL_INFO, F("Unit Name changed.")); + + if (CPluginCall(CPlugin::Function::CPLUGIN_GOT_INVALID, 0)) { // inform controllers that the old name will be invalid from now on. +#if FEATURE_MQTT + MQTTDisconnect(); // disconnect form MQTT Server if invalid message was sent succesfull. +#endif // if FEATURE_MQTT + } +#if FEATURE_MQTT + MQTTclient_should_reconnect = true; +#endif // if FEATURE_MQTT + } + + // Unit name + safe_strncpy(Settings.Name, name.c_str(), sizeof(Settings.Name)); + Settings.appendUnitToHostname(isFormItemChecked(F("appendunittohostname"))); + + // 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: + + webArg2ip(F("iprangelow"), SecuritySettings.AllowedIPrangeLow); + webArg2ip(F("iprangehigh"), SecuritySettings.AllowedIPrangeHigh); + break; + } + + #ifdef USES_ESPEASY_NOW + for (int peer = 0; peer < ESPEASY_NOW_PEER_MAX; ++peer) { + String peer_mac = webArg(concat(F("peer"), peer)); + if (peer_mac.length() == 0) { + peer_mac = F("00:00:00:00:00:00"); + } + MAC_address mac; + if (mac.set(peer_mac.c_str())) { + mac.get(SecuritySettings.EspEasyNowPeerMAC[peer]); + } + /* + String log = F("MAC decoding "); + log += peer_mac; + log += F(" => "); + log += mac.toString(); + addLog(LOG_LEVEL_INFO, log); + */ + } + #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 + addHtmlError(SaveSettings()); + } + + html_add_form(); + html_table_class_normal(); + + addFormHeader(F("Main Settings")); + + Settings.Name[25] = 0; + SecuritySettings.Password[25] = 0; + addFormTextBox(F("Unit Name"), F("name"), Settings.Name, 25); + addFormNote(concat(F("Hostname: "), 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); + } + + 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) { + addFormMACBox(concat(F("Peer "), peer + 1), + concat(F("peer"), peer), + SecuritySettings.EspEasyNowPeerMAC[peer]); + + bool match_STA; + const NodeStruct* nodeInfo = Nodes.getNodeByMac(SecuritySettings.EspEasyNowPeerMAC[peer], match_STA); + if (nodeInfo != nullptr) + { + String summary = nodeInfo->getSummary(); + summary += match_STA ? F(" (STA)") : F(" (AP)"); + addFormNote(summary); + } + + } +#endif + + addFormSubHeader(F("Sleep Mode")); + + addFormNumericBox(F("Sleep awake time"), F("awaketime"), Settings.deepSleep_wakeTime, 0, 255); + addUnit(F("sec")); + addHelpButton(F("SleepMode")); + addFormNote(F("0 = Sleep Disabled, else time awake from sleep")); + + int dsmax = getDeepSleepMax(); + addFormNumericBox(F("Sleep time"), F("delay"), Settings.Delay, 0, dsmax); // limited by hardware + { + addUnit(concat(F("sec (max: "), dsmax) + ')'); + } + + addFormCheckBox(F("Sleep on connection failure"), F("deepsleeponfail"), Settings.deepSleepOnFail); + + addFormSeparator(2); + + #if FEATURE_ALTERNATIVE_CDN_URL + addFormSubHeader(F("CDN (Content delivery network)")); + + addFormTextBox(F("Custom CDN URL"), F("alturl"), get_CDN_url_custom(), 255); + addFormNote(concat(F("Leave empty for default CDN url: "), get_CDN_url_prefix())); + + addFormSeparator(2); + #endif // if FEATURE_ALTERNATIVE_CDN_URL + + html_TR_TD(); + html_TD(); + addSubmitButton(); + html_end_table(); + html_end_form(); + + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + #endif // ifdef WEBSERVER_CONFIG \ No newline at end of file diff --git a/src/src/WebServer/ControllerPage.cpp b/src/src/WebServer/ControllerPage.cpp index 0866988b7..096a5cb06 100644 --- a/src/src/WebServer/ControllerPage.cpp +++ b/src/src/WebServer/ControllerPage.cpp @@ -387,6 +387,9 @@ void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex if (proto.usesTimeout) { addControllerParameterForm(*ControllerSettings, controllerindex, ControllerSettingsStruct::CONTROLLER_TIMEOUT); + if (proto.usesHost) { + addFormNote(F("Typical timeout: 100...300 msec for local host, >500 msec for internet hosts")); + } } if (proto.usesSampleSets) { diff --git a/src/src/WebServer/ControllerPage.h b/src/src/WebServer/ControllerPage.h index d1ac86c7e..bba1b881d 100644 --- a/src/src/WebServer/ControllerPage.h +++ b/src/src/WebServer/ControllerPage.h @@ -1,45 +1,45 @@ -#ifndef WEBSERVER_WEBSERVER_CONTROLLERPAGE_H -#define WEBSERVER_WEBSERVER_CONTROLLERPAGE_H - -#include "../WebServer/common.h" - -#ifdef WEBSERVER_CONTROLLERS - -#include "../DataStructs/ControllerSettingsStruct.h" - -#include "../Globals/CPlugins.h" - -// ******************************************************************************** -// Web Interface controller page -// ******************************************************************************** -void handle_controllers(); - -// ******************************************************************************** -// Selected controller has changed. -// Clear all Controller settings and load some defaults -// ******************************************************************************** -void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -// ******************************************************************************** -// Collect all submitted form data and store in the ControllerSettings -// ******************************************************************************** -void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); - -void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex); - -// ******************************************************************************** -// Show table with all selected controllers -// ******************************************************************************** -void handle_controllers_ShowAllControllersTable(); - -// ******************************************************************************** -// Show the controller settings page -// ******************************************************************************** -void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex); - -#endif // ifdef WEBSERVER_CONTROLLERS - - - - +#ifndef WEBSERVER_WEBSERVER_CONTROLLERPAGE_H +#define WEBSERVER_WEBSERVER_CONTROLLERPAGE_H + +#include "../WebServer/common.h" + +#ifdef WEBSERVER_CONTROLLERS + +#include "../DataStructs/ControllerSettingsStruct.h" + +#include "../Globals/CPlugins.h" + +// ******************************************************************************** +// Web Interface controller page +// ******************************************************************************** +void handle_controllers(); + +// ******************************************************************************** +// Selected controller has changed. +// Clear all Controller settings and load some defaults +// ******************************************************************************** +void handle_controllers_clearLoadDefaults(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); + +// ******************************************************************************** +// Collect all submitted form data and store in the ControllerSettings +// ******************************************************************************** +void handle_controllers_CopySubmittedSettings(uint8_t controllerindex, ControllerSettingsStruct& ControllerSettings); + +void handle_controllers_CopySubmittedSettings_CPluginCall(uint8_t controllerindex); + +// ******************************************************************************** +// Show table with all selected controllers +// ******************************************************************************** +void handle_controllers_ShowAllControllersTable(); + +// ******************************************************************************** +// Show the controller settings page +// ******************************************************************************** +void handle_controllers_ControllerSettingsPage(controllerIndex_t controllerindex); + +#endif // ifdef WEBSERVER_CONTROLLERS + + + + #endif \ No newline at end of file diff --git a/src/src/WebServer/CustomPage.cpp b/src/src/WebServer/CustomPage.cpp index cd27e3051..859e91be4 100644 --- a/src/src/WebServer/CustomPage.cpp +++ b/src/src/WebServer/CustomPage.cpp @@ -1,227 +1,228 @@ -#include "../WebServer/CustomPage.h" - -#ifdef WEBSERVER_CUSTOM - -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Forms.h" - -#include "../Commands/ExecuteCommand.h" -#include "../Globals/Nodes.h" -#include "../Globals/Device.h" -#include "../Globals/Plugins.h" -#include "../Globals/Settings.h" - -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/StringParser.h" - -#include "../../_Plugin_Helper.h" - -// ******************************************************************************** -// Web Interface custom page handler -// ******************************************************************************** -bool handle_custom(const String& path) { - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_custom")); - #endif - - if (!clientIPallowed()) { return false; } - - // create a dynamic custom page, parsing task values into [#] placeholders and parsing %xx% system variables - fs::File dataFile = tryOpenFile(path.c_str(), "r"); - const bool dashboardPage = path.startsWith(F("dashboard")) || path.startsWith(F("/dashboard")); - - if (!dataFile && !dashboardPage) { - return false; // unknown file that does not exist... - } - - #if FEATURE_ESPEASY_P2P - if (dashboardPage) // for the dashboard page, create a default unit dropdown selector - { - // handle page redirects to other unit's as requested by the unit dropdown selector - uint8_t unit = getFormItemInt(F("unit")); - uint8_t btnunit = getFormItemInt(F("btnunit")); - - if (!unit) { unit = btnunit; // unit element prevails, if not used then set to btnunit - } - - navMenuIndex = MENU_INDEX_CUSTOM_PAGE; - if (unit && (unit != Settings.Unit)) - { - auto it = Nodes.find(unit); - - if (it != Nodes.end()) { - TXBuffer.startStream(); - sendHeadandTail(F("TmplDsh"), _HEAD); - addHtml(F("second.IP())); - addHtml(F("/dashboard.esp\">")); - sendHeadandTail(F("TmplDsh"), _TAIL); - TXBuffer.endStream(); - return true; - } - } - - TXBuffer.startStream(); - sendHeadandTail(F("TmplDsh"), _HEAD); - html_add_JQuery_script(); - - #if FEATURE_CHART_JS - html_add_ChartJS_script(); - #endif // if FEATURE_CHART_JS - - #if FEATURE_RULES_EASY_COLOR_CODE - html_add_Easy_color_code_script(); - #endif - - html_add_autosubmit_form(); - html_add_form(); - - // create unit selector dropdown - addSelector_Head_reloadOnChange(F("unit")); - uint8_t choice = Settings.Unit; - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if ((it->second.ip[0] != 0) || (it->first == Settings.Unit)) - { - String name = String(it->first) + F(" - "); - - if (it->first != Settings.Unit) { - name += it->second.getNodeName(); - } - else { - name += Settings.getName(); - } - addSelector_Item(name, it->first, choice == it->first); - } - } - addSelector_Foot(); - - // create <> navigation buttons - uint8_t prev = Settings.Unit; - uint8_t next = Settings.Unit; - - for (uint8_t x = Settings.Unit - 1; x > 0; x--) { - auto it = Nodes.find(x); - - if (it != Nodes.end()) { - if (it->second.ip[0] != 0) { prev = x; break; } - } - } - - for (uint8_t x = Settings.Unit + 1; x < UNIT_NUMBER_MAX; x++) { - auto it = Nodes.find(x); - - if (it != Nodes.end()) { - if (it->second.ip[0] != 0) { next = x; break; } - } - } - - html_add_button_prefix(); - addHtml(path); - addHtml(F("?btnunit=")); - addHtmlInt(prev); - addHtml(F("'><")); - html_add_button_prefix(); - addHtml(path); - addHtml(F("?btnunit=")); - addHtmlInt(next); - addHtml(F("'>>")); - } - #endif - - // handle commands from a custom page - String webrequest = webArg(F("cmd")); - - if (webrequest.length() > 0) { - ExecuteCommand_all_config(EventValueSource::Enum::VALUE_SOURCE_HTTP, webrequest.c_str()); - - // handle some update processes first, before returning page update... - String dummy; - PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); - } - - - if (dataFile) - { - // Read the file per line and serve per line to reduce amount of memory needed. - size_t available = dataFile.available(); - String line; - line.reserve(128); - while (available > 0) { - size_t chunksize = 64; - if (available < chunksize) { - chunksize = available; - } - uint8_t buf[64] = {0}; - const size_t read = dataFile.read(buf, chunksize); - if (read == chunksize) { - for (size_t i = 0; i < chunksize; ++i) { - const char c = (char)buf[i]; - line += c; - if (c == '\n') { - addHtml(parseTemplate(line)); - line.clear(); - line.reserve(128); - } - } - available = dataFile.available(); - } else { - available = 0; - } - } - if (!line.isEmpty()) { - addHtml(parseTemplate(line)); - } - dataFile.close(); - } - else // if the requestef file does not exist, create a default action in case the page is named "dashboard*" - { - if (dashboardPage) - { - // if the custom page does not exist, create a basic task value overview page in case of dashboard request... - addHtml(F( - "")); - html_table_class_normal(); - - for (taskIndex_t x = 0; x < TASKS_MAX; x++) - { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - - if (validDeviceIndex(DeviceIndex)) { - html_TR_TD(); - addHtml(getTaskDeviceName(x)); - - const uint8_t valueCount = getValueCountForTask(x); - - for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) - { - const String taskValueName = getTaskValueName(x, varNr); - if ((varNr < valueCount) && - (!taskValueName.isEmpty())) - { - if (varNr > 0) { - html_TR_TD(); - } - html_TD(); - addHtml(taskValueName); - html_TD(); - addHtml(formatUserVarNoCheck(x, varNr)); - } - } - } - } - } - } - } - sendHeadandTail(F("TmplDsh"), _TAIL); - TXBuffer.endStream(); - return true; -} - +#include "../WebServer/CustomPage.h" + +#ifdef WEBSERVER_CUSTOM + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Forms.h" + +#include "../Commands/ExecuteCommand.h" +#include "../Globals/Nodes.h" +#include "../Globals/Device.h" +#include "../Globals/Plugins.h" +#include "../Globals/Settings.h" + +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/StringParser.h" + +#include "../../_Plugin_Helper.h" + +// ******************************************************************************** +// Web Interface custom page handler +// ******************************************************************************** +bool handle_custom(const String& path) { + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_custom")); + #endif + + if (!clientIPallowed()) { return false; } + + // create a dynamic custom page, parsing task values into [#] placeholders and parsing %xx% system variables + fs::File dataFile = tryOpenFile(path.c_str(), "r"); + const bool dashboardPage = path.startsWith(F("dashboard")) || path.startsWith(F("/dashboard")); + + if (!dataFile && !dashboardPage) { + return false; // unknown file that does not exist... + } + + #if FEATURE_ESPEASY_P2P + if (dashboardPage) // for the dashboard page, create a default unit dropdown selector + { + // handle page redirects to other unit's as requested by the unit dropdown selector + uint8_t unit = getFormItemInt(F("unit")); + uint8_t btnunit = getFormItemInt(F("btnunit")); + + if (!unit) { unit = btnunit; // unit element prevails, if not used then set to btnunit + } + + navMenuIndex = MENU_INDEX_CUSTOM_PAGE; + if (unit && (unit != Settings.Unit)) + { + auto it = Nodes.find(unit); + + if (it != Nodes.end()) { + TXBuffer.startStream(); + sendHeadandTail(F("TmplDsh"), _HEAD); + addHtml(F("second.IP())); + addHtml(F("/dashboard.esp\">")); + sendHeadandTail(F("TmplDsh"), _TAIL); + TXBuffer.endStream(); + return true; + } + } + + TXBuffer.startStream(); + sendHeadandTail(F("TmplDsh"), _HEAD); + html_add_JQuery_script(); + + #if FEATURE_CHART_JS + html_add_ChartJS_script(); + #endif // if FEATURE_CHART_JS + + #if FEATURE_RULES_EASY_COLOR_CODE + html_add_Easy_color_code_script(); + #endif + + html_add_autosubmit_form(); + html_add_form(); + + // create unit selector dropdown + addSelector_Head_reloadOnChange(F("unit")); + uint8_t choice = Settings.Unit; + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if ((it->second.ip[0] != 0) || (it->first == Settings.Unit)) + { + String name = String(it->first) + F(" - "); + + if (it->first != Settings.Unit) { + name += it->second.getNodeName(); + } + else { + name += Settings.getName(); + } + addSelector_Item(name, it->first, choice == it->first); + } + } + addSelector_Foot(); + + // create <> navigation buttons + uint8_t prev = Settings.Unit; + uint8_t next = Settings.Unit; + + for (uint8_t x = Settings.Unit - 1; x > 0; x--) { + auto it = Nodes.find(x); + + if (it != Nodes.end()) { + if (it->second.ip[0] != 0) { prev = x; break; } + } + } + + for (uint8_t x = Settings.Unit + 1; x < UNIT_NUMBER_MAX; x++) { + auto it = Nodes.find(x); + + if (it != Nodes.end()) { + if (it->second.ip[0] != 0) { next = x; break; } + } + } + + html_add_button_prefix(); + addHtml(path); + addHtml(F("?btnunit=")); + addHtmlInt(prev); + addHtml(F("'><")); + html_add_button_prefix(); + addHtml(path); + addHtml(F("?btnunit=")); + addHtmlInt(next); + addHtml(F("'>>")); + } + #endif + + // handle commands from a custom page + String webrequest = webArg(F("cmd")); + + if (webrequest.length() > 0) { + ExecuteCommand_all_config({EventValueSource::Enum::VALUE_SOURCE_HTTP, webrequest.c_str()}); + + // handle some update processes first, before returning page update... + String dummy; + PluginCall(PLUGIN_TEN_PER_SECOND, 0, dummy); + } + + + if (dataFile) + { + // Read the file per line and serve per line to reduce amount of memory needed. + size_t available = dataFile.available(); + String line; + line.reserve(128); + while (available > 0) { + size_t chunksize = 64; + if (available < chunksize) { + chunksize = available; + } + uint8_t buf[64] = {0}; + const size_t read = dataFile.read(buf, chunksize); + if (read == chunksize) { + for (size_t i = 0; i < chunksize; ++i) { + const char c = (char)buf[i]; + line += c; + if (c == '\n') { + addHtml(parseTemplate(line)); + line.clear(); + line.reserve(128); + } + } + available = dataFile.available(); + } else { + available = 0; + } + } + if (!line.isEmpty()) { + addHtml(parseTemplate(line)); + } + dataFile.close(); + } + else // if the requestef file does not exist, create a default action in case the page is named "dashboard*" + { + if (dashboardPage) + { + // if the custom page does not exist, create a basic task value overview page in case of dashboard request... + addHtml(F( + "")); + html_table_class_normal(); + + + for (taskIndex_t x = 0; x < TASKS_MAX; x++) + { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + + if (validDeviceIndex(DeviceIndex)) { + html_TR_TD(); + addHtml(getTaskDeviceName(x)); + + const uint8_t valueCount = getValueCountForTask(x); + + struct EventStruct TempEvent(x); + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + const String taskValueName = Cache.getTaskDeviceValueName(x, varNr); + if (!taskValueName.isEmpty()) + { + if (varNr > 0) { + html_TR_TD(); + } + html_TD(); + addHtml(taskValueName); + html_TD(); + addHtml(formatUserVarNoCheck(&TempEvent, varNr)); + } + } + } + } + } + } + } + sendHeadandTail(F("TmplDsh"), _TAIL); + TXBuffer.endStream(); + return true; +} + #endif \ No newline at end of file diff --git a/src/src/WebServer/DevicesPage.cpp b/src/src/WebServer/DevicesPage.cpp index a2fc5c848..3e24c13b4 100644 --- a/src/src/WebServer/DevicesPage.cpp +++ b/src/src/WebServer/DevicesPage.cpp @@ -1,1512 +1,1524 @@ -#include "../WebServer/DevicesPage.h" - -#ifdef WEBSERVER_DEVICES - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" - -# include "../DataStructs/NodeStruct.h" -#if FEATURE_PLUGIN_STATS -#include "../DataStructs/PluginStats_Config.h" -#endif - - -# include "../Globals/CPlugins.h" -# include "../Globals/Device.h" -# include "../Globals/ExtraTaskSettings.h" -# include "../Globals/Nodes.h" -# include "../Globals/Plugins.h" - -# include "../Static/WebStaticData.h" - -# include "../Helpers/_CPlugin_init.h" -# include "../Helpers/_Plugin_init.h" -# include "../Helpers/_Plugin_SensorTypeHelper.h" -# include "../Helpers/_Plugin_Helper_serial.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/I2C_Plugin_Helper.h" -# include "../Helpers/StringConverter.h" -# include "../Helpers/StringGenerator_GPIO.h" - - - -# include "../../_Plugin_Helper.h" - -# include - - -void handle_devices() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (!isLoggedIn()) { return; } - navMenuIndex = MENU_INDEX_DEVICES; - TXBuffer.startStream(); - sendHeadandTail_stdtemplate(_HEAD); - - - // char tmpString[41]; - - - // String taskindex = webArg(F("index")); - - pluginID_t taskdevicenumber; - - if (hasArg(F("del"))) { - taskdevicenumber.setInvalid(); - } - else { - taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); - } - - - // String taskdeviceid[CONTROLLER_MAX]; - // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins - // and expected to be saved by this code - // String taskdevicepin2 = webArg(F("taskdevicepin2")); - // String taskdevicepin3 = webArg(F("taskdevicepin3")); - // String taskdevicepin1pullup = webArg(F("TDPPU")); - // String taskdevicepin1inversed = webArg(F("TDPI")); - // String taskdevicename = webArg(F("TDN")); - // String taskdeviceport = webArg(F("TDP")); - // String taskdeviceformula[VARS_PER_TASK]; - // String taskdevicevaluename[VARS_PER_TASK]; - // String taskdevicevaluedecimals[VARS_PER_TASK]; - // String taskdevicesenddata[CONTROLLER_MAX]; - // String taskdeviceglobalsync = webArg(F("TDGS")); - // String taskdeviceenabled = webArg(F("TDE")); - - // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) - // { - // char argc[25]; - // String arg = F("TDF"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceformula[varNr] = webArg(argc); - // - // arg = F("TDVN"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluename[varNr] = webArg(argc); - // - // arg = F("TDVD"); - // arg += varNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicevaluedecimals[varNr] = webArg(argc); - // } - - // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - // { - // char argc[25]; - // String arg = F("TDID"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdeviceid[controllerNr] = webArg(argc); - // - // arg = F("TDSD"); - // arg += controllerNr + 1; - // arg.toCharArray(argc, 25); - // taskdevicesenddata[controllerNr] = webArg(argc); - // } - - uint8_t page = getFormItemInt(F("page"), 0); - - if (page == 0) { - page = 1; - } - uint8_t setpage = getFormItemInt(F("setpage"), 0); - - if (setpage > 0) - { - if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { - page = setpage; - } - else { - page = TASKS_MAX / TASKS_PER_PAGE; - } - } - const int edit = getFormItemInt(F("edit"), 0); - - // taskIndex in the URL is 1 ... TASKS_MAX - // For use in other functions, set it to 0 ... (TASKS_MAX - 1) - taskIndex_t taskIndex = getFormItemInt(F("index"), 0); - boolean taskIndexNotSet = taskIndex == 0; - - const bool nosave = isFormItemChecked(F("nosave")); - - if (!taskIndexNotSet) { - --taskIndex; -// LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date - } - - // FIXME TD-er: Might have to clear any caches here. - if ((edit != 0) && !taskIndexNotSet) // when form submitted - { - if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) - { - // change of device: cleanup old device and reset default settings - setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); - const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - - if (validDeviceIndex(DeviceIndex)) { - const DeviceStruct& device = Device[DeviceIndex]; - if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? - bitWrite(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default - } - } - } - else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings - { - handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); - } - - if (taskdevicenumber != INVALID_PLUGIN_ID) { - // Task index has a task device number, so it makes sense to save. - // N.B. When calling delete, the settings were already saved. - if (nosave) { - Cache.updateExtraTaskSettingsCache(); - UserVar.clear_computed(taskIndex); - } else { - addHtmlError(SaveTaskSettings(taskIndex)); - addHtmlError(SaveSettings()); - } - - struct EventStruct TempEvent(taskIndex); - String dummy; - - if (Settings.TaskDeviceEnabled[taskIndex]) { - if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { - PluginCall(PLUGIN_READ, &TempEvent, dummy); - } - } else { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - } - } - - // show all tasks as table - if (taskIndexNotSet) - { - handle_devicess_ShowAllTasksTable(page); - } - - // Show edit form if a specific entry is chosen with the edit button - else - { - handle_devices_TaskSettingsPage(taskIndex, page); - } - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_devices")); - # endif // ifndef BUILD_NO_RAM_TRACKER -# ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { - addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); - } -# endif // ifndef BUILD_NO_DEBUG - sendHeadandTail_stdtemplate(_TAIL); - TXBuffer.endStream(); -} - -// ******************************************************************************** -// Add a device select dropdown list -// TODO TD-er: Add JavaScript filter: -// https://www.w3schools.com/howto/howto_js_filter_dropdown.asp -// ******************************************************************************** -void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) -{ - String deviceName; - - addSelector_Head_reloadOnChange(name); - addSelector_Item(F("- None -"), 0, false); - - deviceIndex_t x; - bool done = false; - while (!done) { - const deviceIndex_t deviceIndex = getDeviceIndex_sorted(x); - if (!validDeviceIndex(deviceIndex)) { - done = true; - } else { - const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); - - if (validPluginID(pluginID)) { - deviceName = getPluginNameFromDeviceIndex(deviceIndex); - - - # if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) - deviceName = concat(get_formatted_Plugin_number(pluginID), F(" - ")) + deviceName; - # endif // if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) - - addSelector_Item(deviceName, - pluginID.value, - choice == pluginID); - } - } - ++x; - } - - addSelector_Foot(); -} - -// ******************************************************************************** -// Collect all submitted form data and store the task settings -// ******************************************************************************** -void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) -{ - if (!validTaskIndex(taskIndex)) { return; } - const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); - - if (!validDeviceIndex(DeviceIndex)) { return; } - - const DeviceStruct& device = Device[DeviceIndex]; - - unsigned long taskdevicetimer = getFormItemInt(F("TDT"), 0); - - Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; - - if (device.Type == DEVICE_TYPE_I2C) { - uint8_t flags = 0; - bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); - -# if FEATURE_I2CMULTIPLEXER - - if (isI2CMultiplexerEnabled()) { - int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); - bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, multipleMuxPortsOption == 1); - - if (multipleMuxPortsOption == 1) { - uint8_t selectedPorts = 0; - - for (int x = 0; x < I2CMultiplexerMaxChannels(); ++x) { - bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); - } - Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; - } else { - Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); - } - } - -# endif // if FEATURE_I2CMULTIPLEXER - - Settings.I2C_Flags[taskIndex] = flags; - } - - // Must load from file system to make sure all caches and checksums match. - ExtraTaskSettings.clear(); - ExtraTaskSettings.TaskIndex = taskIndex; - Cache.clearTaskCache(taskIndex); - - struct EventStruct TempEvent(taskIndex); - - // Save selected output type. - switch (device.OutputDataType) { - case Output_Data_type_t::Default: - { - String dummy; - PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); - break; - } - case Output_Data_type_t::Simple: - case Output_Data_type_t::All: - { - int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - Sensor_VType VType = TempEvent.sensorType; - - if ((pconfigIndex >= 0) && (pconfigIndex < PLUGIN_CONFIGVAR_MAX)) { - VType = static_cast(getFormItemInt(PCONFIG_LABEL(pconfigIndex), 0)); - Settings.TaskDevicePluginConfig[taskIndex][pconfigIndex] = static_cast(VType); - } - ExtraTaskSettings.clearUnusedValueNames(getValueCountFromSensorType(VType)); - break; - } - } - - { - int pins[] = {-1, -1, -1}; - for (int i = 0; i < 3; ++i) { - update_whenset_FormItemInt(concat(F("taskdevicepin"), i + 1), pins[i]); - } - - const bool taskEnabled = isFormItemChecked(F("TDE")); - setBasicTaskValues(taskIndex, taskdevicetimer, - taskEnabled, webArg(F("TDN")), - pins); - } - - #if FEATURE_PLUGIN_PRIORITY - if (device.PowerManager // Check extra priority device flags when available - ) { - bool disablePrio = false; - for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { - if (t != taskIndex) { - disablePrio = Settings.isPriorityTask(t); - } - } - bool statePriority = isFormItemChecked(F("TPRE")); - if (device.PowerManager) { - Settings.setPowerManagerTask(taskIndex, statePriority); - } - // Set alternative Priority flags - // Set to readonly if set as Priority task - Settings.setTaskEnableReadonly(taskIndex, statePriority); - } - #endif // if FEATURE_PLUGIN_PRIORITY - Settings.TaskDevicePort[taskIndex] = getFormItemInt(F("TDP"), 0); - update_whenset_FormItemInt(F("remoteFeed"), Settings.TaskDeviceDataFeed[taskIndex]); - Settings.CombineTaskValues_SingleEvent(taskIndex, isFormItemChecked(F("TVSE"))); - - 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 (device.PullUpOption) { - Settings.TaskDevicePin1PullUp[taskIndex] = isFormItemChecked(F("TDPPU")); - } - - if (device.InverseLogicOption) { - Settings.TaskDevicePin1Inversed[taskIndex] = isFormItemChecked(F("TDPI")); - } - - if (device.isSerial()) - { - # ifdef PLUGIN_USES_SERIAL - serialHelper_webformSave(&TempEvent); - # else // ifdef PLUGIN_USES_SERIAL - addLog(LOG_LEVEL_ERROR, F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - } - - const uint8_t valueCount = getValueCountForTask(taskIndex); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[varNr], getPluginCustomArgName(F("TDF"), varNr)); - update_whenset_FormItemInt(getPluginCustomArgName(F("TDVD"), varNr), ExtraTaskSettings.TaskDeviceValueDecimals[varNr]); - strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[varNr], getPluginCustomArgName(F("TDVN"), varNr)); -#if FEATURE_PLUGIN_FILTER - ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); -#endif -#if FEATURE_PLUGIN_STATS - PluginStats_Config_t pluginStats_Config; - pluginStats_Config.setEnabled(isFormItemChecked(getPluginCustomArgName(F("TDS"), varNr))); - pluginStats_Config.setHidden(isFormItemChecked(getPluginCustomArgName(F("TDSH"), varNr))); - const int selectedAxis = getFormItemInt(getPluginCustomArgName(F("TDSA"), varNr)); - pluginStats_Config.setAxisIndex(selectedAxis); - pluginStats_Config.setAxisPosition( - ((selectedAxis >> 2) == 0) - ? PluginStats_Config_t::AxisPosition::Left - : PluginStats_Config_t::AxisPosition::Right); - - ExtraTaskSettings.setPluginStatsConfig(varNr, pluginStats_Config); -#endif - } - ExtraTaskSettings.clearUnusedValueNames(valueCount); - - // ExtraTaskSettings has changed. - // The content of it is needed for sending CPLUGIN_TASK_CHANGE_NOTIFICATION and TaskInit/TaskExit events - Cache.updateExtraTaskSettingsCache(); - - // allow the plugin to save plugin-specific form settings. - { - String dummy; - if (device.ExitTaskBeforeSave) { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - - PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummy); - - if (device.ErrorStateValues) { - // FIXME TD-er: Must collect these from the web page. - PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); - } - - // Make sure the task needs to reload using the new settings. - if (!device.ExitTaskBeforeSave) { - PluginCall(PLUGIN_EXIT, &TempEvent, dummy); - } - } - - // Store all PCONFIG values on the web page - // Must be done after PLUGIN_WEBFORM_SAVE, to allow tasks to clear the default task value names - // Output type selectors are typically stored in PCONFIG - for (int pconfigIndex = 0; pconfigIndex < PLUGIN_CONFIGVAR_MAX; ++pconfigIndex) { - pconfig_webformSave(&TempEvent, pconfigIndex); - } - // ExtraTaskSettings may have changed during PLUGIN_WEBFORM_SAVE, so again update the cache. - Cache.updateExtraTaskSettingsCache(); - - loadDefaultTaskValueNames_ifEmpty(taskIndex); - Cache.updateExtraTaskSettingsCache(); - - // notify controllers: CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION - for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) - { - TempEvent.ControllerIndex = x; - - 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); - } - } - UserVar.clear_computed(taskIndex); -} - - -void html_add_setPage(uint8_t page, bool isLinkToPrev) { - addHtml(strformat( - F("devices?setpage=%u'>&%ct;"), - static_cast(page), - isLinkToPrev ? 'l' : 'g')); -} - -// ******************************************************************************** -// Show table with all selected Tasks/Devices -// ******************************************************************************** -void handle_devicess_ShowAllTasksTable(uint8_t page) -{ - serve_JS(JSfiles_e::UpdateSensorValuesDevicePage); - html_table_class_multirow(); - html_TR(); - html_table_header(F(""), 70); - - if (TASKS_MAX != TASKS_PER_PAGE) - { - html_add_button_prefix(); - - html_add_setPage((page > 1) ? page - 1 : page, true); - html_add_button_prefix(); - html_add_setPage((page < (TASKS_MAX / TASKS_PER_PAGE)) ? page + 1 : page, false); - } - - html_table_header(F("Task"), 50); - html_table_header(F("Enabled"), 100); - html_table_header(F("Device")); - html_table_header(F("Name")); - html_table_header(F("Port")); - html_table_header(F("Ctr (IDX)"), 100); - html_table_header(F("GPIO")); - html_table_header(F("Values")); - - String deviceName; - - for (taskIndex_t x = (page - 1) * TASKS_PER_PAGE; x < ((page) * TASKS_PER_PAGE) && validTaskIndex(x); x++) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); - - html_TR_TD(); - - if (pluginID_set && !supportedPluginID(Settings.getPluginID_for_task(x))) { - html_add_button_prefix(F("red"), true); - } else { - html_add_button_prefix(); - } - { - const int pageIndex = static_cast(x + 1); - addHtml(strformat( - F("devices?index=%d&page=%u'>"), - pageIndex, - static_cast(page))); - addHtml(pluginID_set ? F("Edit") : F("Add")); - addHtml(concat(F("
"), pageIndex)); - html_TD(); - } - - // Show table of all configured tasks - // A task may also refer to a non supported plugin. - // This will be shown as not supported. - // Editing a task which has a non supported plugin will present the same as when assigning a new plugin to a task. - if (pluginID_set) - { - //LoadTaskSettings(x); - int8_t spi_gpios[3] { -1, -1, -1 }; - struct EventStruct TempEvent(x); - addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); - - html_TD(); - addHtml(getPluginNameFromPluginID(Settings.getPluginID_for_task(x))); - html_TD(); - addHtml(getTaskDeviceName(x)); - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - if (Settings.TaskDeviceDataFeed[x] != 0) { - #if FEATURE_ESPEASY_P2P - // Show originating node number - const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[x]; - format_originating_node(remoteUnit); - #endif - } else { - String portDescr; - - if (PluginCall(PLUGIN_WEBFORM_SHOW_CONFIG, &TempEvent, portDescr)) { - addHtml(portDescr); - } else { - const DeviceStruct& device = Device[DeviceIndex]; - if (device.Type == DEVICE_TYPE_I2C) { - format_I2C_port_description(x); - } else if (device.isSPI()) { - format_SPI_port_description(spi_gpios); - } else if (device.isSerial()) { - # ifdef PLUGIN_USES_SERIAL - addHtml(serialHelper_getSerialTypeLabel(&TempEvent)); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - } else { - // Plugin has no custom port formatting, show default one. - if (device.Ports != 0) - { - addHtml(formatToHex_decimal(Settings.TaskDevicePort[x])); - } - } - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - if (Device[DeviceIndex].SendDataOption) - { - boolean doBR = false; - - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - { - if (Settings.TaskDeviceSendData[controllerNr][x]) - { - if (doBR) { - html_BR(); - } - addHtml(getControllerSymbol(controllerNr)); - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - - if (validProtocolIndex(ProtocolIndex)) { - if (getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) - { - addHtml(strformat( - F(" (%d)"), - static_cast(Settings.TaskDeviceID[controllerNr][x]))); - - if (Settings.TaskDeviceID[controllerNr][x] == 0) { - addHtml(' '); - addHtml(F(HTML_SYMBOL_WARNING)); - } - } - doBR = true; - } - } - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - const DeviceStruct& device = Device[DeviceIndex]; - if (Settings.TaskDeviceDataFeed[x] == 0) - { - String description; - bool pluginHasGPIODescription = pluginWebformShowGPIOdescription(x, F("
"), description); - - bool showpin1 = false; - bool showpin2 = false; - bool showpin3 = false; - - switch (device.Type) { - case DEVICE_TYPE_I2C: - { - format_I2C_pin_description(x); - html_BR(); - break; - } - case DEVICE_TYPE_SPI3: - showpin3 = !pluginHasGPIODescription; - - // Fall Through - case DEVICE_TYPE_SPI2: - showpin2 = !pluginHasGPIODescription; - - // Fall Through - case DEVICE_TYPE_SPI: - format_SPI_pin_description(spi_gpios, x, !pluginHasGPIODescription); - break; - case DEVICE_TYPE_ANALOG: - { - # ifdef ESP8266 - # if FEATURE_ADC_VCC - addHtml(F("ADC (VCC)")); - # else // if FEATURE_ADC_VCC - addHtml(F("ADC (TOUT)")); - # endif // if FEATURE_ADC_VCC - # endif // ifdef ESP8266 - # ifdef ESP32 - showpin1 = true; - addHtml(formatGpioName_ADC(Settings.TaskDevicePin1[x])); - html_BR(); - # endif // ifdef ESP32 - - break; - } - case DEVICE_TYPE_SERIAL_PLUS1: - showpin3 = true; - - // fallthrough - case DEVICE_TYPE_SERIAL: - { - # ifdef PLUGIN_USES_SERIAL - const String serialDescription = serialHelper_getGpioDescription(static_cast(Settings.TaskDevicePort[x]), Settings.TaskDevicePin1[x], - Settings.TaskDevicePin2[x], F("
")); - addHtml(serialDescription); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - - if ( -#ifdef PLUGIN_USES_SERIAL - serialDescription.length() || -#endif - showpin3) { - html_BR(); - } - break; - } - case DEVICE_TYPE_CUSTOM3: - showpin3 = true; - - // fallthrough - case DEVICE_TYPE_CUSTOM2: - showpin2 = true; - - // fallthrough - case DEVICE_TYPE_CUSTOM1: - case DEVICE_TYPE_CUSTOM0: - { - showpin1 = true; - if (pluginHasGPIODescription || (device.Type == DEVICE_TYPE_CUSTOM0)) { - addHtml(description); - showpin1 = false; - showpin2 = false; - showpin3 = false; - } - break; - } - - default: - showpin1 = true; - showpin2 = true; - showpin3 = true; - break; - } - - if (showpin1) - { - addGpioHtml(Settings.getTaskDevicePin(x, 1)); - } - - if (showpin2) - { - html_BR(); - addGpioHtml(Settings.getTaskDevicePin(x, 2)); - } - - if (showpin3) - { - html_BR(); - addGpioHtml(Settings.getTaskDevicePin(x, 3)); - } - - // Allow for tasks to show their own specific GPIO pins. - if (!device.isCustom() && - pluginHasGPIODescription) { - if (showpin1 || showpin2 || showpin3) { - html_BR(); - } - addHtml(description); - } - } - } - - html_TD(); - - if (validDeviceIndex(DeviceIndex)) { - String customValuesString; - const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); - - if (!customValues) - { - const uint8_t valueCount = getValueCountForTask(x); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) - { - pluginWebformShowValue(x, varNr, getTaskValueName(x, varNr), formatUserVarNoCheck(x, varNr)); - } - } - } - } - } - else { - html_TD(6); - } - } // next - html_end_table(); - html_end_form(); -} - -#if FEATURE_ESPEASY_P2P -void format_originating_node(uint8_t remoteUnit) { - addHtml(F("Unit ")); - addHtmlInt(remoteUnit); - - if (remoteUnit != 255) { - const NodeStruct *node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addHtml(F(" - ")); - addHtml(node->getNodeName()); - } else { - addHtml(F(" - Not Seen recently")); - } - } -} -#endif - -void format_I2C_port_description(taskIndex_t x) -{ - addHtml(F("I2C")); - # if FEATURE_I2C_GET_ADDRESS - const uint8_t i2cAddr = getTaskI2CAddress(x); - if (i2cAddr > 0) { - addHtml(' '); - addHtml(formatToHex(i2cAddr, 2)); - } - # endif // if FEATURE_I2C_GET_ADDRESS - # if FEATURE_I2CMULTIPLEXER - - if (isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(x)) { - String mux; - - if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel - mux = F("
Multiplexer channel(s)"); - uint8_t b = 0; // For adding lineBreaks - - for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { - if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { - mux += b == 0 ? F("
") : F(", "); - b++; - mux += String(c); - } - } - } else { // Single channel - mux = concat(F("
Multiplexer channel "), static_cast(Settings.I2C_Multiplexer_Channel[x])); - } - addHtml(mux); - } - # endif // if FEATURE_I2CMULTIPLEXER -} - -void format_SPI_port_description(int8_t spi_gpios[3]) -{ - if (!Settings.getSPI_pins(spi_gpios)) { - addHtml(F("SPI (Not enabled)")); - return; - } - # ifdef ESP32 - addHtml(getSPI_optionToShortString(static_cast(Settings.InitSPI))); - # endif // ifdef ESP32 - # ifdef ESP8266 - addHtml(F("SPI")); - # endif // ifdef ESP8266 -} - -void format_I2C_pin_description(taskIndex_t x) -{ - if (checkI2CConfigValid_toHtml(x)) { - Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.Pin_i2c_sda, false)); - html_BR(); - Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.Pin_i2c_scl, false)); - } -} - -void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) -{ - 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) { - if (i != 0) - html_BR(); - - Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); - } - if (showCSpin) { - html_BR(); - Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); - } - } -} - -// ******************************************************************************** -// Show the task settings page -// ******************************************************************************** -void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) -{ - if (!validTaskIndex(taskIndex)) { return; } - - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); - - //LoadTaskSettings(taskIndex); - - html_add_form(); - html_table_class_normal(); - addFormHeader(F("Task Settings")); - - - addHtml(F("
Device:")); - - // no (supported) device selected, this effectively checks for validDeviceIndex - if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) - { - // takes lots of memory/time so call this only when needed. - addDeviceSelect(F("TDNUM"), Settings.getPluginID_for_task(taskIndex)); // ="taskdevicenumber" - addFormSeparator(4); - } - - // device selected - else - { - const DeviceStruct& device = Device[DeviceIndex]; - // remember selected device number - addHtml(F("'); - - // show selected device name and delete button - addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); - - addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); - addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); - - addFormTextBox(F("Name"), F("TDN"), getTaskDeviceName(taskIndex), NAME_FORMULA_LENGTH_MAX); // ="taskdevicename" - - addFormCheckBox(F("Enabled"), F("TDE"), - Settings.TaskDeviceEnabled[taskIndex], -// Settings.TaskDeviceEnabled[taskIndex].enabled, - Settings.isTaskEnableReadonly(taskIndex)); // ="taskdeviceenabled" - - #if FEATURE_PLUGIN_PRIORITY - if (device.PowerManager) { // Check extra priority device flags when available - bool disablePrio = !Settings.TaskDeviceEnabled[taskIndex]; - for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { - if (t != taskIndex) { // Ignore current device - if (device.PowerManager && Settings.isPowerManagerTask(t)) { - disablePrio = true; // Allow only a single PowerManager plugin - } - // Add other Priority options checks - } - } - addFormSubHeader(F("Priority task")); - addFormCheckBox(F("Priority task"), F("TPRE"), Settings.isPriorityTask(taskIndex), disablePrio); // ="taskpriorityenabled" - if (!disablePrio) { - addFormNote(F("After enabling a Priority task, a reboot is required to activate. See documentation.")); - } - } - #endif // if FEATURE_PLUGIN_PRIORITY - - bool addPinConfig = false; - - // section: Sensor / Actuator - if (!device.Custom && (Settings.TaskDeviceDataFeed[taskIndex] == 0) && - ((device.Ports != 0) || - (device.PullUpOption) || - (device.InverseLogicOption) || - (device.connectedToGPIOpins()))) - { - addFormSubHeader((device.SendDataOption) ? F("Sensor") : F("Actuator")); - - if (device.Ports != 0) { - addFormNumericBox(F("Port"), F("TDP"), Settings.TaskDevicePort[taskIndex]); // ="taskdeviceport" - } - - addPinConfig = true; - } - - if (addPinConfig || (device.Type == DEVICE_TYPE_I2C)) { - if (device.isSerial()) { - # ifdef PLUGIN_USES_SERIAL - devicePage_show_serial_config(taskIndex); - # else // ifdef PLUGIN_USES_SERIAL - addHtml(F("PLUGIN_USES_SERIAL not defined")); - # endif // ifdef PLUGIN_USES_SERIAL - - devicePage_show_pin_config(taskIndex, DeviceIndex); - addPinConfig = false; - - html_add_script(F("document.getElementById('serPort').onchange();"), false); - } else if (device.Type == DEVICE_TYPE_I2C) { - devicePage_show_pin_config(taskIndex, DeviceIndex); - addPinConfig = false; - - if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { - devicePage_show_I2C_config(taskIndex, DeviceIndex); - } - } - - if (addPinConfig) { - devicePage_show_pin_config(taskIndex, DeviceIndex); - } - } - if (DEVICE_TYPE_DUMMY != device.Type) { - addFormSubHeader(F("Device Settings")); - } - - String webformLoadString; - struct EventStruct TempEvent(taskIndex); - // add plugins content - if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors - PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); - #ifndef BUILD_NO_DEBUG - if (webformLoadString.length() > 0) { - String errorMessage; - PluginCall(PLUGIN_GET_DEVICENAME, &TempEvent, errorMessage); - errorMessage += F(": Bug in PLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead"); - addHtmlError(errorMessage); - } - #endif - - PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices - } - else { - #if FEATURE_ESPEASY_P2P - // Show remote feed information. - addFormSubHeader(F("Data Source")); - const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; - addFormNumericBox(F("Remote Unit"), F("RemoteUnit"), remoteUnit, 0, 255); - - if (remoteUnit != 255) { - const NodeStruct* node = Nodes.getNode(remoteUnit); - - if (node != nullptr) { - addUnit(node->getNodeName()); - } else { - addUnit(F("Unknown Unit Name")); - } - } - addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. - #endif - - PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices - } - - devicePage_show_output_data_type(taskIndex, DeviceIndex); - - #if FEATURE_PLUGIN_STATS - // Task statistics and historic data in a chart - devicePage_show_task_statistics(taskIndex, DeviceIndex); - #endif // if FEATURE_PLUGIN_STATS - - // section: Data Acquisition - devicePage_show_controller_config(taskIndex, DeviceIndex); - - addFormSeparator(2); - - devicePage_show_interval_config(taskIndex, DeviceIndex); - - devicePage_show_task_values(taskIndex, DeviceIndex); - } - - html_TR_TD(); - addHtml(F("")); - html_add_button_prefix(); - addHtml(F("devices?setpage=")); - addHtmlInt(page); - addHtml(F("'>Close")); - #if FEATURE_PLUGIN_PRIORITY - if (!Settings.isPriorityTask(taskIndex)) - #endif // if FEATURE_PLUGIN_PRIORITY - { - addSubmitButton(); - } - addHtml(F("")); - addHtml(F("")); - - // if user selected a device, add the delete button, except for Priority tasks - if (validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex)) - #if FEATURE_PLUGIN_PRIORITY - && !Settings.isPriorityTask(taskIndex) - #endif // if FEATURE_PLUGIN_PRIORITY - ) { - addSubmitButton(F("Delete"), F("del")); - } - - html_end_table(); - #if FEATURE_PLUGIN_PRIORITY - if (Settings.isPriorityTask(taskIndex)) { - addFormNote(F("A Priority task can't be updated or deleted. See documentation.")); - } - #endif // if FEATURE_PLUGIN_PRIORITY - html_end_form(); - serve_JS(JSfiles_e::SplitPasteInput); -} - -void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - const DeviceStruct &device = Device[DeviceIndex]; - if (device.PullUpOption) - { - addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" - # if defined(ESP8266) - - if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || - (Settings.TaskDevicePin3[taskIndex] == 16)) { - addFormNote(F("PullDown for GPIO-16 (D0)")); - } - # endif // if defined(ESP8266) - } - - if (device.InverseLogicOption) - { - addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" - addFormNote(F("Will go into effect on next input change.")); - } - - if (device.isSPI() - && (Settings.InitSPI == static_cast(SPI_Options_e::None))) { - addFormNote(F("SPI Interface is not configured yet (Hardware page).")); - } - - if (device.connectedToGPIOpins()) { - // get descriptive GPIO-names from plugin - struct EventStruct TempEvent(taskIndex); - - TempEvent.String1 = F("1st GPIO"); - TempEvent.String2 = F("2nd GPIO"); - TempEvent.String3 = F("3rd GPIO"); - String dummy; - PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); - - if (device.usesTaskDevicePin(1)) { - PinSelectPurpose purpose = PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Pin1 = GPIO <--- TX - purpose = PinSelectPurpose::Serial_input; - } else if (device.isSPI()) - { - // All selectable SPI pins are output only - purpose = PinSelectPurpose::Generic_output; - } - - addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); - } - - if (device.usesTaskDevicePin(2)) { - PinSelectPurpose purpose = PinSelectPurpose::Generic; - - if (device.isSerial()) - { - // Serial Pin2 = GPIO ---> RX - purpose = PinSelectPurpose::Serial_output; - } - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } - addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); - } - - if (device.usesTaskDevicePin(3)) { - PinSelectPurpose purpose = PinSelectPurpose::Generic; - - if (device.isSPI()) - { - // SPI only needs output pins - purpose = PinSelectPurpose::Generic_output; - } - addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); - } - } -} - -#ifdef PLUGIN_USES_SERIAL -void devicePage_show_serial_config(taskIndex_t taskIndex) -{ - struct EventStruct TempEvent(taskIndex); - - serialHelper_webformLoad(&TempEvent); - String webformLoadString; - - PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); -} -#endif - -void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - - addFormSubHeader(F("I2C options")); - - if (!Settings.isI2CEnabled()) { - addFormNote(F("I2C Interface is not configured yet (Hardware page).")); - } - - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); - addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); - if (Device[DeviceIndex].I2CMax100kHz) { - addFormNote(F("This device is specified for max. 100 kHz operation!")); - } - - # if FEATURE_I2CMULTIPLEXER - - // Show selector for an I2C multiplexer port if a multiplexer is configured - if (isI2CMultiplexerEnabled()) { - bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); - { - const __FlashStringHelper *i2c_mux_channels[] = { - F("Single channel"), - F("Multiple channels")}; - constexpr int i2c_mux_channelOptions[] = { 0, 1}; - int i2c_mux_channelCount = 1; - - if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { - multipleMuxPorts = false; // force off - } else { - i2c_mux_channelCount++; - } - addFormSelector(F("Multiplexer channels"), - F("taskdeviceflags1"), - i2c_mux_channelCount, - i2c_mux_channels, - i2c_mux_channelOptions, - multipleMuxPorts ? 1 : 0, - true); - } - - if (multipleMuxPorts) { - addRowLabel(F("Select connections"), EMPTY_STRING); - html_table(EMPTY_STRING, false); // Sub-table - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - html_table_header(F("Channel"), 100); - html_table_header(F("Enable"), 80); - - for (int x = 0; x < I2CMultiplexerMaxChannels(); x++) { - if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels - html_TD(); - addHtml(concat(F("Channel "), x)); - html_TD(); - addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); - } - html_end_table(); - } else { - int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; - const uint32_t mux_max = I2CMultiplexerMaxChannels(); - String i2c_mux_portoptions[mux_max + 1]; - int i2c_mux_portchoices[mux_max + 1]; - i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); - i2c_mux_portchoices[0] = -1; - - for (uint32_t x = 0; x < mux_max; x++) { - const uint32_t mux_opt = x + 1; - i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); - i2c_mux_portchoices[mux_opt] = x; - } - - if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range - addFormSelector(F("Connected to"), - F("taskdevicei2cmuxport"), - mux_max + 1, - i2c_mux_portoptions, - i2c_mux_portchoices, - taskDeviceI2CMuxPort); - } - } - # endif // if FEATURE_I2CMULTIPLEXER -} - -void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - struct EventStruct TempEvent(taskIndex); - int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); - - switch (Device[DeviceIndex].OutputDataType) { - case Output_Data_type_t::Default: - return; - case Output_Data_type_t::Simple: - - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); - return; - } - break; - case Output_Data_type_t::All: - { - if (pconfigIndex >= 0) { - sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); - return; - } - break; - } - } - addFormSubHeader(F("Output Configuration")); - String dummy; - PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); -} - -#if FEATURE_PLUGIN_STATS -void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (Device[DeviceIndex].PluginStats) - { - PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); - - if (taskData != nullptr) { - if (taskData->hasPluginStats()) { - addFormSubHeader(F("Statistics")); - } - #if FEATURE_CHART_JS - if (taskData->nrSamplesPresent() > 0) { - addRowLabel(F("Historic data")); - taskData->plot_ChartJS(); - } - #endif // if FEATURE_CHART_JS - - struct EventStruct TempEvent(taskIndex); - String dummy; - bool somethingAdded = false; - - if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { - somethingAdded = taskData->webformLoad_show_stats(&TempEvent); - } else { somethingAdded = true; } - - if (somethingAdded) { - if (taskData->hasPeaks()) { - addFormNote(strformat( - F("Peak values recorded since last \"%s.resetpeaks\"."), - getTaskDeviceName(taskIndex).c_str())); - } - } - } - } -} -#endif // if FEATURE_PLUGIN_STATS - - - -void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) return; - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.SendDataOption) - { - addFormSubHeader(F("Data Acquisition")); - - if (device.ErrorStateValues) { - struct EventStruct TempEvent(taskIndex); - String dummy; - - PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options - } - - addRowLabel(F("Single event with all values")); - addCheckBox(F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); - addFormNote(strformat( - F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), - getTaskDeviceName(taskIndex).c_str())); - - bool separatorAdded = false; - for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) - { - if (Settings.Protocol[controllerNr] != 0) - { - if (!separatorAdded) { - addFormSeparator(2); - } - 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")))); - html_TD(); - - addHtml(F("")); // remove left padding 2x to align vertically with other inputs - html_TD(F("width:50px;padding-left:0")); - addCheckBox( - getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" - Settings.TaskDeviceSendData[controllerNr][taskIndex]); - - protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); - - if (validProtocolIndex(ProtocolIndex) && - getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) { - html_TD(); - addHtml(F("IDX:")); - html_TD(); - addNumericBox( - getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" - Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); - } - html_end_table(); - } - } - } -} - -void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) return; - - const DeviceStruct& device = Device[DeviceIndex]; - - if (device.TimerOption) - { - // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an unsigned long? addFormNumericBox only supports ints - // for min and max specification - addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" - addUnit(F("sec")); - - if (device.TimerOptional) { - addHtml(F(" (Optional for this Device)")); - } - } -} - -void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) -{ - if (!validDeviceIndex(DeviceIndex)) return; - // section: Values - const uint8_t valueCount = getValueCountForTask(taskIndex); - - const DeviceStruct& device = Device[DeviceIndex]; - - if (!device.Custom && (valueCount > 0)) - { - int colCount = 2; - addFormSubHeader(F("Values")); - html_end_table(); - html_table_class_normal(); - - // table header - addHtml(F("
#")); - html_table_header(F("Name"),500); - - if (device.FormulaOption) - { - html_table_header(F("Formula"), F("EasyFormula"), 500); - ++colCount; - } - - if (device.configurableDecimals()) - { - html_table_header(F("Decimals"), 30); - ++colCount; - } - -#if FEATURE_PLUGIN_STATS - if (device.PluginStats) - { - html_table_header(F("Stats"), 30); - ++colCount; - html_table_header(F("Hide"), 30); - ++colCount; - html_table_header(F("Axis"), 30); - ++colCount; - } -#endif - - //placeholder header - html_table_header(F("")); - ++colCount; - - // table body - for (uint8_t varNr = 0; varNr < valueCount; varNr++) - { - html_TR_TD(); - addHtmlInt(varNr + 1); - html_TD(); - { - const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" - addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.FormulaOption) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" - addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); - } - - if (device.configurableDecimals()) - { - html_TD(); - const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" - addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); - } - -#if FEATURE_PLUGIN_STATS - if (device.PluginStats) - { - PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" - cachedConfig.isEnabled()); - - html_TD(); - addCheckBox( - getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" - cachedConfig.showHidden()); - - 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; - } - - addSelector( - getPluginCustomArgName(F("TDSA"), varNr), - NR_ELEMENTS(chartAxis), - chartAxis, - nullptr, - nullptr, - selected); - } -#endif - } - addFormSeparator(colCount); - } -} - +#include "../WebServer/DevicesPage.h" + +#ifdef WEBSERVER_DEVICES + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" + +# include "../DataStructs/NodeStruct.h" +#if FEATURE_PLUGIN_STATS +#include "../DataStructs/PluginStats_Config.h" +#endif + + +# include "../Globals/CPlugins.h" +# include "../Globals/Device.h" +# include "../Globals/ExtraTaskSettings.h" +# include "../Globals/Nodes.h" +# include "../Globals/Plugins.h" + +# include "../Static/WebStaticData.h" + +# include "../Helpers/_CPlugin_init.h" +# include "../Helpers/_Plugin_init.h" +# include "../Helpers/_Plugin_SensorTypeHelper.h" +# include "../Helpers/_Plugin_Helper_serial.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/I2C_Plugin_Helper.h" +# include "../Helpers/StringConverter.h" +# include "../Helpers/StringGenerator_GPIO.h" + + + +# include "../../_Plugin_Helper.h" + +# include + + +void handle_devices() { + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (!isLoggedIn()) { return; } + navMenuIndex = MENU_INDEX_DEVICES; + TXBuffer.startStream(); + sendHeadandTail_stdtemplate(_HEAD); + + + // char tmpString[41]; + + + // String taskindex = webArg(F("index")); + + pluginID_t taskdevicenumber; + + if (hasArg(F("del"))) { + taskdevicenumber.setInvalid(); + } + else { + taskdevicenumber = pluginID_t::toPluginID(getFormItemInt(F("TDNUM"), 0)); + } + + + // String taskdeviceid[CONTROLLER_MAX]; + // String taskdevicepin1 = webArg(F("taskdevicepin1")); // "taskdevicepin*" should not be changed because it is uses by plugins + // and expected to be saved by this code + // String taskdevicepin2 = webArg(F("taskdevicepin2")); + // String taskdevicepin3 = webArg(F("taskdevicepin3")); + // String taskdevicepin1pullup = webArg(F("TDPPU")); + // String taskdevicepin1inversed = webArg(F("TDPI")); + // String taskdevicename = webArg(F("TDN")); + // String taskdeviceport = webArg(F("TDP")); + // String taskdeviceformula[VARS_PER_TASK]; + // String taskdevicevaluename[VARS_PER_TASK]; + // String taskdevicevaluedecimals[VARS_PER_TASK]; + // String taskdevicesenddata[CONTROLLER_MAX]; + // String taskdeviceglobalsync = webArg(F("TDGS")); + // String taskdeviceenabled = webArg(F("TDE")); + + // for (uint8_t varNr = 0; varNr < VARS_PER_TASK; varNr++) + // { + // char argc[25]; + // String arg = F("TDF"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceformula[varNr] = webArg(argc); + // + // arg = F("TDVN"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluename[varNr] = webArg(argc); + // + // arg = F("TDVD"); + // arg += varNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicevaluedecimals[varNr] = webArg(argc); + // } + + // for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + // { + // char argc[25]; + // String arg = F("TDID"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdeviceid[controllerNr] = webArg(argc); + // + // arg = F("TDSD"); + // arg += controllerNr + 1; + // arg.toCharArray(argc, 25); + // taskdevicesenddata[controllerNr] = webArg(argc); + // } + + uint8_t page = getFormItemInt(F("page"), 0); + + if (page == 0) { + page = 1; + } + uint8_t setpage = getFormItemInt(F("setpage"), 0); + + if (setpage > 0) + { + if (setpage <= (TASKS_MAX / TASKS_PER_PAGE)) { + page = setpage; + } + else { + page = TASKS_MAX / TASKS_PER_PAGE; + } + } + const int edit = getFormItemInt(F("edit"), 0); + + // taskIndex in the URL is 1 ... TASKS_MAX + // For use in other functions, set it to 0 ... (TASKS_MAX - 1) + taskIndex_t taskIndex = getFormItemInt(F("index"), 0); + boolean taskIndexNotSet = taskIndex == 0; + + const bool nosave = isFormItemChecked(F("nosave")); + + if (!taskIndexNotSet) { + --taskIndex; +// LoadTaskSettings(taskIndex); // Make sure ExtraTaskSettings are up-to-date + } + + // FIXME TD-er: Might have to clear any caches here. + if ((edit != 0) && !taskIndexNotSet) // when form submitted + { + if (Settings.getPluginID_for_task(taskIndex) != taskdevicenumber) + { + // change of device: cleanup old device and reset default settings + setTaskDevice_to_TaskIndex(taskdevicenumber, taskIndex); + const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); + + if (validDeviceIndex(DeviceIndex)) { + const DeviceStruct& device = Device[DeviceIndex]; + if ((device.Type == DEVICE_TYPE_I2C) && device.I2CMax100kHz) { // 100 kHz-only I2C device? + bitWrite(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED, 1); // Then: Enable Force Slow I2C speed checkbox by default + } + } + } + else if (taskdevicenumber != INVALID_PLUGIN_ID) // save settings + { + handle_devices_CopySubmittedSettings(taskIndex, taskdevicenumber); + } + + if (taskdevicenumber != INVALID_PLUGIN_ID) { + // Task index has a task device number, so it makes sense to save. + // N.B. When calling delete, the settings were already saved. + if (nosave) { + Cache.updateExtraTaskSettingsCache(); + } else { + addHtmlError(SaveTaskSettings(taskIndex)); + addHtmlError(SaveSettings()); + } + + struct EventStruct TempEvent(taskIndex); + String dummy; + + if (Settings.TaskDeviceEnabled[taskIndex]) { + if (PluginCall(PLUGIN_INIT, &TempEvent, dummy)) { + PluginCall(PLUGIN_READ, &TempEvent, dummy); + } + } else { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + } + } + + // show all tasks as table + if (taskIndexNotSet) + { + handle_devicess_ShowAllTasksTable(page); + } + + // Show edit form if a specific entry is chosen with the edit button + else + { + handle_devices_TaskSettingsPage(taskIndex, page); + } + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_devices")); + # endif // ifndef BUILD_NO_RAM_TRACKER +# ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG_DEV)) { + addLogMove(LOG_LEVEL_DEBUG_DEV, concat(F("DEBUG: String size:"), static_cast(TXBuffer.sentBytes))); + } +# endif // ifndef BUILD_NO_DEBUG + sendHeadandTail_stdtemplate(_TAIL); + TXBuffer.endStream(); +} + +// ******************************************************************************** +// Add a device select dropdown list +// TODO TD-er: Add JavaScript filter: +// https://www.w3schools.com/howto/howto_js_filter_dropdown.asp +// ******************************************************************************** +void addDeviceSelect(const __FlashStringHelper *name, pluginID_t choice) +{ + String deviceName; + + addSelector_Head_reloadOnChange(name); + addSelector_Item(F("- None -"), 0, false); + + deviceIndex_t x; + bool done = false; + while (!done) { + const deviceIndex_t deviceIndex = getDeviceIndex_sorted(x); + if (!validDeviceIndex(deviceIndex)) { + done = true; + } else { + const pluginID_t pluginID = getPluginID_from_DeviceIndex(deviceIndex); + + if (validPluginID(pluginID)) { + deviceName = getPluginNameFromDeviceIndex(deviceIndex); + + + # if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) + deviceName = concat(get_formatted_Plugin_number(pluginID), F(" - ")) + deviceName; + # endif // if defined(PLUGIN_BUILD_DEV) || defined(PLUGIN_SET_MAX) + + addSelector_Item(deviceName, + pluginID.value, + choice == pluginID); + } + } + ++x; + } + + addSelector_Foot(); +} + +// ******************************************************************************** +// Collect all submitted form data and store the task settings +// ******************************************************************************** +void handle_devices_CopySubmittedSettings(taskIndex_t taskIndex, pluginID_t taskdevicenumber) +{ + if (!validTaskIndex(taskIndex)) { return; } + const deviceIndex_t DeviceIndex = getDeviceIndex(taskdevicenumber); + + if (!validDeviceIndex(DeviceIndex)) { return; } + + const DeviceStruct& device = Device[DeviceIndex]; + + unsigned long taskdevicetimer = getFormItemInt(F("TDT"), 0); + + Settings.TaskDeviceNumber[taskIndex] = taskdevicenumber.value; + + if (device.Type == DEVICE_TYPE_I2C) { + uint8_t flags = 0; + bitWrite(flags, I2C_FLAGS_SLOW_SPEED, isFormItemChecked(F("taskdeviceflags0"))); + +# if FEATURE_I2CMULTIPLEXER + + if (isI2CMultiplexerEnabled()) { + int multipleMuxPortsOption = getFormItemInt(F("taskdeviceflags1"), 0); + bitWrite(flags, I2C_FLAGS_MUX_MULTICHANNEL, multipleMuxPortsOption == 1); + + if (multipleMuxPortsOption == 1) { + uint8_t selectedPorts = 0; + + for (int x = 0; x < I2CMultiplexerMaxChannels(); ++x) { + bitWrite(selectedPorts, x, isFormItemChecked(concat(F("taskdeviceflag1ch"), x))); + } + Settings.I2C_Multiplexer_Channel[taskIndex] = selectedPorts; + } else { + Settings.I2C_Multiplexer_Channel[taskIndex] = getFormItemInt(F("taskdevicei2cmuxport"), 0); + } + } + +# endif // if FEATURE_I2CMULTIPLEXER + + Settings.I2C_Flags[taskIndex] = flags; + } + + // Must load from file system to make sure all caches and checksums match. + ExtraTaskSettings.clear(); + ExtraTaskSettings.TaskIndex = taskIndex; + Cache.clearTaskCache(taskIndex); + + struct EventStruct TempEvent(taskIndex); + + // Save selected output type. + switch (device.OutputDataType) { + case Output_Data_type_t::Default: + { + String dummy; + PluginCall(PLUGIN_GET_DEVICEVALUENAMES, &TempEvent, dummy); + break; + } + case Output_Data_type_t::Simple: + case Output_Data_type_t::All: + { + int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); + Sensor_VType VType = TempEvent.sensorType; + + if ((pconfigIndex >= 0) && (pconfigIndex < PLUGIN_CONFIGVAR_MAX)) { + VType = static_cast(getFormItemInt(sensorTypeHelper_webformID(pconfigIndex), 0)); + Settings.TaskDevicePluginConfig[taskIndex][pconfigIndex] = static_cast(VType); + } + ExtraTaskSettings.clearUnusedValueNames(getValueCountFromSensorType(VType)); + break; + } + } + + { + int pins[] = {-1, -1, -1}; + for (int i = 0; i < 3; ++i) { + update_whenset_FormItemInt(concat(F("taskdevicepin"), i + 1), pins[i]); + } + + const bool taskEnabled = isFormItemChecked(F("TDE")); + setBasicTaskValues(taskIndex, taskdevicetimer, + taskEnabled, webArg(F("TDN")), + pins); + } + + #if FEATURE_PLUGIN_PRIORITY + if (device.PowerManager // Check extra priority device flags when available + ) { + bool disablePrio = false; + for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { + if (t != taskIndex) { + disablePrio = Settings.isPriorityTask(t); + } + } + bool statePriority = isFormItemChecked(F("TPRE")); + if (device.PowerManager) { + Settings.setPowerManagerTask(taskIndex, statePriority); + } + // Set alternative Priority flags + // Set to readonly if set as Priority task + Settings.setTaskEnableReadonly(taskIndex, statePriority); + } + #endif // if FEATURE_PLUGIN_PRIORITY + Settings.TaskDevicePort[taskIndex] = getFormItemInt(F("TDP"), 0); + update_whenset_FormItemInt(F("remoteFeed"), Settings.TaskDeviceDataFeed[taskIndex]); + Settings.CombineTaskValues_SingleEvent(taskIndex, isFormItemChecked(F("TVSE"))); + + 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 (device.PullUpOption) { + Settings.TaskDevicePin1PullUp[taskIndex] = isFormItemChecked(F("TDPPU")); + } + + if (device.InverseLogicOption) { + Settings.TaskDevicePin1Inversed[taskIndex] = isFormItemChecked(F("TDPI")); + } + + if (device.isSerial()) + { + # ifdef PLUGIN_USES_SERIAL + serialHelper_webformSave(&TempEvent); + # else // ifdef PLUGIN_USES_SERIAL + addLog(LOG_LEVEL_ERROR, F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + } + + const uint8_t valueCount = getValueCountForTask(taskIndex); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceFormula[varNr], getPluginCustomArgName(F("TDF"), varNr)); + update_whenset_FormItemInt(getPluginCustomArgName(F("TDVD"), varNr), ExtraTaskSettings.TaskDeviceValueDecimals[varNr]); + strncpy_webserver_arg(ExtraTaskSettings.TaskDeviceValueNames[varNr], getPluginCustomArgName(F("TDVN"), varNr)); +#if FEATURE_PLUGIN_FILTER + ExtraTaskSettings.enablePluginFilter(varNr, isFormItemChecked(getPluginCustomArgName(F("TDFIL"), varNr))); +#endif +#if FEATURE_PLUGIN_STATS + PluginStats_Config_t pluginStats_Config; + pluginStats_Config.setEnabled(isFormItemChecked(getPluginCustomArgName(F("TDS"), varNr))); + pluginStats_Config.setHidden(isFormItemChecked(getPluginCustomArgName(F("TDSH"), varNr))); + const int selectedAxis = getFormItemInt(getPluginCustomArgName(F("TDSA"), varNr)); + pluginStats_Config.setAxisIndex(selectedAxis); + pluginStats_Config.setAxisPosition( + ((selectedAxis >> 2) == 0) + ? PluginStats_Config_t::AxisPosition::Left + : PluginStats_Config_t::AxisPosition::Right); + + ExtraTaskSettings.setPluginStatsConfig(varNr, pluginStats_Config); +#endif + } + ExtraTaskSettings.clearUnusedValueNames(valueCount); + + // ExtraTaskSettings has changed. + // The content of it is needed for sending CPLUGIN_TASK_CHANGE_NOTIFICATION and TaskInit/TaskExit events + Cache.updateExtraTaskSettingsCache(); + + // allow the plugin to save plugin-specific form settings. + { + String dummy; + if (device.ExitTaskBeforeSave) { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + + PluginCall(PLUGIN_WEBFORM_SAVE, &TempEvent, dummy); + + if (device.ErrorStateValues) { + // FIXME TD-er: Must collect these from the web page. + PluginCall(DeviceIndex, PLUGIN_INIT_VALUE_RANGES, &TempEvent, dummy); + } + + // Make sure the task needs to reload using the new settings. + if (!device.ExitTaskBeforeSave) { + PluginCall(PLUGIN_EXIT, &TempEvent, dummy); + } + } + + // Store all PCONFIG values on the web page + // Must be done after PLUGIN_WEBFORM_SAVE, to allow tasks to clear the default task value names + // Output type selectors are typically stored in PCONFIG + if (device.OutputDataType != Output_Data_type_t::Default) { + for (int pconfigIndex = 0; pconfigIndex < PLUGIN_CONFIGVAR_MAX; ++pconfigIndex) { + pconfig_webformSave(&TempEvent, pconfigIndex); + } + } + // ExtraTaskSettings may have changed during PLUGIN_WEBFORM_SAVE, so again update the cache. + Cache.updateExtraTaskSettingsCache(); + + loadDefaultTaskValueNames_ifEmpty(taskIndex); + Cache.updateExtraTaskSettingsCache(); + + // notify controllers: CPlugin::Function::CPLUGIN_TASK_CHANGE_NOTIFICATION + for (controllerIndex_t x = 0; x < CONTROLLER_MAX; x++) + { + TempEvent.ControllerIndex = x; + + 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); + } + } + // FIXME TD-er: Is this still needed as it is also cleared on PLUGIN_INIT and PLUGIN_EXIT? + UserVar.clear_computed(taskIndex); +} + + +void html_add_setPage(uint8_t page, bool isLinkToPrev) { + addHtml(strformat( + F("devices?setpage=%u'>&%ct;"), + static_cast(page), + isLinkToPrev ? 'l' : 'g')); +} + +// ******************************************************************************** +// Show table with all selected Tasks/Devices +// ******************************************************************************** +void handle_devicess_ShowAllTasksTable(uint8_t page) +{ + serve_JS(JSfiles_e::UpdateSensorValuesDevicePage); + html_table_class_multirow(); + html_TR(); + html_table_header(F(""), 70); + + if (TASKS_MAX != TASKS_PER_PAGE) + { + html_add_button_prefix(); + + html_add_setPage((page > 1) ? page - 1 : page, true); + html_add_button_prefix(); + html_add_setPage((page < (TASKS_MAX / TASKS_PER_PAGE)) ? page + 1 : page, false); + } + + html_table_header(F("Task"), 50); + html_table_header(F("Enabled"), 100); + html_table_header(F("Device")); + html_table_header(F("Name")); + html_table_header(F("Port")); + html_table_header(F("Ctr (IDX)"), 100); + html_table_header(F("GPIO")); + html_table_header(F("Values")); + + String deviceName; + + for (taskIndex_t x = (page - 1) * TASKS_PER_PAGE; x < ((page) * TASKS_PER_PAGE) && validTaskIndex(x); x++) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); + + html_TR_TD(); + + if (pluginID_set && !supportedPluginID(Settings.getPluginID_for_task(x))) { + html_add_button_prefix(F("red"), true); + } else { + html_add_button_prefix(); + } + { + const int pageIndex = static_cast(x + 1); + addHtml(strformat( + F("devices?index=%d&page=%u'>"), + pageIndex, + static_cast(page))); + addHtml(pluginID_set ? F("Edit") : F("Add")); + addHtml(concat(F(""), pageIndex)); + html_TD(); + } + + // Show table of all configured tasks + // A task may also refer to a non supported plugin. + // This will be shown as not supported. + // Editing a task which has a non supported plugin will present the same as when assigning a new plugin to a task. + if (pluginID_set) + { + //LoadTaskSettings(x); + int8_t spi_gpios[3] { -1, -1, -1 }; + struct EventStruct TempEvent(x); + addEnabled(Settings.TaskDeviceEnabled[x] && validDeviceIndex(DeviceIndex)); + + html_TD(); + addHtml(getPluginNameFromPluginID(Settings.getPluginID_for_task(x))); + html_TD(); + addHtml(getTaskDeviceName(x)); + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + if (Settings.TaskDeviceDataFeed[x] != 0) { + #if FEATURE_ESPEASY_P2P + // Show originating node number + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[x]; + format_originating_node(remoteUnit); + #endif + } else { + String portDescr; + + if (PluginCall(PLUGIN_WEBFORM_SHOW_CONFIG, &TempEvent, portDescr)) { + addHtml(portDescr); + } else { + const DeviceStruct& device = Device[DeviceIndex]; + if (device.Type == DEVICE_TYPE_I2C) { + format_I2C_port_description(x); + } else if (device.isSPI()) { + format_SPI_port_description(spi_gpios); + } else if (device.isSerial()) { + # ifdef PLUGIN_USES_SERIAL + addHtml(serialHelper_getSerialTypeLabel(&TempEvent)); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + } else { + // Plugin has no custom port formatting, show default one. + if (device.Ports != 0) + { + addHtml(formatToHex_decimal(Settings.TaskDevicePort[x])); + } + } + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + if (Device[DeviceIndex].SendDataOption) + { + boolean doBR = false; + + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + { + if (Settings.TaskDeviceSendData[controllerNr][x]) + { + if (doBR) { + html_BR(); + } + addHtml(getControllerSymbol(controllerNr)); + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + + if (validProtocolIndex(ProtocolIndex)) { + if (getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) + { + addHtml(strformat( + F(" (%d)"), + static_cast(Settings.TaskDeviceID[controllerNr][x]))); + + if (Settings.TaskDeviceID[controllerNr][x] == 0) { + addHtml(' '); + addHtml(F(HTML_SYMBOL_WARNING)); + } + } + doBR = true; + } + } + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + const DeviceStruct& device = Device[DeviceIndex]; + if (Settings.TaskDeviceDataFeed[x] == 0) + { + String description; + bool pluginHasGPIODescription = pluginWebformShowGPIOdescription(x, F("
"), description); + + bool showpin1 = false; + bool showpin2 = false; + bool showpin3 = false; + + switch (device.Type) { + case DEVICE_TYPE_I2C: + { + format_I2C_pin_description(x); + html_BR(); + break; + } + case DEVICE_TYPE_SPI3: + showpin3 = !pluginHasGPIODescription; + + // Fall Through + case DEVICE_TYPE_SPI2: + showpin2 = !pluginHasGPIODescription; + + // Fall Through + case DEVICE_TYPE_SPI: + format_SPI_pin_description(spi_gpios, x, !pluginHasGPIODescription); + break; + case DEVICE_TYPE_ANALOG: + { + # ifdef ESP8266 + # if FEATURE_ADC_VCC + addHtml(F("ADC (VCC)")); + # else // if FEATURE_ADC_VCC + addHtml(F("ADC (TOUT)")); + # endif // if FEATURE_ADC_VCC + # endif // ifdef ESP8266 + # ifdef ESP32 + showpin1 = true; + addHtml(formatGpioName_ADC(Settings.TaskDevicePin1[x])); + html_BR(); + # endif // ifdef ESP32 + + break; + } + case DEVICE_TYPE_SERIAL_PLUS1: + showpin3 = true; + + // fallthrough + case DEVICE_TYPE_SERIAL: + { + # ifdef PLUGIN_USES_SERIAL + const String serialDescription = serialHelper_getGpioDescription(static_cast(Settings.TaskDevicePort[x]), Settings.TaskDevicePin1[x], + Settings.TaskDevicePin2[x], F("
")); + addHtml(serialDescription); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + + if ( +#ifdef PLUGIN_USES_SERIAL + serialDescription.length() || +#endif + showpin3) { + html_BR(); + } + break; + } + case DEVICE_TYPE_CUSTOM3: + showpin3 = true; + + // fallthrough + case DEVICE_TYPE_CUSTOM2: + showpin2 = true; + + // fallthrough + case DEVICE_TYPE_CUSTOM1: + case DEVICE_TYPE_CUSTOM0: + { + showpin1 = true; + if (pluginHasGPIODescription || (device.Type == DEVICE_TYPE_CUSTOM0)) { + addHtml(description); + showpin1 = false; + showpin2 = false; + showpin3 = false; + } + break; + } + + default: + showpin1 = true; + showpin2 = true; + showpin3 = true; + break; + } + + if (showpin1) + { + addGpioHtml(Settings.getTaskDevicePin(x, 1)); + } + + if (showpin2) + { + html_BR(); + addGpioHtml(Settings.getTaskDevicePin(x, 2)); + } + + if (showpin3) + { + html_BR(); + addGpioHtml(Settings.getTaskDevicePin(x, 3)); + } + + // Allow for tasks to show their own specific GPIO pins. + if (!device.isCustom() && + pluginHasGPIODescription) { + if (showpin1 || showpin2 || showpin3) { + html_BR(); + } + addHtml(description); + } + } + } + + html_TD(); + + if (validDeviceIndex(DeviceIndex)) { + String customValuesString; + const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); + + if (!customValues) + { + const uint8_t valueCount = getValueCountForTask(x); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) + { + pluginWebformShowValue( + x, + varNr, + Cache.getTaskDeviceValueName(x, varNr), + formatUserVarNoCheck(&TempEvent, varNr)); + } + } + } + } + } + else { + html_TD(6); + } + } // next + html_end_table(); + html_end_form(); +} + +#if FEATURE_ESPEASY_P2P +void format_originating_node(uint8_t remoteUnit) { + addHtml(F("Unit ")); + addHtmlInt(remoteUnit); + + if (remoteUnit != 255) { + const NodeStruct *node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addHtml(F(" - ")); + addHtml(node->getNodeName()); + } else { + addHtml(F(" - Not Seen recently")); + } + } +} +#endif + +void format_I2C_port_description(taskIndex_t x) +{ + addHtml(F("I2C")); + # if FEATURE_I2C_GET_ADDRESS + const uint8_t i2cAddr = getTaskI2CAddress(x); + if (i2cAddr > 0) { + addHtml(' '); + addHtml(formatToHex(i2cAddr, 2)); + } + # endif // if FEATURE_I2C_GET_ADDRESS + # if FEATURE_I2CMULTIPLEXER + + if (isI2CMultiplexerEnabled() && I2CMultiplexerPortSelectedForTask(x)) { + String mux; + + if (bitRead(Settings.I2C_Flags[x], I2C_FLAGS_MUX_MULTICHANNEL)) { // Multi-channel + mux = F("
Multiplexer channel(s)"); + uint8_t b = 0; // For adding lineBreaks + + for (uint8_t c = 0; c < I2CMultiplexerMaxChannels(); c++) { + if (bitRead(Settings.I2C_Multiplexer_Channel[x], c)) { + mux += b == 0 ? F("
") : F(", "); + b++; + mux += String(c); + } + } + } else { // Single channel + mux = concat(F("
Multiplexer channel "), static_cast(Settings.I2C_Multiplexer_Channel[x])); + } + addHtml(mux); + } + # endif // if FEATURE_I2CMULTIPLEXER +} + +void format_SPI_port_description(int8_t spi_gpios[3]) +{ + if (!Settings.getSPI_pins(spi_gpios)) { + addHtml(F("SPI (Not enabled)")); + return; + } + # ifdef ESP32 + addHtml(getSPI_optionToShortString(static_cast(Settings.InitSPI))); + # endif // ifdef ESP32 + # ifdef ESP8266 + addHtml(F("SPI")); + # endif // ifdef ESP8266 +} + +void format_I2C_pin_description(taskIndex_t x) +{ + if (checkI2CConfigValid_toHtml(x)) { + Label_Gpio_toHtml(F("SDA"), formatGpioLabel(Settings.Pin_i2c_sda, false)); + html_BR(); + Label_Gpio_toHtml(F("SCL"), formatGpioLabel(Settings.Pin_i2c_scl, false)); + } +} + +void format_SPI_pin_description(int8_t spi_gpios[3], taskIndex_t x, bool showCSpin) +{ + 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) { + if (i != 0) + html_BR(); + + Label_Gpio_toHtml(labels[i], formatGpioLabel(spi_gpios[i], false)); + } + if (showCSpin) { + html_BR(); + Label_Gpio_toHtml(F("CS"), formatGpioLabel(Settings.TaskDevicePin1[x], false)); + } + } +} + +// ******************************************************************************** +// Show the task settings page +// ******************************************************************************** +void handle_devices_TaskSettingsPage(taskIndex_t taskIndex, uint8_t page) +{ + if (!validTaskIndex(taskIndex)) { return; } + + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskIndex); + + //LoadTaskSettings(taskIndex); + + html_add_form(); + html_table_class_normal(); + addFormHeader(F("Task Settings")); + + + addHtml(F("
Device:")); + + // no (supported) device selected, this effectively checks for validDeviceIndex + if (!supportedPluginID(Settings.getPluginID_for_task(taskIndex))) + { + // takes lots of memory/time so call this only when needed. + addDeviceSelect(F("TDNUM"), Settings.getPluginID_for_task(taskIndex)); // ="taskdevicenumber" + addFormSeparator(4); + } + + // device selected + else + { + const DeviceStruct& device = Device[DeviceIndex]; + // remember selected device number + addHtml(F("'); + + // show selected device name and delete button + addHtml(getPluginNameFromDeviceIndex(DeviceIndex)); + + addHelpButton(concat(F("Plugin"), Settings.getPluginID_for_task(taskIndex).value)); + addRTDPluginButton(Settings.getPluginID_for_task(taskIndex)); + + addFormTextBox(F("Name"), F("TDN"), getTaskDeviceName(taskIndex), NAME_FORMULA_LENGTH_MAX); // ="taskdevicename" + + addFormCheckBox(F("Enabled"), F("TDE"), + Settings.TaskDeviceEnabled[taskIndex], +// Settings.TaskDeviceEnabled[taskIndex].enabled, + Settings.isTaskEnableReadonly(taskIndex)); // ="taskdeviceenabled" + + #if FEATURE_PLUGIN_PRIORITY + if (device.PowerManager) { // Check extra priority device flags when available + bool disablePrio = !Settings.TaskDeviceEnabled[taskIndex]; + for (taskIndex_t t = 0; t < TASKS_MAX && !disablePrio; t++) { + if (t != taskIndex) { // Ignore current device + if (device.PowerManager && Settings.isPowerManagerTask(t)) { + disablePrio = true; // Allow only a single PowerManager plugin + } + // Add other Priority options checks + } + } + addFormSubHeader(F("Priority task")); + addFormCheckBox(F("Priority task"), F("TPRE"), Settings.isPriorityTask(taskIndex), disablePrio); // ="taskpriorityenabled" + if (!disablePrio) { + addFormNote(F("After enabling a Priority task, a reboot is required to activate. See documentation.")); + } + } + #endif // if FEATURE_PLUGIN_PRIORITY + + const uint8_t remoteUnit = Settings.TaskDeviceDataFeed[taskIndex]; + #if FEATURE_ESPEASY_P2P + if (device.SendDataOption) + { + // Show remote feed information. + addFormSubHeader(F("Data Source")); + addFormNumericBox(F("Remote Unit"), F("remoteFeed"), remoteUnit, 0, 255); + + if (remoteUnit != 255) { + const NodeStruct* node = Nodes.getNode(remoteUnit); + + if (node != nullptr) { + addUnit(node->getNodeName()); + } else { + addUnit(F("Unknown Unit Name")); + } + } + addFormNote(F("0 = disable remote feed, 255 = broadcast")); // FIXME TD-er: Must verify if broadcast can be set. + } + #endif + + bool addPinConfig = false; + + // section: Sensor / Actuator + if (!device.Custom && (Settings.TaskDeviceDataFeed[taskIndex] == 0) && + ((device.Ports != 0) || + (device.PullUpOption) || + (device.InverseLogicOption) || + (device.connectedToGPIOpins()))) + { + addFormSubHeader((device.SendDataOption) ? F("Sensor") : F("Actuator")); + + if (device.Ports != 0) { + addFormNumericBox(F("Port"), F("TDP"), Settings.TaskDevicePort[taskIndex]); // ="taskdeviceport" + } + + addPinConfig = true; + } + + if (addPinConfig || (device.Type == DEVICE_TYPE_I2C)) { + if (device.isSerial()) { + # ifdef PLUGIN_USES_SERIAL + devicePage_show_serial_config(taskIndex); + # else // ifdef PLUGIN_USES_SERIAL + addHtml(F("PLUGIN_USES_SERIAL not defined")); + # endif // ifdef PLUGIN_USES_SERIAL + + devicePage_show_pin_config(taskIndex, DeviceIndex); + addPinConfig = false; + + html_add_script(F("document.getElementById('serPort').onchange();"), false); + } else if (device.Type == DEVICE_TYPE_I2C) { + devicePage_show_pin_config(taskIndex, DeviceIndex); + addPinConfig = false; + + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { + devicePage_show_I2C_config(taskIndex, DeviceIndex); + } + } + + if (addPinConfig) { + devicePage_show_pin_config(taskIndex, DeviceIndex); + } + } + + String webformLoadString; + struct EventStruct TempEvent(taskIndex); + + + // add plugins content + if (DEVICE_TYPE_DUMMY != device.Type && remoteUnit == 0) { + addFormSubHeader(F("Device Settings")); + } + if (Settings.TaskDeviceDataFeed[taskIndex] == 0) { // only show additional config for local connected sensors + PluginCall(PLUGIN_WEBFORM_LOAD, &TempEvent, webformLoadString); + #ifndef BUILD_NO_DEBUG + if (webformLoadString.length() > 0) { + String errorMessage; + PluginCall(PLUGIN_GET_DEVICENAME, &TempEvent, errorMessage); + errorMessage += F(": Bug in PLUGIN_WEBFORM_LOAD, should not append to string, use addHtml() instead"); + addHtmlError(errorMessage); + } + #endif + } + PluginCall(PLUGIN_WEBFORM_LOAD_ALWAYS, &TempEvent, webformLoadString); // Load settings also useful for remote-datafeed devices + + devicePage_show_output_data_type(taskIndex, DeviceIndex); + + #if FEATURE_PLUGIN_STATS + // Task statistics and historic data in a chart + devicePage_show_task_statistics(taskIndex, DeviceIndex); + #endif // if FEATURE_PLUGIN_STATS + + // section: Data Acquisition + devicePage_show_controller_config(taskIndex, DeviceIndex); + + addFormSeparator(2); + + devicePage_show_interval_config(taskIndex, DeviceIndex); + + devicePage_show_task_values(taskIndex, DeviceIndex); + } + + html_TR_TD(); + addHtml(F("")); + html_add_button_prefix(); + addHtml(F("devices?setpage=")); + addHtmlInt(page); + addHtml(F("'>Close")); + #if FEATURE_PLUGIN_PRIORITY + if (!Settings.isPriorityTask(taskIndex)) + #endif // if FEATURE_PLUGIN_PRIORITY + { + addSubmitButton(); + } + addHtml(F("")); + addHtml(F("")); + + // if user selected a device, add the delete button, except for Priority tasks + if (validPluginID_fullcheck(Settings.getPluginID_for_task(taskIndex)) + #if FEATURE_PLUGIN_PRIORITY + && !Settings.isPriorityTask(taskIndex) + #endif // if FEATURE_PLUGIN_PRIORITY + ) { + addSubmitButton(F("Delete"), F("del")); + } + + html_end_table(); + #if FEATURE_PLUGIN_PRIORITY + if (Settings.isPriorityTask(taskIndex)) { + addFormNote(F("A Priority task can't be updated or deleted. See documentation.")); + } + #endif // if FEATURE_PLUGIN_PRIORITY + html_end_form(); + serve_JS(JSfiles_e::SplitPasteInput); +} + +void devicePage_show_pin_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + const DeviceStruct &device = Device[DeviceIndex]; + if (device.PullUpOption) + { + addFormCheckBox(F("Internal PullUp"), F("TDPPU"), Settings.TaskDevicePin1PullUp[taskIndex]); // ="taskdevicepin1pullup" + addFormNote(F("Best to (also) configure pull-up on Hardware tab under \"GPIO boot states\"")); + # if defined(ESP8266) + + if ((Settings.TaskDevicePin1[taskIndex] == 16) || (Settings.TaskDevicePin2[taskIndex] == 16) || + (Settings.TaskDevicePin3[taskIndex] == 16)) { + addFormNote(F("PullDown for GPIO-16 (D0)")); + } + # endif // if defined(ESP8266) + } + + if (device.InverseLogicOption) + { + addFormCheckBox(F("Inversed Logic"), F("TDPI"), Settings.TaskDevicePin1Inversed[taskIndex]); // ="taskdevicepin1inversed" + addFormNote(F("Will go into effect on next input change.")); + } + + if (device.isSPI() + && (Settings.InitSPI == static_cast(SPI_Options_e::None))) { + addFormNote(F("SPI Interface is not configured yet (Hardware page).")); + } + + if (device.connectedToGPIOpins()) { + // get descriptive GPIO-names from plugin + struct EventStruct TempEvent(taskIndex); + + TempEvent.String1 = F("1st GPIO"); + TempEvent.String2 = F("2nd GPIO"); + TempEvent.String3 = F("3rd GPIO"); + String dummy; + PluginCall(PLUGIN_GET_DEVICEGPIONAMES, &TempEvent, dummy); + + if (device.usesTaskDevicePin(1)) { + PinSelectPurpose purpose = PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Pin1 = GPIO <--- TX + purpose = PinSelectPurpose::Serial_input; + } else if (device.isSPI()) + { + // All selectable SPI pins are output only + purpose = PinSelectPurpose::Generic_output; + } + + addFormPinSelect(purpose, TempEvent.String1, F("taskdevicepin1"), Settings.TaskDevicePin1[taskIndex]); + } + + if (device.usesTaskDevicePin(2)) { + PinSelectPurpose purpose = PinSelectPurpose::Generic; + + if (device.isSerial()) + { + // Serial Pin2 = GPIO ---> RX + purpose = PinSelectPurpose::Serial_output; + } + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } + addFormPinSelect(purpose, TempEvent.String2, F("taskdevicepin2"), Settings.TaskDevicePin2[taskIndex]); + } + + if (device.usesTaskDevicePin(3)) { + PinSelectPurpose purpose = PinSelectPurpose::Generic; + + if (device.isSPI()) + { + // SPI only needs output pins + purpose = PinSelectPurpose::Generic_output; + } + addFormPinSelect(purpose, TempEvent.String3, F("taskdevicepin3"), Settings.TaskDevicePin3[taskIndex]); + } + } +} + +#ifdef PLUGIN_USES_SERIAL +void devicePage_show_serial_config(taskIndex_t taskIndex) +{ + struct EventStruct TempEvent(taskIndex); + + String webformLoadString; + + PluginCall(PLUGIN_WEBFORM_PRE_SERIAL_PARAMS, &TempEvent, webformLoadString); + + serialHelper_webformLoad(&TempEvent); + + PluginCall(PLUGIN_WEBFORM_SHOW_SERIAL_PARAMS, &TempEvent, webformLoadString); +} +#endif + +void devicePage_show_I2C_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + + addFormSubHeader(F("I2C options")); + + if (!Settings.isI2CEnabled()) { + addFormNote(F("I2C Interface is not configured yet (Hardware page).")); + } + + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_I2C_PARAMS, &TempEvent, dummy); + addFormCheckBox(F("Force Slow I2C speed"), F("taskdeviceflags0"), bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_SLOW_SPEED)); + if (Device[DeviceIndex].I2CMax100kHz) { + addFormNote(F("This device is specified for max. 100 kHz operation!")); + } + + # if FEATURE_I2CMULTIPLEXER + + // Show selector for an I2C multiplexer port if a multiplexer is configured + if (isI2CMultiplexerEnabled()) { + bool multipleMuxPorts = bitRead(Settings.I2C_Flags[taskIndex], I2C_FLAGS_MUX_MULTICHANNEL); + { + const __FlashStringHelper *i2c_mux_channels[] = { + F("Single channel"), + F("Multiple channels")}; + constexpr int i2c_mux_channelOptions[] = { 0, 1}; + int i2c_mux_channelCount = 1; + + if (Settings.I2C_Multiplexer_Type == I2C_MULTIPLEXER_PCA9540) { + multipleMuxPorts = false; // force off + } else { + i2c_mux_channelCount++; + } + addFormSelector(F("Multiplexer channels"), + F("taskdeviceflags1"), + i2c_mux_channelCount, + i2c_mux_channels, + i2c_mux_channelOptions, + multipleMuxPorts ? 1 : 0, + true); + } + + if (multipleMuxPorts) { + addRowLabel(F("Select connections"), EMPTY_STRING); + html_table(EMPTY_STRING, false); // Sub-table + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + html_table_header(F("Channel"), 100); + html_table_header(F("Enable"), 80); + + for (int x = 0; x < I2CMultiplexerMaxChannels(); x++) { + if (x % 2 == 0) { html_TR(); } // Start a new row for every 2 channels + html_TD(); + addHtml(concat(F("Channel "), x)); + html_TD(); + addCheckBox(concat(F("taskdeviceflag1ch"), x), bitRead(Settings.I2C_Multiplexer_Channel[taskIndex], x), false); + } + html_end_table(); + } else { + int taskDeviceI2CMuxPort = Settings.I2C_Multiplexer_Channel[taskIndex]; + const uint32_t mux_max = I2CMultiplexerMaxChannels(); + String i2c_mux_portoptions[mux_max + 1]; + int i2c_mux_portchoices[mux_max + 1]; + i2c_mux_portoptions[0] = F("(Not connected via multiplexer)"); + i2c_mux_portchoices[0] = -1; + + for (uint32_t x = 0; x < mux_max; x++) { + const uint32_t mux_opt = x + 1; + i2c_mux_portoptions[mux_opt] = concat(F("Channel "), x); + i2c_mux_portchoices[mux_opt] = x; + } + + if (taskDeviceI2CMuxPort >= static_cast(mux_max)) { taskDeviceI2CMuxPort = -1; } // Reset if out of range + addFormSelector(F("Connected to"), + F("taskdevicei2cmuxport"), + mux_max + 1, + i2c_mux_portoptions, + i2c_mux_portchoices, + taskDeviceI2CMuxPort); + } + } + # endif // if FEATURE_I2CMULTIPLEXER +} + +void devicePage_show_output_data_type(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + struct EventStruct TempEvent(taskIndex); + int pconfigIndex = checkDeviceVTypeForTask(&TempEvent); + + switch (Device[DeviceIndex].OutputDataType) { + case Output_Data_type_t::Default: + return; + case Output_Data_type_t::Simple: + + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_simple(&TempEvent, pconfigIndex); + return; + } + break; + case Output_Data_type_t::All: + { + if (pconfigIndex >= 0) { + sensorTypeHelper_webformLoad_allTypes(&TempEvent, pconfigIndex); + return; + } + break; + } + } + addFormSubHeader(F("Output Configuration")); + String dummy; + PluginCall(PLUGIN_WEBFORM_LOAD_OUTPUT_SELECTOR, &TempEvent, dummy); +} + +#if FEATURE_PLUGIN_STATS +void devicePage_show_task_statistics(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (Device[DeviceIndex].PluginStats) + { + PluginTaskData_base *taskData = getPluginTaskDataBaseClassOnly(taskIndex); + + if (taskData != nullptr) { + if (taskData->hasPluginStats()) { + addFormSubHeader(F("Statistics")); + } + #if FEATURE_CHART_JS + if (taskData->nrSamplesPresent() > 0) { + addRowLabel(F("Historic data")); + taskData->plot_ChartJS(); + + } + #endif // if FEATURE_CHART_JS + + struct EventStruct TempEvent(taskIndex); + String dummy; + bool somethingAdded = false; + + if (!PluginCall(PLUGIN_WEBFORM_LOAD_SHOW_STATS, &TempEvent, dummy)) { + somethingAdded = taskData->webformLoad_show_stats(&TempEvent); + } else { somethingAdded = true; } + + if (somethingAdded) { + if (taskData->hasPeaks()) { + addFormNote(strformat( + F("Peak values recorded since last \"%s.resetpeaks\"."), + getTaskDeviceName(taskIndex).c_str())); + } + } + } + } +} +#endif // if FEATURE_PLUGIN_STATS + + + +void devicePage_show_controller_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) return; + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.SendDataOption) + { + addFormSubHeader(F("Data Acquisition")); + + if (device.ErrorStateValues) { + struct EventStruct TempEvent(taskIndex); + String dummy; + + PluginCall(PLUGIN_WEBFORM_SHOW_ERRORSTATE_OPT, &TempEvent, dummy); // Show extra settings for Error State Value options + } + + addRowLabel(F("Single event with all values")); + addCheckBox(F("TVSE"), Settings.CombineTaskValues_SingleEvent(taskIndex)); + addFormNote(strformat( + F("Unchecked: Send event per value. Checked: Send single event (%s#All) containing all values"), + getTaskDeviceName(taskIndex).c_str())); + + bool separatorAdded = false; + for (controllerIndex_t controllerNr = 0; controllerNr < CONTROLLER_MAX; controllerNr++) + { + if (Settings.Protocol[controllerNr] != 0) + { + if (!separatorAdded) { + addFormSeparator(2); + } + 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")))); + html_TD(); + + addHtml(F("")); // remove left padding 2x to align vertically with other inputs + html_TD(F("width:50px;padding-left:0")); + addCheckBox( + getPluginCustomArgName(F("TDSD"), controllerNr), // ="taskdevicesenddata" + Settings.TaskDeviceSendData[controllerNr][taskIndex]); + + protocolIndex_t ProtocolIndex = getProtocolIndex_from_ControllerIndex(controllerNr); + + if (validProtocolIndex(ProtocolIndex) && + getProtocolStruct(ProtocolIndex).usesID && (Settings.Protocol[controllerNr] != 0)) { + html_TD(); + addHtml(F("IDX:")); + html_TD(); + addNumericBox( + getPluginCustomArgName(F("TDID"), controllerNr), // ="taskdeviceid" + Settings.TaskDeviceID[controllerNr][taskIndex], 0, DOMOTICZ_MAX_IDX); + } + html_end_table(); + } + } + } +} + +void devicePage_show_interval_config(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) return; + + const DeviceStruct& device = Device[DeviceIndex]; + + if (device.TimerOption) + { + // FIXME: shoudn't the max be ULONG_MAX because Settings.TaskDeviceTimer is an unsigned long? addFormNumericBox only supports ints + // for min and max specification + addFormNumericBox(F("Interval"), F("TDT"), Settings.TaskDeviceTimer[taskIndex], 0, 65535); // ="taskdevicetimer" + addUnit(F("sec")); + + if (device.TimerOptional) { + addHtml(F(" (Optional for this Device)")); + } + } +} + +void devicePage_show_task_values(taskIndex_t taskIndex, deviceIndex_t DeviceIndex) +{ + if (!validDeviceIndex(DeviceIndex)) return; + // section: Values + const uint8_t valueCount = getValueCountForTask(taskIndex); + + const DeviceStruct& device = Device[DeviceIndex]; + + if (!device.Custom && (valueCount > 0)) + { + int colCount = 2; + addFormSubHeader(F("Values")); + html_end_table(); + html_table_class_normal(); + + // table header + addHtml(F("")); - html_TD(); -} - -void html_TR_TD() { - html_TR(); - html_TD(); -} - -void html_BR() { - addHtml(F("
")); -} - -void html_TR() { - addHtml(F("")); -} - -void html_TR_TD_height(int height) { - html_TR(); - addHtml(strformat( - F("")); + html_TD(); +} + +void html_TR_TD() { + html_TR(); + html_TD(); +} + +void html_BR() { + addHtml(F("
")); +} + +void html_TR() { + addHtml(F("")); +} + +void html_TR_TD_height(int height) { + html_TR(); + addHtml(strformat( + F("
#")); + html_table_header(F("Name"),500); + + if (device.FormulaOption) + { + html_table_header(F("Formula"), F("EasyFormula"), 500); + ++colCount; + } + + if (device.configurableDecimals()) + { + html_table_header(F("Decimals"), 30); + ++colCount; + } + +#if FEATURE_PLUGIN_STATS + if (device.PluginStats) + { + html_table_header(F("Stats"), 30); + ++colCount; + html_table_header(F("Hide"), 30); + ++colCount; + html_table_header(F("Axis"), 30); + ++colCount; + } +#endif + + //placeholder header + html_table_header(F("")); + ++colCount; + + // table body + for (uint8_t varNr = 0; varNr < valueCount; varNr++) + { + html_TR_TD(); + addHtmlInt(varNr + 1); + html_TD(); + { + const String id = getPluginCustomArgName(F("TDVN"), varNr); // ="taskdevicevaluename" + addTextBox(id, Cache.getTaskDeviceValueName(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.FormulaOption) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDF"), varNr); // ="taskdeviceformula" + addTextBox(id, Cache.getTaskDeviceFormula(taskIndex, varNr), NAME_FORMULA_LENGTH_MAX); + } + + if (device.configurableDecimals()) + { + html_TD(); + const String id = getPluginCustomArgName(F("TDVD"), varNr); // ="taskdevicevaluedecimals" + addNumericBox(id, Cache.getTaskDeviceValueDecimals(taskIndex, varNr), 0, 6); + } + +#if FEATURE_PLUGIN_STATS + if (device.PluginStats) + { + PluginStats_Config_t cachedConfig = Cache.getPluginStatsConfig(taskIndex, varNr); + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDS"), varNr), // ="taskdevicestats" + cachedConfig.isEnabled()); + + html_TD(); + addCheckBox( + getPluginCustomArgName(F("TDSH"), varNr), // ="taskdevicestats Hidden" + cachedConfig.showHidden()); + + 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; + } + + addSelector( + getPluginCustomArgName(F("TDSA"), varNr), + NR_ELEMENTS(chartAxis), + chartAxis, + nullptr, + nullptr, + selected); + } +#endif + } + addFormSeparator(colCount); + } +} + #endif // ifdef WEBSERVER_DEVICES \ No newline at end of file diff --git a/src/src/WebServer/DownloadPage.cpp b/src/src/WebServer/DownloadPage.cpp index c7a24847c..3cccd4154 100644 --- a/src/src/WebServer/DownloadPage.cpp +++ b/src/src/WebServer/DownloadPage.cpp @@ -2,21 +2,34 @@ #ifdef WEBSERVER_DOWNLOAD -#include "../WebServer/ESPEasy_WebServer.h" -#include "../Globals/ESPEasy_time.h" -#include "../Globals/Settings.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/StringGenerator_System.h" +# include "../WebServer/ESPEasy_WebServer.h" +# include "../DataTypes/SettingsType.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/Settings.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/StringGenerator_System.h" +# if FEATURE_TARSTREAM_SUPPORT +# include "../Helpers/TarStream.h" +# endif // if FEATURE_TARSTREAM_SUPPORT // ******************************************************************************** // Web Interface download page // ******************************************************************************** -void handle_download() -{ - #ifndef BUILD_NO_RAM_TRACKER +void handle_download() { +# if FEATURE_TARSTREAM_SUPPORT + handle_config_download(false); +} + +void handle_full_backup() { + handle_config_download(true); +} + +void handle_config_download(bool fullBackup) { +# endif // if FEATURE_TARSTREAM_SUPPORT + # ifndef BUILD_NO_RAM_TRACKER checkRAM(F("handle_download")); - #endif + # endif // ifndef BUILD_NO_RAM_TRACKER if (!isLoggedIn()) { return; } navMenuIndex = MENU_INDEX_TOOLS; @@ -31,22 +44,116 @@ void handle_download() return; } - String str = F("attachment; filename=config_"); - str += Settings.getName(); - str += F("_U"); - str += Settings.Unit; - str += F("_Build"); - str += getSystemBuildString(); - str += '_'; + String str = F("attachment; filename="); + # if FEATURE_TARSTREAM_SUPPORT + + if (fullBackup) { + str += F("backup_"); + } else + # endif // if FEATURE_TARSTREAM_SUPPORT + { + str += F("config_"); + } + str += strformat(F("%s_U%d_Build%s_"), + Settings.getName().c_str(), + Settings.Unit, + getSystemBuildString().c_str()); if (node_time.systemTimePresent()) { str += node_time.getDateTimeString('\0', '\0', '\0'); } - str += F(".dat"); + + # if FEATURE_TARSTREAM_SUPPORT + bool useTarFile = false; + const int equalsSign = str.indexOf('='); + TarStream *tarStream = (fullBackup || !Settings.DisableSaveConfigAsTar()) + ? new TarStream(str.substring(equalsSign + 1) + F(".tar")) + : nullptr; + + if (fullBackup && (nullptr != tarStream)) { + # if defined(ESP8266) + + fs::Dir dir = ESPEASY_FS.openDir(""); + + while (dir.next()) { + fs::File f = dir.openFile("r"); + + if (f) { + tarStream->addFile(f.name(), f.size()); + f.close(); + } + } + # endif // if defined(ESP8266) + # if defined(ESP32) + fs::File root = ESPEASY_FS.open("/"); + fs::File file = root.openNextFile(); + + while (file) { + if (!file.isDirectory()) { + tarStream->addFile(file.name(), file.size()); + } + file = root.openNextFile(); + } + # endif // if defined(ESP32) + } else { + if (nullptr != tarStream) { + tarStream->addFile(dataFile.name(), dataFile.size()); + + # if FEATURE_EXTENDED_CUSTOM_SETTINGS + + // extcfg.dat files + for (uint8_t n = 0; n < TASKS_MAX; ++n) { + tarStream->addFileIfExists(SettingsType::getSettingsFileName(SettingsType::Enum::CustomTaskSettings_Type, n)); + } + # endif // if FEATURE_EXTENDED_CUSTOM_SETTINGS + + // other config files + tarStream->addFileIfExists(getFileName(FileType::NOTIFICATION_DAT)); + tarStream->addFileIfExists(getFileName(FileType::PROVISIONING_DAT)); + tarStream->addFileIfExists(getFileName(FileType::SECURITY_DAT)); + + // rules.txt files + for (unsigned int rf = 0; rf < RULESETS_MAX; ++rf) { + tarStream->addFileIfExists(getRulesFileName(rf)); + } + } + } + + if (nullptr != tarStream) { + # ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_INFO, strformat(F("Download: %d file(s) added to .tar. Size: %d bytes, filebytes: %d"), + tarStream->getFileCount(), tarStream->size(), tarStream->getFilesSizes())); + # endif // ifndef BUILD_NO_DEBUG + useTarFile = tarStream->getFileCount() > 1; // We should at least have config.dat, so ignore that + } + + if (useTarFile) { + str += F(".tar"); + } else + + # endif // if FEATURE_TARSTREAM_SUPPORT + { + str += F(".dat"); // This is in the 'else' part of the 'if' above! + } sendHeader(F("Content-Disposition"), str); + + # if FEATURE_TARSTREAM_SUPPORT + + if (useTarFile) { + web_server.streamFile(*tarStream, F("application/octet-stream")); + } else { + web_server.streamFile(dataFile, F("application/octet-stream")); + } + + if (nullptr != tarStream) { + delete tarStream; + } + + # else // if FEATURE_TARSTREAM_SUPPORT web_server.streamFile(dataFile, F("application/octet-stream")); + # endif // if FEATURE_TARSTREAM_SUPPORT dataFile.close(); } diff --git a/src/src/WebServer/DownloadPage.h b/src/src/WebServer/DownloadPage.h index 31466ea85..f3b3cfe7d 100644 --- a/src/src/WebServer/DownloadPage.h +++ b/src/src/WebServer/DownloadPage.h @@ -9,6 +9,10 @@ // Web Interface download page // ******************************************************************************** void handle_download(); +# if FEATURE_TARSTREAM_SUPPORT +void handle_full_backup(); +void handle_config_download(bool fullBackup); +# endif // if FEATURE_TARSTREAM_SUPPORT #endif // ifdef WEBSERVER_DOWNLOAD diff --git a/src/src/WebServer/ESPEasy_WebServer.cpp b/src/src/WebServer/ESPEasy_WebServer.cpp index 644c76849..8071ddf7a 100644 --- a/src/src/WebServer/ESPEasy_WebServer.cpp +++ b/src/src/WebServer/ESPEasy_WebServer.cpp @@ -1,1205 +1,1216 @@ -#include "../WebServer/ESPEasy_WebServer.h" - -#include "../WebServer/common.h" - -#include "../WebServer/404.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/AdvancedConfigPage.h" -#include "../WebServer/CacheControllerPages.h" -#include "../WebServer/ConfigPage.h" -#include "../WebServer/ControlPage.h" -#include "../WebServer/ControllerPage.h" -#include "../WebServer/CustomPage.h" -#include "../WebServer/DevicesPage.h" -#include "../WebServer/DownloadPage.h" -#include "../WebServer/FactoryResetPage.h" -#include "../WebServer/FileList.h" -#include "../WebServer/HTML_wrappers.h" -#include "../WebServer/HardwarePage.h" -#include "../WebServer/I2C_Scanner.h" -#include "../WebServer/JSON.h" -#include "../WebServer/LoadFromFS.h" -#include "../WebServer/Log.h" -#include "../WebServer/Markup.h" -#include "../WebServer/Markup_Buttons.h" -#include "../WebServer/Markup_Forms.h" -#include "../WebServer/NotificationPage.h" -#include "../WebServer/PinStates.h" -#include "../WebServer/RootPage.h" -#include "../WebServer/Rules.h" -#include "../WebServer/SettingsArchive.h" -#include "../WebServer/SetupPage.h" -#include "../WebServer/SysInfoPage.h" -#include "../WebServer/Metrics.h" -#include "../WebServer/SysVarPage.h" -#include "../WebServer/TimingStats.h" -#include "../WebServer/ToolsPage.h" -#include "../WebServer/UploadPage.h" -#include "../WebServer/WiFiScanner.h" - -#include "../WebServer/WebTemplateParser.h" - - -#include "../../ESPEasy-Globals.h" -#include "../../_Plugin_Helper.h" -#include "../../ESPEasy_common.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../DataTypes/SettingsType.h" - -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyRules.h" -#include "../ESPEasyCore/ESPEasyWifi.h" - -#include "../Globals/CPlugins.h" -#include "../Globals/Device.h" -#include "../Globals/NetworkState.h" -#include "../Globals/SecuritySettings.h" -#include "../Globals/Settings.h" - -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Hardware_device_info.h" -#include "../Helpers/Networking.h" -#include "../Helpers/OTA.h" -#include "../Helpers/StringConverter.h" - -#include "../Static/WebStaticData.h" - -#include - - -void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size) { - if (hasArg(arg)) { - safe_strncpy(dest, webArg(arg).c_str(), max_size); - } -} - -void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper * arg, size_t max_size) { - safe_strncpy_webserver_arg(dest, String(arg), max_size); -} - -void sendHeadandTail(const __FlashStringHelper * tmplName, bool Tail, bool rebooting) { - // This function is called twice per serving a web page. - // So it must keep track of the timer longer than the scope of this function. - // Therefore use a local static variable. - #if FEATURE_TIMING_STATS - static uint64_t statisticsTimerStart = 0; - - if (!Tail) { - statisticsTimerStart = getMicros64(); - } - #endif // if FEATURE_TIMING_STATS - { - const String fileName = concat(tmplName, F(".htm")); - fs::File f = tryOpenFile(fileName, "r"); - - WebTemplateParser templateParser(Tail, rebooting); - if (f) { - bool success = true; - while (f.available() && success) { - success = templateParser.process((char)f.read()); - } - f.close(); - } else { - getWebPageTemplateDefault(tmplName, templateParser); - } - #ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("sendWebPage")); - #endif // ifndef BUILD_NO_RAM_TRACKER - - // web activity timer - lastWeb = millis(); - } - - if (shouldReboot) { - // we only add this here as a seperate chunk to prevent using too much memory at once - serve_JS(JSfiles_e::Reboot); - } - STOP_TIMER(HANDLE_SERVING_WEBPAGE); -} - -void sendHeadandTail_stdtemplate(bool Tail, bool rebooting) { - sendHeadandTail(F("TmplStd"), Tail, rebooting); - - if (!Tail) { - if (!clientIPinSubnet() && WifiIsAP(WiFi.getMode()) && (WiFi.softAPgetStationNum() > 0)) { - addHtmlError(F("Warning: Connected via AP")); - } - - #ifndef BUILD_NO_DEBUG -/* - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - const int nrArgs = web_server.args(); - - if (nrArgs > 0) { - String log = F(" Webserver "); - log += nrArgs; - log += F(" Arguments"); - - if (nrArgs > 20) { - log += F(" (First 20)"); - } - log += ':'; - - for (int i = 0; i < nrArgs && i < 20; ++i) { - log += ' '; - log += i; - log += F(": '"); - log += web_server.argName(i); - log += F("' length: "); - log += webArg(i).length(); - } - addLogMove(LOG_LEVEL_INFO, log); - } - } - */ - #endif // ifndef BUILD_NO_DEBUG - } -} - -bool captivePortal() { - const IPAddress client_localIP = web_server.client().localIP(); - const bool fromAP = client_localIP == apIP; - const bool hasWiFiCredentials = SecuritySettings.hasWiFiCredentials(); - if (hasWiFiCredentials || !fromAP) { - return false; - } - if (!isIP(web_server.hostHeader()) && web_server.hostHeader() != (NetworkGetHostname() + F(".local"))) { - String redirectURL = concat(F("http://"), formatIP(client_localIP)); - #ifdef WEBSERVER_SETUP - if (fromAP && !hasWiFiCredentials) { - redirectURL += F("/setup"); - } - #endif - sendHeader(F("Location"), redirectURL, true); - web_server.send(302, F("text/plain"), EMPTY_STRING); // Empty content inhibits Content-length header so we have to close the socket ourselves. - web_server.client().stop(); // Stop is needed because we sent no content length - return true; - } - return false; -} - - -// ******************************************************************************** -// Web Interface init -// ******************************************************************************** -// #include "core_version.h" - - -void WebServerInit() -{ - if (webserver_init) { return; } - webserver_init = true; - - // Prepare webserver pages - #ifdef WEBSERVER_ROOT - web_server.on(F("/"), handle_root); - // 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("/fwlink"), handle_root); //Microsoft captive portal. - #endif // ifdef WEBSERVER_ROOT - #ifdef WEBSERVER_ADVANCED - web_server.on(F("/advanced"), handle_advanced); - #endif // ifdef WEBSERVER_ADVANCED - #ifdef WEBSERVER_CONFIG - web_server.on(F("/config"), handle_config); - #endif // ifdef WEBSERVER_CONFIG - #ifdef WEBSERVER_CONTROL - web_server.on(F("/control"), handle_control); - #endif // ifdef WEBSERVER_CONTROL - #ifdef WEBSERVER_CONTROLLERS - web_server.on(F("/controllers"), handle_controllers); - #endif // ifdef WEBSERVER_CONTROLLERS - #ifdef WEBSERVER_DEVICES - web_server.on(F("/devices"), handle_devices); - #endif // ifdef WEBSERVER_DEVICES - #ifdef WEBSERVER_DOWNLOAD - web_server.on(F("/download"), handle_download); - #endif // ifdef WEBSERVER_DOWNLOAD - -#ifdef USES_C016 - - web_server.on(F("/dumpcache"), handle_dumpcache); // C016 specific entrie - web_server.on(F("/cache_json"), handle_cache_json); // C016 specific entrie - web_server.on(F("/cache_csv"), handle_cache_csv); // C016 specific entrie -#endif // USES_C016 - - #ifdef WEBSERVER_FACTORY_RESET - web_server.on(F("/factoryreset"), handle_factoryreset); - #endif // ifdef WEBSERVER_FACTORY_RESET - #if FEATURE_SETTINGS_ARCHIVE - web_server.on(F("/settingsarchive"), handle_settingsarchive); - #endif // if FEATURE_SETTINGS_ARCHIVE - #ifdef WEBSERVER_FILELIST - web_server.on(F("/filelist"), handle_filelist); - #endif // ifdef WEBSERVER_FILELIST - #ifdef WEBSERVER_HARDWARE - web_server.on(F("/hardware"), handle_hardware); - #endif // ifdef WEBSERVER_HARDWARE - #ifdef WEBSERVER_I2C_SCANNER - web_server.on(F("/i2cscanner"), handle_i2cscanner); - #endif // ifdef WEBSERVER_I2C_SCANNER - web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI - web_server.on(F("/csv"), handle_csvval); - web_server.on(F("/log"), handle_log); - web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI -#if FEATURE_NOTIFIER - web_server.on(F("/notifications"), handle_notifications); -#endif // if FEATURE_NOTIFIER - #ifdef WEBSERVER_PINSTATES - web_server.on(F("/pinstates"), handle_pinstates); - #endif // ifdef WEBSERVER_PINSTATES - #ifdef WEBSERVER_RULES - web_server.on(F("/rules"), handle_rules_new); - web_server.on(F("/rules/"), Goto_Rules_Root); - # ifdef WEBSERVER_NEW_RULES - web_server.on(F("/rules/add"), []() - { - handle_rules_edit(web_server.uri(), true); - }); - web_server.on(F("/rules/backup"), handle_rules_backup); - web_server.on(F("/rules/delete"), handle_rules_delete); - # endif // WEBSERVER_NEW_RULES - #endif // WEBSERVER_RULES -#if FEATURE_SD - web_server.on(F("/SDfilelist"), handle_SDfilelist); -#endif // if FEATURE_SD -#ifdef WEBSERVER_SETUP - web_server.on(F("/setup"), handle_setup); -#endif // ifdef WEBSERVER_SETUP -#ifdef WEBSERVER_SYSINFO - web_server.on(F("/sysinfo"), handle_sysinfo); -#endif // ifdef WEBSERVER_SYSINFO -#ifdef WEBSERVER_METRICS - web_server.on(F("/metrics"), handle_metrics); -#endif // ifdef WEBSERVER_METRICS -#ifdef WEBSERVER_SYSVARS - web_server.on(F("/sysvars"), handle_sysvars); -#endif // WEBSERVER_SYSVARS -#ifdef WEBSERVER_TIMINGSTATS - web_server.on(F("/timingstats"), handle_timingstats); -#endif // WEBSERVER_TIMINGSTATS -#ifdef WEBSERVER_TOOLS - web_server.on(F("/tools"), handle_tools); -#endif // ifdef WEBSERVER_TOOLS -#ifdef WEBSERVER_UPLOAD - web_server.on(F("/upload"), HTTP_GET, handle_upload); - web_server.on(F("/upload"), HTTP_POST, handle_upload_post, handleFileUpload); -#endif // ifdef WEBSERVER_UPLOAD -#if FEATURE_SD - web_server.on(F("/uploadsd"), HTTP_GET, handle_upload); - web_server.on(F("/uploadsd"), HTTP_POST, handle_upload_post, handleSDFileUpload); -#endif // if FEATURE_SD -#ifdef WEBSERVER_WIFI_SCANNER - web_server.on(F("/wifiscanner"), handle_wifiscanner); -#endif // ifdef WEBSERVER_WIFI_SCANNER - -#ifdef WEBSERVER_NEW_UI - web_server.on(F("/buildinfo"), handle_buildinfo); // Also part of WEBSERVER_NEW_UI - web_server.on(F("/factoryreset_json"), handle_factoryreset_json); - web_server.on(F("/filelist_json"), handle_filelist_json); - web_server.on(F("/i2cscanner_json"), handle_i2cscanner_json); - #if FEATURE_ESPEASY_P2P - web_server.on(F("/node_list_json"), handle_nodes_list_json); - #endif - web_server.on(F("/pinstates_json"), handle_pinstates_json); - web_server.on(F("/timingstats_json"), handle_timingstats_json); - web_server.on(F("/upload_json"), HTTP_POST, handle_upload_json, handleFileUpload); - web_server.on(F("/wifiscanner_json"), handle_wifiscanner_json); -#endif // WEBSERVER_NEW_UI -#if SHOW_SYSINFO_JSON - web_server.on(F("/sysinfo_json"), handle_sysinfo_json); -#endif//SHOW_SYSINFO_JSON - - web_server.onNotFound(handleNotFound); - - // List of headers to be recorded - // "If-None-Match" is used to see whether we need to serve a static file, or simply can reply with a 304 (not modified) - const char * headerkeys[] = {"If-None-Match"}; - constexpr size_t headerkeyssize = NR_ELEMENTS(headerkeys); - web_server.collectHeaders(headerkeys, headerkeyssize ); - #if defined(ESP8266) || defined(ESP32) - { - # ifndef NO_HTTP_UPDATER - uint32_t maxSketchSize; - bool use2step; - // allow OTA to smaller version of ESPEasy/other firmware - if (Settings.AllowOTAUnlimited() || OTA_possible(maxSketchSize, use2step)) { - httpUpdater.setup(&web_server); - } - # endif // ifndef NO_HTTP_UPDATER - } - #endif // if defined(ESP8266) - - #if defined(ESP8266) - - # if FEATURE_SSDP - - if (Settings.UseSSDP) - { - web_server.on(F("/ssdp.xml"), HTTP_GET, []() { - #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - // See: https://github.com/espressif/arduino-esp32/pull/6676 - web_server.client().setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! - Client &pClient = web_server.client(); - pClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); - #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - web_server.client().setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! - #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS - - SSDP_schema(); - }); - SSDP_begin(); - } - # endif // if FEATURE_SSDP - #endif // if defined(ESP8266) -} - -void setWebserverRunning(bool state) { - if (webserverRunning == state) { - return; - } - - if (state) { - WebServerInit(); - web_server.begin(Settings.WebserverPort); - addLog(LOG_LEVEL_INFO, F("Webserver: start")); - } else { - web_server.client().stop(); - web_server.stop(); - addLog(LOG_LEVEL_INFO, F("Webserver: stop")); - } - webserverRunning = state; - CheckRunningServices(); // Uses webserverRunning state. -} - -void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) -{ - const bool addJS = true; - const bool addMeta = true; - -/* - if (equals(tmplName, F("TmplAP"))) - { - - getWebPageTemplateDefaultHead(parser, addMeta, !addJS); - - if (!parser.isTail()) { - #ifndef WEBPAGE_TEMPLATE_AP_HEADER - parser.process(F("
" - "

Welcome to ESP Easy Mega AP

")); - #else - parser.process(F(WEBPAGE_TEMPLATE_AP_HEADER)); - #endif - - parser.process(F("
")); - } - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } - else - */ - if (equals(tmplName, F("TmplMsg"))) - { - getWebPageTemplateDefaultHead(parser, !addMeta, !addJS); - if (!parser.isTail()) { - parser.process(F("")); - } - getWebPageTemplateDefaultHeader(parser, F("{{name}}"), false); - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } - else if (equals(tmplName, F("TmplDsh"))) - { - getWebPageTemplateDefaultHead(parser, !addMeta, addJS); - parser.process(F("" - "{{content}}" - "")); - } - else // all other template names e.g. TmplStd - { - getWebPageTemplateDefaultHead(parser, addMeta, addJS); - if (!parser.isTail()) { - parser.process(F("")); - } - getWebPageTemplateDefaultHeader(parser, F("{{name}} {{logo}}"), true); - getWebPageTemplateDefaultContentSection(parser); - getWebPageTemplateDefaultFooter(parser); - } -// addLog(LOG_LEVEL_INFO, String(F("tmpl.length(): ")) + String(tmpl.length())); -} - -void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { - if (parser.isTail()) return; - parser.process(F("" - "" - "" - "" - "{{name}}")); - - if (addMeta) { parser.process(F("{{meta}}")); } - - if (addJS) { parser.process(F("{{js}}")); } - - parser.process(F("{{css}}" - "")); -} - -void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper * title, bool addMenu) { - { - if (parser.isTail()) return; - #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - parser.process(F("

ESP Easy Mega: ")); - parser.process(title); - #if BUILD_IN_WEBHEADER - parser.process(F("
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX "
")); - #endif // #if BUILD_IN_WEBHEADER - parser.process(F("


")); - #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); - tmp.replace(F("{{title}}"), title); - parser.process(tmp); - #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER - } - - if (addMenu) { parser.process(F("{{menu}}")); } - parser.process(F("
")); -} - -void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { - parser.process(F("
" - "" - "{{error}}" - "" - "{{content}}" - "
" - )); -} - -void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { - if (!parser.isTail()) return; - #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F("
" - "
" - "
Powered by Let's Control It community" - #if BUILD_IN_WEBFOOTER - "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" - #endif // #if BUILD_IN_WEBFOOTER - "
" - "
" - "" - )); -#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER - parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); -#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER -} - - - -void writeDefaultCSS(void) -{ - return; // TODO - -/* -#ifndef WEBSERVER_USE_CDN_JS_CSS - - if (!fileExists(F("esp.css"))) - { - fs::File f = tryOpenFile(F("esp.css"), "w"); - - if (f) - { - String defaultCSS; - defaultCSS = PGMT(DATA_ESPEASY_DEFAULT_MIN_CSS); - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("CSS : Writing default CSS file to FS ("); - log += defaultCSS.length(); - log += F(" bytes)"); - addLog(LOG_LEVEL_INFO, log); - } - f.write((const unsigned char *)defaultCSS.c_str(), defaultCSS.length()); // note: content must be in RAM - a write of F("XXX") does - // not work - f.close(); - } - } -#endif -*/ -} - -// ******************************************************************************** -// Functions to stream JSON directly to TXBuffer -// FIXME TD-er: replace stream_xxx_json_object* into this code. -// N.B. handling of numerical values differs (string vs. no string) -// ******************************************************************************** - -int8_t level = 0; -int8_t lastLevel = -1; - -void json_quote_name(const __FlashStringHelper * val) { - json_quote_name(String(val)); -} - -void json_quote_name(const String& val) { - if (lastLevel == level) { - addHtml(','); - } - - if (val.length() > 0) { - json_quote_val(val); - addHtml(':'); - } -} - -void json_quote_val(const String& val) { - addHtml('\"'); - addHtml(val); - addHtml('\"'); -} - -void json_open(bool arr) { - json_open(arr, EMPTY_STRING); -} - -void json_open(bool arr, const __FlashStringHelper * name) { - json_quote_name(name); - addHtml(arr ? '[' : '{'); - lastLevel = level; - level++; -} - -void json_open(bool arr, const String& name) { - json_quote_name(name); - addHtml(arr ? '[' : '{'); - lastLevel = level; - level++; -} - -void json_init() { - level = 0; - lastLevel = -1; -} - -void json_close() { - json_close(false); -} - -void json_close(bool arr) { - addHtml(arr ? ']' : '}'); - level--; - lastLevel = level; -} - -void json_number(const __FlashStringHelper * name, const String& value) -{ - json_prop(name, value); -} - - -void json_number(const String& name, const String& value) { - json_prop(name, value); -} - -void json_prop(const __FlashStringHelper * name, const String& value) -{ - json_quote_name(name); - json_quote_val(value); - lastLevel = level; -} - - -void json_prop(const String& name, const String& value) { - json_quote_name(name); - json_quote_val(value); - lastLevel = level; -} - -void json_prop(LabelType::Enum label) { - json_prop(getInternalLabel(label, '-'), getValue(label)); -} - -// ******************************************************************************** -// 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) -{ - String deviceName; - - addHtml(F("'); - - const uint8_t valueCount = getValueCountForTask(TaskIndex); - - for (uint8_t x = 0; x < valueCount; x++) - { - addHtml(F("")); - } -} - - -// ******************************************************************************** -// Login state check -// ******************************************************************************** -bool isLoggedIn(bool mustProvideLogin) -{ - if (!clientIPallowed()) { return false; } - - if (SecuritySettings.Password[0] == 0) { return true; } - - if (!mustProvideLogin) { - return false; - } - - { - String www_username = F(DEFAULT_ADMIN_USERNAME); - if (!web_server.authenticate(www_username.c_str(), SecuritySettings.Password)) - - // Basic Auth Method with Custom realm and Failure Response - // return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); - // Digest Auth Method with realm="Login Required" and empty Failure Response - // return server.requestAuthentication(DIGEST_AUTH); - // Digest Auth Method with Custom realm and empty Failure Response - // return server.requestAuthentication(DIGEST_AUTH, www_realm); - // Digest Auth Method with Custom realm and Failure Response - { -#ifdef CORE_PRE_2_5_0 - - // See https://github.com/esp8266/Arduino/issues/4717 - HTTPAuthMethod mode = BASIC_AUTH; -#else // ifdef CORE_PRE_2_5_0 - HTTPAuthMethod mode = DIGEST_AUTH; -#endif // ifdef CORE_PRE_2_5_0 - String message = F("Login Required (default user: "); - message += www_username; - message += ')'; - web_server.requestAuthentication(mode, message.c_str()); - - if (Settings.UseRules) - { - String event = F("Login#Failed"); - - // TD-er: Do not add to the eventQueue, but execute right now. - rulesProcessing(event); - } - - return false; - } - } - return true; -} - -String getControllerSymbol(uint8_t index) -{ - String ret = F("&#"); - - ret += 10102 + index; - ret += F(";"); - return ret; -} - -/* - String getValueSymbol(uint8_t index) - { - String ret = F("&#"); - ret += 10112 + index; - ret += ';'; - return ret; - } - */ - -void addSVG_param(const char key, int value) -{ - addHtml(strformat(F(" %c=\"%d\""), key, value)); -} - -void addSVG_param(const char key, float value) -{ - addSVG_param(key, toString(value, 2)); -} - -void addSVG_param(const char key, const String& value) -{ - addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); -} - - -void addSVG_param(const __FlashStringHelper * key, int value) { - addHtml(strformat(F(" %s=\"%d\""), String(key).c_str(), value)); -} - -void addSVG_param(const __FlashStringHelper * key, float value) { - addSVG_param(key, toString(value, 2)); -} - -void addSVG_param(const __FlashStringHelper * key, const String& value) { - addHtml(strformat(F(" %s=\"%s\""), String(key).c_str(), value.c_str())); -} - -void createSvgRect_noStroke(const __FlashStringHelper * classname, unsigned int fillColor, float xoffset, float yoffset, float width, float height, float rx, float ry) { - createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); -} - -void createSvgRect(const String& classname, - unsigned int fillColor, - unsigned int strokeColor, - float xoffset, - float yoffset, - float width, - float height, - float strokeWidth, - float rx, - float ry) { - addHtml(F("")); -} - -void createSvgHorRectPath(unsigned int color, int xoffset, int yoffset, int size, int height, int range, float SVG_BAR_WIDTH) { - if (range == 0) { - range = 1; - } - float width = (SVG_BAR_WIDTH * size) / range; - - if (width < 2) { width = 2; } - addHtml(formatToHex(color, F("\n")); -} - -void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { - addHtml(F("', '\n'); - - addHtml(F("'); - - addHtml(text); - addHtml(F("\n")); -} - -#define SVG_BAR_HEIGHT 16 -#define SVG_BAR_WIDTH 400 - -void write_SVG_image_header(int width, int height, bool useViewbox) { - addHtml(F("'); - addHtml(F("")); -} - -/* - void getESPeasyLogo(int width_pixels) { - write_SVG_image_header(width_pixels, width_pixels, true); - addHtml(F(""); - } - */ -void getWiFi_RSSI_icon(int rssi, int width_pixels) -{ - const int nbars_filled = (rssi + 100) / 8; - const int nbars = 5; - int white_between_bar = (static_cast(width_pixels) / nbars) * 0.2f; - - if (white_between_bar < 1) { white_between_bar = 1; } - const int barWidth = (width_pixels - (nbars - 1) * white_between_bar) / nbars; - const int svg_width_pixels = nbars * barWidth + (nbars - 1) * white_between_bar; - - write_SVG_image_header(svg_width_pixels, svg_width_pixels, true); - const float scale = 100.0f / svg_width_pixels; - const int bar_height_step = 100 / nbars; - - for (int i = 0; i < nbars; ++i) { - const unsigned int color = i < nbars_filled ? 0x07d : 0xBFa1a1a1; // Blue/Grey75% - const int barHeight = (i + 1) * bar_height_step; - createSvgRect_noStroke(i < nbars_filled ? F("bar_highlight") : F("bar_dimmed"), color, i * (barWidth + white_between_bar) * scale, 100 - barHeight, barWidth, barHeight, 0, 0); - } - addHtml(F("\n")); -} - -#if FEATURE_CHART_STORAGE_LAYOUT -void getConfig_dat_file_layout() { - const int shiftY = 2; - float yOffset = shiftY; - - write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY); - - int max_index, offset, max_size{}; - int struct_size = 0; - - // background - const uint32_t realSize = SettingsType::getFileSize(SettingsType::Enum::TaskSettings_Type); - - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { - SettingsType::Enum settingsType = static_cast(st); - - if (SettingsType::getSettingsFile(settingsType) == SettingsType::SettingsFileEnum::FILE_CONFIG_type) { - unsigned int color = SettingsType::getSVGcolor(settingsType); - if (SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) - { - for (int i = 0; i < max_index; ++i) { - if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { - // Struct position - createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - } - } - } - } - } - - // Text labels - constexpr float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - - createSvgTextElement(SettingsType::getSettingsFileName(SettingsType::Enum::TaskSettings_Type), textXoffset, textYoffset); - addHtml(F("\n")); -} - -void getStorageTableSVG(SettingsType::Enum settingsType) { - uint32_t realSize = SettingsType::getFileSize(settingsType); - unsigned int color = SettingsType::getSVGcolor(settingsType); - const int shiftY = 2; - - int max_index, offset, max_size{}; - int struct_size = 0; - - if (!SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) - { - return; - } - - if (max_index == 0) { return; } - - // One more to add bar indicating struct size vs. reserved space. - write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - for (int i = 0; i < max_index; ++i) { - if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { - // background - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - // Struct position - createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - - // Text labels - float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - createSvgTextElement(formatHumanReadable(offset, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 60; - createSvgTextElement(formatHumanReadable(max_size, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 130; - createSvgTextElement(String(i), textXoffset, textYoffset); - yOffset += SVG_BAR_HEIGHT; - } - } - - // usage - createSvgHorRectPath(0xcdcdcd, 0, yOffset, max_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); - - // Struct size (used part of the reserved space) - if (struct_size != 0) { - createSvgHorRectPath(color, 0, yOffset, struct_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); - } - - // Text labels - float textXoffset = SVG_BAR_WIDTH + 2; - float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - - if (struct_size != 0) { - String text; - text.reserve(32); - text += formatHumanReadable(struct_size, 1024); - text += '/'; - text += formatHumanReadable(max_size, 1024); - text += F(" per item"); - createSvgTextElement(text, textXoffset, textYoffset); - } else { - createSvgTextElement(F("Variable size"), textXoffset, textYoffset); - } - addHtml(F("\n")); -} - -void drawPartitionChartSVG( - float yOffset, - uint32_t realSize, - uint32_t partitionAddress, - uint32_t partitionSize, - unsigned int partitionColor, - const String& label, - const String& name) -{ - createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - createSvgHorRectPath(partitionColor, partitionAddress, yOffset, partitionSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); - float textXoffset = SVG_BAR_WIDTH + 2; - const float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; - createSvgTextElement(formatHumanReadable(partitionSize, 1024), textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 60; - createSvgTextElement(label, textXoffset, textYoffset); - textXoffset = SVG_BAR_WIDTH + 130; - createSvgTextElement(name, textXoffset, textYoffset); -} - -#ifdef ESP32 - -# include - - -void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { - int nrPartitions = getPartionCount(pType); - - if (nrPartitions == 0) { return; } - const int shiftY = 2; - - uint32_t realSize = getFlashRealSizeInBytes(); - esp_partition_type_t partitionType = static_cast(pType); - const esp_partition_t *_mypart; - esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, ESP_PARTITION_SUBTYPE_ANY, nullptr); - - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - if (_mypartiterator) { - do { - _mypart = esp_partition_get(_mypartiterator); - drawPartitionChartSVG( - yOffset, - realSize, - _mypart->address, - _mypart->size, - partitionColor, - _mypart->label, - getPartitionType(_mypart->type, _mypart->subtype)); - yOffset += SVG_BAR_HEIGHT; - } while ((_mypartiterator = esp_partition_next(_mypartiterator)) != nullptr); - } - addHtml(F("\n")); - esp_partition_iterator_release(_mypartiterator); -} - -#endif // ifdef ESP32 - -#ifdef ESP8266 -void getPartitionTableSVG() { - // sketch / OTA / FS / EEPROM / RFcal / wifi - const int nrPartitions = 6; - const int shiftY = 2; - write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); - float yOffset = shiftY; - - for (int i = 0; i < nrPartitions; ++i) { - const ESP8266_partition_type ptype = static_cast(i); - uint32_t partitionAddress = 0; - int32_t partitionSize = 0; - const int32_t partitionSector = getPartitionInfo(ptype, partitionAddress, partitionSize); - - const __FlashStringHelper * label = F(""); - String descr; - unsigned int partitionColor = 0xab56e6; - switch (ptype) { - case ESP8266_partition_type::sketch: - label = F("sketch"); - partitionColor = 0xab56e6; - break; - case ESP8266_partition_type::ota: - label = F("ota"); - partitionColor = 0x5856e6; - break; - case ESP8266_partition_type::fs: - label = F("fs"); - partitionColor = 0xff7f00; - #ifdef USE_LITTLEFS - descr = F("LittleFS"); - #else - descr = F("SPIFFS"); - #endif - break; - case ESP8266_partition_type::eeprom: - label = F("eeprom"); - descr = concat(F("sector:"), partitionSector); - partitionColor = 0x7fff00; - break; - case ESP8266_partition_type::rf_cal: - label = F("RFcal"); - partitionColor = 0xff007f; - break; - case ESP8266_partition_type::wifi: - label = F("WiFi"); - partitionColor = 0xff00ff; - break; - - } - - drawPartitionChartSVG( - yOffset, - getFlashRealSizeInBytes(), - partitionAddress, - partitionSize, - partitionColor, - label, - descr); - yOffset += SVG_BAR_HEIGHT; - -/* - String debuglog = concat(F("partition: "), (i+1)); - debuglog += concat(F(" FS_st: "), formatToHex((uint32_t)&_FS_start)); - debuglog += concat(F(" FS_end: "), formatToHex((uint32_t)&_FS_end)); - debuglog += concat(F(" EEPROM: "), formatToHex((uint32_t)&_EEPROM_start)); - debuglog += concat(F(" addr: "), formatToHex(partitionAddress, 8)); - debuglog += concat(F(" part.size: "), partitionSize); - debuglog += concat(F(" label: "), label); - addLog(LOG_LEVEL_INFO, debuglog); -*/ - } - addHtml(F("\n")); -} -#endif -#endif - -bool webArg2ip(const __FlashStringHelper * arg, uint8_t *IP) { - return str2ip(webArg(arg), IP); -} +#include "../WebServer/ESPEasy_WebServer.h" + +#include "../WebServer/common.h" + +#include "../WebServer/404.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/AdvancedConfigPage.h" +#include "../WebServer/CacheControllerPages.h" +#include "../WebServer/ConfigPage.h" +#include "../WebServer/ControlPage.h" +#include "../WebServer/ControllerPage.h" +#include "../WebServer/CustomPage.h" +#include "../WebServer/DevicesPage.h" +#include "../WebServer/DownloadPage.h" +#include "../WebServer/FactoryResetPage.h" +#include "../WebServer/FileList.h" +#include "../WebServer/HTML_wrappers.h" +#include "../WebServer/HardwarePage.h" +#include "../WebServer/I2C_Scanner.h" +#include "../WebServer/JSON.h" +#include "../WebServer/LoadFromFS.h" +#include "../WebServer/Log.h" +#include "../WebServer/Markup.h" +#include "../WebServer/Markup_Buttons.h" +#include "../WebServer/Markup_Forms.h" +#include "../WebServer/NotificationPage.h" +#include "../WebServer/PinStates.h" +#include "../WebServer/RootPage.h" +#include "../WebServer/Rules.h" +#include "../WebServer/SettingsArchive.h" +#include "../WebServer/SetupPage.h" +#include "../WebServer/SysInfoPage.h" +#include "../WebServer/Metrics.h" +#include "../WebServer/SysVarPage.h" +#include "../WebServer/TimingStats.h" +#include "../WebServer/ToolsPage.h" +#include "../WebServer/UploadPage.h" +#include "../WebServer/WiFiScanner.h" + +#include "../WebServer/WebTemplateParser.h" + + +#include "../../ESPEasy-Globals.h" +#include "../../_Plugin_Helper.h" +#include "../../ESPEasy_common.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../DataTypes/SettingsType.h" + +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyRules.h" +#include "../ESPEasyCore/ESPEasyWifi.h" + +#include "../Globals/CPlugins.h" +#include "../Globals/Device.h" +#include "../Globals/NetworkState.h" +#include "../Globals/SecuritySettings.h" +#include "../Globals/Settings.h" + +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Hardware_device_info.h" +#include "../Helpers/Networking.h" +#include "../Helpers/OTA.h" +#include "../Helpers/StringConverter.h" + +#include "../Static/WebStaticData.h" + +#include + + +void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size) { + if (hasArg(arg)) { + safe_strncpy(dest, webArg(arg).c_str(), max_size); + } +} + +void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper * arg, size_t max_size) { + safe_strncpy_webserver_arg(dest, String(arg), max_size); +} + +void sendHeadandTail(const __FlashStringHelper * tmplName, bool Tail, bool rebooting) { + // This function is called twice per serving a web page. + // So it must keep track of the timer longer than the scope of this function. + // Therefore use a local static variable. + #if FEATURE_TIMING_STATS + static uint64_t statisticsTimerStart = 0; + + if (!Tail) { + statisticsTimerStart = getMicros64(); + } + #endif // if FEATURE_TIMING_STATS + { + const String fileName = concat(tmplName, F(".htm")); + fs::File f = tryOpenFile(fileName, "r"); + + WebTemplateParser templateParser(Tail, rebooting); + if (f) { + bool success = true; + while (f.available() && success) { + success = templateParser.process((char)f.read()); + } + f.close(); + } else { + getWebPageTemplateDefault(tmplName, templateParser); + } + #ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("sendWebPage")); + #endif // ifndef BUILD_NO_RAM_TRACKER + + // web activity timer + lastWeb = millis(); + } + + if (shouldReboot) { + // we only add this here as a seperate chunk to prevent using too much memory at once + serve_JS(JSfiles_e::Reboot); + } + STOP_TIMER(HANDLE_SERVING_WEBPAGE); +} + +void sendHeadandTail_stdtemplate(bool Tail, bool rebooting) { + sendHeadandTail(F("TmplStd"), Tail, rebooting); + + if (!Tail) { + if (!clientIPinSubnet() && WifiIsAP(WiFi.getMode()) && (WiFi.softAPgetStationNum() > 0)) { + addHtmlError(F("Warning: Connected via AP")); + } + + #ifndef BUILD_NO_DEBUG +/* + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + const int nrArgs = web_server.args(); + + if (nrArgs > 0) { + String log = F(" Webserver "); + log += nrArgs; + log += F(" Arguments"); + + if (nrArgs > 20) { + log += F(" (First 20)"); + } + log += ':'; + + for (int i = 0; i < nrArgs && i < 20; ++i) { + log += ' '; + log += i; + log += F(": '"); + log += web_server.argName(i); + log += F("' length: "); + log += webArg(i).length(); + } + addLogMove(LOG_LEVEL_INFO, log); + } + } + */ + #endif // ifndef BUILD_NO_DEBUG + } + // We have sent a lot of data at once. + // try to flush it to the connected client to free up some RAM + // from pending transfers + TXBuffer.flush(); + delay(10); +} + +bool captivePortal() { + const IPAddress client_localIP = web_server.client().localIP(); + const bool fromAP = client_localIP == apIP; + const bool hasWiFiCredentials = SecuritySettings.hasWiFiCredentials(); + if (hasWiFiCredentials || !fromAP) { + return false; + } + if (!isIP(web_server.hostHeader()) && web_server.hostHeader() != (NetworkGetHostname() + F(".local"))) { + String redirectURL = concat(F("http://"), formatIP(client_localIP)); + #ifdef WEBSERVER_SETUP + if (fromAP && !hasWiFiCredentials) { + redirectURL += F("/setup"); + } + #endif + sendHeader(F("Location"), redirectURL, true); + web_server.send(302, F("text/plain"), EMPTY_STRING); // Empty content inhibits Content-length header so we have to close the socket ourselves. + web_server.client().stop(); // Stop is needed because we sent no content length + return true; + } + return false; +} + + +// ******************************************************************************** +// Web Interface init +// ******************************************************************************** +// #include "core_version.h" + + +void WebServerInit() +{ + if (webserver_init) { return; } + webserver_init = true; + + // Prepare webserver pages + #ifdef WEBSERVER_ROOT + web_server.on(F("/"), handle_root); + // 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("/fwlink"), handle_root); //Microsoft captive portal. + #endif // ifdef WEBSERVER_ROOT + #ifdef WEBSERVER_ADVANCED + web_server.on(F("/advanced"), handle_advanced); + #if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT + web_server.on(F("/backup"), handle_full_backup); + #endif // if defined(WEBSERVER_DOWNLOAD) && FEATURE_TARSTREAM_SUPPORT + #endif // ifdef WEBSERVER_ADVANCED + #ifdef WEBSERVER_CONFIG + web_server.on(F("/config"), handle_config); + #endif // ifdef WEBSERVER_CONFIG + #ifdef WEBSERVER_CONTROL + web_server.on(F("/control"), handle_control); + #endif // ifdef WEBSERVER_CONTROL + #ifdef WEBSERVER_CONTROLLERS + web_server.on(F("/controllers"), handle_controllers); + #endif // ifdef WEBSERVER_CONTROLLERS + #ifdef WEBSERVER_DEVICES + web_server.on(F("/devices"), handle_devices); + #endif // ifdef WEBSERVER_DEVICES + #ifdef WEBSERVER_DOWNLOAD + web_server.on(F("/download"), handle_download); + #endif // ifdef WEBSERVER_DOWNLOAD + +#ifdef USES_C016 + + web_server.on(F("/dumpcache"), handle_dumpcache); // C016 specific entrie + web_server.on(F("/cache_json"), handle_cache_json); // C016 specific entrie + web_server.on(F("/cache_csv"), handle_cache_csv); // C016 specific entrie +#endif // USES_C016 + + #ifdef WEBSERVER_FACTORY_RESET + web_server.on(F("/factoryreset"), handle_factoryreset); + #endif // ifdef WEBSERVER_FACTORY_RESET + #if FEATURE_SETTINGS_ARCHIVE + web_server.on(F("/settingsarchive"), handle_settingsarchive); + #endif // if FEATURE_SETTINGS_ARCHIVE + #ifdef WEBSERVER_FILELIST + web_server.on(F("/filelist"), handle_filelist); + #endif // ifdef WEBSERVER_FILELIST + #ifdef WEBSERVER_HARDWARE + web_server.on(F("/hardware"), handle_hardware); + #endif // ifdef WEBSERVER_HARDWARE + #ifdef WEBSERVER_I2C_SCANNER + web_server.on(F("/i2cscanner"), handle_i2cscanner); + #endif // ifdef WEBSERVER_I2C_SCANNER + web_server.on(F("/json"), handle_json); // Also part of WEBSERVER_NEW_UI + web_server.on(F("/csv"), handle_csvval); + web_server.on(F("/log"), handle_log); + web_server.on(F("/logjson"), handle_log_JSON); // Also part of WEBSERVER_NEW_UI +#if FEATURE_NOTIFIER + web_server.on(F("/notifications"), handle_notifications); +#endif // if FEATURE_NOTIFIER + #ifdef WEBSERVER_PINSTATES + web_server.on(F("/pinstates"), handle_pinstates); + #endif // ifdef WEBSERVER_PINSTATES + #ifdef WEBSERVER_RULES + web_server.on(F("/rules"), handle_rules_new); + web_server.on(F("/rules/"), Goto_Rules_Root); + # ifdef WEBSERVER_NEW_RULES + web_server.on(F("/rules/add"), []() + { + handle_rules_edit(web_server.uri(), true); + }); + web_server.on(F("/rules/backup"), handle_rules_backup); + web_server.on(F("/rules/delete"), handle_rules_delete); + # endif // WEBSERVER_NEW_RULES + #endif // WEBSERVER_RULES +#if FEATURE_SD + web_server.on(F("/SDfilelist"), handle_SDfilelist); +#endif // if FEATURE_SD +#ifdef WEBSERVER_SETUP + web_server.on(F("/setup"), handle_setup); +#endif // ifdef WEBSERVER_SETUP +#ifdef WEBSERVER_SYSINFO + web_server.on(F("/sysinfo"), handle_sysinfo); +#endif // ifdef WEBSERVER_SYSINFO +#ifdef WEBSERVER_METRICS + web_server.on(F("/metrics"), handle_metrics); +#endif // ifdef WEBSERVER_METRICS +#ifdef WEBSERVER_SYSVARS + web_server.on(F("/sysvars"), handle_sysvars); +#endif // WEBSERVER_SYSVARS +#ifdef WEBSERVER_TIMINGSTATS + web_server.on(F("/timingstats"), handle_timingstats); +#endif // WEBSERVER_TIMINGSTATS +#ifdef WEBSERVER_TOOLS + web_server.on(F("/tools"), handle_tools); +#endif // ifdef WEBSERVER_TOOLS +#ifdef WEBSERVER_UPLOAD + web_server.on(F("/upload"), HTTP_GET, handle_upload); + web_server.on(F("/upload"), HTTP_POST, handle_upload_post, handleFileUpload); +#endif // ifdef WEBSERVER_UPLOAD +#if FEATURE_SD + web_server.on(F("/uploadsd"), HTTP_GET, handle_upload); + web_server.on(F("/uploadsd"), HTTP_POST, handle_upload_post, handleSDFileUpload); +#endif // if FEATURE_SD +#ifdef WEBSERVER_WIFI_SCANNER + web_server.on(F("/wifiscanner"), handle_wifiscanner); +#endif // ifdef WEBSERVER_WIFI_SCANNER + +#ifdef WEBSERVER_NEW_UI + web_server.on(F("/buildinfo"), handle_buildinfo); // Also part of WEBSERVER_NEW_UI + web_server.on(F("/factoryreset_json"), handle_factoryreset_json); + web_server.on(F("/filelist_json"), handle_filelist_json); + web_server.on(F("/i2cscanner_json"), handle_i2cscanner_json); + #if FEATURE_ESPEASY_P2P + web_server.on(F("/node_list_json"), handle_nodes_list_json); + #endif + web_server.on(F("/pinstates_json"), handle_pinstates_json); + web_server.on(F("/timingstats_json"), handle_timingstats_json); + web_server.on(F("/upload_json"), HTTP_POST, handle_upload_json, handleFileUpload); + web_server.on(F("/wifiscanner_json"), handle_wifiscanner_json); +#endif // WEBSERVER_NEW_UI +#if SHOW_SYSINFO_JSON + web_server.on(F("/sysinfo_json"), handle_sysinfo_json); +#endif//SHOW_SYSINFO_JSON + + web_server.onNotFound(handleNotFound); + + // List of headers to be recorded + // "If-None-Match" is used to see whether we need to serve a static file, or simply can reply with a 304 (not modified) + const char * headerkeys[] = {"If-None-Match"}; + constexpr size_t headerkeyssize = NR_ELEMENTS(headerkeys); + web_server.collectHeaders(headerkeys, headerkeyssize ); + #if defined(ESP8266) || defined(ESP32) + { + # ifndef NO_HTTP_UPDATER + uint32_t maxSketchSize; + bool use2step; + // allow OTA to smaller version of ESPEasy/other firmware + if (Settings.AllowOTAUnlimited() || OTA_possible(maxSketchSize, use2step)) { + httpUpdater.setup(&web_server); + } + # endif // ifndef NO_HTTP_UPDATER + } + #endif // if defined(ESP8266) + + #if defined(ESP8266) + + # if FEATURE_SSDP + + if (Settings.UseSSDP) + { + web_server.on(F("/ssdp.xml"), HTTP_GET, []() { + #ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + // See: https://github.com/espressif/arduino-esp32/pull/6676 + web_server.client().setTimeout((CONTROLLER_CLIENTTIMEOUT_DFLT + 500) / 1000); // in seconds!!!! + Client &pClient = web_server.client(); + pClient.setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); + #else // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + web_server.client().setTimeout(CONTROLLER_CLIENTTIMEOUT_DFLT); // in msec as it should be! + #endif // ifdef MUSTFIX_CLIENT_TIMEOUT_IN_SECONDS + + SSDP_schema(); + }); + SSDP_begin(); + } + # endif // if FEATURE_SSDP + #endif // if defined(ESP8266) +} + +void setWebserverRunning(bool state) { + if (webserverRunning == state) { + return; + } + + if (state) { + WebServerInit(); + web_server.begin(Settings.WebserverPort); + addLog(LOG_LEVEL_INFO, F("Webserver: start")); + } else { + web_server.client().stop(); + web_server.stop(); + addLog(LOG_LEVEL_INFO, F("Webserver: stop")); + } + webserverRunning = state; + CheckRunningServices(); // Uses webserverRunning state. +} + +void getWebPageTemplateDefault(const String& tmplName, WebTemplateParser& parser) +{ + const bool addJS = true; + const bool addMeta = true; + +/* + if (equals(tmplName, F("TmplAP"))) + { + + getWebPageTemplateDefaultHead(parser, addMeta, !addJS); + + if (!parser.isTail()) { + #ifndef WEBPAGE_TEMPLATE_AP_HEADER + parser.process(F("
" + "

Welcome to ESP Easy Mega AP

")); + #else + parser.process(F(WEBPAGE_TEMPLATE_AP_HEADER)); + #endif + + parser.process(F("
")); + } + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } + else + */ + if (equals(tmplName, F("TmplMsg"))) + { + getWebPageTemplateDefaultHead(parser, !addMeta, !addJS); + if (!parser.isTail()) { + parser.process(F("")); + } + getWebPageTemplateDefaultHeader(parser, F("{{name}}"), false); + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } + else if (equals(tmplName, F("TmplDsh"))) + { + getWebPageTemplateDefaultHead(parser, !addMeta, addJS); + parser.process(F("" + "{{content}}" + "")); + } + else // all other template names e.g. TmplStd + { + getWebPageTemplateDefaultHead(parser, addMeta, addJS); + if (!parser.isTail()) { + parser.process(F("")); + } + getWebPageTemplateDefaultHeader(parser, F("{{name}} {{logo}}"), true); + getWebPageTemplateDefaultContentSection(parser); + getWebPageTemplateDefaultFooter(parser); + } +// addLog(LOG_LEVEL_INFO, concat(F("tmpl.length(): "), tmpl.length())); +} + +void getWebPageTemplateDefaultHead(WebTemplateParser& parser, bool addMeta, bool addJS) { + if (parser.isTail()) return; + parser.process(F("" + "" + "" + "" + "{{name}}")); + + if (addMeta) { parser.process(F("{{meta}}")); } + + if (addJS) { parser.process(F("{{js}}")); } + + parser.process(F("{{css}}" + "")); +} + +void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, const __FlashStringHelper * title, bool addMenu) { + { + if (parser.isTail()) return; + #ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + parser.process(F("

ESP Easy Mega: ")); + parser.process(title); + #if BUILD_IN_WEBHEADER + parser.process(F("
Build: " GITHUB_RELEASES_LINK_PREFIX "{{date}}" GITHUB_RELEASES_LINK_SUFFIX "
")); + #endif // #if BUILD_IN_WEBHEADER + parser.process(F("


")); + #else // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + String tmp = F(WEBPAGE_TEMPLATE_DEFAULT_HEADER); + tmp.replace(F("{{title}}"), title); + parser.process(tmp); + #endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_HEADER + } + + if (addMenu) { parser.process(F("{{menu}}")); } + parser.process(F("
")); +} + +void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser) { + parser.process(F("
" + "" + "{{error}}" + "" + "{{content}}" + "
" + )); +} + +void getWebPageTemplateDefaultFooter(WebTemplateParser& parser) { + if (!parser.isTail()) return; + #ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F("
" + "
" + "
Powered by Let's Control It community" + #if BUILD_IN_WEBFOOTER + "
Build: " GITHUB_RELEASES_LINK_PREFIX "{{build}} {{date}}" GITHUB_RELEASES_LINK_SUFFIX "
" + #endif // #if BUILD_IN_WEBFOOTER + "
" + "
" + "" + )); +#else // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER + parser.process(F(WEBPAGE_TEMPLATE_DEFAULT_FOOTER)); +#endif // ifndef WEBPAGE_TEMPLATE_DEFAULT_FOOTER +} + + + +void writeDefaultCSS(void) +{ + return; // TODO + +/* +#ifndef WEBSERVER_USE_CDN_JS_CSS + + if (!fileExists(F("esp.css"))) + { + fs::File f = tryOpenFile(F("esp.css"), "w"); + + if (f) + { + String defaultCSS; + defaultCSS = PGMT(DATA_ESPEASY_DEFAULT_MIN_CSS); + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("CSS : Writing default CSS file to FS ("); + log += defaultCSS.length(); + log += F(" bytes)"); + addLog(LOG_LEVEL_INFO, log); + } + f.write((const unsigned char *)defaultCSS.c_str(), defaultCSS.length()); // note: content must be in RAM - a write of F("XXX") does + // not work + f.close(); + } + } +#endif +*/ +} + +// ******************************************************************************** +// Functions to stream JSON directly to TXBuffer +// FIXME TD-er: replace stream_xxx_json_object* into this code. +// N.B. handling of numerical values differs (string vs. no string) +// ******************************************************************************** + +int8_t level = 0; +int8_t lastLevel = -1; + +void json_quote_name(const __FlashStringHelper * val) { + json_quote_name(String(val)); +} + +void json_quote_name(const String& val) { + if (lastLevel == level) { + addHtml(','); + } + + if (val.length() > 0) { + json_quote_val(val); + addHtml(':'); + } +} + +void json_quote_val(const String& val) { + addHtml('\"'); + addHtml(val); + addHtml('\"'); +} + +void json_open(bool arr) { + json_open(arr, EMPTY_STRING); +} + +void json_open(bool arr, const __FlashStringHelper * name) { + json_quote_name(name); + addHtml(arr ? '[' : '{'); + lastLevel = level; + level++; +} + +void json_open(bool arr, const String& name) { + json_quote_name(name); + addHtml(arr ? '[' : '{'); + lastLevel = level; + level++; +} + +void json_init() { + level = 0; + lastLevel = -1; +} + +void json_close() { + json_close(false); +} + +void json_close(bool arr) { + addHtml(arr ? ']' : '}'); + level--; + lastLevel = level; +} + +void json_number(const __FlashStringHelper * name, const String& value) +{ + json_prop(name, value); +} + + +void json_number(const String& name, const String& value) { + json_prop(name, value); +} + +void json_prop(const __FlashStringHelper * name, const String& value) +{ + json_quote_name(name); + json_quote_val(value); + lastLevel = level; +} + + +void json_prop(const String& name, const String& value) { + json_quote_name(name); + json_quote_val(value); + lastLevel = level; +} + +void json_prop(LabelType::Enum label) { + json_prop(getInternalLabel(label, '-'), getValue(label)); +} + +// ******************************************************************************** +// 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) +{ + String deviceName; + + addHtml(F("'); + + const uint8_t valueCount = getValueCountForTask(TaskIndex); + + for (uint8_t x = 0; x < valueCount; x++) + { + addHtml(F("")); + } +} + + +// ******************************************************************************** +// Login state check +// ******************************************************************************** +bool isLoggedIn(bool mustProvideLogin) +{ + if (!clientIPallowed()) { return false; } + + if (SecuritySettings.Password[0] == 0) { return true; } + + if (!mustProvideLogin) { + return false; + } + + { + String www_username = F(DEFAULT_ADMIN_USERNAME); + if (!web_server.authenticate(www_username.c_str(), SecuritySettings.Password)) + + // Basic Auth Method with Custom realm and Failure Response + // return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse); + // Digest Auth Method with realm="Login Required" and empty Failure Response + // return server.requestAuthentication(DIGEST_AUTH); + // Digest Auth Method with Custom realm and empty Failure Response + // return server.requestAuthentication(DIGEST_AUTH, www_realm); + // Digest Auth Method with Custom realm and Failure Response + { +#ifdef CORE_PRE_2_5_0 + + // See https://github.com/esp8266/Arduino/issues/4717 + HTTPAuthMethod mode = BASIC_AUTH; +#else // ifdef CORE_PRE_2_5_0 + HTTPAuthMethod mode = DIGEST_AUTH; +#endif // ifdef CORE_PRE_2_5_0 + String message = F("Login Required (default user: "); + message += www_username; + message += ')'; + web_server.requestAuthentication(mode, message.c_str()); + + if (Settings.UseRules) + { + String event = F("Login#Failed"); + + // TD-er: Do not add to the eventQueue, but execute right now. + rulesProcessing(event); + } + + return false; + } + } + return true; +} + +String getControllerSymbol(uint8_t index) +{ + String ret = F("&#"); + + ret += 10102 + index; + ret += F(";"); + return ret; +} + +/* + String getValueSymbol(uint8_t index) + { + String ret = F("&#"); + ret += 10112 + index; + ret += ';'; + return ret; + } + */ + +void addSVG_param(const char key, int value) +{ + addHtml(strformat(F(" %c=\"%d\""), key, value)); +} + +void addSVG_param(const char key, float value) +{ + addSVG_param(key, toString(value, 2)); +} + +void addSVG_param(const char key, const String& value) +{ + addHtml(strformat(F(" %c=\"%s\""), key, value.c_str())); +} + + +void addSVG_param(const __FlashStringHelper * key, int value) { + addHtml(strformat(F(" %s=\"%d\""), key, value)); +} + +void addSVG_param(const __FlashStringHelper * key, float value) { + addSVG_param(key, toString(value, 2)); +} + +void addSVG_param(const __FlashStringHelper * key, const String& value) { + addHtml(strformat(F(" %s=\"%s\""), key, value.c_str())); +} + +void createSvgRect_noStroke(const __FlashStringHelper * classname, unsigned int fillColor, float xoffset, float yoffset, float width, float height, float rx, float ry) { + createSvgRect(classname, fillColor, fillColor, xoffset, yoffset, width, height, 0, rx, ry); +} + +void createSvgRect(const String& classname, + unsigned int fillColor, + unsigned int strokeColor, + float xoffset, + float yoffset, + float width, + float height, + float strokeWidth, + float rx, + float ry) { + addHtml(F("")); +} + +void createSvgHorRectPath(unsigned int color, int xoffset, int yoffset, int size, int height, int range, float SVG_BAR_WIDTH) { + if (range == 0) { + range = 1; + } + float width = (SVG_BAR_WIDTH * size) / range; + + if (width < 2) { width = 2; } + addHtml(formatToHex(color, F("\n")); +} + +void createSvgTextElement(const String& text, float textXoffset, float textYoffset) { + addHtml(F("', '\n'); + + addHtml(F("'); + + addHtml(text); + addHtml(F("\n")); +} + +#define SVG_BAR_HEIGHT 16 +#define SVG_BAR_WIDTH 400 + +void write_SVG_image_header(int width, int height, bool useViewbox) { + addHtml(F("'); + addHtml(F("")); +} + +/* + void getESPeasyLogo(int width_pixels) { + write_SVG_image_header(width_pixels, width_pixels, true); + addHtml(F(""); + } + */ +void getWiFi_RSSI_icon(int rssi, int width_pixels) +{ + const int nbars_filled = (rssi + 100) / 8; + const int nbars = 5; + int white_between_bar = (static_cast(width_pixels) / nbars) * 0.2f; + + if (white_between_bar < 1) { white_between_bar = 1; } + const int barWidth = (width_pixels - (nbars - 1) * white_between_bar) / nbars; + const int svg_width_pixels = nbars * barWidth + (nbars - 1) * white_between_bar; + + write_SVG_image_header(svg_width_pixels, svg_width_pixels, true); + const float scale = 100.0f / svg_width_pixels; + const int bar_height_step = 100 / nbars; + + for (int i = 0; i < nbars; ++i) { + const unsigned int color = i < nbars_filled ? 0x07d : 0xBFa1a1a1; // Blue/Grey75% + const int barHeight = (i + 1) * bar_height_step; + createSvgRect_noStroke(i < nbars_filled ? F("bar_highlight") : F("bar_dimmed"), color, i * (barWidth + white_between_bar) * scale, 100 - barHeight, barWidth, barHeight, 0, 0); + } + addHtml(F("\n")); +} + +#if FEATURE_CHART_STORAGE_LAYOUT +void getConfig_dat_file_layout() { + const int shiftY = 2; + float yOffset = shiftY; + + write_SVG_image_header(SVG_BAR_WIDTH + 250, SVG_BAR_HEIGHT + shiftY); + + int max_index, offset, max_size{}; + int struct_size = 0; + + // background + const uint32_t realSize = SettingsType::getFileSize(SettingsType::Enum::TaskSettings_Type); + + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + for (int st = 0; st < static_cast(SettingsType::Enum::SettingsType_MAX); ++st) { + SettingsType::Enum settingsType = static_cast(st); + + if (SettingsType::getSettingsFile(settingsType) == SettingsType::SettingsFileEnum::FILE_CONFIG_type) { + unsigned int color = SettingsType::getSVGcolor(settingsType); + if (SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) + { + for (int i = 0; i < max_index; ++i) { + if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { + // Struct position + createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + } + } + } + } + } + + // Text labels + constexpr float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + + createSvgTextElement(SettingsType::getSettingsFileName(SettingsType::Enum::TaskSettings_Type), textXoffset, textYoffset); + addHtml(F("\n")); +} + +void getStorageTableSVG(SettingsType::Enum settingsType) { + uint32_t realSize = SettingsType::getFileSize(settingsType); + unsigned int color = SettingsType::getSVGcolor(settingsType); + const int shiftY = 2; + + int max_index, offset, max_size{}; + int struct_size = 0; + + if (!SettingsType::getSettingsParameters(settingsType, 0, max_index, offset, max_size, struct_size)) + { + return; + } + + if (max_index == 0) { return; } + + // One more to add bar indicating struct size vs. reserved space. + write_SVG_image_header(SVG_BAR_WIDTH + 250, (max_index + 1) * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + for (int i = 0; i < max_index; ++i) { + if (SettingsType::getSettingsParameters(settingsType, i, offset, max_size)) { + // background + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + // Struct position + createSvgHorRectPath(color, offset, yOffset, max_size, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + + // Text labels + float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + createSvgTextElement(formatHumanReadable(offset, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 60; + createSvgTextElement(formatHumanReadable(max_size, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 130; + createSvgTextElement(String(i), textXoffset, textYoffset); + yOffset += SVG_BAR_HEIGHT; + } + } + + // usage + createSvgHorRectPath(0xcdcdcd, 0, yOffset, max_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); + + // Struct size (used part of the reserved space) + if (struct_size != 0) { + createSvgHorRectPath(color, 0, yOffset, struct_size, SVG_BAR_HEIGHT - 2, max_size, SVG_BAR_WIDTH); + } + + // Text labels + float textXoffset = SVG_BAR_WIDTH + 2; + float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + + if (struct_size != 0) { + String text; + text.reserve(32); + text += formatHumanReadable(struct_size, 1024); + text += '/'; + text += formatHumanReadable(max_size, 1024); + text += F(" per item"); + createSvgTextElement(text, textXoffset, textYoffset); + } else { + createSvgTextElement(F("Variable size"), textXoffset, textYoffset); + } + addHtml(F("\n")); +} + +void drawPartitionChartSVG( + float yOffset, + uint32_t realSize, + uint32_t partitionAddress, + uint32_t partitionSize, + unsigned int partitionColor, + const String& label, + const String& name) +{ + createSvgHorRectPath(0xcdcdcd, 0, yOffset, realSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + createSvgHorRectPath(partitionColor, partitionAddress, yOffset, partitionSize, SVG_BAR_HEIGHT - 2, realSize, SVG_BAR_WIDTH); + float textXoffset = SVG_BAR_WIDTH + 2; + const float textYoffset = yOffset + 0.9f * SVG_BAR_HEIGHT; + createSvgTextElement(formatHumanReadable(partitionSize, 1024), textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 60; + createSvgTextElement(label, textXoffset, textYoffset); + textXoffset = SVG_BAR_WIDTH + 130; + createSvgTextElement(name, textXoffset, textYoffset); +} + +#ifdef ESP32 + +# include + + +void getPartitionTableSVG(uint8_t pType, unsigned int partitionColor) { + int nrPartitions = getPartionCount(pType); + + if (nrPartitions == 0) { return; } + const int shiftY = 2; + + uint32_t realSize = getFlashRealSizeInBytes(); + esp_partition_type_t partitionType = static_cast(pType); + const esp_partition_t *_mypart; + esp_partition_iterator_t _mypartiterator = esp_partition_find(partitionType, ESP_PARTITION_SUBTYPE_ANY, nullptr); + + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + if (_mypartiterator) { + do { + _mypart = esp_partition_get(_mypartiterator); + drawPartitionChartSVG( + yOffset, + realSize, + _mypart->address, + _mypart->size, + partitionColor, + _mypart->label, + getPartitionType(_mypart->type, _mypart->subtype)); + yOffset += SVG_BAR_HEIGHT; + } while ((_mypartiterator = esp_partition_next(_mypartiterator)) != nullptr); + } + addHtml(F("\n")); + esp_partition_iterator_release(_mypartiterator); +} + +#endif // ifdef ESP32 + +#ifdef ESP8266 +void getPartitionTableSVG() { + // sketch / OTA / FS / EEPROM / RFcal / wifi + const int nrPartitions = 6; + const int shiftY = 2; + write_SVG_image_header(SVG_BAR_WIDTH + 250, nrPartitions * SVG_BAR_HEIGHT + shiftY); + float yOffset = shiftY; + + for (int i = 0; i < nrPartitions; ++i) { + const ESP8266_partition_type ptype = static_cast(i); + uint32_t partitionAddress = 0; + int32_t partitionSize = 0; + const int32_t partitionSector = getPartitionInfo(ptype, partitionAddress, partitionSize); + + const __FlashStringHelper * label = F(""); + String descr; + unsigned int partitionColor = 0xab56e6; + switch (ptype) { + case ESP8266_partition_type::sketch: + label = F("sketch"); + partitionColor = 0xab56e6; + break; + case ESP8266_partition_type::ota: + label = F("ota"); + partitionColor = 0x5856e6; + break; + case ESP8266_partition_type::fs: + label = F("fs"); + partitionColor = 0xff7f00; + #ifdef USE_LITTLEFS + descr = F("LittleFS"); + #else + descr = F("SPIFFS"); + #endif + break; + case ESP8266_partition_type::eeprom: + label = F("eeprom"); + descr = concat(F("sector:"), partitionSector); + partitionColor = 0x7fff00; + break; + case ESP8266_partition_type::rf_cal: + label = F("RFcal"); + partitionColor = 0xff007f; + break; + case ESP8266_partition_type::wifi: + label = F("WiFi"); + partitionColor = 0xff00ff; + break; + + } + + drawPartitionChartSVG( + yOffset, + getFlashRealSizeInBytes(), + partitionAddress, + partitionSize, + partitionColor, + label, + descr); + yOffset += SVG_BAR_HEIGHT; + +/* + String debuglog = concat(F("partition: "), (i+1)); + debuglog += concat(F(" FS_st: "), formatToHex((uint32_t)&_FS_start)); + debuglog += concat(F(" FS_end: "), formatToHex((uint32_t)&_FS_end)); + debuglog += concat(F(" EEPROM: "), formatToHex((uint32_t)&_EEPROM_start)); + debuglog += concat(F(" addr: "), formatToHex(partitionAddress, 8)); + debuglog += concat(F(" part.size: "), partitionSize); + debuglog += concat(F(" label: "), label); + addLog(LOG_LEVEL_INFO, debuglog); +*/ + } + addHtml(F("\n")); +} +#endif +#endif + +bool webArg2ip(const __FlashStringHelper * arg, uint8_t *IP) { + return str2ip(webArg(arg), IP); +} diff --git a/src/src/WebServer/ESPEasy_WebServer.h b/src/src/WebServer/ESPEasy_WebServer.h index 07563f1d0..60b4733eb 100644 --- a/src/src/WebServer/ESPEasy_WebServer.h +++ b/src/src/WebServer/ESPEasy_WebServer.h @@ -1,213 +1,214 @@ -#ifndef WEBSERVER_ESPEASY_WEBSERVER_H -#define WEBSERVER_ESPEASY_WEBSERVER_H - -#include "../WebServer/common.h" - - -#include "../CustomBuild/ESPEasyLimits.h" -#include "../DataTypes/SettingsType.h" -#include "../Globals/Plugins.h" -#include "../Helpers/StringConverter.h" - -#include "../WebServer/WebTemplateParser.h" - - -// Uncrustify must not be used on macros, so turn it off. -// *INDENT-OFF* -#define strncpy_webserver_arg(D, N) safe_strncpy_webserver_arg(D, N, sizeof(D)); -// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. -// *INDENT-ON* - -void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size); - -void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper * arg, size_t max_size); - -void sendHeadandTail(const __FlashStringHelper * tmplName, - bool Tail = false, - bool rebooting = false); - -void sendHeadandTail_stdtemplate(bool Tail, - bool rebooting = false); - - -void WebServerInit(); - -// ******************************************************************************** -// Redirect to captive portal if we got a request for another domain. -// Return true in that case so the page handler does not try to handle the request again. -// ******************************************************************************** -bool captivePortal(); - -void setWebserverRunning(bool state); - -void getWebPageTemplateDefault(const String& tmplName, - WebTemplateParser& parser); - -void getWebPageTemplateDefaultHead(WebTemplateParser& parser, - bool addMeta, - bool addJS); - -void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, - const __FlashStringHelper * title, - bool addMenu); - -void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser); - -void getWebPageTemplateDefaultFooter(WebTemplateParser& parser); - - -void writeDefaultCSS(void); - - -// ******************************************************************************** -// Functions to stream JSON directly to TXBuffer -// FIXME TD-er: replace stream_xxx_json_object* into this code. -// N.B. handling of numerical values differs (string vs. no string) -// ******************************************************************************** - -extern int8_t level; -extern int8_t lastLevel; - -void json_quote_name(const __FlashStringHelper * val); -void json_quote_name(const String& val); - -void json_quote_val(const String& val); - -void json_open(bool arr = false); - -void json_open(bool arr, - const __FlashStringHelper * name); - -void json_open(bool arr, - const String& name); - -void json_init(); - -void json_close(); - -void json_close(bool arr); - -void json_number(const __FlashStringHelper * name, - const String& value); - -void json_number(const String& name, - const String& value); - -void json_prop(const __FlashStringHelper * name, - const String& value); - -void json_prop(const String& name, - const String& value); - -void json_prop(LabelType::Enum label); - -// ******************************************************************************** -// Add a task select dropdown list -// This allows to select a task index based on the existing tasks. -// When changing a selected task, the page reloads with the new settings applied. -// However, these changes will not trigger a save, so make sure to store those in -// PCONFIG() and not in extra settings -// ******************************************************************************** -void addTaskSelect(const String& name, - taskIndex_t choice); - -// ******************************************************************************** -// Add a Value select dropdown list, based on TaskIndex -// This allows to select a task value, based on the existing tasks. -// ******************************************************************************** -void addTaskValueSelect(const String& name, - int choice, - taskIndex_t TaskIndex); - -// ******************************************************************************** -// Login state check -// ******************************************************************************** -bool isLoggedIn(bool mustProvideLogin = true); - -String getControllerSymbol(uint8_t index); - -/* - String getValueSymbol(uint8_t index); - */ -void addSVG_param(const char key, - int value); - -void addSVG_param(const char key, - float value); - -void addSVG_param(const char key, - const String& value); - -void addSVG_param(const __FlashStringHelper * key, - int value); - -void addSVG_param(const __FlashStringHelper * key, - float value); - -void addSVG_param(const __FlashStringHelper * key, - const String& value); - -void createSvgRect_noStroke(const __FlashStringHelper * classname, - unsigned int fillColor, - float xoffset, - float yoffset, - float width, - float height, - float rx, - float ry); - -void createSvgRect(const String& classname, - unsigned int fillColor, - unsigned int strokeColor, - float xoffset, - float yoffset, - float width, - float height, - float strokeWidth, - float rx, - float ry); - -void createSvgHorRectPath(unsigned int color, - int xoffset, - int yoffset, - int size, - int height, - int range, - float SVG_BAR_WIDTH); - -void createSvgTextElement(const String& text, - float textXoffset, - float textYoffset); - -void write_SVG_image_header(int width, - int height, - bool useViewbox = false); - -/* - void getESPeasyLogo(int width_pixels); - */ -void getWiFi_RSSI_icon(int rssi, - int width_pixels); - -#if FEATURE_CHART_STORAGE_LAYOUT -void getConfig_dat_file_layout(); - -void getStorageTableSVG(SettingsType::Enum settingsType); - -#ifdef ESP32 - -void getPartitionTableSVG(uint8_t pType, - unsigned int partitionColor); - -#endif // ifdef ESP32 -#ifdef ESP8266 -void getPartitionTableSVG(); -#endif - -#endif - -bool webArg2ip(const __FlashStringHelper * arg, - uint8_t *IP); - - +#ifndef WEBSERVER_ESPEASY_WEBSERVER_H +#define WEBSERVER_ESPEASY_WEBSERVER_H + +#include "../WebServer/common.h" + + +#include "../CustomBuild/ESPEasyLimits.h" +#include "../DataTypes/SettingsType.h" +#include "../Globals/Plugins.h" +#include "../Helpers/StringConverter.h" + +#include "../WebServer/WebTemplateParser.h" + + +// Uncrustify must not be used on macros, so turn it off. +// *INDENT-OFF* +#define strncpy_webserver_arg(D, N) safe_strncpy_webserver_arg(D, N, sizeof(D)); +// Uncrustify must not be used on macros, but we're now done, so turn Uncrustify on again. +// *INDENT-ON* + +void safe_strncpy_webserver_arg(char *dest, const String& arg, size_t max_size); + +void safe_strncpy_webserver_arg(char *dest, const __FlashStringHelper * arg, size_t max_size); + +void sendHeadandTail(const __FlashStringHelper * tmplName, + bool Tail = false, + bool rebooting = false); + +void sendHeadandTail_stdtemplate(bool Tail, + bool rebooting = false); + + +void WebServerInit(); + +// ******************************************************************************** +// Redirect to captive portal if we got a request for another domain. +// Return true in that case so the page handler does not try to handle the request again. +// ******************************************************************************** +bool captivePortal(); + +void setWebserverRunning(bool state); + +void getWebPageTemplateDefault(const String& tmplName, + WebTemplateParser& parser); + +void getWebPageTemplateDefaultHead(WebTemplateParser& parser, + bool addMeta, + bool addJS); + +void getWebPageTemplateDefaultHeader(WebTemplateParser& parser, + const __FlashStringHelper * title, + bool addMenu); + +void getWebPageTemplateDefaultContentSection(WebTemplateParser& parser); + +void getWebPageTemplateDefaultFooter(WebTemplateParser& parser); + + +void writeDefaultCSS(void); + + +// ******************************************************************************** +// Functions to stream JSON directly to TXBuffer +// FIXME TD-er: replace stream_xxx_json_object* into this code. +// N.B. handling of numerical values differs (string vs. no string) +// ******************************************************************************** + +extern int8_t level; +extern int8_t lastLevel; + +void json_quote_name(const __FlashStringHelper * val); +void json_quote_name(const String& val); + +void json_quote_val(const String& val); + +void json_open(bool arr = false); + +void json_open(bool arr, + const __FlashStringHelper * name); + +void json_open(bool arr, + const String& name); + +void json_init(); + +void json_close(); + +void json_close(bool arr); + +void json_number(const __FlashStringHelper * name, + const String& value); + +void json_number(const String& name, + const String& value); + +void json_prop(const __FlashStringHelper * name, + const String& value); + +void json_prop(const String& name, + const String& value); + +void json_prop(LabelType::Enum label); + +// ******************************************************************************** +// Add a task select dropdown list +// This allows to select a task index based on the existing tasks. +// When changing a selected task, the page reloads with the new settings applied. +// However, these changes will not trigger a save, so make sure to store those in +// PCONFIG() and not in extra settings +// ******************************************************************************** +void addTaskSelect(const String& name, + taskIndex_t choice, + const String& cssclass = "wide"); + +// ******************************************************************************** +// Add a Value select dropdown list, based on TaskIndex +// This allows to select a task value, based on the existing tasks. +// ******************************************************************************** +void addTaskValueSelect(const String& name, + int choice, + taskIndex_t TaskIndex); + +// ******************************************************************************** +// Login state check +// ******************************************************************************** +bool isLoggedIn(bool mustProvideLogin = true); + +String getControllerSymbol(uint8_t index); + +/* + String getValueSymbol(uint8_t index); + */ +void addSVG_param(const char key, + int value); + +void addSVG_param(const char key, + float value); + +void addSVG_param(const char key, + const String& value); + +void addSVG_param(const __FlashStringHelper * key, + int value); + +void addSVG_param(const __FlashStringHelper * key, + float value); + +void addSVG_param(const __FlashStringHelper * key, + const String& value); + +void createSvgRect_noStroke(const __FlashStringHelper * classname, + unsigned int fillColor, + float xoffset, + float yoffset, + float width, + float height, + float rx, + float ry); + +void createSvgRect(const String& classname, + unsigned int fillColor, + unsigned int strokeColor, + float xoffset, + float yoffset, + float width, + float height, + float strokeWidth, + float rx, + float ry); + +void createSvgHorRectPath(unsigned int color, + int xoffset, + int yoffset, + int size, + int height, + int range, + float SVG_BAR_WIDTH); + +void createSvgTextElement(const String& text, + float textXoffset, + float textYoffset); + +void write_SVG_image_header(int width, + int height, + bool useViewbox = false); + +/* + void getESPeasyLogo(int width_pixels); + */ +void getWiFi_RSSI_icon(int rssi, + int width_pixels); + +#if FEATURE_CHART_STORAGE_LAYOUT +void getConfig_dat_file_layout(); + +void getStorageTableSVG(SettingsType::Enum settingsType); + +#ifdef ESP32 + +void getPartitionTableSVG(uint8_t pType, + unsigned int partitionColor); + +#endif // ifdef ESP32 +#ifdef ESP8266 +void getPartitionTableSVG(); +#endif + +#endif + +bool webArg2ip(const __FlashStringHelper * arg, + uint8_t *IP); + + #endif // ifndef WEBSERVER_ESPEASY_WEBSERVER_H \ No newline at end of file diff --git a/src/src/WebServer/HTML_wrappers.cpp b/src/src/WebServer/HTML_wrappers.cpp index c1f485688..43ea53a9c 100644 --- a/src/src/WebServer/HTML_wrappers.cpp +++ b/src/src/WebServer/HTML_wrappers.cpp @@ -1,569 +1,586 @@ -#include "../WebServer/HTML_wrappers.h" - -#include "../Static/WebStaticData.h" - -#include "../WebServer/Markup.h" - -#include "../Helpers/StringConverter.h" - -#include "../Globals/Settings.h" - -// ******************************************************************************** -// HTML string re-use to keep the executable smaller -// Flash strings are not checked for duplication. -// ******************************************************************************** -void wrap_html_tag(const __FlashStringHelper * tag, const String& text) { - addHtml('<'); - addHtml(tag); - addHtml('>'); - addHtml(text); - addHtml('<', '/'); - addHtml(tag); - addHtml('>'); -} - -void wrap_html_tag(const String& tag, const String& text) { - addHtml(strformat( - F("<%s>%s"), - tag.c_str(), - text.c_str(), - tag.c_str())); -} - -void wrap_html_tag(char tag, const String& text) { - addHtml(strformat( - F("<%c>%s"), - tag, - text.c_str(), - tag)); -} - -void html_B(const __FlashStringHelper * text) { - wrap_html_tag('b', text); -} - -void html_B(const String& text) { - wrap_html_tag('b', text); -} - -void html_I(const String& text) { - wrap_html_tag('i', text); -} - -void html_U(const String& text) { - wrap_html_tag('u', text); -} - -void html_TR_TD_highlight() { - addHtml(F("
"), height)); -} - -void html_TD() { - addHtml(F("")); -} - -void html_TD(const __FlashStringHelper * style) { - addHtml(F("")); -} - -void html_TD(int td_cnt) { - for (int i = 0; i < td_cnt; ++i) { - html_TD(); - } -} - -int copyTextCounter = 0; - -void html_reset_copyTextCounter() { - copyTextCounter = 0; -} - -void html_copyText_TD() { - ++copyTextCounter; - - addHtml(strformat( - F(""), copyTextCounter)); -} - -// Add some recognizable token to show which parts will be copied. -void html_copyText_marker() { - addHtml(F("⋄")); // ⋄ ⋄ ⋄ ⋄ ⋄ -} - -void html_add_estimate_symbol() { - addHtml(F(" ≙ ")); // ≙ ≙ ≙ -} - -void html_table_class_normal() { - html_table(F("normal")); -} - -void html_table_class_multirow() { - html_table(F("multirow even"), true); -} - -void html_table_class_multirow_noborder() { - html_table(F("multirow even"), false); -} - -void html_table(const __FlashStringHelper * tableclass, bool boxed) { - html_table(String(tableclass), boxed); -} - -void html_table(const String& tableclass, bool boxed) { - addHtml(F("'); -} - -void html_table_header(const __FlashStringHelper * label) { - html_table_header(label, 0); -} - -void html_table_header(const String& label) { - html_table_header(label, 0); -} - -void html_table_header(const __FlashStringHelper * label, int width) { - html_table_header(label, F(""), F(""), width); -} - -void html_table_header(const String& label, int width) { - html_table_header(label, EMPTY_STRING, EMPTY_STRING, width); -} - -void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, int width) { - html_table_header(label, helpButton, F(""), width); -} - -void html_table_header(const String& label, const __FlashStringHelper * helpButton, int width) { - html_table_header(label, helpButton, EMPTY_STRING, width); -} - -void html_table_header(const __FlashStringHelper * label, const String& helpButton, int width) { - html_table_header(label, helpButton, EMPTY_STRING, width); -} - -void html_table_header(const String& label, const String& helpButton, int width) { - html_table_header(label, helpButton, EMPTY_STRING, width); -} - -void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, const String& rtdHelpButton, int width) { - html_table_header(String(label), String(helpButton), rtdHelpButton, width); -} - -void html_table_header(const String& label, const __FlashStringHelper * helpButton, const String& rtdHelpButton, int width) { - html_table_header(label, String(helpButton), rtdHelpButton, width); -} - -void html_table_header(const __FlashStringHelper * label, const String& helpButton, const String& rtdHelpButton, int width) { - html_table_header(String(label), helpButton, rtdHelpButton, width); -} - -void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, const __FlashStringHelper * rtdHelpButton, int width) { - html_table_header(String(label), String(helpButton), String(rtdHelpButton), width); -} - -void html_table_header(const String& label, const __FlashStringHelper * helpButton, const __FlashStringHelper * rtdHelpButton, int width) { - html_table_header(label, String(helpButton), String(rtdHelpButton), width); -} - -void html_table_header(const __FlashStringHelper * label, const String& helpButton, const __FlashStringHelper * rtdHelpButton, int width) { - html_table_header(String(label), helpButton, String(rtdHelpButton), width); -} - -void html_table_header(const String& label, const String& helpButton, const String& rtdHelpButton, int width) { - addHtml(F(" 0) { - addHtml(strformat( - F(" style='width:%dpx;'"), width)); - } - addHtml('>'); - addHtml(label); - - if (helpButton.length() > 0) { - addHelpButton(helpButton); - } - - if (rtdHelpButton.length() > 0) { - addRTDHelpButton(rtdHelpButton); - } - addHtml(F("")); -} - -void html_end_table() { - addHtml(F("
")); -} - -void html_end_form() { - addHtml(F("")); -} - -void html_add_button_prefix() { - html_add_button_prefix(EMPTY_STRING, true); -} - -void html_add_button_prefix(const __FlashStringHelper * classes, bool enabled) { - html_add_button_prefix(String(classes), enabled); -} - -void html_add_button_prefix(const String& classes, bool enabled) { - 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("")); -} - -#if FEATURE_CHART_JS -void html_add_ChartJS_script() { - // To update the CDN link go to: https://www.chartjs.org/docs/latest/getting-started/installation.html - // - Select a CDN (jsdelivr is fine) - // - 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.0/dist/chart.umd.min.js" - #endif // ifndef CDN_URL_CHART_JS - addHtml(F("")); -} -#endif // if FEATURE_CHART_JS - -#if FEATURE_RULES_EASY_COLOR_CODE -void html_add_Easy_color_code_script() { - serve_JS(JSfiles_e::EasyColorCode_codemirror); - serve_JS(JSfiles_e::EasyColorCode_espeasy); - serve_JS(JSfiles_e::EasyColorCode_cm_plugins); -} -#endif - -void html_add_autosubmit_form() { - addHtml(F("")); -} - -void html_add_script(const __FlashStringHelper * script, bool defer) { - html_add_script(defer); - addHtml(script); - html_add_script_end(); -} - -void html_add_script(const String& script, bool defer) { - html_add_script(defer); - addHtml(script); - html_add_script_end(); -} - -void html_add_script(bool defer) { - html_add_script_arg(F(""), defer); -} - -void html_add_script_arg(const __FlashStringHelper * script_arg, bool defer) { - addHtml(F("'); -} - -void html_add_script_end() { - addHtml(F("")); -} - -// if there is an error-string, add it to the html code with correct formatting -void addHtmlError(const __FlashStringHelper * error) { - addHtmlError(String(error)); -} - -void addHtmlError(const String& error) { - if (error.length() > 0) - { - addHtml(F("
×")); - addHtml(error); - addHtml(F("
")); - } -} - -void addHtml(const char& char1) { - TXBuffer += char1; -} - -void addHtml(const char& char1, const char& char2) { - TXBuffer += char1; - TXBuffer += char2; -} - -void addHtml(const __FlashStringHelper * html) { - TXBuffer.addFlashString((PGM_P)html); -} - -void addHtml(const String& html) { - TXBuffer += html; -} - -void addHtml(String&& html) { - TXBuffer += html; -} - -void addHtmlInt(int8_t int_val) { - addHtml(String(int_val)); -} - -void addHtmlInt(uint8_t int_val) { - addHtml(String(int_val)); -} - -void addHtmlInt(int16_t int_val) { - addHtml(String(int_val)); -} - -#if ESP_IDF_VERSION_MAJOR >= 5 -#ifndef __riscv -void addHtmlInt(int int_val) { - addHtml(String(int_val)); -} - -void addHtmlInt(unsigned int int_val) { - addHtml(String(int_val)); -} -#endif -#endif - -void addHtmlInt(int32_t int_val) { - addHtml(String(int_val)); -} - -void addHtmlInt(uint32_t int_val) { - addHtml(String(int_val)); -} - -void addHtmlInt(int64_t int_val) { - addHtml(ll2String(int_val)); -} - -void addHtmlInt(uint64_t int_val) { - addHtml(ull2String(int_val)); -} - -void addHtmlFloat(const float& value, unsigned int nrDecimals) { - addHtml(toString(value, nrDecimals)); -} - -#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE -void addHtmlFloat(const double& value, unsigned int nrDecimals) { - addHtml(doubleToString(value, nrDecimals)); -} -#endif - - -void addEncodedHtml(const __FlashStringHelper * html) { - // FIXME TD-er: What about the function htmlStrongEscape ?? - addEncodedHtml(String(html)); -} - -void addEncodedHtml(const String& html) { - // FIXME TD-er: What about the function htmlStrongEscape ?? - String copy(html); - - htmlEscape(copy); - addHtml(copy); -} - -void addHtmlAttribute(char label, int value) { - addHtmlAttribute(String(label), value); -} - -void addHtmlAttribute(char label, float value) { - addHtmlAttribute(String(label), toString(value, 2)); -} - -void addHtmlAttribute(const __FlashStringHelper * label, int value) { - addHtml(' '); - addHtml(label); - addHtml('='); - addHtmlInt(value); - addHtml(' '); -} - -void addHtmlAttribute(const __FlashStringHelper * label, float value) { - addHtmlAttribute(label, toString(value, 2)); -} - -void addHtmlAttribute(const String& label, int value) { - addHtml(' '); - addHtml(label); - addHtml('='); - addHtmlInt(value); - addHtml(' '); -} - -void addHtmlAttribute(const __FlashStringHelper * label, const __FlashStringHelper * value) { - addHtmlAttribute(label, String(value)); -} - -void addHtmlAttribute(const __FlashStringHelper * label, const String& value) { - addHtml(' '); - addHtml(label); - addHtml(F("='")); - addEncodedHtml(value); - addHtml(F("' ")); -} - -void addHtmlAttribute(const String& label, const String& value) { - addHtml(' '); - addHtml(label); - addHtml(F("='")); - addEncodedHtml(value); - addHtml(F("' ")); -} - -void addDisabled() { - addHtml(F(" disabled")); -} - -void addHtmlLink(const String& htmlclass, const String& url, const String& label) { - addHtml(F("
'); - addHtml(label); - addHtml(F("")); -} - -void addHtmlDiv(const __FlashStringHelper * htmlclass, const String& content, const String& id, const String& attribute) -{ - addHtmlDiv(String(htmlclass), content, id, attribute); -} - - -void addHtmlDiv(const String& htmlclass) { - addHtmlDiv(htmlclass, EMPTY_STRING); -} - -void addHtmlDiv(const String& htmlclass, const String& content) { - addHtmlDiv(htmlclass, content, EMPTY_STRING); -} - -void addHtmlDiv(const String& htmlclass, const String& content, const String& id, const String& attribute) { - addHtml(F("
0) { - addHtmlAttribute(F("id"), id); - } - if (attribute.length() > 0) { - addHtml(' '); - addHtml(attribute); - } - addHtml('>'); - addHtml(content); - addHtml(F("
")); -} - -void addEnabled(boolean enabled) -{ - addHtml(F("✔")); - } - else { - addHtml(F("off'>❌")); - } - addHtml(F("")); -} - -void addGpioHtml(int8_t pin) { - if (pin == -1) { return; } - addHtml(formatGpioLabel(pin, false)); - - if (Settings.isSPI_pin(pin) || - Settings.isI2C_pin(pin) || - Settings.isEthernetPin(pin) || - Settings.isEthernetPinOptional(pin)) { - addHtml(' '); - addHtml(F(HTML_SYMBOL_WARNING)); - } -} - -void Label_Gpio_toHtml(const __FlashStringHelper *label, const String& gpio_pin_descr) { - addHtml(label); - addHtml(':'); - addHtml(F(" ")); - addHtml(gpio_pin_descr); -} +#include "../WebServer/HTML_wrappers.h" + +#include "../Static/WebStaticData.h" + +#include "../WebServer/Markup.h" + +#include "../Helpers/StringConverter.h" + +#include "../Globals/Settings.h" + +// ******************************************************************************** +// HTML string re-use to keep the executable smaller +// Flash strings are not checked for duplication. +// ******************************************************************************** +void wrap_html_tag(const __FlashStringHelper * tag, const String& text) { + addHtml('<'); + addHtml(tag); + addHtml('>'); + addHtml(text); + addHtml('<', '/'); + addHtml(tag); + addHtml('>'); +} + +void wrap_html_tag(const String& tag, const String& text) { + addHtml(strformat( + F("<%s>%s"), + tag.c_str(), + text.c_str(), + tag.c_str())); +} + +void wrap_html_tag(char tag, const String& text) { + addHtml(strformat( + F("<%c>%s"), + tag, + text.c_str(), + tag)); +} + +void html_B(const __FlashStringHelper * text) { + wrap_html_tag('b', text); +} + +void html_B(const String& text) { + wrap_html_tag('b', text); +} + +void html_I(const String& text) { + wrap_html_tag('i', text); +} + +void html_U(const String& text) { + wrap_html_tag('u', text); +} + +void html_TR_TD_highlight() { + addHtml(F("
"), height)); +} + +void html_TD() { + addHtml(F("")); +} + +void html_TD(const __FlashStringHelper * style) { + addHtml(F("")); +} + +void html_TD(int td_cnt) { + for (int i = 0; i < td_cnt; ++i) { + html_TD(); + } +} + +int copyTextCounter = 0; + +void html_reset_copyTextCounter() { + copyTextCounter = 0; +} + +void html_copyText_TD() { + ++copyTextCounter; + + addHtml(strformat( + F(""), copyTextCounter)); +} + +// Add some recognizable token to show which parts will be copied. +void html_copyText_marker() { + addHtml(F("⋄")); // ⋄ ⋄ ⋄ ⋄ ⋄ +} + +void html_add_estimate_symbol() { + addHtml(F(" ≙ ")); // ≙ ≙ ≙ +} + +void html_table_class_normal() { + html_table(F("normal")); +} + +void html_table_class_multirow() { + html_table(F("multirow even"), true); +} + +void html_table_class_multirow_noborder() { + html_table(F("multirow even"), false); +} + +void html_table(const __FlashStringHelper * tableclass, bool boxed) { + html_table(String(tableclass), boxed); +} + +void html_table(const String& tableclass, bool boxed) { + addHtml(F("'); +} + +void html_table_header(const __FlashStringHelper * label) { + html_table_header(label, 0); +} + +void html_table_header(const String& label) { + html_table_header(label, 0); +} + +void html_table_header(const __FlashStringHelper * label, int width) { + html_table_header(label, F(""), F(""), width); +} + +void html_table_header(const String& label, int width) { + html_table_header(label, EMPTY_STRING, EMPTY_STRING, width); +} + +void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, int width) { + html_table_header(label, helpButton, F(""), width); +} + +void html_table_header(const String& label, const __FlashStringHelper * helpButton, int width) { + html_table_header(label, helpButton, EMPTY_STRING, width); +} + +void html_table_header(const __FlashStringHelper * label, const String& helpButton, int width) { + html_table_header(label, helpButton, EMPTY_STRING, width); +} + +void html_table_header(const String& label, const String& helpButton, int width) { + html_table_header(label, helpButton, EMPTY_STRING, width); +} + +void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, const String& rtdHelpButton, int width) { + html_table_header(String(label), String(helpButton), rtdHelpButton, width); +} + +void html_table_header(const String& label, const __FlashStringHelper * helpButton, const String& rtdHelpButton, int width) { + html_table_header(label, String(helpButton), rtdHelpButton, width); +} + +void html_table_header(const __FlashStringHelper * label, const String& helpButton, const String& rtdHelpButton, int width) { + html_table_header(String(label), helpButton, rtdHelpButton, width); +} + +void html_table_header(const __FlashStringHelper * label, const __FlashStringHelper * helpButton, const __FlashStringHelper * rtdHelpButton, int width) { + html_table_header(String(label), String(helpButton), String(rtdHelpButton), width); +} + +void html_table_header(const String& label, const __FlashStringHelper * helpButton, const __FlashStringHelper * rtdHelpButton, int width) { + html_table_header(label, String(helpButton), String(rtdHelpButton), width); +} + +void html_table_header(const __FlashStringHelper * label, const String& helpButton, const __FlashStringHelper * rtdHelpButton, int width) { + html_table_header(String(label), helpButton, String(rtdHelpButton), width); +} + +void html_table_header(const String& label, const String& helpButton, const String& rtdHelpButton, int width) { + addHtml(F(" 0) { + addHtml(strformat( + F(" style='width:%dpx;'"), width)); + } + addHtml('>'); + addHtml(label); + + if (helpButton.length() > 0) { + addHelpButton(helpButton); + } + + if (rtdHelpButton.length() > 0) { + addRTDHelpButton(rtdHelpButton); + } + addHtml(F("")); +} + +void html_end_table() { + addHtml(F("
")); +} + +void html_end_form() { + addHtml(F("")); +} + +void html_add_button_prefix() { + html_add_button_prefix(EMPTY_STRING, true); +} + +void html_add_button_prefix(const __FlashStringHelper * classes, bool enabled) { + html_add_button_prefix(String(classes), enabled); +} + +void html_add_button_prefix(const String& classes, bool enabled) { + 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("")); +} + +#if FEATURE_CHART_JS +void html_add_ChartJS_script() { + // To update the CDN link go to: https://www.chartjs.org/docs/latest/getting-started/installation.html + // - Select a CDN (jsdelivr is fine) + // - 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" + #endif // ifndef CDN_URL_CHART_JS + + #ifndef CDN_URL_CHART_JS_ADAPTER_DATE + #define CDN_URL_CHART_JS_ADAPTER_DATE "https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js" + #endif + + #ifndef CDN_URL_CHART_JS_HAMMERJS + #define CDN_URL_CHART_JS_HAMMERJS "https://cdn.jsdelivr.net/npm/hammerjs@2.0.8" + #endif + + #ifndef CDN_URL_CHART_JS_PLUGIN_ZOOM + #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 + + + addHtml(F("")); + addHtml(F("")); + addHtml(F("")); + addHtml(F("")); +} +#endif // if FEATURE_CHART_JS + +#if FEATURE_RULES_EASY_COLOR_CODE +void html_add_Easy_color_code_script() { + serve_JS(JSfiles_e::EasyColorCode_codemirror); + serve_JS(JSfiles_e::EasyColorCode_espeasy); + serve_JS(JSfiles_e::EasyColorCode_cm_plugins); +} +#endif + +void html_add_autosubmit_form() { + addHtml(F("")); +} + +void html_add_script(const __FlashStringHelper * script, bool defer) { + html_add_script(defer); + addHtml(script); + html_add_script_end(); +} + +void html_add_script(const String& script, bool defer) { + html_add_script(defer); + addHtml(script); + html_add_script_end(); +} + +void html_add_script(bool defer) { + html_add_script_arg(F(""), defer); +} + +void html_add_script_arg(const __FlashStringHelper * script_arg, bool defer) { + addHtml(F("'); +} + +void html_add_script_end() { + addHtml(F("")); +} + +// if there is an error-string, add it to the html code with correct formatting +void addHtmlError(const __FlashStringHelper * error) { + addHtmlError(String(error)); +} + +void addHtmlError(const String& error) { + if (error.length() > 0) + { + addHtml(F("
×")); + addHtml(error); + addHtml(F("
")); + } +} + +void addHtml(const char& char1) { + TXBuffer += char1; +} + +void addHtml(const char& char1, const char& char2) { + TXBuffer += char1; + TXBuffer += char2; +} + +void addHtml(const __FlashStringHelper * html) { + TXBuffer.addFlashString((PGM_P)html); +} + +void addHtml(const String& html) { + TXBuffer += html; +} + +void addHtml(String&& html) { + TXBuffer += html; +} + +void addHtmlInt(int8_t int_val) { + addHtml(String(int_val)); +} + +void addHtmlInt(uint8_t int_val) { + addHtml(String(int_val)); +} + +void addHtmlInt(int16_t int_val) { + addHtml(String(int_val)); +} + +#if ESP_IDF_VERSION_MAJOR >= 5 +#ifndef __riscv +void addHtmlInt(int int_val) { + addHtml(String(int_val)); +} + +void addHtmlInt(unsigned int int_val) { + addHtml(String(int_val)); +} +#endif +#endif + +void addHtmlInt(int32_t int_val) { + addHtml(String(int_val)); +} + +void addHtmlInt(uint32_t int_val) { + addHtml(String(int_val)); +} + +void addHtmlInt(int64_t int_val) { + addHtml(ll2String(int_val)); +} + +void addHtmlInt(uint64_t int_val) { + addHtml(ull2String(int_val)); +} + +void addHtmlFloat(const float& value, unsigned int nrDecimals) { + addHtml(toString(value, nrDecimals)); +} + +#if FEATURE_USE_DOUBLE_AS_ESPEASY_RULES_FLOAT_TYPE +void addHtmlFloat(const double& value, unsigned int nrDecimals) { + addHtml(doubleToString(value, nrDecimals)); +} +#endif + + +void addEncodedHtml(const __FlashStringHelper * html) { + // FIXME TD-er: What about the function htmlStrongEscape ?? + addEncodedHtml(String(html)); +} + +void addEncodedHtml(const String& html) { + // FIXME TD-er: What about the function htmlStrongEscape ?? + String copy(html); + + htmlEscape(copy); + addHtml(copy); +} + +void addHtmlAttribute(char label, int value) { + addHtmlAttribute(String(label), value); +} + +void addHtmlAttribute(char label, float value) { + addHtmlAttribute(String(label), toString(value, 2)); +} + +void addHtmlAttribute(const __FlashStringHelper * label, int value) { + addHtml(' '); + addHtml(label); + addHtml('='); + addHtmlInt(value); + addHtml(' '); +} + +void addHtmlAttribute(const __FlashStringHelper * label, float value) { + addHtmlAttribute(label, toString(value, 2)); +} + +void addHtmlAttribute(const String& label, int value) { + addHtml(' '); + addHtml(label); + addHtml('='); + addHtmlInt(value); + addHtml(' '); +} + +void addHtmlAttribute(const __FlashStringHelper * label, const __FlashStringHelper * value) { + addHtmlAttribute(label, String(value)); +} + +void addHtmlAttribute(const __FlashStringHelper * label, const String& value) { + addHtml(' '); + addHtml(label); + addHtml(F("='")); + addEncodedHtml(value); + addHtml(F("' ")); +} + +void addHtmlAttribute(const String& label, const String& value) { + addHtml(' '); + addHtml(label); + addHtml(F("='")); + addEncodedHtml(value); + addHtml(F("' ")); +} + +void addDisabled() { + addHtml(F(" disabled")); +} + +void addHtmlLink(const String& htmlclass, const String& url, const String& label) { + addHtml(F("
'); + addHtml(label); + addHtml(F("")); +} + +void addHtmlDiv(const __FlashStringHelper * htmlclass, const String& content, const String& id, const String& attribute) +{ + addHtmlDiv(String(htmlclass), content, id, attribute); +} + + +void addHtmlDiv(const String& htmlclass) { + addHtmlDiv(htmlclass, EMPTY_STRING); +} + +void addHtmlDiv(const String& htmlclass, const String& content) { + addHtmlDiv(htmlclass, content, EMPTY_STRING); +} + +void addHtmlDiv(const String& htmlclass, const String& content, const String& id, const String& attribute) { + addHtml(F("
0) { + addHtmlAttribute(F("id"), id); + } + if (attribute.length() > 0) { + addHtml(' '); + addHtml(attribute); + } + addHtml('>'); + addHtml(content); + addHtml(F("
")); +} + +void addEnabled(boolean enabled) +{ + addHtml(F("✔")); + } + else { + addHtml(F("off'>❌")); + } + addHtml(F("")); +} + +void addGpioHtml(int8_t pin) { + if (pin == -1) { return; } + addHtml(formatGpioLabel(pin, false)); + + if (Settings.isSPI_pin(pin) || + Settings.isI2C_pin(pin) || + Settings.isEthernetPin(pin) || + Settings.isEthernetPinOptional(pin)) { + addHtml(' '); + addHtml(F(HTML_SYMBOL_WARNING)); + } +} + +void Label_Gpio_toHtml(const __FlashStringHelper *label, const String& gpio_pin_descr) { + addHtml(label); + addHtml(':'); + addHtml(F(" ")); + addHtml(gpio_pin_descr); +} diff --git a/src/src/WebServer/HardwarePage.cpp b/src/src/WebServer/HardwarePage.cpp index 0029b93da..c91e85446 100644 --- a/src/src/WebServer/HardwarePage.cpp +++ b/src/src/WebServer/HardwarePage.cpp @@ -72,11 +72,13 @@ void handle_hardware() { Settings.Pin_sd_cs = getFormItemInt(F("sd")); #if FEATURE_ETHERNET Settings.ETH_Phy_Addr = getFormItemInt(F("ethphy")); - Settings.ETH_Pin_mdc = getFormItemInt(F("ethmdc")); - Settings.ETH_Pin_mdio = getFormItemInt(F("ethmdio")); - Settings.ETH_Pin_power = getFormItemInt(F("ethpower")); + 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; @@ -242,41 +244,109 @@ void handle_hardware() { addSelector(F("ethwifi"), 2, ethWifiOptions, nullptr, nullptr, static_cast(Settings.NetworkMedium), false, true); } addFormNote(F("Change Switch between WiFi and Ethernet requires reboot to activate")); - addRowLabel_tr_id(F("Ethernet PHY type"), F("ethtype")); { - #if ESP_IDF_VERSION_MAJOR > 3 - const uint32_t nrItems = 5; - #else - const uint32_t nrItems = 2; - #endif - const __FlashStringHelper * ethPhyTypes[nrItems] = { - toString(EthPhyType_t::LAN8710), - toString(EthPhyType_t::TLK110) - #if ESP_IDF_VERSION_MAJOR > 3 - , - toString(EthPhyType_t::RTL8201), - toString(EthPhyType_t::DP83848), - toString(EthPhyType_t::DM9051) - #endif + 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), + toString(EthPhyType_t::JL1101), + 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::LAN8710), - static_cast(EthPhyType_t::TLK110) - #if ESP_IDF_VERSION_MAJOR > 3 - , - static_cast(EthPhyType_t::RTL8201), - static_cast(EthPhyType_t::DP83848), - static_cast(EthPhyType_t::DM9051) - #endif + 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), + static_cast(EthPhyType_t::JL1101), + 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 }; - addSelector(F("ethtype"), nrItems, ethPhyTypes, ethPhyTypes_index, nullptr, static_cast(Settings.ETH_Phy_Type), false, true); + 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); + + addFormSelector( + F("Ethernet PHY type"), + F("ethtype"), + nrItems, + ethPhyTypes, + ethPhyTypes_index, + choice, + false); } + +#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 (0 or 1 for LAN8720, 31 for TLK110, -1 autodetect)")); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_output(F("Ethernet MDC pin")), F("ethmdc"), Settings.ETH_Pin_mdc); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_input(F("Ethernet MIO pin")), F("ethmdio"), Settings.ETH_Pin_mdio); - addFormPinSelect(PinSelectPurpose::Ethernet, formatGpioName_output(F("Ethernet Power pin")), F("ethpower"), Settings.ETH_Pin_power); + 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[4] = { @@ -287,6 +357,7 @@ void handle_hardware() { }; addSelector(F("ethclock"), 4, ethClockOptions, nullptr, nullptr, static_cast(Settings.ETH_Clock_Mode), false, true); } +#endif #endif // if FEATURE_ETHERNET addFormSubHeader(F("GPIO boot states")); diff --git a/src/src/WebServer/I2C_Scanner.cpp b/src/src/WebServer/I2C_Scanner.cpp index 7bfb3e842..7b3de924f 100644 --- a/src/src/WebServer/I2C_Scanner.cpp +++ b/src/src/WebServer/I2C_Scanner.cpp @@ -180,25 +180,29 @@ String getKnownI2Cdevice(uint8_t address) { switch (address) { case 0x10: - result += F("VEML6075"); + result += F("VEML6075,VEML6040,VEML6030,VEML7700"); break; case 0x11: result += F("VEML6075,I2C_MultiRelay"); break; case 0x12: case 0x13: - case 0x14: - case 0x15: case 0x16: case 0x17: case 0x18: result += F("I2C_MultiRelay"); break; + case 0x14: + result += F("I2C_MultiRelay,GT911"); + break; + case 0x15: + result += F("I2C_MultiRelay,CST820"); + break; case 0x1D: - result += F("ADXL345"); + result += F("ADXL345"); break; case 0x1E: - result += F("HMC5883L"); + result += F("HMC5883L"); break; case 0x20: case 0x21: @@ -206,112 +210,123 @@ String getKnownI2Cdevice(uint8_t address) { case 0x25: case 0x26: case 0x27: - result += F("PCF8574,MCP23017,LCD,PCF8575"); + result += F("PCF8574,MCP23017,LCD,PCF8575"); break; case 0x23: - result += F("PCF8574,MCP23017,LCD,BH1750,PCF8575"); + result += F("PCF8574,MCP23017,LCD,BH1750,PCF8575"); break; case 0x24: - result += F("PCF8574,MCP23017,LCD,PN532,PCF8575"); + result += F("PCF8574,MCP23017,LCD,PN532,PCF8575"); break; case 0x29: - result += F("TSL2561,TSL2591,TCS34725,VL53L0X,VL53L1X"); + result += F("TSL2561,TSL2591,TCS34725,VL53L0X,VL53L1X"); + break; + case 0x2E: + result += F("CHSC5816"); break; case 0x30: - result += F("VL53L0X,VL53L1X"); + result += F("VL53L0X,VL53L1X"); break; case 0x34: - result += F("AXP192"); + result += F("AXP192"); break; case 0x36: - result += F("MAX1704x,Adafruit Rotary enc"); + result += F("MAX1704x,Adafruit Rotary enc, Adafruit Soil moisture"); break; case 0x37: - result += F("Adafruit Rotary enc"); + result += F("Adafruit Rotary enc, Adafruit Soil moisture"); break; case 0x38: - result += F("LCD,PCF8574A,AHT10/20/21,VEML6070,Adafruit Rotary enc"); + result += F("LCD,PCF8574A,AHT10/20/21,VEML6070,Adafruit Rotary enc,FT62x6,Adafruit Soil moisture,DHT20,AM2301B"); break; case 0x39: - result += F("LCD,PCF8574A,TSL2561,APDS9960,AHT10,Adafruit Rotary enc"); + result += F("LCD,PCF8574A,TSL2561,APDS9960,AHT10,Adafruit Rotary enc,Adafruit Soil moisture"); break; case 0x3A: + result += F("LCD,PCF8574A,Adafruit Rotary enc"); + break; case 0x3B: - result += F("LCD,PCF8574A,Adafruit Rotary enc"); + result += F("LCD,PCF8574A,Adafruit Rotary enc,AXS15231"); break; case 0x3C: case 0x3D: - result += F("LCD,PCF8574A,OLED,Adafruit Rotary enc"); + result += F("LCD,PCF8574A,OLED,Adafruit Rotary enc"); break; case 0x3E: case 0x3F: - result += F("LCD,PCF8574A"); + result += F("LCD,PCF8574A"); break; case 0x40: - result += F("SI7021,HTU21D,INA219,PCA9685,HDC10xx,M5Stack Rotary enc"); + result += F("SI7021,HTU21D,INA219,PCA9685,HDC10xx,M5Stack Rotary enc"); break; case 0x41: case 0x42: case 0x43: - result += F("INA219"); + result += F("INA219"); break; case 0x44: case 0x45: - result += F("SHT30/31/35,INA219,SHT4x"); + result += F("SHT30/31/35,INA219,SHT4x"); break; case 0x46: - result += F("SHT4x"); + result += F("SHT4x"); break; case 0x48: + result += F("PCF8591,ADS1x15,LM75A,INA219,TMP117,VEML6030"); + break; case 0x4A: case 0x4B: - result += F("PCF8591,ADS1x15,LM75A,INA219,TMP117"); + result += F("PCF8591,ADS1x15,LM75A,INA219,TMP117"); break; case 0x49: - result += F("PCF8591,ADS1x15,TSL2561,LM75A,INA219,TMP117"); + result += F("PCF8591,ADS1x15,TSL2561,LM75A,INA219,TMP117"); break; case 0x4C: case 0x4E: case 0x4F: - result += F("PCF8591,LM75A,INA219"); + result += F("PCF8591,LM75A,INA219"); break; case 0x4D: - result += F("PCF8591,MCP3221,LM75A,INA219"); + result += F("PCF8591,MCP3221,LM75A,INA219"); break; case 0x51: - result += F("PCF8563"); + result += F("PCF8563"); break; case 0x53: - result += F("ADXL345,LTR390"); + result += F("ADXL345,LTR390"); break; case 0x55: - result += F("DFRobot Rotary enc,BeFlE Moisture"); + result += F("DFRobot Rotary enc,BeFlE Moisture"); break; case 0x54: case 0x56: case 0x57: - result += F("DFRobot Rotary enc"); + result += F("DFRobot Rotary enc"); break; case 0x58: - result += F("SGP30"); + result += F("SGP30,GP8403"); break; case 0x59: - result += F("SGP4x"); + result += F("SGP4x,GP8403"); break; case 0x5A: - result += F("MLX90614,MPR121,CCS811"); + result += F("MLX90614,MPR121,CCS811,GP8403,CST226"); break; case 0x5B: - result += F("MPR121,CCS811"); + result += F("MPR121,CCS811,GP8403"); break; case 0x5C: - result += F("DHT12,AM2320,BH1750,MPR121"); + result += F("DHT12,AM2320,BH1750,MPR121,GP8403"); break; case 0x5D: - result += F("MPR121"); + result += F("MPR121,GP8403,GT911"); + break; + case 0x5E: + case 0x5F: + result += F("GP8403"); break; case 0x60: - result += F("Adafruit Motorshield v2,SI1145"); + result += F("Adafruit Motorshield v2,SI1145"); break; case 0x61: result += F("Atlas EZO DO,SCD30"); @@ -326,33 +341,36 @@ String getKnownI2Cdevice(uint8_t address) { result += F("Atlas EZO EC"); break; case 0x68: - result += F("MPU6050,DS1307,DS3231,PCF8523,ITG3205,CDM7160"); + result += F("MPU6050,DS1307,DS3231,PCF8523,ITG3205,CDM7160"); break; case 0x69: - result += F("ITG3205,CDM7160"); + result += F("ITG3205,CDM7160,SEN5x"); break; case 0x70: - result += F("Adafruit Motorshield v2 (Catchall),HT16K33,TCA9543a/6a/8a I2C multiplexer,PCA9540 I2C multiplexer"); + result += F("Adafruit Motorshield v2 (Catchall),HT16K33,TCA9543a/6a/8a I2C multiplexer,PCA9540 I2C multiplexer"); break; case 0x71: case 0x72: case 0x73: - result += F("HT16K33,TCA9543a/6a/8a I2C multiplexer"); + result += F("HT16K33,TCA9543a/6a/8a I2C multiplexer"); break; case 0x74: - result += F("HT16K33,TCA9546a/8a I2C multiplexer"); + result += F("HT16K33,TCA9546a/8a I2C multiplexer"); break; case 0x75: - result += F("HT16K33,TCA9546a/8a I2C multiplexer,IP5306"); + result += F("HT16K33,TCA9546a/8a I2C multiplexer,IP5306"); break; case 0x76: - result += F("BMP280,BME280,BME680,BMP3xx,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer"); + result += F("BMP280,BME280,BME680,BMP3xx,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer"); break; case 0x77: - result += F("BMP085,BMP180,BMP280,BME280,BME680,BMP3xx,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer"); + result += F("BMP085,BMP180,BMP280,BME280,BME680,BMP3xx,MS5607,MS5611,HT16K33,TCA9546a/8a I2C multiplexer,LiquidLevel"); + break; + case 0x78: + result += F("LiquidLevel"); break; case 0x7f: - result += F("Arduino PME"); + result += F("Arduino PME"); break; } #endif // LIMIT_BUILD_SIZE diff --git a/src/src/WebServer/JSON.cpp b/src/src/WebServer/JSON.cpp index 8c1070667..527245c33 100644 --- a/src/src/WebServer/JSON.cpp +++ b/src/src/WebServer/JSON.cpp @@ -1,729 +1,785 @@ -#include "../WebServer/JSON.h" - -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/JSON.h" -#include "../WebServer/Markup_Forms.h" - -#include "../CustomBuild/CompiletimeDefines.h" - -#include "../DataStructs/TimingStats.h" - -#include "../Globals/Cache.h" -#include "../Globals/Nodes.h" -#include "../Globals/Device.h" -#include "../Globals/Plugins.h" -#include "../Globals/NPlugins.h" - -#include "../Helpers/_Plugin_init.h" -#include "../Helpers/ESPEasyStatistics.h" -#include "../Helpers/ESPEasy_Storage.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringProvider.h" -#include "../Helpers/StringGenerator_System.h" - -#include "../../_Plugin_Helper.h" -#include "../../ESPEasy-Globals.h" - -void stream_comma_newline() { - addHtml(',', '\n'); -} - - -// ******************************************************************************** -// Web Interface get CSV value from task -// ******************************************************************************** -void handle_csvval() -{ - TXBuffer.startJsonStream(); - const int printHeader = getFormItemInt(F("header"), 1); - bool printHeaderValid = true; - 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); - 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)) - { - addHtml(F("ERROR: ValueId not valid!\n")); - valueNumberValid = false; - } - - if (taskValid && valueNumberValid && printHeaderValid) - { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskNr); - - if (validDeviceIndex(DeviceIndex)) - { - const uint8_t taskValCount = getValueCountForTask(taskNr); - - if (printHeader) - { - for (uint8_t x = 0; x < taskValCount; x++) - { - if (valNr == INVALID_VALUE_NUM || valNr == x) - { - addHtml(getTaskValueName(taskNr, x)); - if (x != taskValCount - 1) - { - addHtml(';'); - } - } - } - addHtml('\n'); - } - - for (uint8_t x = 0; x < taskValCount; x++) - { - if ((valNr == INVALID_VALUE_NUM) || (valNr == x)) - { - addHtml(formatUserVarNoCheck(taskNr, x)); - - if (x != taskValCount - 1) - { - addHtml(';'); - } - } - } - addHtml('\n'); - } - } - TXBuffer.endStream(); -} - -// ******************************************************************************** -// Web Interface JSON page (no password!) -// ******************************************************************************** -void handle_json() -{ - START_TIMER - const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); - const bool showSpecificTask = validTaskIndex(taskNr); - bool showSystem = true; - bool showWifi = true; - - #if FEATURE_ETHERNET - bool showEthernet = true; - #endif // if FEATURE_ETHERNET - bool showDataAcquisition = true; - bool showTaskDetails = true; - #if FEATURE_ESPEASY_P2P - bool showNodes = true; - #endif - - if (equals(webArg(F("view")), F("sensorupdate"))) { - showSystem = false; - showWifi = false; - #if FEATURE_ETHERNET - showEthernet = false; - #endif // if FEATURE_ETHERNET - showDataAcquisition = false; - showTaskDetails = false; - #if FEATURE_ESPEASY_P2P - showNodes = false; - #endif - } - - TXBuffer.startJsonStream(); - - if (!showSpecificTask) - { - addHtml('{'); - - if (showSystem) { - addHtml(F("\"System\":{\n")); - - if (wdcounter > 0) - { - stream_next_json_object_value(LabelType::LOAD_PCT); - stream_next_json_object_value(LabelType::LOOP_COUNT); - } - - static const LabelType::Enum labels[] PROGMEM = - { - LabelType::BUILD_DESC, - LabelType::GIT_BUILD, - LabelType::SYSTEM_LIBRARIES, - 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, - 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::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, -#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, -#ifdef SUPPORT_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, -#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_USE_LAST_CONN_FROM_RTC, - LabelType::WIFI_RSSI, - - - LabelType::MAX_LABEL - }; - - 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_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); - } - stream_next_json_object_value(F("ip"), formatIP(it->second.IP())); - 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 - } - } - #endif - } - - 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)) - { - const unsigned long taskInterval = Settings.TaskDeviceTimer[TaskIndex]; - //LoadTaskSettings(TaskIndex); - addHtml('{', '\n'); - - 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")); - - for (uint8_t x = 0; x < valueCount; x++) - { - addHtml('{'); - const String value = formatUserVarNoCheck(TaskIndex, x); - uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); - - if (mustConsiderAsJSONString(value)) { - // Flag as not to treat as a float - nrDecimals = 255; - } - stream_next_json_object_value(F("ValueNumber"), x + 1); - stream_next_json_object_value(F("Name"), Cache.getTaskDeviceValueName(TaskIndex, x)); - stream_next_json_object_value(F("NrDecimals"), nrDecimals); - stream_last_json_object_value(F("Value"), value); - - if (x < (valueCount - 1)) { - stream_comma_newline(); - } - } - addHtml(F("],\n")); - } - - if (showSpecificTask) { - stream_next_json_object_value(F("TTL"), ttl_json * 1000); - } - - if (showDataAcquisition) { - addHtml(F("\"DataAcquisition\": [\n")); - - 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 (x < (CONTROLLER_MAX - 1)) { - stream_comma_newline(); - } - } - addHtml(F("],\n")); - } - - 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])); - } - } - - #if FEATURE_I2CMULTIPLEXER - if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled()) { - 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(); c++) { - if (bitRead(channel, c)) { - if (b > 0) { stream_comma_newline(); } - b++; - addHtml(F("\"Multiplexer channel ")); - addHtmlInt(c); - addHtml('"'); - } - } - addHtml(F("],\n")); - } else { - if (channel == -1){ - stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); - } else { - String i2cChannel = F("Multiplexer channel "); - i2cChannel += String(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")); - stream_last_json_object_value(F("TTL"), lowest_ttl_json * 1000); - } - - TXBuffer.endStream(); - STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); -} - -// ******************************************************************************** -// JSON formatted timing statistics -// ******************************************************************************** - -#ifdef WEBSERVER_NEW_UI -void handle_timingstats_json() { - TXBuffer.startJsonStream(); - json_init(); - json_open(); - # if FEATURE_TIMING_STATS - jsonStatistics(false); - # endif // if FEATURE_TIMING_STATS - json_close(); - TXBuffer.endStream(); -} - -#endif // WEBSERVER_NEW_UI - -#ifdef WEBSERVER_NEW_UI - -#if FEATURE_ESPEASY_P2P -void handle_nodes_list_json() { - if (!isLoggedIn()) { return; } - TXBuffer.startJsonStream(); - json_init(); - json_open(true); - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.ip[0] != 0) - { - json_open(); - bool isThisUnit = it->first == Settings.Unit; - - if (isThisUnit) { - json_number(F("thisunit"), String(1)); - } - - json_number(F("first"), String(it->first)); - json_prop(F("name"), isThisUnit ? Settings.getName() : it->second.getNodeName()); - - if (it->second.build) { json_prop(F("build"), formatSystemBuildNr(it->second.build)); } - json_prop(F("type"), it->second.getNodeTypeDisplayString()); - json_prop(F("ip"), formatIP(it->second.ip)); - json_number(F("age"), String(it->second.getAge() / 1000)); // time in seconds - json_close(); - } - } - json_close(true); - TXBuffer.endStream(); -} -#endif - -void handle_buildinfo() { - if (!isLoggedIn()) { return; } - TXBuffer.startJsonStream(); - json_init(); - json_open(); - { - json_open(true, F("plugins")); - - 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)); - json_prop(F("name"), getPluginNameFromDeviceIndex(x)); - json_close(); - } - } - json_close(true); - } - { - json_open(true, F("controllers")); - - for (protocolIndex_t x = 0; x < getHighestIncludedCPluginID(); x++) { - if (getCPluginID_from_ProtocolIndex(x) != INVALID_C_PLUGIN_ID) { - json_open(); - json_number(F("id"), String(x + 1)); - json_prop(F("name"), getCPluginNameFromProtocolIndex(x)); - json_close(); - } - } - json_close(true); - } -#if FEATURE_NOTIFIER - { - json_open(true, F("notifications")); - - for (uint8_t x = 0; x < NPLUGIN_MAX; x++) { - if (validNPluginID(NPlugin_id[x])) { - json_open(); - json_number(F("id"), String(x + 1)); - json_prop(F("name"), getNPluginNameFromNotifierIndex(x)); - json_close(); - } - } - json_close(true); - } -#endif - json_prop(LabelType::BUILD_DESC); - json_prop(LabelType::GIT_BUILD); - json_prop(LabelType::SYSTEM_LIBRARIES); - json_prop(LabelType::PLUGIN_COUNT); - json_prop(LabelType::PLUGIN_DESCRIPTION); - json_close(); - TXBuffer.endStream(); -} - -#endif // WEBSERVER_NEW_UI - - -/*********************************************************************************************\ - Streaming versions directly to TXBuffer -\*********************************************************************************************/ -void stream_to_json_object_value(const __FlashStringHelper * object, const String& value) { - stream_to_json_object_value(String(object), value); -} - -void stream_to_json_object_value(const String& object, const String& value) { - addHtml(strformat( - F("\"%s\":%s"), - object.c_str(), - to_json_value(value).c_str())); -} - -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(strformat( - F("\"%s\":%d"), - object.c_str(), - value)); -} - -String jsonBool(bool value) { - return boolToString(value); -} - - -// Add JSON formatted data directly to the TXbuffer, including a trailing comma. -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) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const String& object, const String& value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const __FlashStringHelper * object, int value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -void stream_next_json_object_value(const String& object, int value) { - stream_to_json_object_value(object, value); - stream_comma_newline(); -} - -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) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_last_json_object_value(const __FlashStringHelper * object, String&& value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -void stream_last_json_object_value(const String& object, const String& value) { - stream_to_json_object_value(object, value); - stream_newline_close_brace(); -} - -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)); - - while (true) { - const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); - const bool nextIsLast = next == LabelType::MAX_LABEL; - - if (nextIsLast) { - stream_last_json_object_value(cur); - return; - } else { - stream_next_json_object_value(cur); - } - ++i; - cur = next; - } -} - -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)); +#include "../WebServer/JSON.h" + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/JSON.h" +#include "../WebServer/Markup_Forms.h" + +#include "../CustomBuild/CompiletimeDefines.h" + +#include "../DataStructs/TimingStats.h" + +#include "../Globals/Cache.h" +#include "../Globals/Nodes.h" +#include "../Globals/Device.h" +#include "../Globals/Plugins.h" +#include "../Globals/NPlugins.h" + +#include "../Helpers/_Plugin_init.h" +#include "../Helpers/ESPEasyStatistics.h" +#include "../Helpers/ESPEasy_Storage.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringProvider.h" +#include "../Helpers/StringGenerator_System.h" + +#include "../../_Plugin_Helper.h" +#include "../../ESPEasy-Globals.h" + +void stream_comma_newline() { + addHtml(',', '\n'); +} + + +// ******************************************************************************** +// Web Interface get CSV value from task +// ******************************************************************************** +void handle_csvval() +{ + TXBuffer.startJsonStream(); + const int printHeader = getFormItemInt(F("header"), 1); + bool printHeaderValid = true; + 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); + 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)) + { + addHtml(F("ERROR: ValueId not valid!\n")); + valueNumberValid = false; + } + + if (taskValid && valueNumberValid && printHeaderValid) + { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(taskNr); + + if (validDeviceIndex(DeviceIndex)) + { + const uint8_t taskValCount = getValueCountForTask(taskNr); + + if (printHeader) + { + for (uint8_t x = 0; x < taskValCount; x++) + { + if (valNr == INVALID_VALUE_NUM || valNr == x) + { + addHtml(Cache.getTaskDeviceValueName(taskNr, x)); + if (x != taskValCount - 1) + { + addHtml(';'); + } + } + } + addHtml('\n'); + } + + struct EventStruct TempEvent(taskNr); + + for (uint8_t x = 0; x < taskValCount; x++) + { + if ((valNr == INVALID_VALUE_NUM) || (valNr == x)) + { + addHtml(formatUserVarNoCheck(&TempEvent, x)); + + if (x != taskValCount - 1) + { + addHtml(';'); + } + } + } + addHtml('\n'); + } + } + TXBuffer.endStream(); +} + +// ******************************************************************************** +// Web Interface JSON page (no password!) +// ******************************************************************************** +void handle_json() +{ + START_TIMER + const taskIndex_t taskNr = getFormItemInt(F("tasknr"), INVALID_TASK_INDEX); + const bool showSpecificTask = validTaskIndex(taskNr); + bool showSystem = true; + bool showWifi = true; + + #if FEATURE_ETHERNET + bool showEthernet = true; + #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 (equals(webArg(F("view")), F("sensorupdate"))) { + showSystem = false; + showWifi = false; + #if FEATURE_ETHERNET + showEthernet = false; + #endif // if FEATURE_ETHERNET + showDataAcquisition = false; + showTaskDetails = false; + #if FEATURE_ESPEASY_P2P + showNodes = false; + #endif + #if FEATURE_PLUGIN_STATS + showPluginStats = hasArg(F("showpluginstats")); + #endif + } + + TXBuffer.startJsonStream(); + + if (!showSpecificTask) + { + addHtml('{'); + + if (showSystem) { + addHtml(F("\"System\":{\n")); + + if (wdcounter > 0) + { + stream_next_json_object_value(LabelType::LOAD_PCT); + stream_next_json_object_value(LabelType::LOOP_COUNT); + } + + static const LabelType::Enum labels[] PROGMEM = + { + LabelType::BUILD_DESC, + LabelType::GIT_BUILD, + LabelType::SYSTEM_LIBRARIES, + 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, + 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, +#ifdef SUPPORT_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, +#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, +#ifdef ESP32 + LabelType::WIFI_PASSIVE_SCAN, +#endif + LabelType::WIFI_USE_LAST_CONN_FROM_RTC, + LabelType::WIFI_RSSI, +#ifndef ESP32 + LabelType::WAIT_WIFI_CONNECT, +#endif + LabelType::HIDDEN_SSID_SLOW_CONNECT, + LabelType::CONNECT_HIDDEN_SSID, + LabelType::SDK_WIFI_AUTORECONNECT, + + LabelType::MAX_LABEL + }; + + 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); + } + } + 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 + } + } + #endif + } + + 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)) + { + const unsigned long taskInterval = Settings.TaskDeviceTimer[TaskIndex]; + //LoadTaskSettings(TaskIndex); + addHtml('{', '\n'); + + 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++) + { + addHtml('{'); + const String value = formatUserVarNoCheck(&TempEvent, x); + uint8_t nrDecimals = Cache.getTaskDeviceValueDecimals(TaskIndex, x); + + if (mustConsiderAsJSONString(value)) { + // Flag as not to treat as a float + nrDecimals = 255; + } + stream_next_json_object_value(F("ValueNumber"), x + 1); + stream_next_json_object_value(F("Name"), Cache.getTaskDeviceValueName(TaskIndex, x)); + stream_next_json_object_value(F("NrDecimals"), nrDecimals); + stream_last_json_object_value(F("Value"), value); + + if (x < (valueCount - 1)) { + stream_comma_newline(); + } + } + addHtml(F("],\n")); + } + +#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 (showSpecificTask) { + stream_next_json_object_value(F("TTL"), ttl_json * 1000); + } + + if (showDataAcquisition) { + addHtml(F("\"DataAcquisition\": [\n")); + + 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 (x < (CONTROLLER_MAX - 1)) { + stream_comma_newline(); + } + } + addHtml(F("],\n")); + } + + 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])); + } + } + + #if FEATURE_I2CMULTIPLEXER + if (Device[DeviceIndex].Type == DEVICE_TYPE_I2C && isI2CMultiplexerEnabled()) { + 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(); c++) { + if (bitRead(channel, c)) { + if (b > 0) { stream_comma_newline(); } + b++; + addHtml(F("\"Multiplexer channel ")); + addHtmlInt(c); + addHtml('"'); + } + } + addHtml(F("],\n")); + } else { + if (channel == -1){ + stream_next_json_object_value(F("I2Cbus"), F("Standard I2C bus")); + } else { + String i2cChannel = F("Multiplexer channel "); + i2cChannel += String(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")); + stream_last_json_object_value(F("TTL"), lowest_ttl_json * 1000); + } + + TXBuffer.endStream(); + STOP_TIMER(HANDLE_SERVING_WEBPAGE_JSON); +} + +// ******************************************************************************** +// JSON formatted timing statistics +// ******************************************************************************** + +#ifdef WEBSERVER_NEW_UI +void handle_timingstats_json() { + TXBuffer.startJsonStream(); + json_init(); + json_open(); + # if FEATURE_TIMING_STATS + jsonStatistics(false); + # endif // if FEATURE_TIMING_STATS + json_close(); + TXBuffer.endStream(); +} + +#endif // WEBSERVER_NEW_UI + +#ifdef WEBSERVER_NEW_UI + +#if FEATURE_ESPEASY_P2P +void handle_nodes_list_json() { + if (!isLoggedIn()) { return; } + TXBuffer.startJsonStream(); + json_init(); + json_open(true); + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.ip[0] != 0) + { + json_open(); + bool isThisUnit = it->first == Settings.Unit; + + if (isThisUnit) { + json_number(F("thisunit"), String(1)); + } + + json_number(F("first"), String(it->first)); + json_prop(F("name"), isThisUnit ? Settings.getName() : it->second.getNodeName()); + + if (it->second.build) { json_prop(F("build"), formatSystemBuildNr(it->second.build)); } + json_prop(F("type"), it->second.getNodeTypeDisplayString()); + json_prop(F("ip"), formatIP(it->second.ip)); + json_number(F("age"), String(it->second.getAge() / 1000)); // time in seconds + json_close(); + } + } + json_close(true); + TXBuffer.endStream(); +} +#endif + +void handle_buildinfo() { + if (!isLoggedIn()) { return; } + TXBuffer.startJsonStream(); + json_init(); + json_open(); + { + json_open(true, F("plugins")); + + 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)); + json_prop(F("name"), getPluginNameFromDeviceIndex(x)); + json_close(); + } + } + json_close(true); + } + { + json_open(true, F("controllers")); + + for (protocolIndex_t x = 0; x < getHighestIncludedCPluginID(); x++) { + if (getCPluginID_from_ProtocolIndex(x) != INVALID_C_PLUGIN_ID) { + json_open(); + json_number(F("id"), String(x + 1)); + json_prop(F("name"), getCPluginNameFromProtocolIndex(x)); + json_close(); + } + } + json_close(true); + } +#if FEATURE_NOTIFIER + { + json_open(true, F("notifications")); + + for (uint8_t x = 0; x < NPLUGIN_MAX; x++) { + if (validNPluginID(NPlugin_id[x])) { + json_open(); + json_number(F("id"), String(x + 1)); + json_prop(F("name"), getNPluginNameFromNotifierIndex(x)); + json_close(); + } + } + json_close(true); + } +#endif + json_prop(LabelType::BUILD_DESC); + json_prop(LabelType::GIT_BUILD); + json_prop(LabelType::SYSTEM_LIBRARIES); + json_prop(LabelType::PLUGIN_COUNT); + json_prop(LabelType::PLUGIN_DESCRIPTION); + json_close(); + TXBuffer.endStream(); +} + +#endif // WEBSERVER_NEW_UI + + +/*********************************************************************************************\ + Streaming versions directly to TXBuffer +\*********************************************************************************************/ +void stream_to_json_object_value(const __FlashStringHelper * object, const String& value) { + stream_to_json_object_value(String(object), value); +} + +void stream_to_json_object_value(const String& object, const String& value) { + addHtml(strformat( + F("\"%s\":%s"), + object.c_str(), + to_json_value(value).c_str())); +} + +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(strformat( + F("\"%s\":%d"), + object.c_str(), + value)); +} + +String jsonBool(bool value) { + return boolToString(value); +} + + +// Add JSON formatted data directly to the TXbuffer, including a trailing comma. +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) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const String& object, const String& value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const __FlashStringHelper * object, int value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +void stream_next_json_object_value(const String& object, int value) { + stream_to_json_object_value(object, value); + stream_comma_newline(); +} + +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) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_last_json_object_value(const __FlashStringHelper * object, String&& value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +void stream_last_json_object_value(const String& object, const String& value) { + stream_to_json_object_value(object, value); + stream_newline_close_brace(); +} + +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)); + + while (true) { + const LabelType::Enum next = static_cast(pgm_read_byte(labels + i + 1)); + const bool nextIsLast = next == LabelType::MAX_LABEL; + + if (nextIsLast) { + stream_last_json_object_value(cur); + return; + } else { + stream_next_json_object_value(cur); + } + ++i; + cur = next; + } +} + +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)); } \ No newline at end of file diff --git a/src/src/WebServer/Markup.cpp b/src/src/WebServer/Markup.cpp index f4da079df..b05f3e950 100644 --- a/src/src/WebServer/Markup.cpp +++ b/src/src/WebServer/Markup.cpp @@ -431,6 +431,7 @@ void addUnit(const __FlashStringHelper *unit) void addUnit(const String& unit) { + if (unit.isEmpty()) return; addHtml(F(" [")); addHtml(unit); addHtml(']'); @@ -517,6 +518,7 @@ void addRowLabel(LabelType::Enum label) { void addRowLabelValue(LabelType::Enum label) { addRowLabel(getLabel(label)); addHtml(getValue(label)); + addUnit(getFormUnit(label)); } void addRowLabelValues(const LabelType::Enum labels[]) { @@ -537,6 +539,7 @@ void addRowLabelValues(const LabelType::Enum labels[]) { void addRowLabelValue_copy(LabelType::Enum label) { addRowLabel_copy(getLabel(label)); addHtml(getValue(label)); + addUnit(getFormUnit(label)); } // ******************************************************************************** @@ -778,6 +781,8 @@ void addTextBox(const String & id, #if FEATURE_TOOLTIPS , const String& tooltip #endif // if FEATURE_TOOLTIPS + , + const String& datalist ) { addHtml(F(" 0) { addHtmlAttribute(F("maxlength"), maxlength); } + if (!datalist.isEmpty()) { + addHtmlAttribute(F("list"), datalist); + } addHtmlAttribute(F("value"), value); if (readonly) { diff --git a/src/src/WebServer/Markup.h b/src/src/WebServer/Markup.h index e14810f6d..46ae74d2e 100644 --- a/src/src/WebServer/Markup.h +++ b/src/src/WebServer/Markup.h @@ -1,359 +1,361 @@ -#ifndef WEBSERVER_WEBSERVER_MARKUP_H -#define WEBSERVER_WEBSERVER_MARKUP_H - -#include "../WebServer/common.h" -#include "../DataTypes/ProtocolIndex.h" -#include "../DataTypes/CPluginID.h" -#include "../DataTypes/PluginID.h" -#include "../Globals/Plugins.h" -#include "../Helpers/StringGenerator_GPIO.h" - -// ******************************************************************************** -// Add Selector -// ******************************************************************************** -void addSelector(const __FlashStringHelper *id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange = false, - bool enabled = true); - -void addSelector(const String & id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange = false, - bool enabled = true); - -void addSelector(const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange = false, - bool enabled = true); - - -void addSelector(const String & id, - int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , - const String & tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addSelector(const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addSelector_reloadOnChange( - const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const String& onChangeCall, - bool enabled, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - - -void addSelector_options(int optionCount, - const __FlashStringHelper *options[], - const int indices[], - const String attr[], - int selectedIndex); -void addSelector_options(int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex); - -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 String& id, - const __FlashStringHelper * classname, - bool disabled - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addSelector_Head_reloadOnChange(const String& id, - const __FlashStringHelper * classname, - const String& onChangeCall, - bool disabled - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void do_addSelector_Head(const String& id, - const __FlashStringHelper * classname, - const String& onChangeCall, - const bool& disabled - #if FEATURE_TOOLTIPS - , - 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, - const String & attr = EMPTY_STRING); - -void addSelector_Item(const __FlashStringHelper *option, - int index, - bool selected, - bool disabled = false, - const String & attr = EMPTY_STRING); -void addSelector_Item(const String& option, - int index, - bool selected, - bool disabled = false, - const String& attr = EMPTY_STRING); - -void addSelector_Foot(); - -void addUnit(const __FlashStringHelper *unit); -void addUnit(const String& unit); -void addUnit(char unit); - -void addRowLabel_tr_id(const __FlashStringHelper *label, - const __FlashStringHelper *id); -void addRowLabel_tr_id(const __FlashStringHelper *label, - const String & id); -void addRowLabel_tr_id(const String& label, - const String& id); - -void addRowLabel(const __FlashStringHelper *label); -void addRowLabel(const String& label, - const String& id = EMPTY_STRING); - -// 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); - -void addRowLabel(LabelType::Enum label); - -void addRowLabelValue(LabelType::Enum label); - -void addRowLabelValues(const LabelType::Enum labels[]); - -void addRowLabelValue_copy(LabelType::Enum label); - -// ******************************************************************************** -// Add a header -// ******************************************************************************** -void addTableSeparator(const __FlashStringHelper *label, - int colspan, - int h_size); -void addTableSeparator(const __FlashStringHelper *label, - int colspan, - int h_size, - const __FlashStringHelper *helpButton); -void addTableSeparator(const String& label, - int colspan, - int h_size, - const String& helpButton = EMPTY_STRING); - -void addFormHeader(const __FlashStringHelper *header); -void addFormHeader(const __FlashStringHelper *header, - const __FlashStringHelper *helpButton); -void addFormHeader(const __FlashStringHelper *header, - const __FlashStringHelper *helpButton, - const __FlashStringHelper *rtdHelpButton); -/* -void addFormHeader(const String& header, - const String& helpButton = EMPTY_STRING); -void addFormHeader(const String& header, - const String& helpButton, - const String& rtdHelpButton); -*/ -// ******************************************************************************** -// Add a sub header -// ******************************************************************************** -void addFormSubHeader(const __FlashStringHelper *header); -void addFormSubHeader(const String& header); - -// ******************************************************************************** -// Add a checkbox -// ******************************************************************************** -void addCheckBox(const String& id, - bool checked, - bool disabled = false - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); -void addCheckBox(const __FlashStringHelper *id, - 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); -#endif // if FEATURE_TOOLTIPS - -void addFloatNumberBox(const String& id, - float value, - float min, - float max, - unsigned int nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); -void addNumericBox(const __FlashStringHelper *id, - int value, - int min, - int max, - bool disabled = false); -void addNumericBox(const String& id, - int value, - int min, - int max, - bool disabled = false); - -// ******************************************************************************** -// Add Textbox -// ******************************************************************************** -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, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING); -void addTextBox(const String& id, - const String& value, - int maxlength, - bool readonly, - bool required, - const String& pattern, - const __FlashStringHelper * classname - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -// ******************************************************************************** -// Add Textarea -// ******************************************************************************** -void addTextArea(const String& id, - const String& value, - int maxlength, - int rows, - int columns, - bool readonly, - bool required - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -// ******************************************************************************** -// Add Help Buttons -// ******************************************************************************** - -// adds a Help Button with points to the the given Wiki Subpage -// If url starts with "RTD", it will be considered as a Read-the-docs link -void addHelpButton(const __FlashStringHelper *url); -void addHelpButton(const String& url); - -void addRTDHelpButton(const String& url); - -void addHelpButton(const String& url, - bool isRTD); - -void addRTDPluginButton(pluginID_t pluginID); -# ifndef LIMIT_BUILD_SIZE -void addRTDControllerButton(cpluginID_t cpluginID); -# endif // ifndef LIMIT_BUILD_SIZE - -String makeDocLink(const String& url, - bool isRTD); - - -void addPinSelect(PinSelectPurpose purpose, - const __FlashStringHelper *id, - int choice); -void addPinSelect(PinSelectPurpose purpose, - const String & id, - int choice); - - -#ifdef ESP32 -enum class AdcPinSelectPurpose { - TouchOnly, - ADC_Touch, -#if HAS_HALL_EFFECT_SENSOR - ADC_Touch_HallEffect, -#endif - ADC_Touch_Optional -}; -void addADC_PinSelect(AdcPinSelectPurpose purpose, - const String & id, - int choice); -void addDAC_PinSelect(const String& id, - int choice); -#endif // ifdef ESP32 - - -#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_H +#ifndef WEBSERVER_WEBSERVER_MARKUP_H +#define WEBSERVER_WEBSERVER_MARKUP_H + +#include "../WebServer/common.h" +#include "../DataTypes/ProtocolIndex.h" +#include "../DataTypes/CPluginID.h" +#include "../DataTypes/PluginID.h" +#include "../Globals/Plugins.h" +#include "../Helpers/StringGenerator_GPIO.h" + +// ******************************************************************************** +// Add Selector +// ******************************************************************************** +void addSelector(const __FlashStringHelper *id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange = false, + bool enabled = true); + +void addSelector(const String & id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange = false, + bool enabled = true); + +void addSelector(const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange = false, + bool enabled = true); + + +void addSelector(const String & id, + int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , + const String & tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addSelector(const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addSelector_reloadOnChange( + const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const String& onChangeCall, + bool enabled, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + + +void addSelector_options(int optionCount, + const __FlashStringHelper *options[], + const int indices[], + const String attr[], + int selectedIndex); +void addSelector_options(int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex); + +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 String& id, + const __FlashStringHelper * classname, + bool disabled + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addSelector_Head_reloadOnChange(const String& id, + const __FlashStringHelper * classname, + const String& onChangeCall, + bool disabled + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void do_addSelector_Head(const String& id, + const __FlashStringHelper * classname, + const String& onChangeCall, + const bool& disabled + #if FEATURE_TOOLTIPS + , + 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, + const String & attr = EMPTY_STRING); + +void addSelector_Item(const __FlashStringHelper *option, + int index, + bool selected, + bool disabled = false, + const String & attr = EMPTY_STRING); +void addSelector_Item(const String& option, + int index, + bool selected, + bool disabled = false, + const String& attr = EMPTY_STRING); + +void addSelector_Foot(); + +void addUnit(const __FlashStringHelper *unit); +void addUnit(const String& unit); +void addUnit(char unit); + +void addRowLabel_tr_id(const __FlashStringHelper *label, + const __FlashStringHelper *id); +void addRowLabel_tr_id(const __FlashStringHelper *label, + const String & id); +void addRowLabel_tr_id(const String& label, + const String& id); + +void addRowLabel(const __FlashStringHelper *label); +void addRowLabel(const String& label, + const String& id = EMPTY_STRING); + +// 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); + +void addRowLabel(LabelType::Enum label); + +void addRowLabelValue(LabelType::Enum label); + +void addRowLabelValues(const LabelType::Enum labels[]); + +void addRowLabelValue_copy(LabelType::Enum label); + +// ******************************************************************************** +// Add a header +// ******************************************************************************** +void addTableSeparator(const __FlashStringHelper *label, + int colspan, + int h_size); +void addTableSeparator(const __FlashStringHelper *label, + int colspan, + int h_size, + const __FlashStringHelper *helpButton); +void addTableSeparator(const String& label, + int colspan, + int h_size, + const String& helpButton = EMPTY_STRING); + +void addFormHeader(const __FlashStringHelper *header); +void addFormHeader(const __FlashStringHelper *header, + const __FlashStringHelper *helpButton); +void addFormHeader(const __FlashStringHelper *header, + const __FlashStringHelper *helpButton, + const __FlashStringHelper *rtdHelpButton); +/* +void addFormHeader(const String& header, + const String& helpButton = EMPTY_STRING); +void addFormHeader(const String& header, + const String& helpButton, + const String& rtdHelpButton); +*/ +// ******************************************************************************** +// Add a sub header +// ******************************************************************************** +void addFormSubHeader(const __FlashStringHelper *header); +void addFormSubHeader(const String& header); + +// ******************************************************************************** +// Add a checkbox +// ******************************************************************************** +void addCheckBox(const String& id, + bool checked, + bool disabled = false + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); +void addCheckBox(const __FlashStringHelper *id, + 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); +#endif // if FEATURE_TOOLTIPS + +void addFloatNumberBox(const String& id, + float value, + float min, + float max, + unsigned int nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); +void addNumericBox(const __FlashStringHelper *id, + int value, + int min, + int max, + bool disabled = false); +void addNumericBox(const String& id, + int value, + int min, + int max, + bool disabled = false); + +// ******************************************************************************** +// Add Textbox +// ******************************************************************************** +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, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING); +void addTextBox(const String& id, + const String& value, + int maxlength, + bool readonly, + bool required, + const String& pattern, + const __FlashStringHelper * classname + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + const String& datalist = EMPTY_STRING + ); + +// ******************************************************************************** +// Add Textarea +// ******************************************************************************** +void addTextArea(const String& id, + const String& value, + int maxlength, + int rows, + int columns, + bool readonly, + bool required + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +// ******************************************************************************** +// Add Help Buttons +// ******************************************************************************** + +// adds a Help Button with points to the the given Wiki Subpage +// If url starts with "RTD", it will be considered as a Read-the-docs link +void addHelpButton(const __FlashStringHelper *url); +void addHelpButton(const String& url); + +void addRTDHelpButton(const String& url); + +void addHelpButton(const String& url, + bool isRTD); + +void addRTDPluginButton(pluginID_t pluginID); +# ifndef LIMIT_BUILD_SIZE +void addRTDControllerButton(cpluginID_t cpluginID); +# endif // ifndef LIMIT_BUILD_SIZE + +String makeDocLink(const String& url, + bool isRTD); + + +void addPinSelect(PinSelectPurpose purpose, + const __FlashStringHelper *id, + int choice); +void addPinSelect(PinSelectPurpose purpose, + const String & id, + int choice); + + +#ifdef ESP32 +enum class AdcPinSelectPurpose { + TouchOnly, + ADC_Touch, +#if HAS_HALL_EFFECT_SENSOR + ADC_Touch_HallEffect, +#endif + ADC_Touch_Optional +}; +void addADC_PinSelect(AdcPinSelectPurpose purpose, + const String & id, + int choice); +void addDAC_PinSelect(const String& id, + int choice); +#endif // ifdef ESP32 + + +#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_H diff --git a/src/src/WebServer/Markup_Forms.cpp b/src/src/WebServer/Markup_Forms.cpp index 582bf0cde..ac47d98e1 100644 --- a/src/src/WebServer/Markup_Forms.cpp +++ b/src/src/WebServer/Markup_Forms.cpp @@ -1,820 +1,865 @@ -#include "../WebServer/Markup_Forms.h" - -#include "../WebServer/ESPEasy_WebServer.h" -#include "../WebServer/AccessControl.h" -#include "../WebServer/Markup.h" -#include "../WebServer/HTML_wrappers.h" - -#include "../Globals/Settings.h" - -#include "../Helpers/Hardware_GPIO.h" -#include "../Helpers/Numerical.h" -#include "../Helpers/StringConverter.h" -#include "../Helpers/StringGenerator_GPIO.h" - -// ******************************************************************************** -// Add a separator as row start -// ******************************************************************************** -void addFormSeparator(int clspan) -{ - addHtml(strformat( - F("

"), - clspan)); -} - -// ******************************************************************************** -// Add a note as row start -// ******************************************************************************** -void addFormNote(const __FlashStringHelper * text) -{ - addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); - addHtml(F("
Note: ")); - addHtml(text); - addHtml(F("
")); -} - -void addFormNote(const String& text, const String& id) -{ - if (text.isEmpty()) return; - addRowLabel_tr_id(EMPTY_STRING, id); - addHtmlDiv(F("note"), concat(F("Note: "), text)); -} - -// ******************************************************************************** -// Create Forms -// ******************************************************************************** - - -// ******************************************************************************** -// Add a checkbox Form -// ******************************************************************************** - -void addFormCheckBox_disabled(const String& label, const String& id, bool checked - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - addFormCheckBox(label, id, checked, true - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled); -} - -void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled); -} - -void addFormCheckBox(const String& label, const String& id, bool checked, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addCheckBox(id, checked, disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox(LabelType::Enum label, bool checked, bool disabled - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) { - addFormCheckBox(getLabel(label), getInternalLabel(label), checked, disabled - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormCheckBox_disabled(LabelType::Enum label, bool checked) { - addFormCheckBox(label, checked, true); -} - -// ******************************************************************************** -// Add a Numeric Box form -// ******************************************************************************** -void addFormNumericBox(LabelType::Enum label, int value, int min, int max - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , bool disabled - ) -{ - addFormNumericBox(getLabel(label), getInternalLabel(label), value, min, max - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); -} - -void addFormNumericBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int value, - int min, - int max - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - , - bool disabled - ) -{ - addFormNumericBox(String(label), String(id), value, min, max - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); - -} - -void addFormNumericBox(const String& label, const String& id, int value, int min, int max - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - , bool disabled - ) -{ - addRowLabel_tr_id(label, id); - addNumericBox(id, value, min, max - #if FEATURE_TOOLTIPS - , F("widenumber"), tooltip - #endif // if FEATURE_TOOLTIPS - , disabled - ); -} - -void addFormFloatNumberBox(LabelType::Enum label, float value, 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 - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormFloatNumberBox(const String& label, - const String& id, - float value, - float min, - float max, - uint8_t nrDecimals, - float stepsize - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addFloatNumberBox(id, value, min, max, nrDecimals, stepsize - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormFloatNumberBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - float value, - float min, - float max, - uint8_t nrDecimals, - float stepsize - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addFloatNumberBox(id, value, min, max, nrDecimals, stepsize - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - - - -// ******************************************************************************** -// Add a task selector form -// ******************************************************************************** -void addTaskSelectBox(const String& label, const String& id, taskIndex_t choice) -{ - addRowLabel_tr_id(label, id); - addTaskSelect(id, choice); -} - -// ******************************************************************************** -// Add a Text Box form -// ******************************************************************************** -void addFormTextBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - const String& value, - int maxlength, - bool readonly, - bool required, - const String& pattern) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern); -} - -void addFormTextBox(const String & label, - const String & id, - const String & value, - int maxlength, - bool readonly, - bool required, - const String& pattern - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern, F("wide") - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormTextBox(const __FlashStringHelper * classname, - const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly , - bool required , - const String& pattern - #if FEATURE_TOOLTIPS - , - const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addTextBox(id, value, maxlength, readonly, required, pattern, classname - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - - - -void addFormTextArea(const String & label, - const String & id, - const String & value, - int maxlength, - int rows, - int columns, - bool readonly, - bool required - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addTextArea(id, value, maxlength, rows, columns, readonly, required - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -// ******************************************************************************** -// Add a Password Box form -// ******************************************************************************** - -void addFormPasswordBox(const String& label, const String& id, const String& password, int maxlength - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - - addHtml(F(" 0) { - addHtmlAttribute(F("title"), tooltip); - } - #endif // if FEATURE_TOOLTIPS - addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F("*****")); - addHtml('>'); -} - -bool getFormPassword(const String& id, String& password) -{ - password = webArg(id); - return !equals(password, F("*****")); -} - -// ******************************************************************************** -// Add a IP Box form -// ******************************************************************************** -void addFormIPBox(const __FlashStringHelper *label, - const __FlashStringHelper *id, - const uint8_t ip[4]) -{ - addFormIPBox(String(label), String(id), ip); -} - -void addFormTextBox(const String& label, const String& id, const String& value) -{ - addRowLabel_tr_id(label, id); - - addHtml(strformat( - F(""), - id.c_str(), - id.c_str(), - value.c_str() - )); -} - -void addFormIPBox(const String& label, const String& id, const uint8_t ip[4]) -{ - const bool empty_IP = (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0); - - addFormTextBox(label, id, (empty_IP) ? EMPTY_STRING : formatIP(ip)); -} - -// ******************************************************************************** -// Add a MAC Box form -// ******************************************************************************** -void addFormMACBox(const String& label, const String& id, const MAC_address mac) -{ - addFormTextBox( - label, - id, - mac.all_zero() ? EMPTY_STRING.c_str() : mac.toString()); -} - -// ******************************************************************************** -// Add a IP Access Control select dropdown list -// ******************************************************************************** -void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addIPaccessControlSelect(id, choice); -} - -// ******************************************************************************** -// Add a selector form -// ******************************************************************************** -void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(purpose, id, choice); -} - -void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(purpose, id, choice); -} - -/* -void addFormPinSelect(const String& label, const __FlashStringHelper * id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} - -void addFormPinSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} - -void addFormPinSelect(const String& label, const String & id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::Generic, id, choice); -} -*/ - -void addFormPinSelectI2C(const String& label, const String& id, int choice) -{ - addRowLabel_tr_id(label, id); - addPinSelect(PinSelectPurpose::I2C, id, choice); -} - -void addFormSelectorI2C(const String& id, int addressCount, const uint8_t addresses[], int selectedIndex - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(F("I2C Address"), id); - do_addSelector_Head(id, F(""), EMPTY_STRING, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - - for (uint8_t x = 0; x < addressCount; x++) - { - String option = formatToHex_decimal(addresses[x]); - - if (x == 0) { - option += F(" - (default)"); - } - addSelector_Item(option, addresses[x], addresses[x] == selectedIndex); - } - addSelector_Foot(); -} - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) -{ - addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) -{ - addFormSelector(String(label), id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false); -} - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex) -{ - addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, false); -} - -void addFormSelector(const String & label, - const String & id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - int selectedIndex, - bool reloadonchange) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange) -{ - addRowLabel_tr_id(label, id); - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true); -} - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector(const String & label, - const String & id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true, F("wide") - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); -} - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - do_addSelector_Head(id, F("wide"), onChangeCall, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , const String& tooltip - #endif // if FEATURE_TOOLTIPS - ) -{ - addRowLabel_tr_id(label, id); - do_addSelector_Head(id, F("wide"), onChangeCall, false - #if FEATURE_TOOLTIPS - , tooltip - #endif // if FEATURE_TOOLTIPS - ); - addSelector_options(optionCount, options, indices, attr, selectedIndex); - addSelector_Foot(); -} - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int selectedIndex, - bool reloadonchange) -{ - addFormSelector_YesNo(label, String(id), selectedIndex, reloadonchange); -} - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const String& id, - int selectedIndex, - bool reloadonchange) -{ - const __FlashStringHelper *optionsNoYes[2] = { F("No"), F("Yes") }; - int optionValuesNoYes[2] = { 0, 1 }; - addFormSelector(label, id, 2, optionsNoYes, optionValuesNoYes, selectedIndex, reloadonchange); -} - - - -// ******************************************************************************** -// Add a GPIO pin select dropdown list -// ******************************************************************************** -void addFormPinStateSelect(int gpio, int choice) -{ - bool enabled = true; - - if (isSerialConsolePin(gpio)) { - // do not add the pin state select for these pins. - enabled = false; - } - if (Settings.isEthernetPin(gpio)) { - // do not add the pin state select for non-optional Ethernet pins - enabled = false; - } - int pinnr = -1; - bool input, output, warning; - - if (getGpioInfo(gpio, pinnr, input, output, warning)) { - const String id = String('p') + gpio; - addRowLabel_tr_id( - concat( - F("Pin mode "), - createGPIO_label(gpio, pinnr, input, output, warning)), - id); - bool hasPullUp, hasPullDown; - getGpioPullResistor(gpio, hasPullUp, hasPullDown); - int nr_options = 0; - const __FlashStringHelper * options[6]; - int option_val[6]; - options[nr_options] = F("Default"); - option_val[nr_options] = static_cast(PinBootState::Default_state); - ++nr_options; - - if (output) { - options[nr_options] = F("Output Low"); - option_val[nr_options] = static_cast(PinBootState::Output_low); - ++nr_options; - options[nr_options] = F("Output High"); - option_val[nr_options] = static_cast(PinBootState::Output_high); - ++nr_options; - } - - if (input) { - if (hasPullUp) { - options[nr_options] = F("Input pullup"); - option_val[nr_options] = static_cast(PinBootState::Input_pullup); - ++nr_options; - } - - if (hasPullDown) { - options[nr_options] = F("Input pulldown"); - option_val[nr_options] = static_cast(PinBootState::Input_pulldown); - ++nr_options; - } - - if (!hasPullUp && !hasPullDown) { - options[nr_options] = F("Input"); - option_val[nr_options] = static_cast(PinBootState::Input); - ++nr_options; - } - } - addSelector(id, nr_options, options, option_val, nullptr, choice, false, enabled); - { - const String conflict = getConflictingUse(gpio); - if (!conflict.isEmpty()) { - addUnit(conflict); - } - } - } -} - -// ******************************************************************************** -// Retrieve return values from form/checkbox. -// ******************************************************************************** -int getFormItemInt(const __FlashStringHelper * key, int defaultValue) { - return getFormItemInt(String(key), defaultValue); -} - -int getFormItemInt(const String& key, int defaultValue) { - int value = defaultValue; - - getCheckWebserverArg_int(key, value); - return value; -} - -bool getCheckWebserverArg_int(const String& key, int& value) { - const String valueStr = webArg(key); - if (valueStr.isEmpty()) return false; - // FIXME TD-er: Since ESP_IDF 5.1 int32_t != int - int32_t tmp{}; - const bool res = validIntFromString(valueStr, tmp); - value = tmp; - return res; -} - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - int & value) -{ - return update_whenset_FormItemInt(String(key), value); -} - -bool update_whenset_FormItemInt(const String& key, int& value) { - int tmpVal; - - if (getCheckWebserverArg_int(key, tmpVal)) { - value = tmpVal; - return true; - } - return false; -} - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - uint8_t& value) -{ - return update_whenset_FormItemInt(String(key), value); -} - - -bool update_whenset_FormItemInt(const String& key, uint8_t& value) { - int tmpVal; - - if (getCheckWebserverArg_int(key, tmpVal)) { - value = tmpVal; - return true; - } - return false; -} - -// Note: Checkbox values will not appear in POST Form data if unchecked. -// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. -bool isFormItemChecked(const __FlashStringHelper * id) -{ - return isFormItemChecked(String(id)); -} - -bool isFormItemChecked(const String& id) -{ - return equals(webArg(id), F("on")); -} - -bool isFormItemChecked(const LabelType::Enum& id) -{ - return isFormItemChecked(getInternalLabel(id)); -} - -int getFormItemInt(const __FlashStringHelper * id) -{ - return getFormItemInt(String(id), 0); -} - -int getFormItemInt(const String& id) -{ - return getFormItemInt(id, 0); -} - -int getFormItemInt(const LabelType::Enum& id) -{ - return getFormItemInt(getInternalLabel(id), 0); -} - -float getFormItemFloat(const __FlashStringHelper * id) -{ - return getFormItemFloat(String(id)); -} - -float getFormItemFloat(const String& id) -{ - const String val = webArg(id); - float res = 0.0; - if (val.length() > 0) { - validFloatFromString(val, res); - } - return res; -} - -float getFormItemFloat(const LabelType::Enum& id) -{ - return getFormItemFloat(getInternalLabel(id)); -} - -bool isFormItem(const String& id) -{ - return !webArg(id).isEmpty(); -} - -void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength) -{ - String password; - - if (getFormPassword(id, password)) { - safe_strncpy(pPassword, password.c_str(), maxlength); - } -} +#include "../WebServer/Markup_Forms.h" + +#include "../WebServer/ESPEasy_WebServer.h" +#include "../WebServer/AccessControl.h" +#include "../WebServer/Markup.h" +#include "../WebServer/HTML_wrappers.h" + +#include "../Globals/Settings.h" + +#include "../Helpers/Hardware_GPIO.h" +#include "../Helpers/Numerical.h" +#include "../Helpers/StringConverter.h" +#include "../Helpers/StringGenerator_GPIO.h" + +// ******************************************************************************** +// Add a separator as row start +// ******************************************************************************** +void addFormSeparator(int clspan) +{ + addHtml(strformat( + F("

"), + clspan)); +} + +// ******************************************************************************** +// Add a note as row start +// ******************************************************************************** +void addFormNote(const __FlashStringHelper * text) +{ + addRowLabel_tr_id(EMPTY_STRING, EMPTY_STRING); + addHtml(F("
Note: ")); + addHtml(text); + addHtml(F("
")); +} + +void addFormNote(const String& text, const String& id) +{ + if (text.isEmpty()) return; + addRowLabel_tr_id(EMPTY_STRING, id); + addHtmlDiv(F("note"), concat(F("Note: "), text)); +} + +void addFormNote(const LabelType::Enum& label) +{ + addUnit(getFormUnit(label)); + addFormNote(getFormNote(label)); +} + +// ******************************************************************************** +// Create Forms +// ******************************************************************************** + + +// ******************************************************************************** +// Add a checkbox Form +// ******************************************************************************** + +void addFormCheckBox_disabled(const String& label, const String& id, bool checked + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + addFormCheckBox(label, id, checked, true + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormCheckBox(const __FlashStringHelper * label, const __FlashStringHelper * id, bool checked, bool disabled) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled); +} + +void addFormCheckBox(const __FlashStringHelper * label, const String& id, bool checked, bool disabled) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled); +} + +void addFormCheckBox(const String& label, const String& id, bool checked, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addCheckBox(id, checked, disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormCheckBox(LabelType::Enum label, bool checked, bool disabled + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) { + addFormCheckBox(getLabel(label), getInternalLabel(label), checked, disabled + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addFormNote(label); +} + +void addFormCheckBox_disabled(LabelType::Enum label, bool checked) { + addFormCheckBox(label, checked, true); +} + +// ******************************************************************************** +// Add a Numeric Box form +// ******************************************************************************** +void addFormNumericBox(LabelType::Enum label, int value, int min, int max + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , bool disabled + ) +{ + addFormNumericBox(getLabel(label), getInternalLabel(label), value, min, max + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); + addFormNote(label); +} + +void addFormNumericBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int value, + int min, + int max + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + bool disabled + ) +{ + addFormNumericBox(String(label), String(id), value, min, max + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); + +} + +void addFormNumericBox(const String& label, const String& id, int value, int min, int max + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , bool disabled + ) +{ + addRowLabel_tr_id(label, id); + addNumericBox(id, value, min, max + #if FEATURE_TOOLTIPS + , F("widenumber"), tooltip + #endif // if FEATURE_TOOLTIPS + , disabled + ); +} + +void addFormFloatNumberBox(LabelType::Enum label, float value, 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 + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addFormNote(label); +} + +void addFormFloatNumberBox(const String& label, + const String& id, + float value, + float min, + float max, + uint8_t nrDecimals, + float stepsize + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addFloatNumberBox(id, value, min, max, nrDecimals, stepsize + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormFloatNumberBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + float value, + float min, + float max, + uint8_t nrDecimals, + float stepsize + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addFloatNumberBox(id, value, min, max, nrDecimals, stepsize + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + + + +// ******************************************************************************** +// Add a task selector form +// ******************************************************************************** +void addTaskSelectBox(const String& label, const String& id, taskIndex_t choice) +{ + addRowLabel_tr_id(label, id); + addTaskSelect(id, choice); +} + +// ******************************************************************************** +// Add a Text Box form +// ******************************************************************************** +void addFormTextBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + const String& value, + int maxlength, + bool readonly, + bool required, + const String& pattern) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern); +} + +void addFormTextBox(const String & label, + const String & id, + const String & value, + int maxlength, + bool readonly, + bool required, + const String& pattern + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + const String& datalist + ) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern, F("wide") + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , datalist + ); +} + +void addFormTextBox(const __FlashStringHelper * classname, + const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly , + bool required , + const String& pattern + #if FEATURE_TOOLTIPS + , + const String& tooltip + #endif // if FEATURE_TOOLTIPS + , + const String& datalist + ) +{ + addRowLabel_tr_id(label, id); + addTextBox(id, value, maxlength, readonly, required, pattern, classname + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + , datalist + ); +} + + + +void addFormTextArea(const String & label, + const String & id, + const String & value, + int maxlength, + int rows, + int columns, + bool readonly, + bool required + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addTextArea(id, value, maxlength, rows, columns, readonly, required + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +// ******************************************************************************** +// Add a Password Box form +// ******************************************************************************** + +void addFormPasswordBox(const String& label, const String& id, const String& password, int maxlength + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + + addHtml(F(" 0) { + addHtmlAttribute(F("title"), tooltip); + } + #endif // if FEATURE_TOOLTIPS + addHtmlAttribute(F("value"), (password.length() == 0) ? F("") : F("*****")); + addHtml('>'); +} + +bool getFormPassword(const String& id, String& password) +{ + password = webArg(id); + return !equals(password, F("*****")); +} + +// ******************************************************************************** +// Add a IP Box form +// ******************************************************************************** +void addFormIPBox(const __FlashStringHelper *label, + const __FlashStringHelper *id, + const uint8_t ip[4]) +{ + addFormIPBox(String(label), String(id), ip); +} + +void addFormTextBox(const String& label, const String& id, const String& value) +{ + addRowLabel_tr_id(label, id); + + addHtml(strformat( + F(""), + id.c_str(), + id.c_str(), + value.c_str() + )); +} + +void addFormIPBox(const String& label, const String& id, const uint8_t ip[4]) +{ + const bool empty_IP = (ip[0] == 0 && ip[1] == 0 && ip[2] == 0 && ip[3] == 0); + + addFormTextBox(label, id, (empty_IP) ? EMPTY_STRING : formatIP(ip)); +} + +// ******************************************************************************** +// Add a MAC Box form +// ******************************************************************************** +void addFormMACBox(const String& label, const String& id, const MAC_address mac) +{ + addFormTextBox( + label, + id, + mac.all_zero() ? EMPTY_STRING.c_str() : mac.toString()); +} + +// ******************************************************************************** +// Add a IP Access Control select dropdown list +// ******************************************************************************** +void addFormIPaccessControlSelect(const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addIPaccessControlSelect(id, choice); +} + +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText) { + const int len = charset.length() + 1; + String charList[len]; + int charOpts[len]; + + charList[0] = F("None"); + charOpts[0] = 0; + + for (uint16_t i = 0; i < charset.length(); i++) { + charList[i + 1] = charset[i]; + charOpts[i + 1] = static_cast(charset[i]); + } + addFormSelector(rowLabel, id, len, charList, charOpts, value); + + if (!String(additionalText).isEmpty()) { + addUnit(additionalText); + } +} + +// ******************************************************************************** +// Add a selector form +// ******************************************************************************** +void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(purpose, id, choice); +} + +void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(purpose, id, choice); +} + +/* +void addFormPinSelect(const String& label, const __FlashStringHelper * id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} + +void addFormPinSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} + +void addFormPinSelect(const String& label, const String & id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::Generic, id, choice); +} +*/ + +void addFormPinSelectI2C(const String& label, const String& id, int choice) +{ + addRowLabel_tr_id(label, id); + addPinSelect(PinSelectPurpose::I2C, id, choice); +} + +void addFormSelectorI2C(const String& id, + int addressCount, + const uint8_t addresses[], + int selectedIndex, + uint8_t defaultAddress + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(F("I2C Address"), id); + do_addSelector_Head(id, F(""), EMPTY_STRING, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + + for (int x = 0; x < addressCount; x++) + { + String option = formatToHex_decimal(addresses[x]); + + if (((x == 0) && (defaultAddress == 0)) || (defaultAddress == addresses[x])) { + option += F(" - (default)"); + } + addSelector_Item(option, addresses[x], addresses[x] == selectedIndex); + } + addSelector_Foot(); +} + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) +{ + addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange) +{ + addFormSelector(String(label), id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false); +} + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex) +{ + addFormSelector(String(label), String(id), optionCount, options, indices, nullptr, selectedIndex, false); +} + +void addFormSelector(const String & label, + const String & id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + int selectedIndex, + bool reloadonchange) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange) +{ + addRowLabel_tr_id(label, id); + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true); +} + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addFormSelector(label, id, optionCount, options, indices, nullptr, selectedIndex, reloadonchange + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector(const String & label, + const String & id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + addSelector(id, optionCount, options, indices, attr, selectedIndex, reloadonchange, true, F("wide") + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); +} + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + do_addSelector_Head(id, F("wide"), onChangeCall, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , const String& tooltip + #endif // if FEATURE_TOOLTIPS + ) +{ + addRowLabel_tr_id(label, id); + do_addSelector_Head(id, F("wide"), onChangeCall, false + #if FEATURE_TOOLTIPS + , tooltip + #endif // if FEATURE_TOOLTIPS + ); + addSelector_options(optionCount, options, indices, attr, selectedIndex); + addSelector_Foot(); +} + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int selectedIndex, + bool reloadonchange) +{ + addFormSelector_YesNo(label, String(id), selectedIndex, reloadonchange); +} + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const String& id, + int selectedIndex, + bool reloadonchange) +{ + const __FlashStringHelper *optionsNoYes[] = { F("No"), F("Yes") }; + int optionValuesNoYes[] = { 0, 1 }; + addFormSelector(label, id, NR_ELEMENTS(optionValuesNoYes), optionsNoYes, optionValuesNoYes, selectedIndex, reloadonchange); +} + + + +// ******************************************************************************** +// Add a GPIO pin select dropdown list +// ******************************************************************************** +void addFormPinStateSelect(int gpio, int choice) +{ + bool enabled = true; + + if (isSerialConsolePin(gpio)) { + // do not add the pin state select for these pins. + enabled = false; + } + if (Settings.isEthernetPin(gpio)) { + // do not add the pin state select for non-optional Ethernet pins + enabled = false; + } + int pinnr = -1; + bool input, output, warning; + + if (getGpioInfo(gpio, pinnr, input, output, warning)) { + const String id = String('p') + gpio; + addRowLabel_tr_id( + concat( + F("Pin mode "), + createGPIO_label(gpio, pinnr, input, output, warning)), + id); + bool hasPullUp, hasPullDown; + getGpioPullResistor(gpio, hasPullUp, hasPullDown); + int nr_options = 0; + const __FlashStringHelper * options[6]; + int option_val[6]; + options[nr_options] = F("Default"); + option_val[nr_options] = static_cast(PinBootState::Default_state); + ++nr_options; + + if (output) { + options[nr_options] = F("Output Low"); + option_val[nr_options] = static_cast(PinBootState::Output_low); + ++nr_options; + options[nr_options] = F("Output High"); + option_val[nr_options] = static_cast(PinBootState::Output_high); + ++nr_options; + } + + if (input) { + if (hasPullUp) { + options[nr_options] = F("Input pullup"); + option_val[nr_options] = static_cast(PinBootState::Input_pullup); + ++nr_options; + } + + if (hasPullDown) { + options[nr_options] = F("Input pulldown"); + option_val[nr_options] = static_cast(PinBootState::Input_pulldown); + ++nr_options; + } + + if (!hasPullUp && !hasPullDown) { + options[nr_options] = F("Input"); + option_val[nr_options] = static_cast(PinBootState::Input); + ++nr_options; + } + } + addSelector(id, nr_options, options, option_val, nullptr, choice, false, enabled); + { + const String conflict = getConflictingUse(gpio); + if (!conflict.isEmpty()) { + addUnit(conflict); + } + } + } +} + +// ******************************************************************************** +// Retrieve return values from form/checkbox. +// ******************************************************************************** +int getFormItemInt(const __FlashStringHelper * key, int defaultValue) { + return getFormItemInt(String(key), defaultValue); +} + +int getFormItemInt(const String& key, int defaultValue) { + int value = defaultValue; + + getCheckWebserverArg_int(key, value); + return value; +} + +bool getCheckWebserverArg_int(const String& key, int& value) { + const String valueStr = webArg(key); + if (valueStr.isEmpty()) return false; + // FIXME TD-er: Since ESP_IDF 5.1 int32_t != int + int32_t tmp{}; + const bool res = validIntFromString(valueStr, tmp); + value = tmp; + return res; +} + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + int & value) +{ + return update_whenset_FormItemInt(String(key), value); +} + +bool update_whenset_FormItemInt(const String& key, int& value) { + int tmpVal; + + if (getCheckWebserverArg_int(key, tmpVal)) { + value = tmpVal; + return true; + } + return false; +} + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + uint8_t& value) +{ + return update_whenset_FormItemInt(String(key), value); +} + + +bool update_whenset_FormItemInt(const String& key, uint8_t& value) { + int tmpVal; + + if (getCheckWebserverArg_int(key, tmpVal)) { + value = tmpVal; + return true; + } + return false; +} + +// Note: Checkbox values will not appear in POST Form data if unchecked. +// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. +bool isFormItemChecked(const __FlashStringHelper * id) +{ + return isFormItemChecked(String(id)); +} + +bool isFormItemChecked(const String& id) +{ + return equals(webArg(id), F("on")); +} + +bool isFormItemChecked(const LabelType::Enum& id) +{ + return isFormItemChecked(getInternalLabel(id)); +} + +int getFormItemInt(const __FlashStringHelper * id) +{ + return getFormItemInt(String(id), 0); +} + +int getFormItemInt(const String& id) +{ + return getFormItemInt(id, 0); +} + +int getFormItemInt(const LabelType::Enum& id) +{ + return getFormItemInt(getInternalLabel(id), 0); +} + +float getFormItemFloat(const __FlashStringHelper * id) +{ + return getFormItemFloat(String(id)); +} + +float getFormItemFloat(const String& id) +{ + const String val = webArg(id); + float res = 0.0; + if (val.length() > 0) { + validFloatFromString(val, res); + } + return res; +} + +float getFormItemFloat(const LabelType::Enum& id) +{ + return getFormItemFloat(getInternalLabel(id)); +} + +bool isFormItem(const String& id) +{ + return !webArg(id).isEmpty(); +} + +void copyFormPassword(const __FlashStringHelper * id, char *pPassword, int maxlength) +{ + String password; + + if (getFormPassword(id, password)) { + safe_strncpy(pPassword, password.c_str(), maxlength); + } +} diff --git a/src/src/WebServer/Markup_Forms.h b/src/src/WebServer/Markup_Forms.h index b72b82623..71a5ea34a 100644 --- a/src/src/WebServer/Markup_Forms.h +++ b/src/src/WebServer/Markup_Forms.h @@ -1,430 +1,445 @@ -#ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H -#define WEBSERVER_WEBSERVER_MARKUP_FORMS_H - -#include "../WebServer/common.h" - -#include "../DataStructs/MAC_address.h" -#include "../Globals/Plugins.h" -#include "../Helpers/StringGenerator_GPIO.h" - - -// ******************************************************************************** -// Add a separator as row start -// ******************************************************************************** -void addFormSeparator(int clspan); - -// ******************************************************************************** -// Add a note as row start -// ******************************************************************************** -void addFormNote(const __FlashStringHelper * text); -void addFormNote(const String& text, const String& id = EMPTY_STRING); - -// ******************************************************************************** -// Create Forms -// ******************************************************************************** - - -// ******************************************************************************** -// Add a checkbox Form -// ******************************************************************************** - -void addFormCheckBox_disabled(const String& label, - const String& id, - bool checked - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox(const String& label, - const String& id, - bool checked, - bool disabled = false - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox(LabelType::Enum label, - bool checked, - bool disabled = false - #if FEATURE_TOOLTIPS - , - const String & tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormCheckBox_disabled(LabelType::Enum label, - bool checked); -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); - -// ******************************************************************************** -// Add a Numeric Box form -// ******************************************************************************** -void addFormNumericBox(LabelType::Enum label, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String & tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - -void addFormNumericBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - -void addFormNumericBox(const String& label, - const String& id, - int value, - int min = INT_MIN, - int max = INT_MAX - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - , - bool disabled = false - ); - - -void addFormFloatNumberBox(LabelType::Enum label, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormFloatNumberBox(const String& label, - const String& id, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormFloatNumberBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - float value, - float min, - float max, - uint8_t nrDecimals = 6, - float stepsize = 0.0f - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - - -// ******************************************************************************** -// Add a task selector form -// ******************************************************************************** -void addTaskSelectBox(const String& label, - const String& id, - taskIndex_t choice); - -// ******************************************************************************** -// Add a Text Box form -// ******************************************************************************** -void addFormTextBox(const __FlashStringHelper * label, - const __FlashStringHelper * id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING); - -void addFormTextBox(const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -void addFormTextBox(const __FlashStringHelper * classname, - const String& label, - const String& id, - const String& value, - int maxlength, - bool readonly = false, - bool required = false, - const String& pattern = EMPTY_STRING - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - - -void addFormTextArea(const String& label, - const String& id, - const String& value, - int maxlength, - int rows, - int columns, - bool readonly = false, - bool required = false - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -// ******************************************************************************** -// Add a Password Box form -// ******************************************************************************** - -void addFormPasswordBox(const String& label, - const String& id, - const String& password, - int maxlength - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif // if FEATURE_TOOLTIPS - ); - -bool getFormPassword(const String& id, - String & password); - -// ******************************************************************************** -// Add a IP Box form -// ******************************************************************************** - -void addFormIPBox(const __FlashStringHelper *label, - const __FlashStringHelper *id, - const uint8_t ip[4]); - -void addFormIPBox(const String& label, - const String& id, - const uint8_t ip[4]); - -// ******************************************************************************** -// Add a MAC address Box form -// ******************************************************************************** -void addFormMACBox(const String& label, const String& id, const MAC_address mac); - -// ******************************************************************************** -// Add a IP Access Control select dropdown list -// ******************************************************************************** -void addFormIPaccessControlSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice); - -// ******************************************************************************** -// Add a selector form -// ******************************************************************************** - -/* -void addFormPinSelect(const String& label, - const String& id, - int choice); -void addFormPinSelect(const String& label, - const __FlashStringHelper * id, - int choice); -void addFormPinSelect(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int choice); -*/ -void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice); - -void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); - -void addFormPinSelectI2C(const String& label, - const String& id, - int choice); - -void addFormSelectorI2C(const String& id, - int addressCount, - const uint8_t addresses[], - int selectedIndex - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); -void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); -void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex); -void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - int selectedIndex, - bool reloadonchange); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange); - - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector(const String& label, - const String& id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - bool reloadonchange - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const __FlashStringHelper * options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - - -void addFormSelector_script(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int optionCount, - const String options[], - const int indices[], - const String attr[], - int selectedIndex, - const __FlashStringHelper * onChangeCall - #if FEATURE_TOOLTIPS - , - const String& tooltip = EMPTY_STRING - #endif - ); - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const __FlashStringHelper * id, - int selectedIndex, - bool reloadonchange); - -void addFormSelector_YesNo(const __FlashStringHelper * label, - const String& id, - int selectedIndex, - bool reloadonchange); - -// ******************************************************************************** -// Add a GPIO pin select dropdown list -// ******************************************************************************** -void addFormPinStateSelect(int gpio, - int choice); - -// ******************************************************************************** -// Retrieve return values from form/checkbox. -// ******************************************************************************** - - -int getFormItemInt(const __FlashStringHelper * key, int defaultValue); -int getFormItemInt(const String& key, int defaultValue); - -bool getCheckWebserverArg_int(const String& key, - int & value); - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - int & value); - -bool update_whenset_FormItemInt(const String& key, - int & value); - -bool update_whenset_FormItemInt(const __FlashStringHelper * key, - uint8_t & value); - -bool update_whenset_FormItemInt(const String& key, - uint8_t & value); - -// Note: Checkbox values will not appear in POST Form data if unchecked. -// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. -bool isFormItemChecked(const __FlashStringHelper * id); -bool isFormItemChecked(const String& id); -bool isFormItemChecked(const LabelType::Enum& id); - -int getFormItemInt(const __FlashStringHelper * id); -int getFormItemInt(const String& id); -int getFormItemInt(const LabelType::Enum& id); - -float getFormItemFloat(const __FlashStringHelper * id); -float getFormItemFloat(const String& id); -float getFormItemFloat(const LabelType::Enum& id); - -bool isFormItem(const String& id); - -void copyFormPassword(const __FlashStringHelper * id, - char *pPassword, - int maxlength); - - -#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H +#ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H +#define WEBSERVER_WEBSERVER_MARKUP_FORMS_H + +#include "../WebServer/common.h" + +#include "../DataStructs/MAC_address.h" +#include "../Globals/Plugins.h" +#include "../Helpers/StringGenerator_GPIO.h" + + +// ******************************************************************************** +// Add a separator as row start +// ******************************************************************************** +void addFormSeparator(int clspan); + +// ******************************************************************************** +// Add a note as row start +// ******************************************************************************** +void addFormNote(const __FlashStringHelper * text); +void addFormNote(const String& text, const String& id = EMPTY_STRING); +void addFormNote(const LabelType::Enum& label); + +// ******************************************************************************** +// Create Forms +// ******************************************************************************** + + +// ******************************************************************************** +// Add a checkbox Form +// ******************************************************************************** + +void addFormCheckBox_disabled(const String& label, + const String& id, + bool checked + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox(const String& label, + const String& id, + bool checked, + bool disabled = false + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox(LabelType::Enum label, + bool checked, + bool disabled = false + #if FEATURE_TOOLTIPS + , + const String & tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormCheckBox_disabled(LabelType::Enum label, + bool checked); +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); + +// ******************************************************************************** +// Add a Numeric Box form +// ******************************************************************************** +void addFormNumericBox(LabelType::Enum label, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String & tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + +void addFormNumericBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + +void addFormNumericBox(const String& label, + const String& id, + int value, + int min = INT_MIN, + int max = INT_MAX + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + bool disabled = false + ); + + +void addFormFloatNumberBox(LabelType::Enum label, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormFloatNumberBox(const String& label, + const String& id, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +void addFormFloatNumberBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + float value, + float min, + float max, + uint8_t nrDecimals = 6, + float stepsize = 0.0f + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + + +// ******************************************************************************** +// Add a task selector form +// ******************************************************************************** +void addTaskSelectBox(const String& label, + const String& id, + taskIndex_t choice); + +// ******************************************************************************** +// Add a Text Box form +// ******************************************************************************** +void addFormTextBox(const __FlashStringHelper * label, + const __FlashStringHelper * id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING); + +void addFormTextBox(const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + const String& datalist = EMPTY_STRING + ); + +void addFormTextBox(const __FlashStringHelper * classname, + const String& label, + const String& id, + const String& value, + int maxlength, + bool readonly = false, + bool required = false, + const String& pattern = EMPTY_STRING + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + , + const String& datalist = EMPTY_STRING + ); + + +void addFormTextArea(const String& label, + const String& id, + const String& value, + int maxlength, + int rows, + int columns, + bool readonly = false, + bool required = false + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +// ******************************************************************************** +// Add a Password Box form +// ******************************************************************************** + +void addFormPasswordBox(const String& label, + const String& id, + const String& password, + int maxlength + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif // if FEATURE_TOOLTIPS + ); + +bool getFormPassword(const String& id, + String & password); + +// ******************************************************************************** +// Add a IP Box form +// ******************************************************************************** + +void addFormIPBox(const __FlashStringHelper *label, + const __FlashStringHelper *id, + const uint8_t ip[4]); + +void addFormIPBox(const String& label, + const String& id, + const uint8_t ip[4]); + +// ******************************************************************************** +// Add a MAC address Box form +// ******************************************************************************** +void addFormMACBox(const String& label, const String& id, const MAC_address mac); + +// ******************************************************************************** +// Add a IP Access Control select dropdown list +// ******************************************************************************** +void addFormIPaccessControlSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice); + +// ******************************************************************************** +// a Separator character selector +// ******************************************************************************** +void addFormSeparatorCharInput(const __FlashStringHelper *rowLabel, + const __FlashStringHelper *id, + int value, + const String & charset, + const __FlashStringHelper *additionalText); + +// ******************************************************************************** +// Add a selector form +// ******************************************************************************** + +/* +void addFormPinSelect(const String& label, + const String& id, + int choice); +void addFormPinSelect(const String& label, + const __FlashStringHelper * id, + int choice); +void addFormPinSelect(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int choice); +*/ +void addFormPinSelect(PinSelectPurpose purpose, const String& label, const __FlashStringHelper * id, int choice); + +void addFormPinSelect(PinSelectPurpose purpose, const __FlashStringHelper * label, const __FlashStringHelper * id, int choice); + +void addFormPinSelectI2C(const String& label, + const String& id, + int choice); + +void addFormSelectorI2C(const String& id, + int addressCount, + const uint8_t addresses[], + int selectedIndex, + uint8_t defaultAddress = 0 // Address 0 is invalid + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); +void addFormSelector(const __FlashStringHelper * label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex, bool reloadonchange = false); +void addFormSelector(const String& label, const String& id, int optionCount, const __FlashStringHelper * options[], const int indices[], int selectedIndex); +void addFormSelector(const __FlashStringHelper * label, const __FlashStringHelper * id, int optionCount, const String options[], const int indices[], int selectedIndex); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + int selectedIndex, + bool reloadonchange); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange); + + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector(const String& label, + const String& id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + bool reloadonchange + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const __FlashStringHelper * options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + + +void addFormSelector_script(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int optionCount, + const String options[], + const int indices[], + const String attr[], + int selectedIndex, + const __FlashStringHelper * onChangeCall + #if FEATURE_TOOLTIPS + , + const String& tooltip = EMPTY_STRING + #endif + ); + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const __FlashStringHelper * id, + int selectedIndex, + bool reloadonchange); + +void addFormSelector_YesNo(const __FlashStringHelper * label, + const String& id, + int selectedIndex, + bool reloadonchange); + +// ******************************************************************************** +// Add a GPIO pin select dropdown list +// ******************************************************************************** +void addFormPinStateSelect(int gpio, + int choice); + +// ******************************************************************************** +// Retrieve return values from form/checkbox. +// ******************************************************************************** + + +int getFormItemInt(const __FlashStringHelper * key, int defaultValue); +int getFormItemInt(const String& key, int defaultValue); + +bool getCheckWebserverArg_int(const String& key, + int & value); + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + int & value); + +bool update_whenset_FormItemInt(const String& key, + int & value); + +bool update_whenset_FormItemInt(const __FlashStringHelper * key, + uint8_t & value); + +bool update_whenset_FormItemInt(const String& key, + uint8_t & value); + +// Note: Checkbox values will not appear in POST Form data if unchecked. +// So if webserver does not have an argument for a checkbox form, it means it should be considered unchecked. +bool isFormItemChecked(const __FlashStringHelper * id); +bool isFormItemChecked(const String& id); +bool isFormItemChecked(const LabelType::Enum& id); + +int getFormItemInt(const __FlashStringHelper * id); +int getFormItemInt(const String& id); +int getFormItemInt(const LabelType::Enum& id); + +float getFormItemFloat(const __FlashStringHelper * id); +float getFormItemFloat(const String& id); +float getFormItemFloat(const LabelType::Enum& id); + +bool isFormItem(const String& id); + +void copyFormPassword(const __FlashStringHelper * id, + char *pPassword, + int maxlength); + + +#endif // ifndef WEBSERVER_WEBSERVER_MARKUP_FORMS_H diff --git a/src/src/WebServer/Metrics.cpp b/src/src/WebServer/Metrics.cpp index 31c0ca04f..5bf219e71 100644 --- a/src/src/WebServer/Metrics.cpp +++ b/src/src/WebServer/Metrics.cpp @@ -1,138 +1,139 @@ -#include "../WebServer/Metrics.h" -#include "../WebServer/ESPEasy_WebServer.h" -#include "../../ESPEasy-Globals.h" -#include "../Commands/Diagnostic.h" -#include "../ESPEasyCore/ESPEasyNetwork.h" -#include "../ESPEasyCore/ESPEasyWifi.h" -#include "../../_Plugin_Helper.h" -#include "../Helpers/ESPEasyStatistics.h" -#include "../Static/WebStaticData.h" - -#ifdef WEBSERVER_METRICS - -# ifdef ESP32 -# include -# endif // ifdef ESP32 - -void handle_metrics() { - TXBuffer.startStream(F("text/plain"), F("*")); - const __FlashStringHelper *prefixHELP = F("# HELP espeasy_"); - const __FlashStringHelper *prefixTYPE = F("# TYPE espeasy_"); - - // uptime - addHtml(prefixHELP); - addHtml(F("uptime current device uptime in minutes\n")); - addHtml(prefixTYPE); - addHtml(F("uptime counter\n")); - addHtml(F("espeasy_uptime ")); - addHtml(getValue(LabelType::UPTIME)); - addHtml('\n'); - - // load - addHtml(prefixHELP); - addHtml(F("load device percentage load\n")); - addHtml(prefixTYPE); - addHtml(F("load gauge\n")); - addHtml(F("espeasy_load ")); - addHtml(getValue(LabelType::LOAD_PCT)); - addHtml('\n'); - - // Free RAM - addHtml(prefixHELP); - addHtml(F("free_ram device amount of RAM free in Bytes\n")); - addHtml(prefixTYPE); - addHtml(F("free_ram gauge\n")); - addHtml(F("espeasy_free_ram ")); - addHtml(getValue(LabelType::FREE_MEM)); - addHtml('\n'); - - // Free RAM - addHtml(prefixHELP); - addHtml(F("free_stack device amount of Stack free in Bytes\n")); - addHtml(prefixTYPE); - addHtml(F("free_stack gauge\n")); - addHtml(F("espeasy_free_stack ")); - addHtml(getValue(LabelType::FREE_STACK)); - addHtml('\n'); - - // Wifi strength - addHtml(prefixHELP); - addHtml(F("wifi_rssi Wifi connection Strength\n")); - addHtml(prefixTYPE); - addHtml(F("wifi_rssi gauge\n")); - addHtml(F("espeasy_wifi_rssi ")); - addHtml(getValue(LabelType::WIFI_RSSI)); - addHtml('\n'); - - // Wifi uptime - addHtml(prefixHELP); - addHtml(F("wifi_connected Time wifi has been connected in milliseconds\n")); - addHtml(prefixTYPE); - addHtml(F("wifi_connected counter\n")); - addHtml(F("espeasy_wifi_connected ")); - addHtml(getValue(LabelType::CONNECTED_MSEC)); - addHtml('\n'); - - // Wifi reconnects - addHtml(prefixHELP); - addHtml(F("wifi_reconnects Number of times Wifi has reconnected since boot\n")); - addHtml(prefixTYPE); - addHtml(F("wifi_reconnects counter\n")); - addHtml(F("espeasy_wifi_reconnects ")); - addHtml(getValue(LabelType::NUMBER_RECONNECTS)); - addHtml('\n'); - - // devices - handle_metrics_devices(); - - TXBuffer.endStream(); -} - -void handle_metrics_devices() { - for (taskIndex_t x = 0; validTaskIndex(x); x++) { - const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); - const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); - - if (pluginID_set) { - if (Settings.TaskDeviceEnabled[x]) { - String deviceName = getTaskDeviceName(x); - - if (deviceName.isEmpty()) { // Empty name, then use taskN - deviceName = F("task"); - deviceName += x + 1; - } - addHtml(F("# HELP espeasy_device_")); - addHtml(deviceName); - addHtml(F(" Values from connected device\n")); - addHtml(F("# TYPE espeasy_device_")); - addHtml(deviceName); - addHtml(F(" gauge\n")); - - if (validDeviceIndex(DeviceIndex)) { - String customValuesString; - - // const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); - const bool customValues = 0; // TODO: handle custom values - - if (!customValues) { - const uint8_t valueCount = getValueCountForTask(x); - - for (uint8_t varNr = 0; varNr < valueCount; varNr++) { - if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { - addHtml(F("espeasy_device_")); - addHtml(deviceName); - addHtml(F("{valueName=\"")); - addHtml(getTaskValueName(x, varNr)); - addHtml(F("\"} ")); - addHtml(formatUserVarNoCheck(x, varNr)); - addHtml('\n'); - } - } - } - } - } - } - } -} - -#endif // WEBSERVER_METRICS +#include "../WebServer/Metrics.h" +#include "../WebServer/ESPEasy_WebServer.h" +#include "../../ESPEasy-Globals.h" +#include "../Commands/Diagnostic.h" +#include "../ESPEasyCore/ESPEasyNetwork.h" +#include "../ESPEasyCore/ESPEasyWifi.h" +#include "../../_Plugin_Helper.h" +#include "../Helpers/ESPEasyStatistics.h" +#include "../Static/WebStaticData.h" + +#ifdef WEBSERVER_METRICS + +# ifdef ESP32 +# include +# endif // ifdef ESP32 + +void handle_metrics() { + TXBuffer.startStream(F("text/plain"), F("*")); + const __FlashStringHelper *prefixHELP = F("# HELP espeasy_"); + const __FlashStringHelper *prefixTYPE = F("# TYPE espeasy_"); + + // uptime + addHtml(prefixHELP); + addHtml(F("uptime current device uptime in minutes\n")); + addHtml(prefixTYPE); + addHtml(F("uptime counter\n")); + addHtml(F("espeasy_uptime ")); + addHtml(getValue(LabelType::UPTIME)); + addHtml('\n'); + + // load + addHtml(prefixHELP); + addHtml(F("load device percentage load\n")); + addHtml(prefixTYPE); + addHtml(F("load gauge\n")); + addHtml(F("espeasy_load ")); + addHtml(getValue(LabelType::LOAD_PCT)); + addHtml('\n'); + + // Free RAM + addHtml(prefixHELP); + addHtml(F("free_ram device amount of RAM free in Bytes\n")); + addHtml(prefixTYPE); + addHtml(F("free_ram gauge\n")); + addHtml(F("espeasy_free_ram ")); + addHtml(getValue(LabelType::FREE_MEM)); + addHtml('\n'); + + // Free RAM + addHtml(prefixHELP); + addHtml(F("free_stack device amount of Stack free in Bytes\n")); + addHtml(prefixTYPE); + addHtml(F("free_stack gauge\n")); + addHtml(F("espeasy_free_stack ")); + addHtml(getValue(LabelType::FREE_STACK)); + addHtml('\n'); + + // Wifi strength + addHtml(prefixHELP); + addHtml(F("wifi_rssi Wifi connection Strength\n")); + addHtml(prefixTYPE); + addHtml(F("wifi_rssi gauge\n")); + addHtml(F("espeasy_wifi_rssi ")); + addHtml(getValue(LabelType::WIFI_RSSI)); + addHtml('\n'); + + // Wifi uptime + addHtml(prefixHELP); + addHtml(F("wifi_connected Time wifi has been connected in milliseconds\n")); + addHtml(prefixTYPE); + addHtml(F("wifi_connected counter\n")); + addHtml(F("espeasy_wifi_connected ")); + addHtml(getValue(LabelType::CONNECTED_MSEC)); + addHtml('\n'); + + // Wifi reconnects + addHtml(prefixHELP); + addHtml(F("wifi_reconnects Number of times Wifi has reconnected since boot\n")); + addHtml(prefixTYPE); + addHtml(F("wifi_reconnects counter\n")); + addHtml(F("espeasy_wifi_reconnects ")); + addHtml(getValue(LabelType::NUMBER_RECONNECTS)); + addHtml('\n'); + + // devices + handle_metrics_devices(); + + TXBuffer.endStream(); +} + +void handle_metrics_devices() { + for (taskIndex_t x = 0; validTaskIndex(x); x++) { + const deviceIndex_t DeviceIndex = getDeviceIndex_from_TaskIndex(x); + const bool pluginID_set = INVALID_PLUGIN_ID != Settings.getPluginID_for_task(x); + + if (pluginID_set) { + if (Settings.TaskDeviceEnabled[x]) { + String deviceName = getTaskDeviceName(x); + + if (deviceName.isEmpty()) { // Empty name, then use taskN + deviceName = F("task"); + deviceName += x + 1; + } + addHtml(F("# HELP espeasy_device_")); + addHtml(deviceName); + addHtml(F(" Values from connected device\n")); + addHtml(F("# TYPE espeasy_device_")); + addHtml(deviceName); + addHtml(F(" gauge\n")); + + if (validDeviceIndex(DeviceIndex)) { + String customValuesString; + + // const bool customValues = PluginCall(PLUGIN_WEBFORM_SHOW_VALUES, &TempEvent, customValuesString); + const bool customValues = 0; // TODO: handle custom values + + if (!customValues) { + const uint8_t valueCount = getValueCountForTask(x); + struct EventStruct TempEvent(x); + + for (uint8_t varNr = 0; varNr < valueCount; varNr++) { + if (validPluginID_fullcheck(Settings.getPluginID_for_task(x))) { + addHtml(F("espeasy_device_")); + addHtml(deviceName); + addHtml(F("{valueName=\"")); + addHtml(Cache.getTaskDeviceValueName(x, varNr)); + addHtml(F("\"} ")); + addHtml(formatUserVarNoCheck(&TempEvent, varNr)); + addHtml('\n'); + } + } + } + } + } + } + } +} + +#endif // WEBSERVER_METRICS diff --git a/src/src/WebServer/NotificationPage.cpp b/src/src/WebServer/NotificationPage.cpp index a8320649c..39a8784f1 100644 --- a/src/src/WebServer/NotificationPage.cpp +++ b/src/src/WebServer/NotificationPage.cpp @@ -73,6 +73,7 @@ void handle_notifications() { NPlugin_ptr[NotificationProtocolIndex](NPlugin::Function::NPLUGIN_WEBFORM_SAVE, 0, dummyString); } NotificationSettings.Port = getFormItemInt(F("port"), 0); + NotificationSettings.Timeout = getFormItemInt(F("timeout"), NPLUGIN_001_DEF_TM/1000); NotificationSettings.Pin1 = getFormItemInt(F("pin1"), -1); NotificationSettings.Pin2 = getFormItemInt(F("pin2"), -1); Settings.NotificationEnabled[notificationindex] = isFormItemChecked(F("notificationenabled")); @@ -153,7 +154,7 @@ void handle_notifications() { if (NotificationSettings.Port){ addHtmlInt(NotificationSettings.Port); } else { - //MFD: we display the GPIO + //MFD: we display the GPIO addGpioHtml(NotificationSettings.Pin1); if (NotificationSettings.Pin2>=0) @@ -205,7 +206,10 @@ void handle_notifications() { { addFormTextBox(F("Domain"), F("domain"), NotificationSettings.Domain, sizeof(NotificationSettings.Domain) - 1); addFormTextBox(F("Server"), F("server"), NotificationSettings.Server, sizeof(NotificationSettings.Server) - 1); - addFormNumericBox(F("Port"), F("port"), NotificationSettings.Port, 1, 65535); + addFormNumericBox(F("Port"), F("port"), NotificationSettings.Port, 1, 65535, F("NOTE: SSL/TLS servers NOT supported!")); + if (NotificationSettings.TimeoutNPLUGIN_001_MAX_TM/1000) {NotificationSettings.Timeout=NPLUGIN_001_DEF_TM/1000;} + addFormNumericBox(F("Timeout"), F("timeout"), NotificationSettings.Timeout, NPLUGIN_001_MIN_TM/1000, NPLUGIN_001_MAX_TM/1000, F("Maximum Server Response Time)")); + addUnit(F("Seconds")); addFormTextBox(F("Sender"), F("sender"), NotificationSettings.Sender, sizeof(NotificationSettings.Sender) - 1); addFormTextBox(F("Receiver"), F("receiver"), NotificationSettings.Receiver, sizeof(NotificationSettings.Receiver) - 1); diff --git a/src/src/WebServer/RootPage.cpp b/src/src/WebServer/RootPage.cpp index 4c0dcba44..a489a1e4f 100644 --- a/src/src/WebServer/RootPage.cpp +++ b/src/src/WebServer/RootPage.cpp @@ -1,438 +1,470 @@ -#include "../WebServer/RootPage.h" - - -#ifdef WEBSERVER_ROOT - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/LoadFromFS.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" - -# include "../Commands/ExecuteCommand.h" -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../Globals/ESPEasy_time.h" -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/MainLoopCommand.h" -# include "../Globals/NetworkState.h" -# include "../Globals/Nodes.h" -# include "../Globals/Settings.h" -# include "../Globals/Statistics.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/Memory.h" -# include "../Helpers/Misc.h" -# include "../Helpers/StringGenerator_System.h" -# include "../Helpers/WebServer_commandHelper.h" - - -# include "../../ESPEasy-Globals.h" - -# if FEATURE_MQTT -# include "../Globals/MQTT.h" -# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller -# endif // if FEATURE_MQTT - - -# ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN - # define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false -# endif // ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN - -// Define main page elements present -# ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON - # define MAIN_PAGE_SHOW_SYSINFO_BUTTON true -# endif // ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON - -# ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - # define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON false -# endif // ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - -# ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD - # define MAIN_PAGE_SHOW_NODE_LIST_BUILD true -# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD -# ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE - # define MAIN_PAGE_SHOW_NODE_LIST_TYPE true -# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE - - -// ******************************************************************************** -// Web Interface root page -// ******************************************************************************** -void handle_root() { - # ifdef USE_SECOND_HEAP - HeapSelectDram ephemeral; - # endif // ifdef USE_SECOND_HEAP - - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_root")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - if (captivePortal()) { // If captive portal redirect instead of displaying the page. - return; - } - - // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. - if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) - { - web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); - return; - } - - if (!MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN) { - if (!isLoggedIn()) { return; } - } - - const bool loggedIn = isLoggedIn(false); - - navMenuIndex = 0; - - // if index.htm exists on FS serve that one (first check if gziped version exists) - if (loadFromFS(F("/index.htm.gz"))) { return; } - - if (loadFromFS(F("/index.htm"))) { return; } - - TXBuffer.startStream(); - - boolean rebootCmd = false; - String sCommand = webArg(F("cmd")); - 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 - // because the webconnection is still active at this point - // disconnect here could result into a crash/reboot... - if (strcasecmp_P(sCommand.c_str(), PSTR("wifidisconnect")) == 0) - { - addLog(LOG_LEVEL_INFO, F("WIFI : Disconnecting...")); - cmd_within_mainloop = CMD_WIFI_DISCONNECT; - addHtml(F("OK")); - } else if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) - { - addLog(LOG_LEVEL_INFO, F(" : Rebooting...")); - cmd_within_mainloop = CMD_REBOOT; - addHtml(F("OK")); - } else if (strcasecmp_P(sCommand.c_str(), PSTR("reset")) == 0) - { - if (loggedIn) { - addLog(LOG_LEVEL_INFO, F(" : factory reset...")); - 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()); - return; - } - } else { - if (loggedIn) { - handle_command_from_web(EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand); - printToWeb = false; - printToWebJSON = false; - } - - addHtml(F("
")); - html_table_class_normal(); - addFormHeader(F("System Info")); - - addRowLabelValue(LabelType::UNIT_NR); - addRowLabelValue(LabelType::GIT_BUILD); - addRowLabel(LabelType::LOCAL_TIME); - - if (node_time.systemTimePresent()) - { - addHtml(getValue(LabelType::LOCAL_TIME)); - } - else { - addHtml(F("No system time source")); - } - addRowLabelValue(LabelType::TIME_SOURCE); - - addRowLabel(LabelType::UPTIME); - { - addHtml(getExtendedValue(LabelType::UPTIME)); - } - addRowLabel(LabelType::LOAD_PCT); - - if (wdcounter > 0) - { - addHtmlFloat(getCPUload()); - addHtml(F("% (LC=")); - addHtmlInt(getLoopCountPerSec()); - addHtml(')'); - } - { - addRowLabel(LabelType::FREE_MEM); - addHtmlInt(freeMem); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestRAM, - lowestRAMfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - { - # ifdef USE_SECOND_HEAP - addRowLabelValue(LabelType::FREE_HEAP_IRAM); - # endif // ifdef USE_SECOND_HEAP - } - { - addRowLabel(LabelType::FREE_STACK); - addHtmlInt(getCurrentFreeStack()); -# ifndef BUILD_NO_RAM_TRACKER - addHtml(strformat( - F(" (%d - %s)"), - lowestFreeStack, - lowestFreeStackfunction.c_str())); -# endif // ifndef BUILD_NO_RAM_TRACKER - } - - # if FEATURE_ETHERNET - addRowLabelValue(LabelType::ETH_WIFI_MODE); - # endif // if FEATURE_ETHERNET - - if (!WiFiEventData.WiFiDisconnected()) - { - addRowLabelValue(LabelType::IP_ADDRESS); -#if FEATURE_USE_IPV6 - addRowLabelValue(LabelType::IP6_LOCAL); - addRowLabelValue(LabelType::IP6_GLOBAL); -#endif - addRowLabel(LabelType::WIFI_RSSI); - addHtml(strformat( - F("%d dBm (%s)"), - WiFi.RSSI(), - WiFi.SSID().c_str())); - } - - # if FEATURE_ETHERNET - - if (active_network_medium == NetworkMedium_t::Ethernet) { - addRowLabelValue(LabelType::ETH_SPEED_STATE); - addRowLabelValue(LabelType::ETH_IP_ADDRESS); -#if FEATURE_USE_IPV6 - addRowLabelValue(LabelType::IP6_LOCAL); - addRowLabelValue(LabelType::IP6_GLOBAL); -#endif - } - # endif // if FEATURE_ETHERNET - - # if FEATURE_MDNS - { - addRowLabel(LabelType::M_DNS); - addHtml(F("")); - addHtml(url); - addHtml(F("")); - } - # endif // if FEATURE_MDNS - - # if FEATURE_MQTT - { - if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { - addRowLabel(F("MQTT Client Connected")); - addEnabled(MQTTclient_connected); - } - } - # endif // if FEATURE_MQTT - - - # if MAIN_PAGE_SHOW_SYSINFO_BUTTON - html_TR_TD(); - html_TD(); - addButton(F("sysinfo"), F("More info")); - # endif // if MAIN_PAGE_SHOW_SYSINFO_BUTTON - # if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - html_TR_TD(); - html_TD(); - addButton(F("setup"), F("WiFi Setup")); - # endif // if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON - - if (loggedIn) { - if (printWebString.length() > 0) - { - html_BR(); - html_BR(); - addFormHeader(F("Command Argument")); - addRowLabel(F("Command")); - addHtml(sCommand); - - addHtml(F("
Command Output
")); - printWebString = String(); - } - } - html_end_table(); - - # if FEATURE_ESPEASY_P2P - html_BR(); - - if ((Settings.Unit == 0) && - (Settings.UDPPort != 0)) { addFormNote(F("Warning: Unit number is 0, please change it if you want to send data to other units.")); } - html_BR(); - html_table_class_multirow_noborder(); - html_TR(); - html_table_header(F("Node List")); - html_table_header(F("Name")); - - if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { - html_table_header(getLabel(LabelType::BUILD_DESC)); - } - - if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { - 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("Age (s)")); - # ifdef USES_ESPEASY_NOW - - if (Settings.UseESPEasyNow()) { - html_table_header(F("Dist")); - html_table_header(F("Peer Info"), 160); - } - # endif // ifdef USES_ESPEASY_NOW - - for (auto it = Nodes.begin(); it != Nodes.end(); ++it) - { - if (it->second.valid()) - { - bool isThisUnit = it->first == Settings.Unit; - - if (isThisUnit) { - html_TR_TD_highlight(); - } - else { - html_TR_TD(); - } - - addHtml(F("Unit ")); - addHtmlInt(it->first); - html_TD(); - - if (isThisUnit) { - addHtml(Settings.getName()); - } - else { - addHtml(it->second.getNodeName()); - } - html_TD(); - - if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { - if (it->second.build) { - addHtml(formatSystemBuildNr(it->second.build)); - } - html_TD(); - } - - if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { - addHtml(it->second.getNodeTypeDisplayString()); - html_TD(); - } - - if (it->second.ip[0] != 0 -#if FEATURE_USE_IPV6 - || it->second.hasIPv6_mac_based_link_local - || it->second.hasIPv6_mac_based_link_global -#endif - ) - { - html_add_wide_button_prefix(); - - addHtml(F("http://")); - IPAddress ip = it->second.IP(); - #if FEATURE_USE_IPV6 - bool isIPv6 = false; -// if (!it->second.hasIPv4) { - if (it->second.hasIPv6_mac_based_link_local) { - ip = it->second.IPv6_link_local(); - if (ip.zone() != 0) { - // Clear the zone as it is of no use here. - ip = IPAddress(IPv6, &ip[0], 0); - } - - isIPv6 = true; - } else if (it->second.hasIPv6_mac_based_link_global) { - ip = it->second.IPv6_global(); - isIPv6 = true; - } - // } - if (isIPv6) { - addHtml(wrap_String(formatIP(ip), '[', ']')); - } else { - addHtml(formatIP(ip)); - } - #else - addHtml(formatIP(ip)); - #endif - - uint16_t port = it->second.webgui_portnumber; - - if ((port != 0) && (port != 80)) { - addHtml(':'); - addHtmlInt(port); - } - addHtml('\'', '>'); - addHtml(formatIP(ip)); - addHtml(F("")); - } - html_TD(); - const float load = it->second.getLoad(); - - if (load > 0.1) { - addHtmlFloat(load); - } - html_TD(); - addHtmlInt(static_cast(it->second.getAge() / 1000)); // time in seconds - # ifdef USES_ESPEASY_NOW - - if (Settings.UseESPEasyNow()) { - html_TD(); - - if (it->second.distance != 255) { - addHtmlInt(it->second.distance); - } - html_TD(); - - if (it->second.ESPEasyNowPeer) { - addHtml(F(ESPEASY_NOW_NAME)); - addHtml(' '); - addHtml(it->second.ESPEasy_Now_MAC().toString()); - addHtml(F(" (ch: ")); - addHtmlInt(it->second.channel); - int8_t rssi = it->second.getRSSI(); - - if (rssi < 0) { - addHtml(' '); - addHtmlInt(rssi); - } - addHtml(')'); - const ESPEasy_now_traceroute_struct *trace = Nodes.getDiscoveryRoute(it->second.unit); - - if (trace != nullptr) { - addHtml(' '); - addHtml(trace->toString()); - } - } - } - # endif // ifdef USES_ESPEASY_NOW - } - } - - html_end_table(); - # endif // if FEATURE_ESPEASY_P2P - html_end_form(); - - printWebString = String(); - printToWeb = false; - sendHeadandTail_stdtemplate(_TAIL); - } - TXBuffer.endStream(); -} - -#endif // ifdef WEBSERVER_ROOT +#include "../WebServer/RootPage.h" + + +#ifdef WEBSERVER_ROOT + +# include "../WebServer/ESPEasy_WebServer.h" +# include "../WebServer/HTML_wrappers.h" +# include "../WebServer/LoadFromFS.h" +# include "../WebServer/Markup.h" +# include "../WebServer/Markup_Buttons.h" +# include "../WebServer/Markup_Forms.h" + +# include "../Commands/ExecuteCommand.h" +# include "../ESPEasyCore/ESPEasyNetwork.h" +# include "../Globals/ESPEasy_time.h" +# include "../Globals/ESPEasyWiFiEvent.h" +# include "../Globals/MainLoopCommand.h" +# include "../Globals/NetworkState.h" +# include "../Globals/Nodes.h" +# include "../Globals/Settings.h" +# include "../Globals/Statistics.h" +# include "../Helpers/ESPEasy_Storage.h" +# include "../Helpers/Memory.h" +# include "../Helpers/Misc.h" +# include "../Helpers/StringGenerator_System.h" +# include "../Helpers/WebServer_commandHelper.h" + + +# include "../../ESPEasy-Globals.h" + +# if FEATURE_MQTT +# include "../Globals/MQTT.h" +# include "../ESPEasyCore/Controller.h" // For finding enabled MQTT controller +# endif // if FEATURE_MQTT + + +# ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN + # define MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN false +# endif // ifndef MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN + +// Define main page elements present +# ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON + # define MAIN_PAGE_SHOW_SYSINFO_BUTTON true +# endif // ifndef MAIN_PAGE_SHOW_SYSINFO_BUTTON + +# ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + # define MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON false +# endif // ifndef MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + +# ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD + # define MAIN_PAGE_SHOW_NODE_LIST_BUILD true +# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_BUILD +# ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE + # define MAIN_PAGE_SHOW_NODE_LIST_TYPE true +# endif // ifndef MAIN_PAGE_SHOW_NODE_LIST_TYPE + + +// ******************************************************************************** +// Web Interface root page +// ******************************************************************************** +void handle_root() { + # ifdef USE_SECOND_HEAP + HeapSelectDram ephemeral; + # endif // ifdef USE_SECOND_HEAP + + # ifndef BUILD_NO_RAM_TRACKER + checkRAM(F("handle_root")); + # endif // ifndef BUILD_NO_RAM_TRACKER + + if (captivePortal()) { // If captive portal redirect instead of displaying the page. + return; + } + + // if Wifi setup, launch setup wizard if AP_DONT_FORCE_SETUP is not set. + if (WiFiEventData.wifiSetup && !Settings.ApDontForceSetup()) + { + web_server.send_P(200, (PGM_P)F("text/html"), (PGM_P)F("")); + return; + } + + if (!MAIN_PAGE_SHOW_BASIC_INFO_NOT_LOGGED_IN) { + if (!isLoggedIn()) { return; } + } + + const bool loggedIn = isLoggedIn(false); + + navMenuIndex = 0; + + // if index.htm exists on FS serve that one (first check if gziped version exists) + if (loadFromFS(F("/index.htm.gz"))) { return; } + + if (loadFromFS(F("/index.htm"))) { return; } + + TXBuffer.startStream(); + + boolean rebootCmd = false; + String sCommand = webArg(F("cmd")); + 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 + // because the webconnection is still active at this point + // disconnect here could result into a crash/reboot... + if (strcasecmp_P(sCommand.c_str(), PSTR("wifidisconnect")) == 0) + { + addLog(LOG_LEVEL_INFO, F("WIFI : Disconnecting...")); + cmd_within_mainloop = CMD_WIFI_DISCONNECT; + addHtml(F("OK")); + } else if (strcasecmp_P(sCommand.c_str(), PSTR("reboot")) == 0) + { + addLog(LOG_LEVEL_INFO, F(" : Rebooting...")); + cmd_within_mainloop = CMD_REBOOT; + addHtml(F("OK")); + } else if (strcasecmp_P(sCommand.c_str(), PSTR("reset")) == 0) + { + if (loggedIn) { + addLog(LOG_LEVEL_INFO, F(" : factory reset...")); + 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); + return; + } + } else { + if (loggedIn) { + handle_command_from_web(EventValueSource::Enum::VALUE_SOURCE_HTTP, sCommand); + printToWeb = false; + printToWebJSON = false; + } + + addHtml(F("")); + html_table_class_normal(); + addFormHeader(F("System Info")); + + addRowLabelValue(LabelType::UNIT_NR); + addRowLabelValue(LabelType::GIT_BUILD); + addRowLabel(LabelType::LOCAL_TIME); + + if (node_time.systemTimePresent()) + { + addHtml(getValue(LabelType::LOCAL_TIME)); + } + else { + addHtml(F("No system time source")); + } + addRowLabelValue(LabelType::TIME_SOURCE); + + addRowLabel(LabelType::UPTIME); + { + addHtml(getExtendedValue(LabelType::UPTIME)); + } + addRowLabel(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 + } + { +# ifdef USE_SECOND_HEAP + addRowLabelValue(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 + } + + # if FEATURE_ETHERNET + addRowLabelValue(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())); + } + + # if FEATURE_ETHERNET + + 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("")); + } + # endif // if FEATURE_MDNS + + # if FEATURE_MQTT + { + if (validControllerIndex(firstEnabledMQTT_ControllerIndex())) { + addRowLabel(F("MQTT Client Connected")); + addEnabled(MQTTclient_connected); + } + } + # endif // if FEATURE_MQTT + + + # if MAIN_PAGE_SHOW_SYSINFO_BUTTON + html_TR_TD(); + html_TD(); + addButton(F("sysinfo"), F("More info")); + # endif // if MAIN_PAGE_SHOW_SYSINFO_BUTTON + # if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + html_TR_TD(); + html_TD(); + addButton(F("setup"), F("WiFi Setup")); + # endif // if MAIN_PAGE_SHOW_WiFi_SETUP_BUTTON + + if (loggedIn) { + if (printWebString.length() > 0) + { + html_BR(); + html_BR(); + addFormHeader(F("Command Argument")); + addRowLabel(F("Command")); + addHtml(sCommand); + + addHtml(F("
Command Output
")); + printWebString = String(); + } + } + html_end_table(); + + # if FEATURE_ESPEASY_P2P + html_BR(); + + if ((Settings.Unit == 0) && + (Settings.UDPPort != 0)) { addFormNote(F("Warning: Unit number is 0, please change it if you want to send data to other units.")); } + html_BR(); + html_table_class_multirow_noborder(); + html_TR(); + html_table_header(F("Node List")); + html_table_header(F("Name")); + + if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { + html_table_header(getLabel(LabelType::BUILD_DESC)); + } + + if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { + 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("Age (s)")); + # ifdef USES_ESPEASY_NOW + + if (Settings.UseESPEasyNow()) { + html_table_header(F("Dist")); + html_table_header(F("Peer Info"), 160); + } + # endif // ifdef USES_ESPEASY_NOW + + for (auto it = Nodes.begin(); it != Nodes.end(); ++it) + { + if (it->second.valid()) + { + bool isThisUnit = it->first == Settings.Unit; + + if (isThisUnit) { + html_TR_TD_highlight(); + } + else { + html_TR_TD(); + } + + addHtml(F("Unit ")); + addHtmlInt(it->first); + html_TD(); + + if (isThisUnit) { + addHtml(Settings.getName()); + } + else { + addHtml(it->second.getNodeName()); + } + html_TD(); + + if (MAIN_PAGE_SHOW_NODE_LIST_BUILD) { + if (it->second.build) { + addHtml(formatSystemBuildNr(it->second.build)); + } + html_TD(); + } + + if (MAIN_PAGE_SHOW_NODE_LIST_TYPE) { + addHtml(it->second.getNodeTypeDisplayString()); + html_TD(); + } + + if (it->second.ip[0] != 0 +#if FEATURE_USE_IPV6 + || (Settings.EnableIPv6() && + (it->second.hasIPv6_mac_based_link_local || + it->second.hasIPv6_mac_based_link_global) + ) +#endif + ) + { + IPAddress ip = it->second.IP(); + const uint16_t port = it->second.webgui_portnumber; + +#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); + isIPv6 = true; + } else if (it->second.hasIPv6_mac_based_link_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); + } + addHtml('\'', '>'); + addHtml(formatIP(ip)); + addHtml(F("")); + + // Now prepare 2nd button prefix + addHtml(F("
")); + html_add_wide_button_prefix(); + ip = it->second.IP(); + isIPv6 = false; + } else { + // Add single wide button + html_add_wide_button_prefix(); + } +#else + html_add_wide_button_prefix(); +#endif + addHtml(F("http://")); +#if FEATURE_USE_IPV6 + + if (isIPv6) { + addHtml(wrap_String(formatIP(ip), '[', ']')); + } else { + addHtml(formatIP(ip)); + } + #else + addHtml(formatIP(ip)); + #endif + + if ((port != 0) && (port != 80)) { + addHtml(':'); + addHtmlInt(port); + } + addHtml('\'', '>'); + addHtml(formatIP(ip)); + addHtml(F("")); + } + html_TD(); + const float load = it->second.getLoad(); + + if (load > 0.1) { + addHtmlFloat(load); + } + html_TD(); + addHtmlInt(static_cast(it->second.getAge() / 1000)); // time in seconds + # ifdef USES_ESPEASY_NOW + + if (Settings.UseESPEasyNow()) { + html_TD(); + + if (it->second.distance != 255) { + addHtmlInt(it->second.distance); + } + html_TD(); + + if (it->second.ESPEasyNowPeer) { + addHtml(F(ESPEASY_NOW_NAME)); + addHtml(' '); + addHtml(it->second.ESPEasy_Now_MAC().toString()); + addHtml(F(" (ch: ")); + addHtmlInt(it->second.channel); + int8_t rssi = it->second.getRSSI(); + + if (rssi < 0) { + addHtml(' '); + addHtmlInt(rssi); + } + addHtml(')'); + const ESPEasy_now_traceroute_struct *trace = Nodes.getDiscoveryRoute(it->second.unit); + + if (trace != nullptr) { + addHtml(' '); + addHtml(trace->toString()); + } + } + } + # endif // ifdef USES_ESPEASY_NOW + } + } + + html_end_table(); + # endif // if FEATURE_ESPEASY_P2P + html_end_form(); + + printWebString = String(); + printToWeb = false; + sendHeadandTail_stdtemplate(_TAIL); + } + TXBuffer.endStream(); +} + +#endif // ifdef WEBSERVER_ROOT diff --git a/src/src/WebServer/Rules.cpp b/src/src/WebServer/Rules.cpp index e526ccf0f..ecde7b476 100644 --- a/src/src/WebServer/Rules.cpp +++ b/src/src/WebServer/Rules.cpp @@ -192,7 +192,7 @@ void handle_rules_new() { if (fi.isDirectory) { addHtml(F("
")); - addSaveButton(String(F("/rules/backup?directory=")) + URLEncode(fi.Name) + addSaveButton(concat(F("/rules/backup?directory="), URLEncode(fi.Name)) , F("Backup") ); } @@ -214,11 +214,11 @@ void handle_rules_new() { // Actions html_TD(); - addSaveButton(String(F("/rules/backup?fileName=")) + encodedPath + addSaveButton(concat(F("/rules/backup?fileName="), encodedPath) , F("Backup") ); - addDeleteButton(String(F("/rules/delete?fileName=")) + encodedPath + addDeleteButton(concat(F("/rules/delete?fileName="), encodedPath) , F("Delete") ); } @@ -245,13 +245,13 @@ void handle_rules_new() { int showIdx = startIdx - rulesListPageSize; if (showIdx < 0) { showIdx = 0; } - addButton(String(F("/rules?start=")) + String(showIdx) + addButton(concat(F("/rules?start="), showIdx) , F("Previous")); } if (hasMore && (count >= endIdx)) { - addButton(String(F("/rules?start=")) + String(endIdx + 1) + addButton(concat(F("/rules?start="), endIdx + 1) , F("Next")); } @@ -292,7 +292,7 @@ void handle_rules_backup() { { if (!Rule_Download(fi.Name)) { - error += String(F("Invalid path: ")) + fi.Name; + error += concat(F("Invalid path: "), fi.Name); } } return true; @@ -306,7 +306,7 @@ void handle_rules_backup() { if (!Rule_Download(fileName)) { - error = String(F("Invalid path: ")) + fileName; + error = concat(F("Invalid path: "), fileName); } } else @@ -361,7 +361,7 @@ void handle_rules_delete() { } else { - String error = String(F("Delete rule Invalid path: ")) + fileName; + String error = concat(F("Delete rule Invalid path: "), fileName); addLog(LOG_LEVEL_ERROR, error); TXBuffer.startStream(); sendHeadandTail(F("TmplMsg"), _HEAD); @@ -440,8 +440,8 @@ bool handle_rules_edit(String originalUri, bool isAddNew) { // Overwrite verification if (isEdit && isNew) { - error = String(F("There is another rule with the same name: ")) - + fileName; + error = concat(F("There is another rule with the same name: "), + fileName); addLog(LOG_LEVEL_ERROR, error); isAddNew = true; isOverwrite = true; @@ -455,8 +455,8 @@ bool handle_rules_edit(String originalUri, bool isAddNew) { // Check rules size else if (rules.length() > RULES_MAX_SIZE) { - error = String(F("Data was not saved, exceeds web editor limit! ")) - + fileName; + error = concat(F("Data was not saved, exceeds web editor limit! "), + fileName); addLog(LOG_LEVEL_ERROR, error); } @@ -552,17 +552,9 @@ void Rule_showRuleTextArea(const String& fileName) { addHtml(F("")); html_TR_TD(); - { - addHtml(F("Current size: ")); - addHtmlInt(size); - addHtml(F(" characters (Max ")); - addHtmlInt(RULES_MAX_SIZE); - addHtml(F(")")); - } - - if (size > RULES_MAX_SIZE) { - addHtml(F("Filesize exceeds web editor limit!")); - } + addHtml(F("Current size: ")); + addHtmlInt(size); + addHtml(F(" characters")); } bool Rule_Download(const String& path) @@ -578,9 +570,9 @@ bool Rule_Download(const String& path) addLog(LOG_LEVEL_ERROR, concat(F("Invalid path: "), path)); return false; } - String filename = path + String(F(".txt")); + String filename = concat(path, F(".txt")); filename.replace(RULE_FILE_SEPARAROR, '_'); - String str = String(F("attachment; filename=")) + filename; + String str = concat(F("attachment; filename="), filename); sendHeader(F("Content-Disposition"), str); sendHeader(F("Cache-Control"), F("max-age=3600, public")); sendHeader(F("Vary"), "*"); diff --git a/src/src/WebServer/SetupPage.cpp b/src/src/WebServer/SetupPage.cpp index 496471948..373f98634 100644 --- a/src/src/WebServer/SetupPage.cpp +++ b/src/src/WebServer/SetupPage.cpp @@ -1,412 +1,421 @@ -#include "../WebServer/SetupPage.h" - - -#ifdef WEBSERVER_SETUP - -# include "../WebServer/ESPEasy_WebServer.h" -# include "../WebServer/AccessControl.h" -# include "../WebServer/HTML_wrappers.h" -# include "../WebServer/Markup.h" -# include "../WebServer/Markup_Buttons.h" -# include "../WebServer/Markup_Forms.h" -# include "../WebServer/SysInfoPage.h" - -# include "../ESPEasyCore/ESPEasyNetwork.h" -# include "../ESPEasyCore/ESPEasyWifi.h" - -# include "../Globals/ESPEasyWiFiEvent.h" -# include "../Globals/NetworkState.h" -# include "../Globals/RTC.h" -# include "../Globals/Settings.h" -# include "../Globals/SecuritySettings.h" -# include "../Globals/WiFi_AP_Candidates.h" - -# include "../Helpers/Misc.h" -# include "../Helpers/Networking.h" -# include "../Helpers/ESPEasy_Storage.h" -# include "../Helpers/StringConverter.h" - - - -#ifndef SETUP_PAGE_SHOW_CONFIG_BUTTON - #define SETUP_PAGE_SHOW_CONFIG_BUTTON true -#endif - - - -// ******************************************************************************** -// Web Interface Setup Wizard -// ******************************************************************************** - -# define HANDLE_SETUP_SCAN_STAGE 0 -# define HANDLE_SETUP_CONNECTING_STAGE 1 - -void handle_setup() { - # ifndef BUILD_NO_RAM_TRACKER - checkRAM(F("handle_setup")); - # endif // ifndef BUILD_NO_RAM_TRACKER - - // Do not check client IP range allowed. - TXBuffer.startStream(); - - const bool connected = NetworkConnected(); - - -// if (connected) { - navMenuIndex = MENU_INDEX_SETUP; - sendHeadandTail_stdtemplate(_HEAD); -/* } else { - sendHeadandTail(F("TmplAP")); - } - */ - - const bool clearButtonPressed = hasArg(F("performclearcredentials")); - const bool clearWiFiCredentials = - isFormItemChecked(F("clearcredentials")) && clearButtonPressed; - - { - if (clearWiFiCredentials) { - SecuritySettings.clearWiFiCredentials(); - addHtmlError(SaveSecuritySettings()); - - html_add_form(); - html_table_class_normal(); - - 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; - - 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)); - 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")); - - } - */ - - html_table_class_normal(); - html_TR(); -#if defined(WEBSERVER_SYSINFO) && !defined(WEBSERVER_SYSINFO_MINIMAL) - handle_sysinfo_NetworkServices(); -#endif - if (connected) { - - //addFormHeader(F("Current network configuration")); - -#ifdef WEBSERVER_SYSINFO - handle_sysinfo_Network(); -#endif - - addFormSeparator(2); - - html_TR_TD(); - html_TD(); - - #if SETUP_PAGE_SHOW_CONFIG_BUTTON - if (!clientIPinSubnet()) { - String host = formatIP(NetworkLocalIP()); - String url = F("http://"); - url += host; - url += F("/config"); - addButton(url, host); - } - #endif - - WiFiEventData.wifiSetup = false; - } - html_end_table(); - - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - html_BR(); - - html_table_class_normal(); - - addFormHeader(F("Advanced WiFi settings")); - - addFormCheckBox(LabelType::CONNECT_HIDDEN_SSID, Settings.IncludeHiddenSSID()); - addFormNote(F("Must be checked to connect to a hidden SSID")); - - addFormCheckBox(LabelType::HIDDEN_SSID_SLOW_CONNECT, Settings.HiddenSSID_SlowConnectPerBSSID()); - addFormNote(F("Required for some AP brands like Mikrotik to connect to hidden SSID")); - - html_BR(); - html_BR(); - - addFormHeader(F("Clear WiFi credentials")); - addFormCheckBox(F("Confirm clear"), F("clearcredentials"), false); - - html_TR_TD(); - html_TD(); - addSubmitButton(F("Clear and Reboot"), F("performclearcredentials"), F("red")); - html_end_table(); - } - - html_end_form(); - } -// 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 = scanCompleteStatus <= 0 || WiFiScanAllowed(); - if (needsRescan) { - WiFiMode_t cur_wifimode = WiFi.getMode(); - WifiScan(false); - scanCompleteStatus = WiFi_AP_Candidates.scanComplete(); - setWifiMode(cur_wifimode); - } - - - if (scanCompleteStatus <= 0) { - addHtml(F("No Access Points found")); - } - else - { - html_table_class_multirow(); - html_TR(); - html_table_header(F("Pick"), 50); - 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) - { - html_TR_TD(); - const String id = it->toString(""); - addHtml(F("")); - - html_TD(); - addHtml(F("")); - - html_TD(); - addHtml(F("")); - } - html_end_table(); - } - - html_BR(); - - addSubmitButton(F("Rescan"), F("performrescan")); - - html_BR(); - - html_table_class_normal(); - html_TR_TD(); - - addHtml(F("")); - - html_TD(); - - addHtml(F("")); - - - html_TR(); - - html_BR(); - html_BR(); - - addFormSeparator(2); - - html_BR(); - - addFormPasswordBox(F("Password"), F("pass"), password, 63); - addFormCheckBox(F("Allow Empty Password"), F("emptypass"), false); - -/* - if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::first)) { - addFormCheckBox(F("Clear Stored SSID1"), F("clearssid1"), false); - addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID1)); - } - if (SecuritySettings.hasWiFiCredentials(SecurityStruct::WiFiCredentialsSlot::second)) { - addFormCheckBox(F("Clear Stored SSID2"), F("clearssid2"), false); - addFormNote(String(F("Current: ")) + getValue(LabelType::WIFI_STORED_SSID2)); - } - */ - - html_TR_TD(); - html_TD(); - html_BR(); - addSubmitButton(F("Connect"), EMPTY_STRING); - - html_end_table(); -} - -bool handle_setup_connectingStage(uint8_t refreshCount) { - if (refreshCount > 0) - { - // safe_strncpy(SecuritySettings.WifiSSID, "ssid", sizeof(SecuritySettings.WifiSSID)); - // SecuritySettings.WifiKey[0] = 0; - addButton(F("/setup"), F("Back to Setup")); - html_TR_TD(); - html_BR(); - WiFiEventData.wifiSetupConnect = false; - return false; - } - int wait = WIFI_RECONNECT_WAIT / 1000; - - if (refreshCount != 0) { - wait = 3; - } - addHtml(F("Please wait for

20..

" - "\n', ' \n', ' \n', @@ -379,6 +380,10 @@ def generate_manifest_files(bin_folder, output_prefix): '
\n', '
\n', ' See latest/ for a pre-release test build.\n', + '
\n', + ' See ../ for last official build.\n', + '
\n', + ' all.zip containing all bin files in a single zip file.\n', '